summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/test-current.txt15
-rw-r--r--core/java/android/app/TaskInfo.java22
-rw-r--r--core/java/android/content/pm/ShortcutServiceInternal.java3
-rw-r--r--core/java/android/hardware/OWNERS2
-rw-r--r--core/java/android/hardware/input/input_framework.aconfig7
-rw-r--r--core/java/android/provider/Settings.java12
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig7
-rw-r--r--core/java/com/android/internal/jank/Cuj.java24
-rw-r--r--core/res/res/xml/power_profile.xml6
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_open_in_browser.xml19
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml19
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml5
-rw-r--r--libs/WindowManager/Shell/res/values/strings.xml2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java70
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt83
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java71
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt6
-rw-r--r--media/java/android/media/AudioManager.java15
-rw-r--r--media/java/android/media/IAudioService.aidl2
-rw-r--r--media/java/android/media/VolumePolicy.java19
-rw-r--r--packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java18
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java1
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java19
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig7
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt1
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt5
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/systemui/communal/ui/compose/CommunalSwipeDetector.kt (renamed from packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/CommunalSwipeDetector.kt)12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorTest.kt126
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.kt236
-rw-r--r--packages/SystemUI/res/layout/clipboard_overlay.xml32
-rw-r--r--packages/SystemUI/res/layout/clipboard_overlay2.xml202
-rw-r--r--packages/SystemUI/res/layout/screen_share_dialog.xml2
-rw-r--r--packages/SystemUI/res/values/strings.xml42
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java106
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt367
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/shared/model/WakeSleepReason.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java57
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/model/MediaRouterCastModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt52
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegate.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/ProjectionChipModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt54
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegate.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegate.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java84
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt86
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt370
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt54
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt700
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/domian/interactor/MediaRouterChipInteractorTest.kt14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt127
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt105
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegateTest.kt14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt75
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt219
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt144
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java178
-rw-r--r--packages/SystemUI/tests/utils/src/android/app/admin/AlarmManagerKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/app/admin/DevicePolicyManagerKosmos.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/internal/widget/LockPatternUtilsKosmos.kt9
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt45
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt2
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java26
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java28
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java10
-rw-r--r--services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java498
-rw-r--r--services/core/java/com/android/server/TelephonyRegistry.java2
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java7
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java45
-rw-r--r--services/core/java/com/android/server/location/altitude/AltitudeService.java5
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java6
-rw-r--r--services/core/java/com/android/server/notification/ShortcutHelper.java162
-rw-r--r--services/core/java/com/android/server/pm/ShortcutService.java8
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java18
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java17
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java3
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java31
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt272
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java113
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java43
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java212
126 files changed, 4216 insertions, 1973 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 099dbbc2764f..2ca9f2e8cdd6 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1982,6 +1982,7 @@ package android.media {
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public float getRs2Value();
method public int getStreamMinVolumeInt(int);
method @NonNull public java.util.Map<java.lang.Integer,java.lang.Boolean> getSurroundFormats();
+ method @NonNull public android.media.VolumePolicy getVolumePolicy();
method public boolean hasRegisteredDynamicPolicy();
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public boolean isCsdEnabled();
method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.QUERY_AUDIO_STATE, android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED}) public boolean isFullVolumeDevice();
@@ -2073,6 +2074,20 @@ package android.media {
method public android.media.PlaybackParams setAudioStretchMode(int);
}
+ public final class VolumePolicy implements android.os.Parcelable {
+ ctor public VolumePolicy(boolean, boolean, boolean, int);
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int A11Y_MODE_INDEPENDENT_A11Y_VOLUME = 1; // 0x1
+ field public static final int A11Y_MODE_MEDIA_A11Y_VOLUME = 0; // 0x0
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.VolumePolicy> CREATOR;
+ field @NonNull public static final android.media.VolumePolicy DEFAULT;
+ field public final boolean doNotDisturbWhenSilent;
+ field public final int vibrateToSilentDebounce;
+ field public final boolean volumeDownToEnterSilent;
+ field public final boolean volumeUpToExitSilent;
+ }
+
public static final class VolumeShaper.Configuration.Builder {
method @NonNull public android.media.VolumeShaper.Configuration.Builder setOptionFlags(int);
}
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index aca9bb4d07c1..531537c374ce 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -30,6 +30,7 @@ import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
+import android.net.Uri;
import android.os.Build;
import android.os.IBinder;
import android.os.Parcel;
@@ -303,6 +304,19 @@ public class TaskInfo {
public boolean isTopActivityStyleFloating;
/**
+ * The URI of the intent that generated the top-most activity opened using a URL.
+ * @hide
+ */
+ @Nullable
+ public Uri capturedLink;
+
+ /**
+ * The time of the last launch of the activity opened using the {@link #capturedLink}.
+ * @hide
+ */
+ public long capturedLinkTimestamp;
+
+ /**
* Encapsulate specific App Compat information.
* @hide
*/
@@ -436,6 +450,8 @@ public class TaskInfo {
&& Objects.equals(topActivity, that.topActivity)
&& isTopActivityTransparent == that.isTopActivityTransparent
&& isTopActivityStyleFloating == that.isTopActivityStyleFloating
+ && Objects.equals(capturedLink, that.capturedLink)
+ && capturedLinkTimestamp == that.capturedLinkTimestamp
&& appCompatTaskInfo.equalsForTaskOrganizer(that.appCompatTaskInfo);
}
@@ -506,6 +522,8 @@ public class TaskInfo {
displayAreaFeatureId = source.readInt();
isTopActivityTransparent = source.readBoolean();
isTopActivityStyleFloating = source.readBoolean();
+ capturedLink = source.readTypedObject(Uri.CREATOR);
+ capturedLinkTimestamp = source.readLong();
appCompatTaskInfo = source.readTypedObject(AppCompatTaskInfo.CREATOR);
}
@@ -554,6 +572,8 @@ public class TaskInfo {
dest.writeInt(displayAreaFeatureId);
dest.writeBoolean(isTopActivityTransparent);
dest.writeBoolean(isTopActivityStyleFloating);
+ dest.writeTypedObject(capturedLink, flags);
+ dest.writeLong(capturedLinkTimestamp);
dest.writeTypedObject(appCompatTaskInfo, flags);
}
@@ -592,6 +612,8 @@ public class TaskInfo {
+ " displayAreaFeatureId=" + displayAreaFeatureId
+ " isTopActivityTransparent=" + isTopActivityTransparent
+ " isTopActivityStyleFloating=" + isTopActivityStyleFloating
+ + " capturedLink=" + capturedLink
+ + " capturedLinkTimestamp=" + capturedLinkTimestamp
+ " appCompatTaskInfo=" + appCompatTaskInfo
+ "}";
}
diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java
index 55d0bc1deedc..c811a47e5b05 100644
--- a/core/java/android/content/pm/ShortcutServiceInternal.java
+++ b/core/java/android/content/pm/ShortcutServiceInternal.java
@@ -91,6 +91,9 @@ public abstract class ShortcutServiceInternal {
public abstract void addShortcutChangeCallback(
@NonNull LauncherApps.ShortcutChangeCallback callback);
+ public abstract void removeShortcutChangeCallback(
+ @NonNull LauncherApps.ShortcutChangeCallback callback);
+
public abstract int getShortcutIconResId(int launcherUserId, @NonNull String callingPackage,
@NonNull String packageName, @NonNull String shortcutId, int userId);
diff --git a/core/java/android/hardware/OWNERS b/core/java/android/hardware/OWNERS
index 51ad1519941b..43d3f5466ccf 100644
--- a/core/java/android/hardware/OWNERS
+++ b/core/java/android/hardware/OWNERS
@@ -5,7 +5,7 @@ michaelwr@google.com
sumir@google.com
# Camera
-per-file *Camera*=cychen@google.com,epeev@google.com,etalvala@google.com,shuzhenwang@google.com,zhijunhe@google.com,jchowdhary@google.com
+per-file *Camera*=file:platform/frameworks/av:/camera/OWNERS
# Sensor Privacy
per-file *SensorPrivacy* = file:platform/frameworks/native:/libs/sensorprivacy/OWNERS
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index acd0d00f812d..16d9ef270f75 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -61,3 +61,10 @@ flag {
description: "Allows system to provide keyboard specific key drawables and shortcuts via config files"
bug: "345440920"
}
+
+flag {
+ namespace: "input_native"
+ name: "keyboard_a11y_mouse_keys"
+ description: "Controls if the mouse keys accessibility feature for physical keyboard is available to the user"
+ bug: "341799888"
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c954cdb270e8..2562c8e31095 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -12324,6 +12324,18 @@ public final class Settings {
"accessibility_force_invert_color_enabled";
/**
+ * Whether to enable mouse keys for Physical Keyboard accessibility.
+ *
+ * If set to true, key presses (of the mouse keys) on
+ * physical keyboard will control mouse pointer on the display.
+ *
+ * @hide
+ */
+ @Readable
+ public static final String ACCESSIBILITY_MOUSE_KEYS_ENABLED =
+ "accessibility_mouse_keys_enabled";
+
+ /**
* Whether the Adaptive connectivity option is enabled.
*
* @hide
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index e54853631d5f..245c0e7c630c 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -122,6 +122,13 @@ flag {
}
flag {
+ name: "enable_caption_compat_inset_force_consumption"
+ namespace: "lse_desktop_experience"
+ description: "Enables force-consumption of caption bar insets for immersive apps in freeform"
+ bug: "316231589"
+}
+
+flag {
name: "show_desktop_windowing_dev_option"
namespace: "lse_desktop_experience"
description: "Whether to show developer option for enabling desktop windowing mode"
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index 1638699c6175..5c818f178739 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -175,9 +175,17 @@ public class Cuj {
/** Track launching a dialog from a status bar chip. */
public static final int CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP = 111;
+ /** Track Launcher Keyboard Quick Switch View opening animation */
+ public static final int CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN = 112;
+
+ /** Track Launcher Keyboard Quick Switch View closing animation */
+ public static final int CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE = 113;
+
+ /** Track launching an app through the Launcher Keyboard Quick Switch View */
+ public static final int CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH = 114;
// When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
- @VisibleForTesting static final int LAST_CUJ = CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP;
+ @VisibleForTesting static final int LAST_CUJ = CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH;
/** @hide */
@IntDef({
@@ -280,7 +288,10 @@ public class Cuj {
CUJ_DESKTOP_MODE_EXIT_MODE,
CUJ_DESKTOP_MODE_MINIMIZE_WINDOW,
CUJ_DESKTOP_MODE_DRAG_WINDOW,
- CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP
+ CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP,
+ CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN,
+ CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE,
+ CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {}
@@ -394,6 +405,9 @@ public class Cuj {
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_MINIMIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_MINIMIZE_WINDOW;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_DRAG_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_DRAG_WINDOW;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH;
}
private Cuj() {
@@ -612,6 +626,12 @@ public class Cuj {
return "DESKTOP_MODE_DRAG_WINDOW";
case CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP:
return "STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP";
+ case CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN:
+ return "CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN";
+ case CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE:
+ return "CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE";
+ case CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH:
+ return "CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH";
}
return "UNKNOWN";
}
diff --git a/core/res/res/xml/power_profile.xml b/core/res/res/xml/power_profile.xml
index fc63657f04d0..f67ad3f5575e 100644
--- a/core/res/res/xml/power_profile.xml
+++ b/core/res/res/xml/power_profile.xml
@@ -33,14 +33,14 @@
There must be one of these for each display, labeled:
ambient.on.display0, ambient.on.display1, etc...
- Each display suffix number should match it's ordinal in its display device config.
+ Each display suffix number should match its ordinal in its display device config.
-->
<item name="ambient.on.display0">0.1</item> <!-- ~100mA -->
<!-- Average battery current draw of display0 while on without backlight.
There must be one of these for each display, labeled:
screen.on.display0, screen.on.display1, etc...
- Each display suffix number should match it's ordinal in its display device config.
+ Each display suffix number should match its ordinal in its display device config.
-->
<item name="screen.on.display0">0.1</item> <!-- ~100mA -->
<!-- Average battery current draw of the backlight at full brightness.
@@ -50,7 +50,7 @@
There must be one of these for each display, labeled:
screen.full.display0, screen.full.display1, etc...
- Each display suffix number should match it's ordinal in its display device config.
+ Each display suffix number should match its ordinal in its display device config.
-->
<item name="screen.full.display0">0.1</item> <!-- ~100mA -->
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_open_in_browser.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_open_in_browser.xml
new file mode 100644
index 000000000000..7d912a24c443
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_open_in_browser.xml
@@ -0,0 +1,19 @@
+<?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="M160,880Q127,880 103.5,856.5Q80,833 80,800L80,440Q80,407 103.5,383.5Q127,360 160,360L240,360L240,160Q240,127 263.5,103.5Q287,80 320,80L800,80Q833,80 856.5,103.5Q880,127 880,160L880,520Q880,553 856.5,576.5Q833,600 800,600L720,600L720,800Q720,833 696.5,856.5Q673,880 640,880L160,880ZM160,800L640,800Q640,800 640,800Q640,800 640,800L640,520L160,520L160,800Q160,800 160,800Q160,800 160,800ZM720,520L800,520Q800,520 800,520Q800,520 800,520L800,240L320,240L320,360L640,360Q673,360 696.5,383.5Q720,407 720,440L720,520Z"/>
+</vector>
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 d5724cc6a420..419d5c0af1a4 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
@@ -135,5 +135,24 @@
android:drawableTint="?androidprv:attr/materialColorOnSurface"
style="@style/DesktopModeHandleMenuActionButton"/>
</LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/open_in_browser_pill"
+ android:layout_width="match_parent"
+ 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:elevation="1dp"
+ android:background="@drawable/desktop_mode_decor_handle_menu_background">
+
+ <Button
+ android:id="@+id/open_in_browser_button"
+ 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"/>
+ </LinearLayout>
</LinearLayout>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 595d34664cfa..d143263b69a5 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -507,8 +507,11 @@
<!-- The height of the handle menu's "More Actions" pill in desktop mode. -->
<dimen name="desktop_mode_handle_menu_more_actions_pill_height">52dp</dimen>
+ <!-- The height of the handle menu's "Open in browser" pill in desktop mode. -->
+ <dimen name="desktop_mode_handle_menu_open_in_browser_pill_height">52dp</dimen>
+
<!-- The height of the handle menu in desktop mode. -->
- <dimen name="desktop_mode_handle_menu_height">328dp</dimen>
+ <dimen name="desktop_mode_handle_menu_height">380dp</dimen>
<!-- The top margin of the handle menu in desktop mode. -->
<dimen name="desktop_mode_handle_menu_margin_top">4dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 47846746b205..4e7cfb638a12 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -280,6 +280,8 @@
<string name="select_text">Select</string>
<!-- Accessibility text for the handle menu screenshot button [CHAR LIMIT=NONE] -->
<string name="screenshot_text">Screenshot</string>
+ <!-- Accessibility text for the handle menu open in browser button [CHAR LIMIT=NONE] -->
+ <string name="open_in_browser_text">Open in browser</string>
<!-- Accessibility text for the handle menu close button [CHAR LIMIT=NONE] -->
<string name="close_text">Close</string>
<!-- Accessibility text for the handle menu close menu button [CHAR LIMIT=NONE] -->
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 1be33e5d2d95..faf6a627febe 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
@@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_HOVER_ENTER;
@@ -41,12 +42,17 @@ import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.input.InputManager;
+import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
@@ -410,6 +416,26 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
decoration.closeMaximizeMenu();
}
+ private void onOpenInBrowser(@NonNull DesktopModeWindowDecoration decor, @NonNull Uri uri) {
+ openInBrowser(uri);
+ decor.closeHandleMenu();
+ decor.closeMaximizeMenu();
+ }
+
+ private void openInBrowser(Uri uri) {
+ final Intent intent = new Intent(Intent.ACTION_VIEW, uri)
+ .setComponent(getDefaultBrowser())
+ .addFlags(FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(intent);
+ }
+
+ private ComponentName getDefaultBrowser() {
+ final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://"));
+ final ResolveInfo info = mContext.getPackageManager()
+ .resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ return info.getComponentInfo().getComponentName();
+ }
+
private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener,
View.OnGenericMotionListener, DragDetector.MotionEventHandler {
@@ -489,6 +515,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
} else if (id == R.id.split_screen_button) {
decoration.closeHandleMenu();
mDesktopTasksController.requestSplit(decoration.mTaskInfo);
+ } else if (id == R.id.open_in_browser_button) {
+ // TODO(b/346441962): let the decoration handle the click gesture and only call back
+ // to the ViewModel via #setOpenInBrowserClickListener
+ decoration.onOpenInBrowserClick();
} else if (id == R.id.collapse_menu_button) {
decoration.closeHandleMenu();
} else if (id == R.id.maximize_window) {
@@ -1091,6 +1121,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
windowDecoration.setOnRightSnapClickListener((taskId, tag) -> {
onSnapResize(taskId, false /* isLeft */);
});
+ windowDecoration.setOpenInBrowserClickListener(this::onOpenInBrowser);
windowDecoration.setCaptionListeners(
touchEventListener, touchEventListener, touchEventListener, touchEventListener);
windowDecoration.setExclusionRegionListener(mExclusionRegionListener);
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 b62194ca6239..5ffd883a7ceb 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
@@ -44,6 +44,7 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
+import android.net.Uri;
import android.os.Handler;
import android.os.Trace;
import android.util.Log;
@@ -88,6 +89,7 @@ import java.util.function.Supplier;
*/
public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> {
private static final String TAG = "DesktopModeWindowDecoration";
+ private static final int CAPTURED_LINK_TIMEOUT_MS = 7000;
@VisibleForTesting
static final long CLOSE_MAXIMIZE_MENU_DELAY_MS = 150L;
@@ -124,6 +126,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private Bitmap mResizeVeilBitmap;
private CharSequence mAppName;
+ private CapturedLink mCapturedLink;
+ private OpenInBrowserClickListener mOpenInBrowserClickListener;
private ExclusionRegionListener mExclusionRegionListener;
@@ -138,6 +142,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
// being hovered. There's a small delay after stopping the hover, to allow a quick reentry
// to cancel the close.
private final Runnable mCloseMaximizeWindowRunnable = this::closeMaximizeMenu;
+ private final Runnable mCapturedLinkExpiredRunnable = this::onCapturedLinkExpired;
DesktopModeWindowDecoration(
Context context,
@@ -153,8 +158,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
handler, choreographer, syncQueue, rootTaskDisplayAreaOrganizer,
SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
WindowContainerTransaction::new, SurfaceControl::new,
- new SurfaceControlViewHostFactory() {},
- DefaultMaximizeMenuFactory.INSTANCE);
+ new SurfaceControlViewHostFactory() {}, DefaultMaximizeMenuFactory.INSTANCE);
}
DesktopModeWindowDecoration(
@@ -232,6 +236,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mDragDetector.setTouchSlop(ViewConfiguration.get(mContext).getScaledTouchSlop());
}
+ void setOpenInBrowserClickListener(OpenInBrowserClickListener listener) {
+ mOpenInBrowserClickListener = listener;
+ }
+
@Override
void relayout(ActivityManager.RunningTaskInfo taskInfo) {
final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
@@ -323,6 +331,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) {
Trace.beginSection("DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces");
+
+ if (Flags.enableDesktopWindowingAppToWeb()) {
+ setCapturedLink(taskInfo.capturedLink, taskInfo.capturedLinkTimestamp);
+ }
+
if (isHandleMenuActive()) {
mHandleMenu.relayout(startT);
}
@@ -367,6 +380,28 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
Trace.endSection(); // DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces
}
+ private void setCapturedLink(Uri capturedLink, long timeStamp) {
+ if (capturedLink == null
+ || (mCapturedLink != null && mCapturedLink.mTimeStamp == timeStamp)) {
+ return;
+ }
+ mCapturedLink = new CapturedLink(capturedLink, timeStamp);
+ mHandler.postDelayed(mCapturedLinkExpiredRunnable, CAPTURED_LINK_TIMEOUT_MS);
+ }
+
+ private void onCapturedLinkExpired() {
+ mHandler.removeCallbacks(mCapturedLinkExpiredRunnable);
+ if (mCapturedLink != null) {
+ mCapturedLink.setExpired();
+ }
+ }
+
+ void onOpenInBrowserClick() {
+ if (mOpenInBrowserClickListener == null || mCapturedLink == null) return;
+ mOpenInBrowserClickListener.onClick(this, mCapturedLink.mUri);
+ onCapturedLinkExpired();
+ }
+
private void updateDragResizeListener(SurfaceControl oldDecorationSurface) {
if (!isDragResizable(mTaskInfo)) {
if (!mTaskInfo.positionInParent.equals(mPositionInParent)) {
@@ -827,11 +862,17 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
.setCaptionHeight(mResult.mCaptionHeight)
.setDisplayController(mDisplayController)
.setSplitScreenController(splitScreenController)
+ .setBrowserLinkAvailable(browserLinkAvailable())
.build();
mWindowDecorViewHolder.onHandleMenuOpened();
mHandleMenu.show();
}
+ @VisibleForTesting
+ boolean browserLinkAvailable() {
+ return mCapturedLink != null && !mCapturedLink.mExpired;
+ }
+
/**
* Close the handle menu window.
*/
@@ -1121,6 +1162,31 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
}
+ @VisibleForTesting
+ static class CapturedLink {
+ private final long mTimeStamp;
+ private final Uri mUri;
+ private boolean mExpired;
+
+ CapturedLink(@NonNull Uri uri, long timeStamp) {
+ mUri = uri;
+ mTimeStamp = timeStamp;
+ mExpired = false;
+ }
+
+ void setExpired() {
+ mExpired = true;
+ }
+ }
+
+
+ /** Listener for the handle menu's "Open in browser" button */
+ interface OpenInBrowserClickListener {
+
+ /** Inform the implementing class that the "Open in browser" button has been clicked */
+ void onClick(DesktopModeWindowDecoration decoration, Uri uri);
+ }
+
interface ExclusionRegionListener {
/** Inform the implementing class of this task's change in region resize handles */
void onExclusionRegionChanged(int taskId, Region region);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
index df0836c1121d..7e44f32bcbeb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
@@ -42,6 +42,7 @@ import android.graphics.Rect;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
+import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
@@ -81,6 +82,7 @@ class HandleMenu {
// those as well.
final Point mGlobalMenuPosition = new Point();
private final boolean mShouldShowWindowingPill;
+ private final boolean mShouldShowBrowserPill;
private final Bitmap mAppIconBitmap;
private final CharSequence mAppName;
private final View.OnClickListener mOnClickListener;
@@ -101,7 +103,7 @@ class HandleMenu {
View.OnClickListener onClickListener, View.OnTouchListener onTouchListener,
Bitmap appIcon, CharSequence appName, DisplayController displayController,
SplitScreenController splitScreenController, boolean shouldShowWindowingPill,
- int captionHeight) {
+ boolean shouldShowBrowserPill, int captionHeight) {
mParentDecor = parentDecor;
mContext = mParentDecor.mDecorWindowContext;
mTaskInfo = mParentDecor.mTaskInfo;
@@ -113,6 +115,7 @@ class HandleMenu {
mAppIconBitmap = appIcon;
mAppName = appName;
mShouldShowWindowingPill = shouldShowWindowingPill;
+ mShouldShowBrowserPill = shouldShowBrowserPill;
mCaptionHeight = captionHeight;
loadHandleMenuDimensions();
updateHandleMenuPillPositions();
@@ -170,6 +173,7 @@ class HandleMenu {
setupWindowingPill(handleMenu);
}
setupMoreActionsPill(handleMenu);
+ setupOpenInBrowserPill(handleMenu);
}
/**
@@ -228,6 +232,15 @@ class HandleMenu {
}
}
+ private void setupOpenInBrowserPill(View handleMenu) {
+ if (!mShouldShowBrowserPill) {
+ handleMenu.findViewById(R.id.open_in_browser_pill).setVisibility(View.GONE);
+ return;
+ }
+ final Button browserButton = handleMenu.findViewById(R.id.open_in_browser_button);
+ browserButton.setOnClickListener(mOnClickListener);
+ }
+
/**
* Returns array of windowing icon color based on current UI theme. First element of the
* array is for inactive icons and the second is for active icons.
@@ -423,6 +436,10 @@ class HandleMenu {
menuHeight -= loadDimensionPixelSize(resources,
R.dimen.desktop_mode_handle_menu_more_actions_pill_height);
}
+ if (!mShouldShowBrowserPill) {
+ menuHeight -= loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_open_in_browser_pill_height);
+ }
return menuHeight;
}
@@ -457,6 +474,7 @@ class HandleMenu {
private int mCaptionHeight;
private DisplayController mDisplayController;
private SplitScreenController mSplitScreenController;
+ private boolean mShowBrowserPill;
Builder(@NonNull DesktopModeWindowDecoration parent) {
mParent = parent;
@@ -507,10 +525,15 @@ class HandleMenu {
return this;
}
+ Builder setBrowserLinkAvailable(Boolean showBrowserPill) {
+ mShowBrowserPill = showBrowserPill;
+ return this;
+ }
+
HandleMenu build() {
return new HandleMenu(mParent, mLayoutId, mOnClickListener,
mOnTouchListener, mAppIcon, mName, mDisplayController, mSplitScreenController,
- mShowWindowingPill, mCaptionHeight);
+ mShowWindowingPill, mShowBrowserPill, mCaptionHeight);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
index 8c5d4a2c2ffb..25a829b44448 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
@@ -26,6 +26,7 @@ import android.view.View.SCALE_Y
import android.view.View.TRANSLATION_Y
import android.view.View.TRANSLATION_Z
import android.view.ViewGroup
+import android.widget.Button
import androidx.core.animation.doOnEnd
import androidx.core.view.children
import com.android.wm.shell.R
@@ -72,6 +73,7 @@ class HandleMenuAnimator(
private val appInfoPill: ViewGroup = handleMenu.requireViewById(R.id.app_info_pill)
private val windowingPill: ViewGroup = handleMenu.requireViewById(R.id.windowing_pill)
private val moreActionsPill: ViewGroup = handleMenu.requireViewById(R.id.more_actions_pill)
+ private val openInBrowserPill: ViewGroup = handleMenu.requireViewById(R.id.open_in_browser_pill)
/** Animates the opening of the handle menu. */
fun animateOpen() {
@@ -80,6 +82,7 @@ class HandleMenuAnimator(
animateAppInfoPillOpen()
animateWindowingPillOpen()
animateMoreActionsPillOpen()
+ animateOpenInBrowserPill()
runAnimations()
}
@@ -94,6 +97,7 @@ class HandleMenuAnimator(
animateAppInfoPillOpen()
animateWindowingPillOpen()
animateMoreActionsPillOpen()
+ animateOpenInBrowserPill()
runAnimations()
}
@@ -109,6 +113,7 @@ class HandleMenuAnimator(
animateAppInfoPillFadeOut()
windowingPillClose()
moreActionsPillClose()
+ openInBrowserPillClose()
runAnimations(after)
}
@@ -125,6 +130,7 @@ class HandleMenuAnimator(
animateAppInfoPillFadeOut()
windowingPillClose()
moreActionsPillClose()
+ openInBrowserPillClose()
runAnimations(after)
}
@@ -137,6 +143,7 @@ class HandleMenuAnimator(
appInfoPill.children.forEach { it.alpha = 0f }
windowingPill.alpha = 0f
moreActionsPill.alpha = 0f
+ openInBrowserPill.alpha = 0f
// Setup pivots.
handleMenu.pivotX = menuWidth / 2f
@@ -147,6 +154,9 @@ class HandleMenuAnimator(
moreActionsPill.pivotX = menuWidth / 2f
moreActionsPill.pivotY = appInfoPill.measuredHeight.toFloat()
+
+ openInBrowserPill.pivotX = menuWidth / 2f
+ openInBrowserPill.pivotY = appInfoPill.measuredHeight.toFloat()
}
private fun animateAppInfoPillOpen() {
@@ -268,12 +278,50 @@ class HandleMenuAnimator(
// More Actions Content Opacity Animation
moreActionsPill.children.forEach {
animators +=
- ObjectAnimator.ofFloat(it, ALPHA, 1f).apply {
+ ObjectAnimator.ofFloat(it, ALPHA, 1f).apply {
+ startDelay = BODY_ALPHA_OPEN_DELAY
+ duration = BODY_CONTENT_ALPHA_OPEN_DURATION
+ interpolator = Interpolators.FAST_OUT_SLOW_IN
+ }
+ }
+ }
+
+ private fun animateOpenInBrowserPill() {
+ // Open in Browser X & Y Scaling Animation
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply {
+ startDelay = BODY_SCALE_OPEN_DELAY
+ duration = BODY_SCALE_OPEN_DURATION
+ }
+
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply {
+ startDelay = BODY_SCALE_OPEN_DELAY
+ duration = BODY_SCALE_OPEN_DURATION
+ }
+
+ // Open in Browser Opacity Animation
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, ALPHA, 1f).apply {
+ startDelay = BODY_ALPHA_OPEN_DELAY
+ duration = BODY_ALPHA_OPEN_DURATION
+ }
+
+ // Open in Browser Elevation Animation
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, TRANSLATION_Z, 1f).apply {
+ startDelay = ELEVATION_OPEN_DELAY
+ duration = BODY_ELEVATION_OPEN_DURATION
+ }
+
+ // Open in Browser Button Opacity Animation
+ val button = openInBrowserPill.requireViewById<Button>(R.id.open_in_browser_button)
+ animators +=
+ ObjectAnimator.ofFloat(button, ALPHA, 1f).apply {
startDelay = BODY_ALPHA_OPEN_DELAY
duration = BODY_CONTENT_ALPHA_OPEN_DURATION
interpolator = Interpolators.FAST_OUT_SLOW_IN
}
- }
}
private fun appInfoPillCollapse() {
@@ -379,6 +427,37 @@ class HandleMenuAnimator(
}
}
+ private fun openInBrowserPillClose() {
+ // Open in Browser X & Y Scaling Animation
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, SCALE_X, HALF_INITIAL_SCALE).apply {
+ duration = BODY_CLOSE_DURATION
+ }
+
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, SCALE_Y, HALF_INITIAL_SCALE).apply {
+ duration = BODY_CLOSE_DURATION
+ }
+
+ // Open in Browser Opacity Animation
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, ALPHA, 0f).apply {
+ duration = BODY_CLOSE_DURATION
+ }
+
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, ALPHA, 0f).apply {
+ duration = BODY_CLOSE_DURATION
+ }
+
+ // Upward Open in Browser y-translation Animation
+ val yStart: Float = -captionHeight / 2
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, TRANSLATION_Y, yStart).apply {
+ duration = BODY_CLOSE_DURATION
+ }
+ }
+
/**
* Runs the list of hide animators concurrently.
*
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 b355137a9077..d8606093ac5c 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
@@ -31,6 +31,7 @@ import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
@@ -50,6 +51,7 @@ import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.PointF;
+import android.net.Uri;
import android.os.Handler;
import android.os.SystemProperties;
import android.platform.test.annotations.DisableFlags;
@@ -118,6 +120,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
private static final String USE_ROUNDED_CORNERS_SYSPROP_KEY =
"persist.wm.debug.desktop_use_rounded_corners";
+ private static final Uri TEST_URI = Uri.parse("www.google.com");
+
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
@Mock
@@ -150,6 +154,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
private PackageManager mMockPackageManager;
@Mock
private Handler mMockHandler;
+ @Mock
+ private DesktopModeWindowDecoration.OpenInBrowserClickListener mMockOpenInBrowserClickListener;
@Captor
private ArgumentCaptor<Function1<Boolean, Unit>> mOnMaxMenuHoverChangeListener;
@Captor
@@ -555,6 +561,65 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
verify(mMockHandler).removeCallbacks(any());
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
+ public void capturedLink_postsOnCapturedLinkExpiredRunnable() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
+ final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
+
+ decor.relayout(taskInfo);
+ // Assert captured link is set
+ assertTrue(decor.browserLinkAvailable());
+ // Asset runnable posted to set captured link to expired
+ verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong());
+ runnableArgument.getValue().run();
+ assertFalse(decor.browserLinkAvailable());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
+ public void capturedLink_capturedLinkNotResetToSameLink() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
+ final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
+
+ // Set captured link and run on captured link expired runnable
+ decor.relayout(taskInfo);
+ verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong());
+ runnableArgument.getValue().run();
+
+ decor.relayout(taskInfo);
+ // Assert captured link not set to same value twice
+ assertFalse(decor.browserLinkAvailable());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
+ public void capturedLink_capturedLinkExpiresAfterClick() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
+
+ decor.relayout(taskInfo);
+ // Assert captured link is set
+ assertTrue(decor.browserLinkAvailable());
+ decor.onOpenInBrowserClick();
+ //Assert Captured link expires after button is clicked
+ assertFalse(decor.browserLinkAvailable());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
+ public void capturedLink_openInBrowserListenerCalledOnClick() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
+
+ decor.relayout(taskInfo);
+ decor.onOpenInBrowserClick();
+
+ verify(mMockOpenInBrowserClickListener).onClick(any(), any());
+ }
+
private void createMaximizeMenu(DesktopModeWindowDecoration decoration, MaximizeMenu menu) {
final OnTaskActionClickListener l = (taskId, tag) -> {};
decoration.setOnMaximizeOrRestoreClickListener(l);
@@ -595,11 +660,11 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
mMockHandler, mMockChoreographer, mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer,
SurfaceControl.Builder::new, mMockTransactionSupplier,
WindowContainerTransaction::new, SurfaceControl::new,
- mMockSurfaceControlViewHostFactory,
- maximizeMenuFactory);
+ mMockSurfaceControlViewHostFactory, maximizeMenuFactory);
windowDecor.setCaptionListeners(mMockTouchEventListener, mMockTouchEventListener,
mMockTouchEventListener, mMockTouchEventListener);
windowDecor.setExclusionRegionListener(mMockExclusionRegionListener);
+ windowDecor.setOpenInBrowserClickListener(mMockOpenInBrowserClickListener);
return windowDecor;
}
@@ -615,6 +680,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
"DesktopModeWindowDecorationTests");
taskInfo.baseActivity = new ComponentName("com.android.wm.shell.windowdecor",
"DesktopModeWindowDecorationTests");
+ taskInfo.capturedLink = TEST_URI;
+ taskInfo.capturedLinkTimestamp = System.currentTimeMillis();
return taskInfo;
}
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 5582e0f46321..0c50ab6b5008 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
@@ -196,9 +196,9 @@ class HandleMenuTest : ShellTestCase() {
R.layout.desktop_mode_app_header
}
val handleMenu = HandleMenu(mockDesktopWindowDecoration, layoutId,
- onClickListener, onTouchListener, appIcon, appName, displayController,
- splitScreenController, true /* shouldShowWindowingPill */,
- 50 /* captionHeight */ )
+ onClickListener, onTouchListener, appIcon, appName, displayController,
+ splitScreenController, true /* shouldShowWindowingPill */,
+ true /* shouldShowBrowserPill */, 50 /* captionHeight */)
handleMenu.show()
return handleMenu
}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 124f1f0ddd43..25c767a88b17 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -7435,6 +7435,21 @@ public class AudioManager {
}
/**
+ * @hide
+ * Queries the volume policy
+ * @return the volume policy currently in use
+ */
+ @TestApi
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+ public @NonNull VolumePolicy getVolumePolicy() {
+ try {
+ return getService().getVolumePolicy();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Set Hdmi Cec system audio mode.
*
* @param on whether to be on system audio mode
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 08cc126fb1a8..d42b25681e11 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -386,6 +386,8 @@ interface IAudioService {
void setVolumePolicy(in VolumePolicy policy);
+ VolumePolicy getVolumePolicy();
+
boolean hasRegisteredDynamicPolicy();
void registerRecordingCallback(in IRecordingConfigDispatcher rcdb);
diff --git a/media/java/android/media/VolumePolicy.java b/media/java/android/media/VolumePolicy.java
index b193b70bac2c..96de72dfd10d 100644
--- a/media/java/android/media/VolumePolicy.java
+++ b/media/java/android/media/VolumePolicy.java
@@ -16,39 +16,53 @@
package android.media;
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Objects;
/** @hide */
+@TestApi
+@SuppressLint("UnflaggedApi") // @TestApi without associated feature.
public final class VolumePolicy implements Parcelable {
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+ @NonNull
public static final VolumePolicy DEFAULT = new VolumePolicy(false, false, false, 400);
/**
* Accessibility volume policy where the STREAM_MUSIC volume (i.e. media volume) affects
* the STREAM_ACCESSIBILITY volume, and vice-versa.
*/
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
public static final int A11Y_MODE_MEDIA_A11Y_VOLUME = 0;
/**
* Accessibility volume policy where the STREAM_ACCESSIBILITY volume is independent from
* any other volume.
*/
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
public static final int A11Y_MODE_INDEPENDENT_A11Y_VOLUME = 1;
/** Allow volume adjustments lower from vibrate to enter ringer mode = silent */
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
public final boolean volumeDownToEnterSilent;
/** Allow volume adjustments higher to exit ringer mode = silent */
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
public final boolean volumeUpToExitSilent;
/** Automatically enter do not disturb when ringer mode = silent */
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
public final boolean doNotDisturbWhenSilent;
/** Only allow volume adjustment from vibrate to silent after this
number of milliseconds since an adjustment from normal to vibrate. */
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
public final int vibrateToSilentDebounce;
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
public VolumePolicy(boolean volumeDownToEnterSilent, boolean volumeUpToExitSilent,
boolean doNotDisturbWhenSilent, int vibrateToSilentDebounce) {
this.volumeDownToEnterSilent = volumeDownToEnterSilent;
@@ -82,19 +96,22 @@ public final class VolumePolicy implements Parcelable {
&& other.vibrateToSilentDebounce == vibrateToSilentDebounce;
}
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
@Override
public int describeContents() {
return 0;
}
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(volumeDownToEnterSilent ? 1 : 0);
dest.writeInt(volumeUpToExitSilent ? 1 : 0);
dest.writeInt(doNotDisturbWhenSilent ? 1 : 0);
dest.writeInt(vibrateToSilentDebounce);
}
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
public static final @android.annotation.NonNull Parcelable.Creator<VolumePolicy> CREATOR
= new Parcelable.Creator<VolumePolicy>() {
@Override
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
index 66ab81bf02b1..e00533422072 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
@@ -554,11 +554,18 @@ public class CompanionAssociationActivity extends FragmentActivity implements
mSelectedDevice = requireNonNull(selectedDevice);
Slog.d(TAG, "onDeviceClicked(): " + mSelectedDevice.toShortString());
-
+ // The permission consent dialog should not be displayed if it's a isSkipPrompt(true)
+ // AssociationRequest or when there is no device profile available
+ // for the multiple devices dialog.
+ // See AssociationRequestsProcessor#mayAssociateWithoutPrompt.
+ final String deviceProfile = mRequest.getDeviceProfile();
+ if (deviceProfile == null || mRequest.isSkipPrompt()) {
+ onUserSelectedDevice(mSelectedDevice);
+ return;
+ }
+ // The permission consent dialog should be displayed for the multiple device
+ // dialog if a device profile exists.
updateSingleDeviceUi();
-
- if (mRequest.isSkipPrompt()) return;
-
mSummary.setVisibility(View.VISIBLE);
mButtonAllow.setVisibility(View.VISIBLE);
mButtonNotAllow.setVisibility(View.VISIBLE);
@@ -588,9 +595,6 @@ public class CompanionAssociationActivity extends FragmentActivity implements
if (deviceProfile == null && mRequest.isSingleDevice()) {
summary = getHtmlFromResources(this, summaryResourceId, remoteDeviceName);
mConstraintList.setVisibility(View.GONE);
- } else if (deviceProfile == null) {
- onUserSelectedDevice(mSelectedDevice);
- return;
} else {
summary = getHtmlFromResources(
this, summaryResourceId, getString(R.string.device_type));
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index d4a4703d5caf..5f236516785d 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -228,6 +228,7 @@ public class SecureSettings {
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED,
+ Settings.Secure.ACCESSIBILITY_MOUSE_KEYS_ENABLED,
Settings.Secure.ACCESSIBILITY_PINCH_TO_ZOOM_ANYWHERE_ENABLED,
Settings.Secure.ACCESSIBILITY_SINGLE_FINGER_PANNING_ENABLED,
Settings.Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 6df1c45bd2ac..c8da8afc25c3 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -439,6 +439,7 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS, ANY_LONG_VALIDATOR);
VALIDATORS.put(Secure.ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS, ANY_LONG_VALIDATOR);
VALIDATORS.put(Secure.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS, NONE_NEGATIVE_LONG_VALIDATOR);
+ VALIDATORS.put(Secure.ACCESSIBILITY_MOUSE_KEYS_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.MANDATORY_BIOMETRICS, new InclusiveIntegerRangeValidator(0, 1));
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index e77cf2fa6543..2227943c0cc0 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -189,22 +189,13 @@ public final class DeviceConfigService extends Binder {
public static HashMap<String, String> getAllFlags(IContentProvider provider) {
HashMap<String, String> allFlags = new HashMap<String, String>();
- try {
- Bundle args = new Bundle();
- args.putInt(Settings.CALL_METHOD_USER_KEY,
- ActivityManager.getService().getCurrentUser().id);
- Bundle b = provider.call(new AttributionSource(Process.myUid(),
- resolveCallingPackage(), null), Settings.AUTHORITY,
- Settings.CALL_METHOD_LIST_CONFIG, null, args);
- if (b != null) {
- Map<String, String> flagsToValues =
- (HashMap) b.getSerializable(Settings.NameValueTable.VALUE);
- allFlags.putAll(flagsToValues);
+ for (DeviceConfig.Properties properties : DeviceConfig.getAllProperties()) {
+ List<String> keys = new ArrayList<>(properties.getKeyset());
+ for (String flagName : properties.getKeyset()) {
+ String fullName = properties.getNamespace() + "/" + flagName;
+ allFlags.put(fullName, properties.getString(flagName, null));
}
- } catch (RemoteException e) {
- throw new RuntimeException("Failed in IPC", e);
}
-
return allFlags;
}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index a1f1a08d4bf2..e2ecda320065 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -591,13 +591,6 @@ flag {
}
flag {
- name: "screenshot_shelf_ui2"
- namespace: "systemui"
- description: "Use new shelf UI flow for screenshots"
- bug: "329659738"
-}
-
-flag {
name: "run_fingerprint_detect_on_dismissible_keyguard"
namespace: "systemui"
description: "Run fingerprint detect instead of authenticate if the keyguard is dismissible."
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index d0466313cf81..43d51c37aa36 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -28,7 +28,6 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.Back
-import com.android.compose.animation.scene.CommunalSwipeDetector
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.ElementMatcher
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index e2d693eb1089..f6535ec0b710 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -661,10 +661,7 @@ private fun Toolbar(
val addWidgetText = stringResource(R.string.hub_mode_add_widget_button_text)
ToolbarButton(
isPrimary = !removeEnabled,
- modifier =
- Modifier.align(Alignment.CenterStart).semantics {
- contentDescription = addWidgetText
- },
+ modifier = Modifier.align(Alignment.CenterStart),
onClick = onOpenWidgetPicker,
) {
Icon(Icons.Default.Add, null)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/CommunalSwipeDetector.kt b/packages/SystemUI/compose/scene/src/com/android/systemui/communal/ui/compose/CommunalSwipeDetector.kt
index 7be34cabfaf8..3fda9b85a5c2 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/CommunalSwipeDetector.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/systemui/communal/ui/compose/CommunalSwipeDetector.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.compose.animation.scene
+package com.android.systemui.communal.ui.compose
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.input.pointer.PointerInputChange
@@ -22,10 +22,12 @@ import androidx.compose.ui.input.pointer.positionChange
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.SwipeDetector
+import com.android.compose.animation.scene.SwipeSource
+import com.android.compose.animation.scene.SwipeSourceDetector
import kotlin.math.abs
-private const val TRAVEL_RATIO_THRESHOLD = .5f
-
/**
* {@link CommunalSwipeDetector} provides an implementation of {@link SwipeDetector} and {@link
* SwipeSourceDetector} to enable fullscreen swipe handling to transition to and from the glanceable
@@ -33,6 +35,10 @@ private const val TRAVEL_RATIO_THRESHOLD = .5f
*/
class CommunalSwipeDetector(private var lastDirection: SwipeSource? = null) :
SwipeSourceDetector, SwipeDetector {
+ companion object {
+ private const val TRAVEL_RATIO_THRESHOLD = .5f
+ }
+
override fun source(
layoutSize: IntSize,
position: IntOffset,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorTest.kt
new file mode 100644
index 000000000000..e4916b1a7e46
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorTest.kt
@@ -0,0 +1,126 @@
+/*
+ * 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.communal.domain.interactor
+
+import android.app.admin.DevicePolicyManager
+import android.app.admin.devicePolicyManager
+import android.content.Intent
+import android.content.pm.UserInfo
+import android.os.UserManager
+import android.os.userManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.fakeUserTracker
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.repository.fakeUserRepository
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalSettingsInteractorTest : SysuiTestCase() {
+
+ private lateinit var userManager: UserManager
+ private lateinit var userRepository: FakeUserRepository
+ private lateinit var userTracker: FakeUserTracker
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private lateinit var underTest: CommunalSettingsInteractor
+
+ @Before
+ fun setUp() {
+ userManager = kosmos.userManager
+ userRepository = kosmos.fakeUserRepository
+ userTracker = kosmos.fakeUserTracker
+
+ val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
+ userRepository.setUserInfos(userInfos)
+ userTracker.set(
+ userInfos = userInfos,
+ selectedUserIndex = 0,
+ )
+
+ underTest = kosmos.communalSettingsInteractor
+ }
+
+ @Test
+ fun filterUsers_dontFilteredUsersWhenAllAreAllowed() =
+ testScope.runTest {
+ // If no users have any keyguard features disabled...
+ val disallowedUser by
+ collectLastValue(underTest.workProfileUserDisallowedByDevicePolicy)
+ // ...then the disallowed user should be null
+ assertNull(disallowedUser)
+ }
+
+ @Test
+ fun filterUsers_filterWorkProfileUserWhenDisallowed() =
+ testScope.runTest {
+ // If the work profile user has keyguard widgets disabled...
+ setKeyguardFeaturesDisabled(
+ USER_INFO_WORK,
+ DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL
+ )
+ // ...then the disallowed user match the work profile
+ val disallowedUser by
+ collectLastValue(underTest.workProfileUserDisallowedByDevicePolicy)
+ assertNotNull(disallowedUser)
+ assertEquals(USER_INFO_WORK.id, disallowedUser!!.id)
+ }
+
+ private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) {
+ whenever(
+ kosmos.devicePolicyManager.getKeyguardDisabledFeatures(
+ anyOrNull(),
+ ArgumentMatchers.eq(user.id)
+ )
+ )
+ .thenReturn(disabledFlags)
+ kosmos.broadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
+ )
+ }
+
+ private companion object {
+ val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
+ val USER_INFO_WORK =
+ UserInfo(
+ 10,
+ "work",
+ /* iconPath= */ "",
+ /* flags= */ 0,
+ UserManager.USER_TYPE_PROFILE_MANAGED,
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.kt
new file mode 100644
index 000000000000..1b704b4c4902
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.kt
@@ -0,0 +1,236 @@
+/*
+ * 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.util.service
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.testKosmos
+import com.android.systemui.util.service.ObservableServiceConnection.DISCONNECT_REASON_DISCONNECTED
+import com.android.systemui.util.time.fakeSystemClock
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PersistentConnectionManagerTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
+ private val fakeClock = kosmos.fakeSystemClock
+ private val fakeExecutor = kosmos.fakeExecutor
+
+ private class Proxy {
+ // Fake proxy class
+ }
+
+ private val connection: ObservableServiceConnection<Proxy> = mock()
+ private val observer: Observer = mock()
+
+ private val underTest: PersistentConnectionManager<Proxy> by lazy {
+ PersistentConnectionManager(
+ /* clock = */ fakeClock,
+ /* bgExecutor = */ fakeExecutor,
+ /* dumpManager = */ kosmos.dumpManager,
+ /* dumpsysName = */ DUMPSYS_NAME,
+ /* serviceConnection = */ connection,
+ /* maxReconnectAttempts = */ MAX_RETRIES,
+ /* baseReconnectDelayMs = */ RETRY_DELAY_MS,
+ /* minConnectionDurationMs = */ CONNECTION_MIN_DURATION_MS,
+ /* observer = */ observer
+ )
+ }
+
+ /** Validates initial connection. */
+ @Test
+ fun testConnect() {
+ underTest.start()
+ captureCallbackAndVerifyBind(connection).onConnected(connection, mock<Proxy>())
+ }
+
+ /** Ensures reconnection on disconnect. */
+ @Test
+ fun testExponentialRetryOnDisconnect() {
+ underTest.start()
+
+ // IF service is connected...
+ val captor = argumentCaptor<ObservableServiceConnection.Callback<Proxy>>()
+ verify(connection, times(1)).bind()
+ verify(connection).addCallback(captor.capture())
+ val callback = captor.lastValue
+ callback.onConnected(connection, mock<Proxy>())
+
+ // ...AND service becomes disconnected within CONNECTION_MIN_DURATION_MS
+ callback.onDisconnected(connection, DISCONNECT_REASON_DISCONNECTED)
+
+ // THEN verify we retry to bind after the retry delay. (RETRY #1)
+ verify(connection, times(1)).bind()
+ fakeClock.advanceTime(RETRY_DELAY_MS.toLong())
+ verify(connection, times(2)).bind()
+
+ // IF service becomes disconnected for a second time after first retry...
+ callback.onConnected(connection, mock<Proxy>())
+ callback.onDisconnected(connection, DISCONNECT_REASON_DISCONNECTED)
+
+ // THEN verify we retry after a longer delay of 2 * RETRY_DELAY_MS (RETRY #2)
+ fakeClock.advanceTime(RETRY_DELAY_MS.toLong())
+ verify(connection, times(2)).bind()
+ fakeClock.advanceTime(RETRY_DELAY_MS.toLong())
+ verify(connection, times(3)).bind()
+
+ // IF service becomes disconnected for a third time after the second retry...
+ callback.onConnected(connection, mock<Proxy>())
+ callback.onDisconnected(connection, DISCONNECT_REASON_DISCONNECTED)
+
+ // THEN verify we retry after a longer delay of 4 * RETRY_DELAY_MS (RETRY #3)
+ fakeClock.advanceTime(3 * RETRY_DELAY_MS.toLong())
+ verify(connection, times(3)).bind()
+ fakeClock.advanceTime(RETRY_DELAY_MS.toLong())
+ verify(connection, times(4)).bind()
+ }
+
+ @Test
+ fun testDoesNotRetryAfterMaxRetries() {
+ underTest.start()
+
+ val captor = argumentCaptor<ObservableServiceConnection.Callback<Proxy>>()
+ verify(connection).addCallback(captor.capture())
+ val callback = captor.lastValue
+
+ // IF we retry MAX_TRIES times...
+ for (attemptCount in 0 until MAX_RETRIES + 1) {
+ verify(connection, times(attemptCount + 1)).bind()
+ callback.onConnected(connection, mock<Proxy>())
+ callback.onDisconnected(connection, DISCONNECT_REASON_DISCONNECTED)
+ fakeClock.advanceTime(Math.scalb(RETRY_DELAY_MS.toDouble(), attemptCount).toLong())
+ }
+
+ // THEN we should not retry again after the last attempt.
+ fakeExecutor.advanceClockToLast()
+ verify(connection, times(MAX_RETRIES + 1)).bind()
+ }
+
+ @Test
+ fun testEnsureNoRetryIfServiceNeverConnectsAfterRetry() {
+ underTest.start()
+
+ with(captureCallbackAndVerifyBind(connection)) {
+ // IF service initially connects and then disconnects...
+ onConnected(connection, mock<Proxy>())
+ onDisconnected(connection, DISCONNECT_REASON_DISCONNECTED)
+ fakeExecutor.advanceClockToLast()
+ fakeExecutor.runAllReady()
+
+ // ...AND we retry once.
+ verify(connection, times(1)).bind()
+
+ // ...AND service disconnects after initial retry without ever connecting again.
+ onDisconnected(connection, DISCONNECT_REASON_DISCONNECTED)
+ fakeExecutor.advanceClockToLast()
+ fakeExecutor.runAllReady()
+
+ // THEN verify another retry is not triggered.
+ verify(connection, times(1)).bind()
+ }
+ }
+
+ @Test
+ fun testEnsureNoRetryIfServiceNeverInitiallyConnects() {
+ underTest.start()
+
+ with(captureCallbackAndVerifyBind(connection)) {
+ // IF service never connects and we just receive the disconnect signal...
+ onDisconnected(connection, DISCONNECT_REASON_DISCONNECTED)
+ fakeExecutor.advanceClockToLast()
+ fakeExecutor.runAllReady()
+
+ // THEN do not retry
+ verify(connection, never()).bind()
+ }
+ }
+
+ /** Ensures manual unbind does not reconnect. */
+ @Test
+ fun testStopDoesNotReconnect() {
+ underTest.start()
+
+ val connectionCallbackCaptor = argumentCaptor<ObservableServiceConnection.Callback<Proxy>>()
+ verify(connection).addCallback(connectionCallbackCaptor.capture())
+ verify(connection).bind()
+ clearInvocations(connection)
+
+ underTest.stop()
+ fakeExecutor.advanceClockToNext()
+ fakeExecutor.runAllReady()
+ verify(connection, never()).bind()
+ }
+
+ /** Ensures rebind on package change. */
+ @Test
+ fun testAttemptOnPackageChange() {
+ underTest.start()
+
+ verify(connection).bind()
+
+ val callbackCaptor = argumentCaptor<Observer.Callback>()
+ captureCallbackAndVerifyBind(connection).onConnected(connection, mock<Proxy>())
+
+ verify(observer).addCallback(callbackCaptor.capture())
+ callbackCaptor.lastValue.onSourceChanged()
+ verify(connection).bind()
+ }
+
+ @Test
+ fun testAddConnectionCallback() {
+ val connectionCallback: ObservableServiceConnection.Callback<Proxy> = mock()
+ underTest.addConnectionCallback(connectionCallback)
+ verify(connection).addCallback(connectionCallback)
+ }
+
+ @Test
+ fun testRemoveConnectionCallback() {
+ val connectionCallback: ObservableServiceConnection.Callback<Proxy> = mock()
+ underTest.removeConnectionCallback(connectionCallback)
+ verify(connection).removeCallback(connectionCallback)
+ }
+
+ /** Helper method to capture the [ObservableServiceConnection.Callback] */
+ private fun captureCallbackAndVerifyBind(
+ mConnection: ObservableServiceConnection<Proxy>,
+ ): ObservableServiceConnection.Callback<Proxy> {
+
+ val connectionCallbackCaptor = argumentCaptor<ObservableServiceConnection.Callback<Proxy>>()
+ verify(mConnection).addCallback(connectionCallbackCaptor.capture())
+ verify(mConnection).bind()
+ clearInvocations(mConnection)
+
+ return connectionCallbackCaptor.lastValue
+ }
+
+ companion object {
+ private const val MAX_RETRIES = 3
+ private const val RETRY_DELAY_MS = 1000
+ private const val CONNECTION_MIN_DURATION_MS = 5000
+ private const val DUMPSYS_NAME = "dumpsys_name"
+ }
+}
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index 250076950907..65005f840598 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -24,18 +24,29 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/clipboard_overlay_window_name">
- <ImageView
+ <!-- Min edge spacing guideline off of which the preview and actions can be anchored (without
+ this we'd need to express margins as the sum of two different dimens). -->
+ <androidx.constraintlayout.widget.Guideline
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/min_edge_guideline"
+ app:layout_constraintGuide_begin="@dimen/overlay_action_container_minimum_edge_spacing"
+ android:orientation="vertical"/>
+ <!-- Negative horizontal margin because this container background must render beyond the thing
+ it's constrained by (the actions themselves). -->
+ <FrameLayout
android:id="@+id/actions_container_background"
android:visibility="gone"
android:layout_height="0dp"
android:layout_width="0dp"
android:elevation="4dp"
- android:background="@drawable/action_chip_container_background"
- android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
+ android:background="@drawable/shelf_action_chip_container_background"
+ android:layout_marginStart="@dimen/negative_overlay_action_container_minimum_edge_spacing"
+ android:layout_marginEnd="@dimen/negative_overlay_action_container_minimum_edge_spacing"
android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="@+id/actions_container"
- app:layout_constraintEnd_toEndOf="@+id/actions_container"
+ app:layout_constraintStart_toStartOf="@id/min_edge_guideline"
+ app:layout_constraintTop_toTopOf="@id/actions_container"
+ app:layout_constraintEnd_toEndOf="@id/actions_container"
app:layout_constraintBottom_toBottomOf="parent"/>
<HorizontalScrollView
android:id="@+id/actions_container"
@@ -56,10 +67,13 @@
android:id="@+id/actions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:paddingStart="@dimen/shelf_action_chip_margin_start"
+ android:showDividers="middle"
+ android:divider="@drawable/shelf_action_chip_divider"
android:animateLayoutChanges="true">
- <include layout="@layout/overlay_action_chip"
+ <include layout="@layout/shelf_action_chip"
android:id="@+id/share_chip"/>
- <include layout="@layout/overlay_action_chip"
+ <include layout="@layout/shelf_action_chip"
android:id="@+id/remote_copy_chip"/>
</LinearLayout>
</HorizontalScrollView>
@@ -73,7 +87,7 @@
android:layout_marginBottom="@dimen/overlay_preview_container_margin"
android:elevation="7dp"
android:background="@drawable/overlay_border"
- app:layout_constraintStart_toStartOf="@id/actions_container_background"
+ app:layout_constraintStart_toStartOf="@id/min_edge_guideline"
app:layout_constraintTop_toTopOf="@id/clipboard_preview"
app:layout_constraintEnd_toEndOf="@id/clipboard_preview"
app:layout_constraintBottom_toBottomOf="@id/actions_container_background"/>
diff --git a/packages/SystemUI/res/layout/clipboard_overlay2.xml b/packages/SystemUI/res/layout/clipboard_overlay2.xml
deleted file mode 100644
index 65005f840598..000000000000
--- a/packages/SystemUI/res/layout/clipboard_overlay2.xml
+++ /dev/null
@@ -1,202 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2021 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<com.android.systemui.clipboardoverlay.ClipboardOverlayView
- 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"
- android:id="@+id/clipboard_ui"
- android:theme="@style/FloatingOverlay"
- android:alpha="0"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:contentDescription="@string/clipboard_overlay_window_name">
- <!-- Min edge spacing guideline off of which the preview and actions can be anchored (without
- this we'd need to express margins as the sum of two different dimens). -->
- <androidx.constraintlayout.widget.Guideline
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:id="@+id/min_edge_guideline"
- app:layout_constraintGuide_begin="@dimen/overlay_action_container_minimum_edge_spacing"
- android:orientation="vertical"/>
- <!-- Negative horizontal margin because this container background must render beyond the thing
- it's constrained by (the actions themselves). -->
- <FrameLayout
- android:id="@+id/actions_container_background"
- android:visibility="gone"
- android:layout_height="0dp"
- android:layout_width="0dp"
- android:elevation="4dp"
- android:background="@drawable/shelf_action_chip_container_background"
- android:layout_marginStart="@dimen/negative_overlay_action_container_minimum_edge_spacing"
- android:layout_marginEnd="@dimen/negative_overlay_action_container_minimum_edge_spacing"
- android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
- app:layout_constraintStart_toStartOf="@id/min_edge_guideline"
- app:layout_constraintTop_toTopOf="@id/actions_container"
- app:layout_constraintEnd_toEndOf="@id/actions_container"
- app:layout_constraintBottom_toBottomOf="parent"/>
- <HorizontalScrollView
- android:id="@+id/actions_container"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal"
- android:paddingEnd="@dimen/overlay_action_container_padding_end"
- android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
- android:elevation="4dp"
- android:scrollbars="none"
- app:layout_constraintHorizontal_bias="0"
- app:layout_constraintWidth_percent="1.0"
- app:layout_constraintWidth_max="wrap"
- app:layout_constraintStart_toEndOf="@+id/preview_border"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintBottom_toBottomOf="@id/actions_container_background">
- <LinearLayout
- android:id="@+id/actions"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:paddingStart="@dimen/shelf_action_chip_margin_start"
- android:showDividers="middle"
- android:divider="@drawable/shelf_action_chip_divider"
- android:animateLayoutChanges="true">
- <include layout="@layout/shelf_action_chip"
- android:id="@+id/share_chip"/>
- <include layout="@layout/shelf_action_chip"
- android:id="@+id/remote_copy_chip"/>
- </LinearLayout>
- </HorizontalScrollView>
- <View
- android:id="@+id/preview_border"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_marginStart="@dimen/overlay_preview_container_margin"
- android:layout_marginTop="@dimen/overlay_border_width_neg"
- android:layout_marginEnd="@dimen/overlay_border_width_neg"
- android:layout_marginBottom="@dimen/overlay_preview_container_margin"
- android:elevation="7dp"
- android:background="@drawable/overlay_border"
- app:layout_constraintStart_toStartOf="@id/min_edge_guideline"
- app:layout_constraintTop_toTopOf="@id/clipboard_preview"
- app:layout_constraintEnd_toEndOf="@id/clipboard_preview"
- app:layout_constraintBottom_toBottomOf="@id/actions_container_background"/>
- <FrameLayout
- android:id="@+id/clipboard_preview"
- android:layout_width="@dimen/clipboard_preview_size"
- android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/overlay_border_width"
- android:layout_marginBottom="@dimen/overlay_border_width"
- android:layout_gravity="center"
- android:elevation="7dp"
- android:background="@drawable/overlay_preview_background"
- android:clipChildren="true"
- android:clipToOutline="true"
- android:clipToPadding="true"
- app:layout_constraintStart_toStartOf="@id/preview_border"
- app:layout_constraintBottom_toBottomOf="@id/preview_border">
- <TextView android:id="@+id/text_preview"
- android:textFontWeight="500"
- android:padding="8dp"
- android:gravity="center|start"
- android:ellipsize="end"
- android:autoSizeTextType="uniform"
- android:autoSizeMinTextSize="@dimen/clipboard_overlay_min_font"
- android:autoSizeMaxTextSize="@dimen/clipboard_overlay_max_font"
- android:textColor="?attr/overlayButtonTextColor"
- android:textColorLink="?attr/overlayButtonTextColor"
- android:background="?androidprv:attr/colorAccentSecondary"
- android:layout_width="@dimen/clipboard_preview_size"
- android:layout_height="@dimen/clipboard_preview_size"/>
- <ImageView
- android:id="@+id/image_preview"
- android:scaleType="fitCenter"
- android:adjustViewBounds="true"
- android:contentDescription="@string/clipboard_image_preview"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"/>
- <TextView
- android:id="@+id/hidden_preview"
- android:visibility="gone"
- android:textFontWeight="500"
- android:padding="8dp"
- android:gravity="center"
- android:textSize="14sp"
- android:textColor="?attr/overlayButtonTextColor"
- android:background="?androidprv:attr/colorAccentSecondary"
- android:layout_width="@dimen/clipboard_preview_size"
- android:layout_height="@dimen/clipboard_preview_size"/>
- </FrameLayout>
- <LinearLayout
- android:id="@+id/minimized_preview"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:visibility="gone"
- android:elevation="7dp"
- android:padding="8dp"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
- android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
- android:background="@drawable/clipboard_minimized_background">
- <ImageView
- android:src="@drawable/ic_content_paste"
- android:tint="?attr/overlayButtonTextColor"
- android:layout_width="24dp"
- android:layout_height="24dp"/>
- <ImageView
- android:src="@*android:drawable/ic_chevron_end"
- android:tint="?attr/overlayButtonTextColor"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:paddingEnd="-8dp"
- android:paddingStart="-4dp"/>
- </LinearLayout>
- <androidx.constraintlayout.widget.Barrier
- android:id="@+id/clipboard_content_top"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- app:barrierDirection="top"
- app:constraint_referenced_ids="clipboard_preview,minimized_preview"/>
- <androidx.constraintlayout.widget.Barrier
- android:id="@+id/clipboard_content_end"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- app:barrierDirection="end"
- app:constraint_referenced_ids="clipboard_preview,minimized_preview"/>
- <FrameLayout
- android:id="@+id/dismiss_button"
- android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
- android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
- android:elevation="10dp"
- android:visibility="gone"
- android:alpha="0"
- app:layout_constraintStart_toEndOf="@id/clipboard_content_end"
- app:layout_constraintEnd_toEndOf="@id/clipboard_content_end"
- app:layout_constraintTop_toTopOf="@id/clipboard_content_top"
- app:layout_constraintBottom_toTopOf="@id/clipboard_content_top"
- android:contentDescription="@string/clipboard_dismiss_description">
- <ImageView
- android:id="@+id/dismiss_image"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_margin="@dimen/overlay_dismiss_button_margin"
- android:background="@drawable/circular_background"
- android:backgroundTint="?androidprv:attr/materialColorPrimaryFixedDim"
- android:tint="?androidprv:attr/materialColorOnPrimaryFixed"
- android:padding="4dp"
- android:src="@drawable/ic_close"/>
- </FrameLayout>
-</com.android.systemui.clipboardoverlay.ClipboardOverlayView> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/screen_share_dialog.xml b/packages/SystemUI/res/layout/screen_share_dialog.xml
index 2616e8ae25e8..aa083ad9fdea 100644
--- a/packages/SystemUI/res/layout/screen_share_dialog.xml
+++ b/packages/SystemUI/res/layout/screen_share_dialog.xml
@@ -46,7 +46,7 @@
android:layout_marginTop="@dimen/screenrecord_title_margin_top"
android:gravity="center"/>
<Spinner
- android:id="@+id/screen_share_mode_spinner"
+ android:id="@+id/screen_share_mode_options"
android:layout_width="match_parent"
android:layout_height="@dimen/screenrecord_spinner_height"
android:layout_marginTop="@dimen/screenrecord_spinner_margin"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 0bc2c82538c8..dee55289958e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -321,11 +321,11 @@
<!-- A toast message shown when the screen recording cannot be started due to a generic error [CHAR LIMIT=NONE] -->
<string name="screenrecord_start_error">Error starting screen recording</string>
<!-- Title for a dialog shown to the user that will let them stop recording their screen [CHAR LIMIT=50] -->
- <string name="screenrecord_stop_dialog_title">Stop recording screen?</string>
- <!-- Text telling a user that they will stop recording their screen if they click the "Stop recording" button [CHAR LIMIT=100] -->
- <string name="screenrecord_stop_dialog_message">You will stop recording your screen</string>
- <!-- Text telling a user that they will stop recording the contents of the specified [app_name] if they click the "Stop recording" button. Note that the app name will appear in bold. [CHAR LIMIT=100] -->
- <string name="screenrecord_stop_dialog_message_specific_app">You will stop recording &lt;b><xliff:g id="app_name" example="Photos App">%1$s</xliff:g>&lt;/b></string>
+ <string name="screenrecord_stop_dialog_title">Stop recording?</string>
+ <!-- Text telling a user that they're currently recording their screen [CHAR LIMIT=100] -->
+ <string name="screenrecord_stop_dialog_message">You\'re currently recording your entire screen</string>
+ <!-- Text telling a user that they're currently recording the contents of the specified [app_name]. [CHAR LIMIT=100] -->
+ <string name="screenrecord_stop_dialog_message_specific_app">You\'re currently recording <xliff:g id="app_name" example="Photos App">%1$s</xliff:g></string>
<!-- Button to stop a screen recording [CHAR LIMIT=35] -->
<string name="screenrecord_stop_dialog_button">Stop recording</string>
@@ -333,25 +333,33 @@
<string name="share_to_app_chip_accessibility_label">Sharing screen</string>
<!-- Title for a dialog shown to the user that will let them stop sharing their screen to another app on the device [CHAR LIMIT=50] -->
<string name="share_to_app_stop_dialog_title">Stop sharing screen?</string>
- <!-- Text telling a user that they will stop sharing their screen if they click the "Stop sharing" button [CHAR LIMIT=100] -->
- <string name="share_to_app_stop_dialog_message">You will stop sharing your screen</string>
- <!-- Text telling a user that they will stop sharing the contents of the specified [app_name] if they click the "Stop sharing" button. Note that the app name will appear in bold. [CHAR LIMIT=100] -->
- <string name="share_to_app_stop_dialog_message_specific_app">You will stop sharing &lt;b><xliff:g id="app_name" example="Photos App">%1$s</xliff:g>&lt;/b></string>
+ <!-- Text telling a user that they're currently sharing their entire screen to [host_app_name] (i.e. [host_app_name] can currently see all screen content) [CHAR LIMIT=150] -->
+ <string name="share_to_app_stop_dialog_message_entire_screen_with_host_app">You\'re currently sharing your entire screen with <xliff:g id="host_app_name" example="Screen Recorder App">%1$s</xliff:g></string>
+ <!-- Text telling a user that they're currently sharing their entire screen to an app (but we don't know what app) [CHAR LIMIT=150] -->
+ <string name="share_to_app_stop_dialog_message_entire_screen">You\'re currently sharing your entire screen with an app</string>
+ <!-- Text telling a user that they're currently sharing the contents of [app_being_shared_name]. (i.e. some app can currently see the content of [app_being_shared_name]). [CHAR LIMIT=150] -->
+ <string name="share_to_app_stop_dialog_message_single_app_specific">You\'re currently sharing <xliff:g id="app_being_shared_name" example="Photos App">%1$s</xliff:g></string>
+ <!-- Text telling a user that they're currently sharing their screen [CHAR LIMIT=150] -->
+ <string name="share_to_app_stop_dialog_message_single_app_generic">You\'re currently sharing an app</string>
<!-- Button to stop screen sharing [CHAR LIMIT=35] -->
<string name="share_to_app_stop_dialog_button">Stop sharing</string>
<!-- Content description for the status bar chip shown to the user when they're casting their screen to a different device [CHAR LIMIT=NONE] -->
<string name="cast_screen_to_other_device_chip_accessibility_label">Casting screen</string>
- <!-- Title for a dialog shown to the user that will let them stop casting their screen to a different device [CHAR LIMIT=50] -->
- <string name="cast_screen_to_other_device_stop_dialog_title">Stop casting screen?</string>
<!-- Title for a dialog shown to the user that will let them stop casting to a different device [CHAR LIMIT=50] -->
<string name="cast_to_other_device_stop_dialog_title">Stop casting?</string>
- <!-- Text telling a user that they will stop casting their screen to a different device if they click the "Stop casting" button [CHAR LIMIT=100] -->
- <string name="cast_screen_to_other_device_stop_dialog_message">You will stop casting your screen</string>
- <!-- Text telling a user that they will stop casting the contents of the specified [app_name] to a different device if they click the "Stop casting" button. Note that the app name will appear in bold. [CHAR LIMIT=100] -->
- <string name="cast_screen_to_other_device_stop_dialog_message_specific_app">You will stop casting &lt;b><xliff:g id="app_name" example="Photos App">%1$s</xliff:g>&lt;/b></string>
- <!-- Text telling a user that they're currently casting to a different device [CHAR LIMIT=100] -->
- <string name="cast_to_other_device_stop_dialog_message">You\'re currently casting</string>
+ <!-- Text telling a user that they're currently casting their screen to a different device. The device receiving the cast is named [device_name]. [CHAR LIMIT=150] -->
+ <string name="cast_to_other_device_stop_dialog_message_entire_screen_with_device">You\'re currently casting your entire screen to <xliff:g id="device_name" example="Living Room Device">%1$s</xliff:g></string>
+ <!-- Text telling a user that they're currently casting their screen to a nearby device. [CHAR LIMIT=150] -->
+ <string name="cast_to_other_device_stop_dialog_message_entire_screen">You\'re currently casting your entire screen to a nearby device</string>
+ <!-- Text telling a user that they're currently casting the contents of [app_being_shared_name] to a different device. The device receiving the cast is named [device_name]. [CHAR LIMIT=150] -->
+ <string name="cast_to_other_device_stop_dialog_message_specific_app_with_device">You\'re currently casting <xliff:g id="app_being_shared_name" example="Photos App">%1$s</xliff:g> to <xliff:g id="device_name" example="Living Room Device">%2$s</xliff:g></string>
+ <!-- Text telling a user that they're currently casting the contents of [app_being_shared_name] to a different device. [CHAR LIMIT=150] -->
+ <string name="cast_to_other_device_stop_dialog_message_specific_app">You\'re currently casting <xliff:g id="app_being_shared_name" example="Photos App">%1$s</xliff:g> to a nearby device</string>
+ <!-- Text telling a user that they're currently casting to a different device. The device receiving the cast is named [device_name]. [CHAR LIMIT=100] -->
+ <string name="cast_to_other_device_stop_dialog_message_generic_with_device">You\'re currently casting to <xliff:g id="device_name" example="Living Room Device">%1$s</xliff:g></string>
+ <!-- Text telling a user that they're currently casting to a nearby device [CHAR LIMIT=100] -->
+ <string name="cast_to_other_device_stop_dialog_message_generic">You\'re currently casting to a nearby device</string>
<!-- Button to stop screen casting to a different device [CHAR LIMIT=35] -->
<string name="cast_to_other_device_stop_dialog_button">Stop casting</string>
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
index ba236ba016ff..1762d82b3237 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
@@ -18,8 +18,6 @@ package com.android.systemui.clipboardoverlay;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static com.android.systemui.Flags.screenshotShelfUi2;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
@@ -61,7 +59,6 @@ import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import com.android.systemui.res.R;
import com.android.systemui.screenshot.DraggableConstraintLayout;
import com.android.systemui.screenshot.FloatingWindowUtil;
-import com.android.systemui.screenshot.OverlayActionChip;
import com.android.systemui.screenshot.ui.binder.ActionButtonViewBinder;
import com.android.systemui.screenshot.ui.viewmodel.ActionButtonAppearance;
import com.android.systemui.screenshot.ui.viewmodel.ActionButtonViewModel;
@@ -152,66 +149,47 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
}
private void bindDefaultActionChips() {
- if (screenshotShelfUi2()) {
- mActionButtonViewBinder.bind(mRemoteCopyChip,
- ActionButtonViewModel.Companion.withNextId(
- new ActionButtonAppearance(
- Icon.createWithResource(mContext,
- R.drawable.ic_baseline_devices_24).loadDrawable(
- mContext),
- null,
- mContext.getString(R.string.clipboard_send_nearby_description),
- true),
- new Function0<>() {
- @Override
- public Unit invoke() {
- if (mClipboardCallbacks != null) {
- mClipboardCallbacks.onRemoteCopyButtonTapped();
- }
- return null;
+ mActionButtonViewBinder.bind(mRemoteCopyChip,
+ ActionButtonViewModel.Companion.withNextId(
+ new ActionButtonAppearance(
+ Icon.createWithResource(mContext,
+ R.drawable.ic_baseline_devices_24).loadDrawable(
+ mContext),
+ null,
+ mContext.getString(R.string.clipboard_send_nearby_description),
+ true),
+ new Function0<>() {
+ @Override
+ public Unit invoke() {
+ if (mClipboardCallbacks != null) {
+ mClipboardCallbacks.onRemoteCopyButtonTapped();
}
- }));
- mActionButtonViewBinder.bind(mShareChip,
- ActionButtonViewModel.Companion.withNextId(
- new ActionButtonAppearance(
- Icon.createWithResource(mContext,
- R.drawable.ic_screenshot_share).loadDrawable(mContext),
- null,
- mContext.getString(com.android.internal.R.string.share),
- true),
- new Function0<>() {
- @Override
- public Unit invoke() {
- if (mClipboardCallbacks != null) {
- mClipboardCallbacks.onShareButtonTapped();
- }
- return null;
+ return null;
+ }
+ }));
+ mActionButtonViewBinder.bind(mShareChip,
+ ActionButtonViewModel.Companion.withNextId(
+ new ActionButtonAppearance(
+ Icon.createWithResource(mContext,
+ R.drawable.ic_screenshot_share).loadDrawable(mContext),
+ null,
+ mContext.getString(com.android.internal.R.string.share),
+ true),
+ new Function0<>() {
+ @Override
+ public Unit invoke() {
+ if (mClipboardCallbacks != null) {
+ mClipboardCallbacks.onShareButtonTapped();
}
- }));
- } else {
- mShareChip.setAlpha(1);
- mRemoteCopyChip.setAlpha(1);
-
- ((ImageView) mRemoteCopyChip.findViewById(R.id.overlay_action_chip_icon)).setImageIcon(
- Icon.createWithResource(mContext, R.drawable.ic_baseline_devices_24));
- ((ImageView) mShareChip.findViewById(R.id.overlay_action_chip_icon)).setImageIcon(
- Icon.createWithResource(mContext, R.drawable.ic_screenshot_share));
-
- mShareChip.setContentDescription(
- mContext.getString(com.android.internal.R.string.share));
- mRemoteCopyChip.setContentDescription(
- mContext.getString(R.string.clipboard_send_nearby_description));
- }
+ return null;
+ }
+ }));
}
@Override
public void setCallbacks(SwipeDismissCallbacks callbacks) {
super.setCallbacks(callbacks);
ClipboardOverlayCallbacks clipboardCallbacks = (ClipboardOverlayCallbacks) callbacks;
- if (!screenshotShelfUi2()) {
- mShareChip.setOnClickListener(v -> clipboardCallbacks.onShareButtonTapped());
- mRemoteCopyChip.setOnClickListener(v -> clipboardCallbacks.onRemoteCopyButtonTapped());
- }
mDismissButton.setOnClickListener(v -> clipboardCallbacks.onDismissButtonTapped());
mClipboardPreview.setOnClickListener(v -> clipboardCallbacks.onPreviewTapped());
mMinimizedPreview.setOnClickListener(v -> clipboardCallbacks.onMinimizedViewTapped());
@@ -495,12 +473,7 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
void setActionChip(RemoteAction action, Runnable onFinish) {
mActionContainerBackground.setVisibility(View.VISIBLE);
- View chip;
- if (screenshotShelfUi2()) {
- chip = constructShelfActionChip(action, onFinish);
- } else {
- chip = constructActionChip(action, onFinish);
- }
+ View chip = constructShelfActionChip(action, onFinish);
mActionContainer.addView(chip);
mActionChips.add(chip);
}
@@ -534,17 +507,6 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
return chip;
}
- private OverlayActionChip constructActionChip(RemoteAction action, Runnable onFinish) {
- OverlayActionChip chip = (OverlayActionChip) LayoutInflater.from(mContext).inflate(
- R.layout.overlay_action_chip, mActionContainer, false);
- chip.setText(action.getTitle());
- chip.setContentDescription(action.getTitle());
- chip.setIcon(action.getIcon(), false);
- chip.setPendingIntent(action.getActionIntent(), onFinish);
- chip.setAlpha(1);
- return chip;
- }
-
private static void updateTextSize(CharSequence text, TextView textView) {
Paint paint = new Paint(textView.getPaint());
Resources res = textView.getResources();
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java
index 740a93eb081c..ff9fba4c03f1 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java
@@ -18,8 +18,6 @@ package com.android.systemui.clipboardoverlay.dagger;
import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
-import static com.android.systemui.Flags.screenshotShelfUi2;
-
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import android.content.Context;
@@ -59,13 +57,8 @@ public interface ClipboardOverlayModule {
*/
@Provides
static ClipboardOverlayView provideClipboardOverlayView(@OverlayWindowContext Context context) {
- if (screenshotShelfUi2()) {
- return (ClipboardOverlayView) LayoutInflater.from(context).inflate(
- R.layout.clipboard_overlay2, null);
- } else {
- return (ClipboardOverlayView) LayoutInflater.from(context).inflate(
- R.layout.clipboard_overlay, null);
- }
+ return (ClipboardOverlayView) LayoutInflater.from(context).inflate(
+ R.layout.clipboard_overlay, null);
}
@Qualifier
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index f5255ac4d545..86f5fe1cac57 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -20,6 +20,7 @@ import android.app.smartspace.SmartspaceTarget
import android.content.ComponentName
import android.content.Intent
import android.content.IntentFilter
+import android.content.pm.UserInfo
import android.os.UserHandle
import android.os.UserManager
import android.provider.Settings
@@ -386,11 +387,11 @@ constructor(
combine(
widgetRepository.communalWidgets
.map { filterWidgetsByExistingUsers(it) }
- .combine(communalSettingsInteractor.allowedByDevicePolicyForWorkProfile) {
+ .combine(communalSettingsInteractor.workProfileUserDisallowedByDevicePolicy) {
// exclude widgets under work profile if not allowed by device policy
widgets,
- allowedForWorkProfile ->
- filterWidgetsAllowedByDevicePolicy(widgets, allowedForWorkProfile)
+ disallowedByPolicyUser ->
+ filterWidgetsAllowedByDevicePolicy(widgets, disallowedByPolicyUser)
},
updateOnWorkProfileBroadcastReceived,
) { widgets, _ ->
@@ -418,13 +419,11 @@ constructor(
/** Filter widgets based on whether their associated profile is allowed by device policy. */
private fun filterWidgetsAllowedByDevicePolicy(
list: List<CommunalWidgetContentModel>,
- allowedByDevicePolicyForWorkProfile: Boolean
+ disallowedByDevicePolicyUser: UserInfo?
): List<CommunalWidgetContentModel> =
- if (allowedByDevicePolicyForWorkProfile) {
+ if (disallowedByDevicePolicyUser == null) {
list
} else {
- // Get associated work profile for the currently selected user.
- val workProfile = userTracker.userProfiles.find { it.isManagedProfile }
list.filter { model ->
val uid =
when (model) {
@@ -432,7 +431,7 @@ constructor(
model.providerInfo.profile.identifier
is CommunalWidgetContentModel.Pending -> model.user.identifier
}
- uid != workProfile?.id
+ uid != disallowedByDevicePolicyUser.id
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
index 47b75c458d20..3b01aec6861d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
@@ -92,15 +92,22 @@ constructor(
awaitClose { userTracker.removeCallback(callback) }
}
- /** Whether or not keyguard widgets are allowed for work profile by device policy manager. */
- val allowedByDevicePolicyForWorkProfile: StateFlow<Boolean> =
+ /**
+ * A user that device policy says shouldn't allow communal widgets, or null if there are no
+ * restrictions.
+ */
+ val workProfileUserDisallowedByDevicePolicy: StateFlow<UserInfo?> =
workProfileUserInfoCallbackFlow
.flatMapLatest { workProfile ->
- workProfile?.let { repository.getAllowedByDevicePolicy(it) } ?: flowOf(false)
+ workProfile?.let {
+ repository.getAllowedByDevicePolicy(it).map { allowed ->
+ if (!allowed) it else null
+ }
+ } ?: flowOf(null)
}
.stateIn(
scope = bgScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = false
+ initialValue = null
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 4f54fee3f498..18b343eb97c4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -186,6 +186,10 @@ constructor(
AppWidgetManager.EXTRA_CATEGORY_FILTER,
CommunalWidgetCategories.defaultCategories
)
+
+ communalSettingsInteractor.workProfileUserDisallowedByDevicePolicy.value?.let {
+ putExtra(EXTRA_USER_ID_FILTER, arrayListOf(it.id))
+ }
putExtra(EXTRA_UI_SURFACE_KEY, EXTRA_UI_SURFACE_VALUE)
putExtra(EXTRA_PICKER_TITLE, resources.getString(R.string.communal_widget_picker_title))
putExtra(
@@ -223,6 +227,7 @@ constructor(
private const val EXTRA_PICKER_DESCRIPTION = "picker_description"
private const val EXTRA_UI_SURFACE_KEY = "ui_surface"
private const val EXTRA_UI_SURFACE_VALUE = "widgets_hub"
+ private const val EXTRA_USER_ID_FILTER = "filtered_user_ids"
const val EXTRA_ADDED_APP_WIDGETS_KEY = "added_app_widgets"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt
index 297ad847caa6..befd822e14cd 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt
@@ -161,5 +161,6 @@ constructor(
TASK_FRAGMENT_TRANSIT_CLOSE,
false
)
+ organizer.unregisterOrganizer()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index c4b70d8013bf..9f3311373709 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -81,6 +81,7 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardWakeDirectlyToGoneInteractor;
import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier;
import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindViewBinder;
import com.android.systemui.keyguard.ui.binder.WindowManagerLockscreenVisibilityViewBinder;
@@ -317,7 +318,7 @@ public class KeyguardService extends Service {
private final WindowManagerOcclusionManager mWmOcclusionManager;
private final KeyguardEnabledInteractor mKeyguardEnabledInteractor;
-
+ private final KeyguardWakeDirectlyToGoneInteractor mKeyguardWakeDirectlyToGoneInteractor;
private final Lazy<FoldGracePeriodProvider> mFoldGracePeriodProvider = new Lazy<>() {
@Override
public FoldGracePeriodProvider get() {
@@ -344,7 +345,8 @@ public class KeyguardService extends Service {
@Main Executor mainExecutor,
KeyguardInteractor keyguardInteractor,
KeyguardEnabledInteractor keyguardEnabledInteractor,
- Lazy<KeyguardStateCallbackStartable> keyguardStateCallbackStartableLazy) {
+ Lazy<KeyguardStateCallbackStartable> keyguardStateCallbackStartableLazy,
+ KeyguardWakeDirectlyToGoneInteractor keyguardWakeDirectlyToGoneInteractor) {
super();
mKeyguardViewMediator = keyguardViewMediator;
mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
@@ -372,6 +374,7 @@ public class KeyguardService extends Service {
mWmOcclusionManager = windowManagerOcclusionManager;
mKeyguardEnabledInteractor = keyguardEnabledInteractor;
+ mKeyguardWakeDirectlyToGoneInteractor = keyguardWakeDirectlyToGoneInteractor;
}
@Override
@@ -486,6 +489,7 @@ public class KeyguardService extends Service {
public void onDreamingStarted() {
trace("onDreamingStarted");
checkPermission();
+ mKeyguardWakeDirectlyToGoneInteractor.onDreamingStarted();
mKeyguardInteractor.setDreaming(true);
mKeyguardViewMediator.onDreamingStarted();
}
@@ -494,6 +498,7 @@ public class KeyguardService extends Service {
public void onDreamingStopped() {
trace("onDreamingStopped");
checkPermission();
+ mKeyguardWakeDirectlyToGoneInteractor.onDreamingStopped();
mKeyguardInteractor.setDreaming(false);
mKeyguardViewMediator.onDreamingStopped();
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 608e25a82eff..33f9209fea09 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -42,6 +42,7 @@ import com.android.systemui.CoreStartable
import com.android.systemui.biometrics.ui.binder.DeviceEntryUnlockTrackerViewBinder
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
@@ -73,6 +74,7 @@ import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import dagger.Lazy
import java.util.Optional
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -108,6 +110,7 @@ constructor(
private val clockInteractor: KeyguardClockInteractor,
private val keyguardViewMediator: KeyguardViewMediator,
private val deviceEntryUnlockTrackerViewBinder: Optional<DeviceEntryUnlockTrackerViewBinder>,
+ @Main private val mainDispatcher: CoroutineDispatcher,
) : CoreStartable {
private var rootViewHandle: DisposableHandle? = null
@@ -215,6 +218,7 @@ constructor(
vibratorHelper,
falsingManager,
keyguardViewMediator,
+ mainDispatcher,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index f837d8efdcc5..ae751dbacd68 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -127,6 +127,30 @@ interface KeyguardRepository {
*/
val isKeyguardEnabled: StateFlow<Boolean>
+ /**
+ * Whether we can transition directly back to GONE from AOD/DOZING without any authentication
+ * events (such as a fingerprint wake and unlock), even though authentication would normally be
+ * required. This means that if you tap the screen or press the power button, you'll return
+ * directly to the unlocked app content without seeing the lockscreen, even if a secure
+ * authentication method (PIN/password/biometrics) is set.
+ *
+ * This is true in these cases:
+ * - The screen timed out, but the "lock after screen timeout" duration (default 5 seconds) has
+ * not yet elapsed.
+ * - The power button was pressed, but "power button instantly locks" is not enabled, and the
+ * "lock after screen timeout" duration has not elapsed.
+ *
+ * Note that this value specifically tells us if we can *ignore* authentication that would
+ * otherwise be required to transition from AOD/DOZING -> GONE. AOD/DOZING -> GONE is also
+ * possible if keyguard is disabled, either from an app request or because security is set to
+ * "none", but in that case, auth is not required so this boolean is not relevant.
+ *
+ * See [KeyguardWakeToGoneInteractor].
+ */
+ val canIgnoreAuthAndReturnToGone: StateFlow<Boolean>
+
+ fun setCanIgnoreAuthAndReturnToGone(canWake: Boolean)
+
/** Is the always-on display available to be used? */
val isAodAvailable: StateFlow<Boolean>
@@ -386,6 +410,13 @@ constructor(
MutableStateFlow(!lockPatternUtils.isLockScreenDisabled(userTracker.userId))
override val isKeyguardEnabled: StateFlow<Boolean> = _isKeyguardEnabled.asStateFlow()
+ private val _canIgnoreAuthAndReturnToGone = MutableStateFlow(false)
+ override val canIgnoreAuthAndReturnToGone = _canIgnoreAuthAndReturnToGone.asStateFlow()
+
+ override fun setCanIgnoreAuthAndReturnToGone(canWakeToGone: Boolean) {
+ _canIgnoreAuthAndReturnToGone.value = canWakeToGone
+ }
+
private val _isDozing = MutableStateFlow(statusBarStateController.isDozing)
override val isDozing: StateFlow<Boolean> = _isDozing.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 868c4629dbb3..1167cc47448c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -53,6 +53,7 @@ constructor(
powerInteractor: PowerInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
val deviceEntryRepository: DeviceEntryRepository,
+ private val wakeToGoneInteractor: KeyguardWakeDirectlyToGoneInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.AOD,
@@ -98,6 +99,7 @@ constructor(
keyguardInteractor.primaryBouncerShowing,
keyguardInteractor.isKeyguardOccluded,
canDismissLockscreen,
+ wakeToGoneInteractor.canWakeDirectlyToGone,
)
.collect {
(
@@ -107,6 +109,7 @@ constructor(
primaryBouncerShowing,
isKeyguardOccludedLegacy,
canDismissLockscreen,
+ canWakeDirectlyToGone,
) ->
if (!maybeHandleInsecurePowerGesture()) {
val shouldTransitionToLockscreen =
@@ -131,8 +134,7 @@ constructor(
val shouldTransitionToGone =
(!KeyguardWmStateRefactor.isEnabled && canDismissLockscreen) ||
- (KeyguardWmStateRefactor.isEnabled &&
- !deviceEntryRepository.isLockscreenEnabled())
+ (KeyguardWmStateRefactor.isEnabled && canWakeDirectlyToGone)
if (shouldTransitionToGone) {
startTransitionTo(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 76e88a2a2cd6..aee65a81880d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -55,6 +55,7 @@ constructor(
private val communalInteractor: CommunalInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
val deviceEntryRepository: DeviceEntryRepository,
+ private val wakeToGoneInteractor: KeyguardWakeDirectlyToGoneInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.DOZING,
@@ -181,7 +182,7 @@ constructor(
.sample(
communalInteractor.isIdleOnCommunal,
keyguardInteractor.biometricUnlockState,
- canTransitionToGoneOnWake,
+ wakeToGoneInteractor.canWakeDirectlyToGone,
keyguardInteractor.primaryBouncerShowing,
)
.collect {
@@ -189,27 +190,14 @@ constructor(
_,
isIdleOnCommunal,
biometricUnlockState,
- canDismissLockscreen,
+ canWakeDirectlyToGone,
primaryBouncerShowing) ->
if (
!maybeStartTransitionToOccludedOrInsecureCamera() &&
// Handled by dismissFromDozing().
!isWakeAndUnlock(biometricUnlockState.mode)
) {
- if (!KeyguardWmStateRefactor.isEnabled && canDismissLockscreen) {
- if (SceneContainerFlag.isEnabled) {
- // TODO(b/336576536): Check if adaptation for scene framework is
- // needed
- } else {
- startTransitionTo(
- KeyguardState.GONE,
- ownerReason = "waking from dozing"
- )
- }
- } else if (
- KeyguardWmStateRefactor.isEnabled &&
- !deviceEntryRepository.isLockscreenEnabled()
- ) {
+ if (canWakeDirectlyToGone) {
if (SceneContainerFlag.isEnabled) {
// TODO(b/336576536): Check if adaptation for scene framework is
// needed
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 0e764879d8f6..cfb161cd3d33 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -23,6 +23,7 @@ import com.android.systemui.Flags.communalHub
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
@@ -37,11 +38,14 @@ import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class FromDreamingTransitionInteractor
@Inject
@@ -56,6 +60,7 @@ constructor(
private val glanceableHubTransitions: GlanceableHubTransitions,
powerInteractor: PowerInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
+ private val deviceEntryInteractor: DeviceEntryInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.DREAMING,
@@ -72,7 +77,7 @@ constructor(
listenForDreamingToOccluded()
listenForDreamingToGoneWhenDismissable()
listenForDreamingToGoneFromBiometricUnlock()
- listenForDreamingToLockscreen()
+ listenForDreamingToLockscreenOrGone()
listenForDreamingToAodOrDozing()
listenForTransitionToCamera(scope, keyguardInteractor)
listenForDreamingToGlanceableHub()
@@ -132,17 +137,7 @@ constructor(
@OptIn(FlowPreview::class)
private fun listenForDreamingToOccluded() {
- if (KeyguardWmStateRefactor.isEnabled) {
- scope.launch {
- combine(
- keyguardInteractor.isDreaming,
- keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop,
- ::Pair
- )
- .filterRelevantKeyguardStateAnd { (isDreaming, _) -> !isDreaming }
- .collect { maybeStartTransitionToOccludedOrInsecureCamera() }
- }
- } else {
+ if (!KeyguardWmStateRefactor.isEnabled) {
scope.launch {
combine(
keyguardInteractor.isKeyguardOccluded,
@@ -168,21 +163,41 @@ constructor(
}
}
- private fun listenForDreamingToLockscreen() {
+ private fun listenForDreamingToLockscreenOrGone() {
if (!KeyguardWmStateRefactor.isEnabled) {
return
}
scope.launch {
- keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop
- .filterRelevantKeyguardStateAnd { onTop -> !onTop }
- .collect { startTransitionTo(KeyguardState.LOCKSCREEN) }
+ keyguardInteractor.isDreaming
+ .filter { !it }
+ .sample(deviceEntryInteractor.isUnlocked, ::Pair)
+ .collect { (_, dismissable) ->
+ // TODO(b/349837588): Add check for -> OCCLUDED.
+ if (dismissable) {
+ startTransitionTo(
+ KeyguardState.GONE,
+ ownerReason = "No longer dreaming; dismissable"
+ )
+ } else {
+ startTransitionTo(
+ KeyguardState.LOCKSCREEN,
+ ownerReason = "No longer dreaming"
+ )
+ }
+ }
}
}
private fun listenForDreamingToGoneWhenDismissable() {
- // TODO(b/336576536): Check if adaptation for scene framework is needed
- if (SceneContainerFlag.isEnabled) return
+ if (SceneContainerFlag.isEnabled) {
+ return // TODO(b/336576536): Check if adaptation for scene framework is needed
+ }
+
+ if (KeyguardWmStateRefactor.isEnabled) {
+ return
+ }
+
scope.launch {
keyguardInteractor.isAbleToDream
.sampleCombine(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 859326a29b28..ec03a6d8121f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -52,7 +52,6 @@ import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.notification.NotificationUtils.interpolate
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
-import com.android.systemui.util.kotlin.Utils.Companion.sampleFilter
import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
@@ -251,13 +250,17 @@ constructor(
/** Keyguard can be clipped at the top as the shade is dragged */
val topClippingBounds: Flow<Int?> by lazy {
- repository.topClippingBounds
- .sampleFilter(
+ combineTransform(
keyguardTransitionInteractor
.transitionValue(scene = Scenes.Gone, stateWithoutSceneContainer = GONE)
- .onStart { emit(0f) }
- ) { goneValue ->
- goneValue != 1f
+ .map { it == 1f }
+ .onStart { emit(false) }
+ .distinctUntilChanged(),
+ repository.topClippingBounds
+ ) { isGone, topClippingBounds ->
+ if (!isGone) {
+ emit(topClippingBounds)
+ }
}
.distinctUntilChanged()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
new file mode 100644
index 000000000000..6a1b7cfb7d7e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
@@ -0,0 +1,367 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import android.annotation.SuppressLint
+import android.app.AlarmManager
+import android.app.PendingIntent
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.provider.Settings
+import android.provider.Settings.Secure
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.KeyguardViewMediator
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.Companion.deviceIsAsleepInState
+import com.android.systemui.keyguard.shared.model.KeyguardState.Companion.deviceIsAwakeInState
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SystemSettings
+import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
+import kotlin.math.max
+import kotlin.math.min
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.distinctUntilChangedBy
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+/**
+ * Logic related to the ability to wake directly to GONE from asleep (AOD/DOZING), without going
+ * through LOCKSCREEN or a BOUNCER state.
+ *
+ * This is possible in the following scenarios:
+ * - The lockscreen is disabled, either from an app request (SUW does this), or by the security
+ * "None" setting.
+ * - A biometric authentication event occurred while we were asleep (fingerprint auth, etc). This
+ * specifically is referred to throughout the codebase as "wake and unlock".
+ * - The screen timed out, but the "lock after screen timeout" duration has not elapsed.
+ * - The power button was pressed, but "power button instantly locks" is disabled and the "lock
+ * after screen timeout" duration has not elapsed.
+ *
+ * In these cases, no (further) authentication is required, and we can transition directly from
+ * AOD/DOZING -> GONE.
+ */
+@SysUISingleton
+class KeyguardWakeDirectlyToGoneInteractor
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ private val context: Context,
+ private val repository: KeyguardRepository,
+ private val systemClock: SystemClock,
+ private val alarmManager: AlarmManager,
+ private val transitionInteractor: KeyguardTransitionInteractor,
+ private val powerInteractor: PowerInteractor,
+ private val secureSettings: SecureSettings,
+ private val lockPatternUtils: LockPatternUtils,
+ private val systemSettings: SystemSettings,
+ private val selectedUserInteractor: SelectedUserInteractor,
+) {
+
+ /**
+ * Whether the lockscreen was disabled as of the last wake/sleep event, according to
+ * LockPatternUtils.
+ *
+ * This will always be true if [repository.isKeyguardServiceEnabled]=false, but it can also be
+ * true when the keyguard service is enabled if the lockscreen has been disabled via adb using
+ * the `adb shell locksettings set-disabled true` command, which is often done in tests.
+ *
+ * Unlike keyguardServiceEnabled, changes to this value should *not* immediately show or hide
+ * the keyguard. If the lockscreen is disabled in this way, it will just not show on the next
+ * sleep/wake.
+ */
+ private val isLockscreenDisabled: Flow<Boolean> =
+ powerInteractor.isAwake.map { isLockscreenDisabled() }
+
+ /**
+ * Whether we can wake from AOD/DOZING directly to GONE, bypassing LOCKSCREEN/BOUNCER states.
+ *
+ * This is possible in the following cases:
+ * - Keyguard is disabled, either from an app request or from security being set to "None".
+ * - We're wake and unlocking (fingerprint auth occurred while asleep).
+ * - We're allowed to ignore auth and return to GONE, due to timeouts not elapsing.
+ */
+ val canWakeDirectlyToGone =
+ combine(
+ repository.isKeyguardEnabled,
+ isLockscreenDisabled,
+ repository.biometricUnlockState,
+ repository.canIgnoreAuthAndReturnToGone,
+ ) {
+ keyguardEnabled,
+ isLockscreenDisabled,
+ biometricUnlockState,
+ canIgnoreAuthAndReturnToGone ->
+ (!keyguardEnabled || isLockscreenDisabled) ||
+ BiometricUnlockMode.isWakeAndUnlock(biometricUnlockState.mode) ||
+ canIgnoreAuthAndReturnToGone
+ }
+ .distinctUntilChanged()
+
+ /**
+ * Counter that is incremented every time we wake up or stop dreaming. Upon sleeping/dreaming,
+ * we put the current value of this counter into the intent extras of the timeout alarm intent.
+ * If this value has changed by the time we receive the intent, it is discarded since it's out
+ * of date.
+ */
+ var timeoutCounter = 0
+
+ var isAwake = false
+
+ private val broadcastReceiver: BroadcastReceiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ if (DELAYED_KEYGUARD_ACTION == intent.action) {
+ val sequence = intent.getIntExtra(SEQ_EXTRA_KEY, 0)
+ synchronized(this) {
+ if (timeoutCounter == sequence) {
+ // If the sequence # matches, we have not woken up or stopped dreaming
+ // since
+ // the alarm was set. That means this is still relevant - the lock
+ // timeout
+ // has elapsed, so let the repository know that we can no longer return
+ // to
+ // GONE without authenticating.
+ repository.setCanIgnoreAuthAndReturnToGone(false)
+ }
+ }
+ }
+ }
+ }
+
+ init {
+ setOrCancelAlarmFromWakefulness()
+ listenForWakeToClearCanIgnoreAuth()
+ registerBroadcastReceiver()
+ }
+
+ fun onDreamingStarted() {
+ // If we start dreaming while awake, lock after the normal timeout.
+ if (isAwake) {
+ setResetCanIgnoreAuthAlarm()
+ }
+ }
+
+ fun onDreamingStopped() {
+ // Cancel the timeout if we stop dreaming while awake.
+ if (isAwake) {
+ cancelCanIgnoreAuthAlarm()
+ }
+ }
+
+ private fun setOrCancelAlarmFromWakefulness() {
+ scope.launch {
+ powerInteractor.detailedWakefulness
+ .distinctUntilChangedBy { it.isAwake() }
+ .sample(transitionInteractor.currentKeyguardState, ::Pair)
+ .collect { (wakefulness, currentState) ->
+ // Save isAwake for use in onDreamingStarted/onDreamingStopped.
+ this@KeyguardWakeDirectlyToGoneInteractor.isAwake = wakefulness.isAwake()
+
+ // If we're sleeping from GONE, check the timeout and lock instantly settings.
+ // These are not relevant if we're coming from non-GONE states.
+ if (!isAwake && currentState == KeyguardState.GONE) {
+ val lockTimeoutDuration = getCanIgnoreAuthAndReturnToGoneDuration()
+
+ // If the screen timed out and went to sleep, and the lock timeout is > 0ms,
+ // then we can return to GONE until that duration elapses. If the power
+ // button was pressed but "instantly locks" is disabled, then we can also
+ // return to GONE until the timeout duration elapses.
+ if (
+ (wakefulness.lastSleepReason == WakeSleepReason.TIMEOUT &&
+ lockTimeoutDuration > 0) ||
+ (wakefulness.lastSleepReason == WakeSleepReason.POWER_BUTTON &&
+ !willLockImmediately())
+ ) {
+
+ // Let the repository know that we can return to GONE until we notify
+ // it otherwise.
+ repository.setCanIgnoreAuthAndReturnToGone(true)
+ setResetCanIgnoreAuthAlarm()
+ }
+ } else if (isAwake) {
+ // If we're waking up, ignore the alarm if it goes off since it's no longer
+ // relevant. Once a wake KeyguardTransition is started, we'll also clear the
+ // canIgnoreAuthAndReturnToGone value in listenForWakeToClearCanIgnoreAuth.
+ cancelCanIgnoreAuthAlarm()
+ }
+ }
+ }
+ }
+
+ /** Clears the canIgnoreAuthAndReturnToGone value upon waking. */
+ private fun listenForWakeToClearCanIgnoreAuth() {
+ scope.launch {
+ transitionInteractor
+ .isInTransitionWhere(
+ fromStatePredicate = { deviceIsAsleepInState(it) },
+ toStatePredicate = { deviceIsAwakeInState(it) },
+ )
+ .collect {
+ // This value is reset when the timeout alarm fires, but if the device is woken
+ // back up before then, it needs to be reset here. The alarm is cancelled
+ // immediately upon waking up, but since this value is used by keyguard
+ // transition internals to decide whether we can transition to GONE, wait until
+ // that decision is made before resetting it.
+ repository.setCanIgnoreAuthAndReturnToGone(false)
+ }
+ }
+ }
+
+ /**
+ * Registers the broadcast receiver to receive the alarm intent.
+ *
+ * TODO(b/351817381): Investigate using BroadcastDispatcher vs. ignoring this lint warning.
+ */
+ @SuppressLint("WrongConstant", "RegisterReceiverViaContext")
+ private fun registerBroadcastReceiver() {
+ val delayedActionFilter = IntentFilter()
+ delayedActionFilter.addAction(KeyguardViewMediator.DELAYED_KEYGUARD_ACTION)
+ // TODO(b/346803756): Listen for DELAYED_LOCK_PROFILE_ACTION.
+ delayedActionFilter.priority = IntentFilter.SYSTEM_HIGH_PRIORITY
+ context.registerReceiver(
+ broadcastReceiver,
+ delayedActionFilter,
+ SYSTEMUI_PERMISSION,
+ null /* scheduler */,
+ Context.RECEIVER_EXPORTED_UNAUDITED
+ )
+ }
+
+ /** Set an alarm for */
+ private fun setResetCanIgnoreAuthAlarm() {
+ val intent =
+ Intent(DELAYED_KEYGUARD_ACTION).apply {
+ setPackage(context.packageName)
+ putExtra(SEQ_EXTRA_KEY, timeoutCounter)
+ addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ }
+
+ val sender =
+ PendingIntent.getBroadcast(
+ context,
+ 0,
+ intent,
+ PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+
+ val time = systemClock.elapsedRealtime() + getCanIgnoreAuthAndReturnToGoneDuration()
+ alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, time, sender)
+
+ // TODO(b/346803756): Migrate support for child profiles.
+ }
+
+ /**
+ * Cancel the timeout by incrementing the counter so that we ignore the intent when it's
+ * received.
+ */
+ private fun cancelCanIgnoreAuthAlarm() {
+ timeoutCounter++
+ }
+
+ /**
+ * Whether pressing the power button locks the device immediately; vs. waiting for a specified
+ * timeout first.
+ */
+ private fun willLockImmediately(
+ userId: Int = selectedUserInteractor.getSelectedUserId()
+ ): Boolean {
+ return lockPatternUtils.getPowerButtonInstantlyLocks(userId) ||
+ !lockPatternUtils.isSecure(userId)
+ }
+
+ /**
+ * Returns whether the lockscreen is disabled, either because the keyguard service is disabled
+ * or because an adb command has disabled the lockscreen.
+ */
+ private fun isLockscreenDisabled(
+ userId: Int = selectedUserInteractor.getSelectedUserId()
+ ): Boolean {
+ return lockPatternUtils.isLockScreenDisabled(userId)
+ }
+
+ /**
+ * Returns the duration within which we can return to GONE without auth after a screen timeout
+ * (or power button press, if lock instantly is disabled).
+ *
+ * This takes into account the user's settings as well as device policy maximums.
+ */
+ private fun getCanIgnoreAuthAndReturnToGoneDuration(
+ userId: Int = selectedUserInteractor.getSelectedUserId()
+ ): Long {
+ // The timeout duration from settings (Security > Device Unlock > Gear icon > "Lock after
+ // screen timeout".
+ val durationSetting: Long =
+ secureSettings
+ .getIntForUser(
+ Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
+ KEYGUARD_CAN_IGNORE_AUTH_DURATION,
+ userId
+ )
+ .toLong()
+
+ // Device policy maximum timeout.
+ val durationDevicePolicyMax =
+ lockPatternUtils.devicePolicyManager.getMaximumTimeToLock(null, userId)
+
+ return if (durationDevicePolicyMax <= 0) {
+ durationSetting
+ } else {
+ var displayTimeout =
+ systemSettings
+ .getIntForUser(
+ Settings.System.SCREEN_OFF_TIMEOUT,
+ KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT,
+ userId
+ )
+ .toLong()
+
+ // Ignore negative values. I don't know why this would be negative, but this check has
+ // been around since 2016 and I see no upside to removing it.
+ displayTimeout = max(displayTimeout, 0)
+
+ // Respect the shorter of: the device policy (maximum duration between last user action
+ // and fully locking) or the "Lock after screen timeout" setting.
+ max(min(durationDevicePolicyMax - displayTimeout, durationSetting), 0)
+ }
+ }
+
+ companion object {
+ private const val DELAYED_KEYGUARD_ACTION =
+ "com.android.internal.policy.impl.PhoneWindowManager.DELAYED_KEYGUARD"
+ private const val DELAYED_LOCK_PROFILE_ACTION =
+ "com.android.internal.policy.impl.PhoneWindowManager.DELAYED_LOCK"
+ private const val SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF"
+ private const val SEQ_EXTRA_KEY = "count"
+
+ private const val KEYGUARD_CAN_IGNORE_AUTH_DURATION = 5000
+ private const val KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
index 3355ffd83138..0985e69ff8ff 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -21,14 +21,17 @@ package com.android.systemui.keyguard.domain.interactor
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.Companion.deviceIsAsleepInState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
+import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import dagger.Lazy
@@ -41,11 +44,13 @@ import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class WindowManagerLockscreenVisibilityInteractor
@Inject
constructor(
keyguardInteractor: KeyguardInteractor,
+ transitionRepository: KeyguardTransitionRepository,
transitionInteractor: KeyguardTransitionInteractor,
surfaceBehindInteractor: KeyguardSurfaceBehindInteractor,
fromLockscreenInteractor: FromLockscreenTransitionInteractor,
@@ -54,9 +59,15 @@ constructor(
notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor,
sceneInteractor: Lazy<SceneInteractor>,
deviceEntryInteractor: Lazy<DeviceEntryInteractor>,
+ wakeToGoneInteractor: KeyguardWakeDirectlyToGoneInteractor,
) {
private val defaultSurfaceBehindVisibility =
- transitionInteractor.finishedKeyguardState.map(::isSurfaceVisible)
+ combine(
+ transitionInteractor.finishedKeyguardState,
+ wakeToGoneInteractor.canWakeDirectlyToGone,
+ ) { finishedState, canWakeDirectlyToGone ->
+ isSurfaceVisible(finishedState) || canWakeDirectlyToGone
+ }
/**
* Surface visibility provided by the From*TransitionInteractor responsible for the currently
@@ -203,9 +214,13 @@ constructor(
if (SceneContainerFlag.isEnabled) {
isDeviceNotEntered
} else {
- transitionInteractor.currentKeyguardState
- .sample(transitionInteractor.startedStepWithPrecedingStep, ::Pair)
- .map { (currentState, startedWithPrev) ->
+ combine(
+ transitionInteractor.currentKeyguardState,
+ wakeToGoneInteractor.canWakeDirectlyToGone,
+ ::Pair
+ )
+ .sample(transitionInteractor.startedStepWithPrecedingStep, ::toTriple)
+ .map { (currentState, canWakeDirectlyToGone, startedWithPrev) ->
val startedFromStep = startedWithPrev.previousValue
val startedStep = startedWithPrev.newValue
val returningToGoneAfterCancellation =
@@ -213,16 +228,33 @@ constructor(
startedFromStep.transitionState == TransitionState.CANCELED &&
startedFromStep.from == KeyguardState.GONE
- if (!returningToGoneAfterCancellation) {
- // By default, apply the lockscreen visibility of the current state.
- deviceEntryInteractor.get().isLockscreenEnabled() &&
- KeyguardState.lockscreenVisibleInState(currentState)
- } else {
- // If we're transitioning to GONE after a prior canceled transition from
- // GONE, then this is the camera launch transition from an asleep state back
- // to GONE. We don't want to show the lockscreen since we're aborting the
- // lock and going back to GONE.
+ val transitionInfo = transitionRepository.currentTransitionInfoInternal.value
+ val wakingDirectlyToGone =
+ deviceIsAsleepInState(transitionInfo.from) &&
+ transitionInfo.to == KeyguardState.GONE
+
+ if (returningToGoneAfterCancellation || wakingDirectlyToGone) {
+ // GONE -> AOD/DOZING (cancel) -> GONE is the camera launch transition,
+ // which means we never want to show the lockscreen throughout the
+ // transition. Same for waking directly to gone, due to the lockscreen being
+ // disabled or because the device was woken back up before the lock timeout
+ // duration elapsed.
KeyguardState.lockscreenVisibleInState(KeyguardState.GONE)
+ } else if (canWakeDirectlyToGone) {
+ // Never show the lockscreen if we can wake directly to GONE. This means
+ // that the lock timeout has not yet elapsed, or the keyguard is disabled.
+ // In either case, we don't show the activity lock screen until one of those
+ // conditions changes.
+ false
+ } else if (
+ currentState == KeyguardState.DREAMING &&
+ deviceEntryInteractor.get().isUnlocked.value
+ ) {
+ // Dreams dismiss keyguard and return to GONE if they can.
+ false
+ } else {
+ // Otherwise, use the visibility of the current state.
+ KeyguardState.lockscreenVisibleInState(currentState)
}
}
.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 8f149fbe99de..f96f053b8da1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -37,6 +37,7 @@ import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.animation.Interpolators
+import com.android.app.tracing.coroutines.launch
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
import com.android.systemui.Flags.newAodTransition
@@ -80,6 +81,7 @@ import com.android.systemui.util.ui.isAnimating
import com.android.systemui.util.ui.stopAnimating
import com.android.systemui.util.ui.value
import kotlin.math.min
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.coroutineScope
@@ -110,6 +112,7 @@ object KeyguardRootViewBinder {
vibratorHelper: VibratorHelper?,
falsingManager: FalsingManager?,
keyguardViewMediator: KeyguardViewMediator?,
+ mainImmediateDispatcher: CoroutineDispatcher,
): DisposableHandle {
val disposables = DisposableHandles()
val childViews = mutableMapOf<Int, View>()
@@ -128,6 +131,30 @@ object KeyguardRootViewBinder {
val burnInParams = MutableStateFlow(BurnInParameters())
val viewState = ViewStateAccessor(alpha = { view.alpha })
+
+ disposables +=
+ view.repeatWhenAttached(mainImmediateDispatcher) {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ if (MigrateClocksToBlueprint.isEnabled) {
+ launch("$TAG#topClippingBounds") {
+ val clipBounds = Rect()
+ viewModel.topClippingBounds.collect { clipTop ->
+ if (clipTop == null) {
+ view.setClipBounds(null)
+ } else {
+ clipBounds.apply {
+ top = clipTop
+ left = view.getLeft()
+ right = view.getRight()
+ bottom = view.getBottom()
+ }
+ view.setClipBounds(clipBounds)
+ }
+ }
+ }
+ }
+ }
+ }
disposables +=
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
@@ -192,23 +219,6 @@ object KeyguardRootViewBinder {
}
launch {
- val clipBounds = Rect()
- viewModel.topClippingBounds.collect { clipTop ->
- if (clipTop == null) {
- view.setClipBounds(null)
- } else {
- clipBounds.apply {
- top = clipTop
- left = view.getLeft()
- right = view.getRight()
- bottom = view.getBottom()
- }
- view.setClipBounds(clipBounds)
- }
- }
- }
-
- launch {
viewModel.lockscreenStateAlpha(viewState).collect { alpha ->
childViews[statusViewId]?.alpha = alpha
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 4f0ac42a0e87..bc5b7b923082 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -394,6 +394,7 @@ constructor(
null, // device entry haptics not required for preview mode
null, // falsing manager not required for preview mode
null, // keyguard view mediator is not required for preview mode
+ mainDispatcher,
)
}
rootView.addView(
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
index 6224170fd906..83f694bb1980 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
@@ -104,7 +104,7 @@ abstract class BaseMediaProjectionPermissionDialogDelegate<T : AlertDialog>(
private fun initScreenShareSpinner() {
val adapter = OptionsAdapter(dialog.context.applicationContext, screenShareOptions)
- screenShareModeSpinner = dialog.requireViewById(R.id.screen_share_mode_spinner)
+ screenShareModeSpinner = dialog.requireViewById(R.id.screen_share_mode_options)
screenShareModeSpinner.adapter = adapter
screenShareModeSpinner.onItemSelectedListener = this
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
index 8858041ae529..9ce8070131fa 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
@@ -30,7 +30,7 @@ class MediaProjectionPermissionDialogDelegate(
private val onStartRecordingClicked: Consumer<MediaProjectionPermissionDialogDelegate>,
private val onCancelClicked: Runnable,
private val appName: String?,
- private val forceShowPartialScreenshare: Boolean,
+ forceShowPartialScreenshare: Boolean,
hostUid: Int,
mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
) :
diff --git a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakeSleepReason.kt b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakeSleepReason.kt
index 7505566898c0..776a8f47f056 100644
--- a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakeSleepReason.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakeSleepReason.kt
@@ -54,7 +54,10 @@ enum class WakeSleepReason(
OTHER(isTouch = false, PowerManager.WAKE_REASON_UNKNOWN),
/** Device goes to sleep due to folding of a foldable device. */
- FOLD(isTouch = false, PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD);
+ FOLD(isTouch = false, PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD),
+
+ /** Device goes to sleep because it timed out. */
+ TIMEOUT(isTouch = false, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT);
companion object {
fun fromPowerManagerWakeReason(reason: Int): WakeSleepReason {
@@ -75,6 +78,7 @@ enum class WakeSleepReason(
fun fromPowerManagerSleepReason(reason: Int): WakeSleepReason {
return when (reason) {
PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON -> POWER_BUTTON
+ PowerManager.GO_TO_SLEEP_REASON_TIMEOUT -> TIMEOUT
PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD -> FOLD
else -> OTHER
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
index 0bb4cfa327a9..127ef8468111 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
@@ -22,6 +22,7 @@ import android.graphics.drawable.Animatable
import android.service.quicksettings.Tile.STATE_ACTIVE
import android.service.quicksettings.Tile.STATE_INACTIVE
import android.text.TextUtils
+import android.util.Log
import androidx.appcompat.content.res.AppCompatResources
import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
import androidx.compose.animation.graphics.res.animatedVectorResource
@@ -81,7 +82,6 @@ import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.common.ui.compose.load
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.TileUiState
import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
import com.android.systemui.qs.panels.ui.viewmodel.toUiState
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
@@ -91,7 +91,6 @@ import com.android.systemui.res.R
import java.util.function.Supplier
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.mapLatest
object TileType
@@ -103,29 +102,27 @@ fun Tile(
showLabels: Boolean = false,
modifier: Modifier,
) {
- val state: TileUiState by
- tile.state
- .mapLatest { it.toUiState() }
- .collectAsStateWithLifecycle(tile.currentState.toUiState())
- val colors = TileDefaults.getColorForState(state.state)
+ val state by tile.state.collectAsStateWithLifecycle(tile.currentState)
+ val uiState = remember(state) { state.toUiState() }
+ val colors = TileDefaults.getColorForState(uiState.state)
TileContainer(
colors = colors,
showLabels = showLabels,
- label = state.label.toString(),
+ label = uiState.label,
iconOnly = iconOnly,
clickEnabled = true,
onClick = tile::onClick,
onLongClick = tile::onLongClick,
modifier = modifier,
) {
- val icon = getTileIcon(icon = state.icon)
+ val icon = getTileIcon(icon = uiState.icon)
if (iconOnly) {
TileIcon(icon = icon, color = colors.icon, modifier = Modifier.align(Alignment.Center))
} else {
LargeTileContent(
- label = state.label.toString(),
- secondaryLabel = state.secondaryLabel.toString(),
+ label = uiState.label,
+ secondaryLabel = uiState.secondaryLabel,
icon = icon,
colors = colors,
clickEnabled = true,
@@ -234,19 +231,26 @@ private fun LargeTileContent(
Text(
label,
color = colors.label,
- modifier = Modifier.basicMarquee(),
+ modifier = Modifier.tileMarquee(),
)
if (!TextUtils.isEmpty(secondaryLabel)) {
Text(
secondaryLabel ?: "",
color = colors.secondaryLabel,
- modifier = Modifier.basicMarquee(),
+ modifier = Modifier.tileMarquee(),
)
}
}
}
}
+private fun Modifier.tileMarquee(): Modifier {
+ return basicMarquee(
+ iterations = 1,
+ initialDelayMillis = 200,
+ )
+}
+
@Composable
fun TileLazyGrid(
modifier: Modifier = Modifier,
@@ -452,6 +456,7 @@ private fun TileIcon(
animateToEnd: Boolean = false,
modifier: Modifier = Modifier,
) {
+ Log.d("Fabian", "Recomposing tile icon")
val iconModifier = modifier.size(dimensionResource(id = R.dimen.qs_icon_size))
val context = LocalContext.current
val loadedDrawable =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
index b3acaced8ef2..bb004946a4d1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
@@ -26,8 +26,8 @@ import com.android.systemui.qs.pipeline.shared.TileSpec
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.mapLatest
@@ -54,14 +54,24 @@ constructor(
quickQuickSettingsRowInteractor.defaultRows
)
- val tileViewModels: Flow<List<SizedTile<TileViewModel>>> =
- columns.flatMapLatest { columns ->
- tilesInteractor.currentTiles.combine(rows, ::Pair).mapLatest { (tiles, rows) ->
- tiles
- .map { SizedTile(TileViewModel(it.tile, it.spec), it.spec.width) }
- .let { splitInRowsSequence(it, columns).take(rows).toList().flatten() }
+ val tileViewModels: StateFlow<List<SizedTile<TileViewModel>>> =
+ columns
+ .flatMapLatest { columns ->
+ tilesInteractor.currentTiles.combine(rows, ::Pair).mapLatest { (tiles, rows) ->
+ tiles
+ .map { SizedTile(TileViewModel(it.tile, it.spec), it.spec.width) }
+ .let { splitInRowsSequence(it, columns).take(rows).toList().flatten() }
+ }
}
- }
+ .stateIn(
+ applicationScope,
+ SharingStarted.WhileSubscribed(),
+ tilesInteractor.currentTiles.value
+ .map { SizedTile(TileViewModel(it.tile, it.spec), it.spec.width) }
+ .let {
+ splitInRowsSequence(it, columns.value).take(rows.value).toList().flatten()
+ }
+ )
private val TileSpec.width: Int
get() = if (iconTilesViewModel.isIconTile(this)) 1 else 2
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
index 578a292deb7c..4ec59c969a59 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
@@ -16,20 +16,22 @@
package com.android.systemui.qs.panels.ui.viewmodel
+import androidx.compose.runtime.Immutable
import com.android.systemui.plugins.qs.QSTile
import java.util.function.Supplier
+@Immutable
data class TileUiState(
- val label: CharSequence,
- val secondaryLabel: CharSequence,
+ val label: String,
+ val secondaryLabel: String,
val state: Int,
val icon: Supplier<QSTile.Icon>,
)
fun QSTile.State.toUiState(): TileUiState {
return TileUiState(
- label ?: "",
- secondaryLabel ?: "",
+ label?.toString() ?: "",
+ secondaryLabel?.toString() ?: "",
state,
icon?.let { Supplier { icon } } ?: iconSupplier,
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt
index 7505b90ee844..8578bb0ef9a1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.panels.ui.viewmodel
+import androidx.compose.runtime.Immutable
import com.android.systemui.animation.Expandable
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -25,6 +26,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.onStart
+@Immutable
class TileViewModel(private val tile: QSTile, val spec: TileSpec) {
val state: Flow<QSTile.State> =
conflatedCallbackFlow {
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index bbf4e51b35e9..8a51ad4cbd71 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -87,7 +87,10 @@ constructor(
setNegativeButton(R.string.cancel) { _, _ -> }
setPositiveButton(R.string.qs_record_issue_start) { _, _ -> onStarted.run() }
}
- bgExecutor.execute { traceurMessageSender.bindToTraceur(dialog.context) }
+ bgExecutor.execute {
+ traceurMessageSender.onBoundToTraceur.add { traceurMessageSender.getTags() }
+ traceurMessageSender.bindToTraceur(dialog.context)
+ }
}
override fun createDialog(): SystemUIDialog = factory.create(this)
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt b/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt
index 903d662c69ff..a31a9ef26b16 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt
@@ -45,11 +45,15 @@ class TraceurMessageSender @Inject constructor(@Background private val backgroun
private var binder: Messenger? = null
private var isBound: Boolean = false
+ val onBoundToTraceur = mutableListOf<Runnable>()
+
private val traceurConnection =
object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
binder = Messenger(service)
isBound = true
+ onBoundToTraceur.forEach(Runnable::run)
+ onBoundToTraceur.clear()
}
override fun onServiceDisconnected(className: ComponentName) {
@@ -103,11 +107,17 @@ class TraceurMessageSender @Inject constructor(@Background private val backgroun
@WorkerThread
fun shareTraces(context: Context, screenRecord: Uri?) {
- val replyHandler = Messenger(TraceurMessageHandler(context, screenRecord, backgroundLooper))
+ val replyHandler = Messenger(ShareFilesHandler(context, screenRecord, backgroundLooper))
notifyTraceur(MessageConstants.SHARE_WHAT, replyTo = replyHandler)
}
@WorkerThread
+ fun getTags() {
+ val replyHandler = Messenger(TagsHandler(backgroundLooper))
+ notifyTraceur(MessageConstants.TAGS_WHAT, replyTo = replyHandler)
+ }
+
+ @WorkerThread
private fun notifyTraceur(what: Int, data: Bundle = Bundle(), replyTo: Messenger? = null) {
try {
binder!!.send(
@@ -122,7 +132,7 @@ class TraceurMessageSender @Inject constructor(@Background private val backgroun
}
}
- private class TraceurMessageHandler(
+ private class ShareFilesHandler(
private val context: Context,
private val screenRecord: Uri?,
looper: Looper,
@@ -154,4 +164,29 @@ class TraceurMessageSender @Inject constructor(@Background private val backgroun
context.startActivity(fileSharingIntent)
}
}
+
+ private class TagsHandler(looper: Looper) : Handler(looper) {
+
+ override fun handleMessage(msg: Message) {
+ if (MessageConstants.TAGS_WHAT == msg.what) {
+ val keys = msg.data.getStringArrayList(MessageConstants.BUNDLE_KEY_TAGS)
+ val values =
+ msg.data.getStringArrayList(MessageConstants.BUNDLE_KEY_TAG_DESCRIPTIONS)
+ if (keys == null || values == null) {
+ throw IllegalArgumentException(
+ "Neither keys: $keys, nor values: $values can " + "be null"
+ )
+ }
+
+ val tags = keys.zip(values).map { "${it.first}: ${it.second}" }.toSet()
+ Log.e(
+ TAG,
+ "These tags: $tags will be saved and used for the Custom Trace" +
+ " Config dialog in a future CL. This log will be removed."
+ )
+ } else {
+ throw IllegalArgumentException("received unknown msg.what: " + msg.what)
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index c87b1f526957..95ee2e06817b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -20,7 +20,6 @@ import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
import static com.android.systemui.Flags.screenshotPrivateProfileAccessibilityAnnouncementFix;
-import static com.android.systemui.Flags.screenshotShelfUi2;
import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
import static com.android.systemui.screenshot.LogConfig.DEBUG_INPUT;
@@ -407,22 +406,16 @@ public class ScreenshotController implements ScreenshotHandler {
}
final UUID requestId;
- if (screenshotShelfUi2()) {
- requestId = mActionsController.setCurrentScreenshot(screenshot);
- saveScreenshotInBackground(screenshot, requestId, finisher);
-
- if (screenshot.getTaskId() >= 0) {
- mAssistContentRequester.requestAssistContent(
- screenshot.getTaskId(),
- assistContent ->
- mActionsController.onAssistContent(requestId, assistContent));
- } else {
- mActionsController.onAssistContent(requestId, null);
- }
+ requestId = mActionsController.setCurrentScreenshot(screenshot);
+ saveScreenshotInBackground(screenshot, requestId, finisher);
+
+ if (screenshot.getTaskId() >= 0) {
+ mAssistContentRequester.requestAssistContent(
+ screenshot.getTaskId(),
+ assistContent ->
+ mActionsController.onAssistContent(requestId, assistContent));
} else {
- requestId = UUID.randomUUID(); // passed through but unused for legacy UI
- saveScreenshotInWorkerThread(screenshot.getUserHandle(), finisher,
- this::showUiOnActionsReady, this::showUiOnQuickShareActionReady);
+ mActionsController.onAssistContent(requestId, null);
}
// The window is focusable by default
@@ -458,9 +451,6 @@ public class ScreenshotController implements ScreenshotHandler {
// ignore system bar insets for the purpose of window layout
mWindow.getDecorView().setOnApplyWindowInsetsListener(
(v, insets) -> WindowInsets.CONSUMED);
- if (!screenshotShelfUi2()) {
- mScreenshotHandler.cancelTimeout(); // restarted after animation
- }
}
private boolean shouldShowUi() {
@@ -515,11 +505,7 @@ public class ScreenshotController implements ScreenshotHandler {
}
boolean isPendingSharedTransition() {
- if (screenshotShelfUi2()) {
- return mActionExecutor.isPendingSharedTransition();
- } else {
- return mViewProxy.isPendingSharedTransition();
- }
+ return mActionExecutor.isPendingSharedTransition();
}
// Any cleanup needed when the service is being destroyed.
@@ -603,11 +589,7 @@ public class ScreenshotController implements ScreenshotHandler {
if (mConfigChanges.applyNewConfig(mContext.getResources())) {
// Hide the scroll chip until we know it's available in this
// orientation
- if (screenshotShelfUi2()) {
- mActionsController.onScrollChipInvalidated();
- } else {
- mViewProxy.hideScrollChip();
- }
+ mActionsController.onScrollChipInvalidated();
// Delay scroll capture eval a bit to allow the underlying activity
// to set up in the new orientation.
mScreenshotHandler.postDelayed(
@@ -640,13 +622,8 @@ public class ScreenshotController implements ScreenshotHandler {
(response) -> {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION,
0, response.getPackageName());
- if (screenshotShelfUi2()) {
- mActionsController.onScrollChipReady(requestId,
- () -> onScrollButtonClicked(owner, response));
- } else {
- mViewProxy.showScrollChip(response.getPackageName(),
- () -> onScrollButtonClicked(owner, response));
- }
+ mActionsController.onScrollChipReady(requestId,
+ () -> onScrollButtonClicked(owner, response));
return Unit.INSTANCE;
}
);
@@ -715,11 +692,9 @@ public class ScreenshotController implements ScreenshotHandler {
mWindowManager.addView(decorView, mWindowLayoutParams);
decorView.requestApplyInsets();
- if (screenshotShelfUi2()) {
- ViewGroup layout = decorView.requireViewById(android.R.id.content);
- layout.setClipChildren(false);
- layout.setClipToPadding(false);
- }
+ ViewGroup layout = decorView.requireViewById(android.R.id.content);
+ layout.setClipChildren(false);
+ layout.setClipToPadding(false);
}
void removeWindow() {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index 8235325fffad..56ba1af4e83b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -16,15 +16,12 @@
package com.android.systemui.screenshot.dagger;
-import static com.android.systemui.Flags.screenshotShelfUi2;
-
import android.app.Service;
import android.view.accessibility.AccessibilityManager;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.screenshot.ImageCapture;
import com.android.systemui.screenshot.ImageCaptureImpl;
-import com.android.systemui.screenshot.LegacyScreenshotViewProxy;
import com.android.systemui.screenshot.ScreenshotPolicy;
import com.android.systemui.screenshot.ScreenshotPolicyImpl;
import com.android.systemui.screenshot.ScreenshotShelfViewProxy;
@@ -96,14 +93,7 @@ public abstract class ScreenshotModule {
return new ScreenshotViewModel(accessibilityManager);
}
- @Provides
- static ScreenshotViewProxy.Factory providesScreenshotViewProxyFactory(
- ScreenshotShelfViewProxy.Factory shelfScreenshotViewProxyFactory,
- LegacyScreenshotViewProxy.Factory legacyScreenshotViewProxyFactory) {
- if (screenshotShelfUi2()) {
- return shelfScreenshotViewProxyFactory;
- } else {
- return legacyScreenshotViewProxyFactory;
- }
- }
+ @Binds
+ abstract ScreenshotViewProxy.Factory bindScreenshotViewProxyFactory(
+ ScreenshotShelfViewProxy.Factory shelfScreenshotViewProxyFactory);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt
index 21f301c47467..6917f468ce14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt
@@ -49,7 +49,7 @@ constructor(
activeCastDevice
.map {
if (it != null) {
- MediaRouterCastModel.Casting
+ MediaRouterCastModel.Casting(deviceName = it.name)
} else {
MediaRouterCastModel.DoingNothing
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/model/MediaRouterCastModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/model/MediaRouterCastModel.kt
index b228922d1721..1f84d7cb60b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/model/MediaRouterCastModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/model/MediaRouterCastModel.kt
@@ -21,6 +21,10 @@ sealed interface MediaRouterCastModel {
/** MediaRouter isn't aware of any active cast. */
data object DoingNothing : MediaRouterCastModel
- /** MediaRouter has an active cast. */
- data object Casting : MediaRouterCastModel
+ /**
+ * MediaRouter has an active cast.
+ *
+ * @property deviceName the name of the device receiving the cast.
+ */
+ data class Casting(val deviceName: String?) : MediaRouterCastModel
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt
index ffb20a7fd3b8..cac3f252a7e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt
@@ -16,7 +16,9 @@
package com.android.systemui.statusbar.chips.casttootherdevice.ui.view
+import android.content.Context
import android.os.Bundle
+import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel.CastToOtherDeviceChipViewModel.Companion.CAST_TO_OTHER_DEVICE_ICON
import com.android.systemui.statusbar.chips.mediaprojection.domain.model.ProjectionChipModel
@@ -26,6 +28,7 @@ import com.android.systemui.statusbar.phone.SystemUIDialog
/** A dialog that lets the user stop an ongoing cast-screen-to-other-device event. */
class EndCastScreenToOtherDeviceDialogDelegate(
private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
+ private val context: Context,
private val stopAction: () -> Unit,
private val state: ProjectionChipModel.Projecting,
) : SystemUIDialog.Delegate {
@@ -36,16 +39,8 @@ class EndCastScreenToOtherDeviceDialogDelegate(
override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
with(dialog) {
setIcon(CAST_TO_OTHER_DEVICE_ICON)
- setTitle(R.string.cast_screen_to_other_device_stop_dialog_title)
- // TODO(b/332662551): Include device name in this string.
- setMessage(
- endMediaProjectionDialogHelper.getDialogMessage(
- state.projectionState,
- genericMessageResId = R.string.cast_screen_to_other_device_stop_dialog_message,
- specificAppMessageResId =
- R.string.cast_screen_to_other_device_stop_dialog_message_specific_app,
- )
- )
+ setTitle(R.string.cast_to_other_device_stop_dialog_title)
+ setMessage(getMessage())
// No custom on-click, because the dialog will automatically be dismissed when the
// button is clicked anyway.
setNegativeButton(R.string.close_dialog_button, /* onClick= */ null)
@@ -54,4 +49,41 @@ class EndCastScreenToOtherDeviceDialogDelegate(
}
}
}
+
+ private fun getMessage(): String {
+ return if (state.projectionState is MediaProjectionState.Projecting.SingleTask) {
+ val appBeingSharedName =
+ endMediaProjectionDialogHelper.getAppName(state.projectionState)
+ if (appBeingSharedName != null && state.deviceName != null) {
+ context.getString(
+ R.string.cast_to_other_device_stop_dialog_message_specific_app_with_device,
+ appBeingSharedName,
+ state.deviceName,
+ )
+ } else if (appBeingSharedName != null) {
+ context.getString(
+ R.string.cast_to_other_device_stop_dialog_message_specific_app,
+ appBeingSharedName,
+ )
+ } else if (state.deviceName != null) {
+ context.getString(
+ R.string.cast_to_other_device_stop_dialog_message_generic_with_device,
+ state.deviceName
+ )
+ } else {
+ context.getString(R.string.cast_to_other_device_stop_dialog_message_generic)
+ }
+ } else {
+ if (state.deviceName != null) {
+ context.getString(
+ R.string.cast_to_other_device_stop_dialog_message_entire_screen_with_device,
+ state.deviceName
+ )
+ } else {
+ context.getString(
+ R.string.cast_to_other_device_stop_dialog_message_entire_screen,
+ )
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegate.kt
index afe67b489abb..7dc9b255badc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegate.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.chips.casttootherdevice.ui.view
+import android.content.Context
import android.os.Bundle
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel.CastToOtherDeviceChipViewModel.Companion.CAST_TO_OTHER_DEVICE_ICON
@@ -29,6 +30,8 @@ import com.android.systemui.statusbar.phone.SystemUIDialog
*/
class EndGenericCastToOtherDeviceDialogDelegate(
private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
+ private val context: Context,
+ private val deviceName: String?,
private val stopAction: () -> Unit,
) : SystemUIDialog.Delegate {
override fun createDialog(): SystemUIDialog {
@@ -36,11 +39,19 @@ class EndGenericCastToOtherDeviceDialogDelegate(
}
override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+ val message =
+ if (deviceName != null) {
+ context.getString(
+ R.string.cast_to_other_device_stop_dialog_message_generic_with_device,
+ deviceName,
+ )
+ } else {
+ context.getString(R.string.cast_to_other_device_stop_dialog_message_generic)
+ }
with(dialog) {
setIcon(CAST_TO_OTHER_DEVICE_ICON)
setTitle(R.string.cast_to_other_device_stop_dialog_title)
- // TODO(b/332662551): Include device name in this string.
- setMessage(R.string.cast_to_other_device_stop_dialog_message)
+ setMessage(message)
// No custom on-click, because the dialog will automatically be dismissed when the
// button is clicked anyway.
setNegativeButton(R.string.close_dialog_button, /* onClick= */ null)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
index 2eff33610754..4183cdd4369d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel
+import android.content.Context
import androidx.annotation.DrawableRes
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.common.shared.model.ContentDescription
@@ -53,6 +54,7 @@ class CastToOtherDeviceChipViewModel
@Inject
constructor(
@Application private val scope: CoroutineScope,
+ private val context: Context,
private val mediaProjectionChipInteractor: MediaProjectionChipInteractor,
private val mediaRouterChipInteractor: MediaRouterChipInteractor,
private val systemClock: SystemClock,
@@ -115,7 +117,7 @@ constructor(
// This does mean that the audio-only casting chip will *never* show a
// timer, because audio-only casting never activates the MediaProjection
// APIs and those are the only cast APIs that show a timer.
- createIconOnlyCastChip()
+ createIconOnlyCastChip(routerModel.deviceName)
}
}
}
@@ -178,7 +180,7 @@ constructor(
)
}
- private fun createIconOnlyCastChip(): OngoingActivityChipModel.Shown {
+ private fun createIconOnlyCastChip(deviceName: String?): OngoingActivityChipModel.Shown {
return OngoingActivityChipModel.Shown.IconOnly(
icon =
Icon.Resource(
@@ -188,7 +190,7 @@ constructor(
),
colors = ColorsModel.Red,
createDialogLaunchOnClickListener(
- createGenericCastToOtherDeviceDialogDelegate(),
+ createGenericCastToOtherDeviceDialogDelegate(deviceName),
dialogTransitionAnimator,
),
)
@@ -199,13 +201,16 @@ constructor(
) =
EndCastScreenToOtherDeviceDialogDelegate(
endMediaProjectionDialogHelper,
+ context,
stopAction = this::stopProjecting,
state,
)
- private fun createGenericCastToOtherDeviceDialogDelegate() =
+ private fun createGenericCastToOtherDeviceDialogDelegate(deviceName: String?) =
EndGenericCastToOtherDeviceDialogDelegate(
endMediaProjectionDialogHelper,
+ context,
+ deviceName,
stopAction = this::stopMediaRouterCasting,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
index cda17cecbeff..ce60fab3ea6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
@@ -74,7 +74,8 @@ constructor(
},
{ "State: Projecting(type=$str1 hostPackage=$str2)" }
)
- ProjectionChipModel.Projecting(type, state)
+ // TODO(b/351851835): Get the device name.
+ ProjectionChipModel.Projecting(type, state, deviceName = null)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/ProjectionChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/ProjectionChipModel.kt
index 85682f5eb8ff..a1a5e82e808e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/ProjectionChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/ProjectionChipModel.kt
@@ -26,10 +26,16 @@ sealed class ProjectionChipModel {
/** There is no media being projected. */
data object NotProjecting : ProjectionChipModel()
- /** Media is currently being projected. */
+ /**
+ * Media is currently being projected.
+ *
+ * @property deviceName the name of the device receiving the projection, or null if the
+ * projection is to this device (as opposed to a different device).
+ */
data class Projecting(
val type: Type,
val projectionState: MediaProjectionState.Projecting,
+ val deviceName: String?,
) : ProjectionChipModel()
enum class Type {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt
index 402306a12144..600436557efb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt
@@ -16,13 +16,8 @@
package com.android.systemui.statusbar.chips.mediaprojection.ui.view
-import android.annotation.StringRes
import android.app.ActivityManager
-import android.content.Context
import android.content.pm.PackageManager
-import android.text.Html
-import android.text.Html.FROM_HTML_MODE_LEGACY
-import android.text.TextUtils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.statusbar.phone.SystemUIDialog
@@ -35,63 +30,38 @@ class EndMediaProjectionDialogHelper
constructor(
private val dialogFactory: SystemUIDialog.Factory,
private val packageManager: PackageManager,
- private val context: Context
) {
/** Creates a new [SystemUIDialog] using the given delegate. */
fun createDialog(delegate: SystemUIDialog.Delegate): SystemUIDialog {
return dialogFactory.create(delegate)
}
- /** See other [getDialogMessage]. */
- fun getDialogMessage(
- state: MediaProjectionState.Projecting,
- @StringRes genericMessageResId: Int,
- @StringRes specificAppMessageResId: Int,
- ): CharSequence {
+ fun getAppName(state: MediaProjectionState.Projecting): CharSequence? {
val specificTaskInfo =
if (state is MediaProjectionState.Projecting.SingleTask) {
state.task
} else {
null
}
- return getDialogMessage(specificTaskInfo, genericMessageResId, specificAppMessageResId)
+ return getAppName(specificTaskInfo)
+ }
+
+ fun getAppName(specificTaskInfo: ActivityManager.RunningTaskInfo?): CharSequence? {
+ val packageName = specificTaskInfo?.baseIntent?.component?.packageName ?: return null
+ return getAppName(packageName)
}
/**
- * Returns the message to show in the dialog based on the specific media projection state.
- *
- * @param genericMessageResId a res ID for a more generic "end projection" message
- * @param specificAppMessageResId a res ID for an "end projection" message that also lets us
- * specify which app is currently being projected.
+ * Returns the human-readable application name for the given package, or null if it couldn't be
+ * found for any reason.
*/
- fun getDialogMessage(
- specificTaskInfo: ActivityManager.RunningTaskInfo?,
- @StringRes genericMessageResId: Int,
- @StringRes specificAppMessageResId: Int,
- ): CharSequence {
- if (specificTaskInfo == null) {
- return context.getString(genericMessageResId)
- }
- val packageName =
- specificTaskInfo.baseIntent.component?.packageName
- ?: return context.getString(genericMessageResId)
+ fun getAppName(packageName: String): CharSequence? {
return try {
val appInfo = packageManager.getApplicationInfo(packageName, 0)
- val appName = appInfo.loadLabel(packageManager)
- getSpecificAppMessageText(specificAppMessageResId, appName)
+ appInfo.loadLabel(packageManager)
} catch (e: PackageManager.NameNotFoundException) {
// TODO(b/332662551): Log this error.
- context.getString(genericMessageResId)
+ null
}
}
-
- private fun getSpecificAppMessageText(
- @StringRes specificAppMessageResId: Int,
- appName: CharSequence,
- ): CharSequence {
- // https://developer.android.com/guide/topics/resources/string-resource#StylingWithHTML
- val escapedAppName = TextUtils.htmlEncode(appName.toString())
- val text = context.getString(specificAppMessageResId, escapedAppName)
- return Html.fromHtml(text, FROM_HTML_MODE_LEGACY)
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegate.kt
index 9adbff9d304a..1eca827d55c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegate.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.chips.screenrecord.ui.view
import android.app.ActivityManager
+import android.content.Context
import android.os.Bundle
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper
@@ -26,6 +27,7 @@ import com.android.systemui.statusbar.phone.SystemUIDialog
/** A dialog that lets the user stop an ongoing screen recording. */
class EndScreenRecordingDialogDelegate(
private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
+ val context: Context,
private val stopAction: () -> Unit,
private val recordedTask: ActivityManager.RunningTaskInfo?,
) : SystemUIDialog.Delegate {
@@ -35,16 +37,18 @@ class EndScreenRecordingDialogDelegate(
}
override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+ val appName = endMediaProjectionDialogHelper.getAppName(recordedTask)
+ val message =
+ if (appName != null) {
+ context.getString(R.string.screenrecord_stop_dialog_message_specific_app, appName)
+ } else {
+ context.getString(R.string.screenrecord_stop_dialog_message)
+ }
+
with(dialog) {
setIcon(ScreenRecordChipViewModel.ICON)
setTitle(R.string.screenrecord_stop_dialog_title)
- setMessage(
- endMediaProjectionDialogHelper.getDialogMessage(
- recordedTask,
- genericMessageResId = R.string.screenrecord_stop_dialog_message,
- specificAppMessageResId = R.string.screenrecord_stop_dialog_message_specific_app
- )
- )
+ setMessage(message)
// No custom on-click, because the dialog will automatically be dismissed when the
// button is clicked anyway.
setNegativeButton(R.string.close_dialog_button, /* onClick= */ null)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
index 53679f1c0a6c..df25d57315ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel
import android.app.ActivityManager
+import android.content.Context
import androidx.annotation.DrawableRes
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.common.shared.model.ContentDescription
@@ -47,6 +48,7 @@ class ScreenRecordChipViewModel
@Inject
constructor(
@Application private val scope: CoroutineScope,
+ private val context: Context,
private val interactor: ScreenRecordChipInteractor,
private val systemClock: SystemClock,
private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
@@ -90,6 +92,7 @@ constructor(
): EndScreenRecordingDialogDelegate {
return EndScreenRecordingDialogDelegate(
endMediaProjectionDialogHelper,
+ context,
stopAction = interactor::stopRecording,
recordedTask,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegate.kt
index 7e7ef402b226..564f20e4b596 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegate.kt
@@ -16,7 +16,9 @@
package com.android.systemui.statusbar.chips.sharetoapp.ui.view
+import android.content.Context
import android.os.Bundle
+import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.mediaprojection.domain.model.ProjectionChipModel
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper
@@ -26,6 +28,7 @@ import com.android.systemui.statusbar.phone.SystemUIDialog
/** A dialog that lets the user stop an ongoing share-screen-to-app event. */
class EndShareToAppDialogDelegate(
private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
+ private val context: Context,
private val stopAction: () -> Unit,
private val state: ProjectionChipModel.Projecting,
) : SystemUIDialog.Delegate {
@@ -37,13 +40,7 @@ class EndShareToAppDialogDelegate(
with(dialog) {
setIcon(SHARE_TO_APP_ICON)
setTitle(R.string.share_to_app_stop_dialog_title)
- setMessage(
- endMediaProjectionDialogHelper.getDialogMessage(
- state.projectionState,
- genericMessageResId = R.string.share_to_app_stop_dialog_message,
- specificAppMessageResId = R.string.share_to_app_stop_dialog_message_specific_app
- )
- )
+ setMessage(getMessage())
// No custom on-click, because the dialog will automatically be dismissed when the
// button is clicked anyway.
setNegativeButton(R.string.close_dialog_button, /* onClick= */ null)
@@ -52,4 +49,32 @@ class EndShareToAppDialogDelegate(
}
}
}
+
+ private fun getMessage(): String {
+ return if (state.projectionState is MediaProjectionState.Projecting.SingleTask) {
+ // If a single app is being shared, use the name of the app being shared in the dialog
+ val appBeingSharedName =
+ endMediaProjectionDialogHelper.getAppName(state.projectionState)
+ if (appBeingSharedName != null) {
+ context.getString(
+ R.string.share_to_app_stop_dialog_message_single_app_specific,
+ appBeingSharedName,
+ )
+ } else {
+ context.getString(R.string.share_to_app_stop_dialog_message_single_app_generic)
+ }
+ } else {
+ // Otherwise, use the name of the app *receiving* the share
+ val hostAppName =
+ endMediaProjectionDialogHelper.getAppName(state.projectionState.hostPackage)
+ if (hostAppName != null) {
+ context.getString(
+ R.string.share_to_app_stop_dialog_message_entire_screen_with_host_app,
+ hostAppName
+ )
+ } else {
+ context.getString(R.string.share_to_app_stop_dialog_message_entire_screen)
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
index 8aef5a4e7629..c09772068093 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel
+import android.content.Context
import androidx.annotation.DrawableRes
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.common.shared.model.ContentDescription
@@ -48,6 +49,7 @@ class ShareToAppChipViewModel
@Inject
constructor(
@Application private val scope: CoroutineScope,
+ private val context: Context,
private val mediaProjectionChipInteractor: MediaProjectionChipInteractor,
private val systemClock: SystemClock,
private val dialogTransitionAnimator: DialogTransitionAnimator,
@@ -97,6 +99,7 @@ constructor(
private fun createShareToAppDialogDelegate(state: ProjectionChipModel.Projecting) =
EndShareToAppDialogDelegate(
endMediaProjectionDialogHelper,
+ context,
stopAction = this::stopProjecting,
state,
)
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
index 64f8246118c8..a58a264c8348 100644
--- a/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
@@ -27,6 +27,7 @@ import android.util.Log;
import androidx.annotation.NonNull;
+import com.android.app.tracing.TraceStateLogger;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager;
@@ -41,11 +42,11 @@ import javax.inject.Named;
/**
* The {@link PersistentConnectionManager} is responsible for maintaining a connection to a
* {@link ObservableServiceConnection}.
+ *
* @param <T> The transformed connection type handled by the service.
*/
public class PersistentConnectionManager<T> implements Dumpable {
private static final String TAG = "PersistentConnManager";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final SystemClock mSystemClock;
private final DelayableExecutor mBgExecutor;
@@ -55,6 +56,7 @@ public class PersistentConnectionManager<T> implements Dumpable {
private final Observer mObserver;
private final DumpManager mDumpManager;
private final String mDumpsysName;
+ private final TraceStateLogger mConnectionReasonLogger;
private int mReconnectAttempts = 0;
private Runnable mCurrentReconnectCancelable;
@@ -64,36 +66,52 @@ public class PersistentConnectionManager<T> implements Dumpable {
private final Runnable mConnectRunnable = new Runnable() {
@Override
public void run() {
+ mConnectionReasonLogger.log("ConnectionReasonRetry");
mCurrentReconnectCancelable = null;
mConnection.bind();
}
};
- private final Observer.Callback mObserverCallback = () -> initiateConnectionAttempt();
+ private final Observer.Callback mObserverCallback = () -> initiateConnectionAttempt(
+ "ConnectionReasonObserver");
private final ObservableServiceConnection.Callback<T> mConnectionCallback =
new ObservableServiceConnection.Callback<>() {
- private long mStartTime;
-
- @Override
- public void onConnected(ObservableServiceConnection connection, Object proxy) {
- mStartTime = mSystemClock.currentTimeMillis();
- }
-
- @Override
- public void onDisconnected(ObservableServiceConnection connection, int reason) {
- // Do not attempt to reconnect if we were manually unbound
- if (reason == ObservableServiceConnection.DISCONNECT_REASON_UNBIND) {
- return;
- }
-
- if (mSystemClock.currentTimeMillis() - mStartTime > mMinConnectionDuration) {
- initiateConnectionAttempt();
- } else {
- scheduleConnectionAttempt();
- }
- }
- };
+ private long mStartTime = -1;
+
+ @Override
+ public void onConnected(ObservableServiceConnection connection, Object proxy) {
+ mStartTime = mSystemClock.currentTimeMillis();
+ }
+
+ @Override
+ public void onDisconnected(ObservableServiceConnection connection, int reason) {
+ // Do not attempt to reconnect if we were manually unbound
+ if (reason == ObservableServiceConnection.DISCONNECT_REASON_UNBIND) {
+ return;
+ }
+
+ if (mStartTime <= 0) {
+ Log.e(TAG, "onDisconnected called with invalid connection start time: "
+ + mStartTime);
+ return;
+ }
+
+ final float connectionDuration = mSystemClock.currentTimeMillis() - mStartTime;
+ // Reset the start time.
+ mStartTime = -1;
+
+ if (connectionDuration > mMinConnectionDuration) {
+ Log.i(TAG, "immediately reconnecting since service was connected for "
+ + connectionDuration
+ + "ms which is longer than the min duration of "
+ + mMinConnectionDuration + "ms");
+ initiateConnectionAttempt("ConnectionReasonMinDurationMet");
+ } else {
+ scheduleConnectionAttempt();
+ }
+ }
+ };
@Inject
public PersistentConnectionManager(
@@ -112,6 +130,7 @@ public class PersistentConnectionManager<T> implements Dumpable {
mObserver = observer;
mDumpManager = dumpManager;
mDumpsysName = TAG + "#" + dumpsysName;
+ mConnectionReasonLogger = new TraceStateLogger(mDumpsysName);
mMaxReconnectAttempts = maxReconnectAttempts;
mBaseReconnectDelayMs = baseReconnectDelayMs;
@@ -125,7 +144,7 @@ public class PersistentConnectionManager<T> implements Dumpable {
mDumpManager.registerCriticalDumpable(mDumpsysName, this);
mConnection.addCallback(mConnectionCallback);
mObserver.addCallback(mObserverCallback);
- initiateConnectionAttempt();
+ initiateConnectionAttempt("ConnectionReasonStart");
}
/**
@@ -140,6 +159,7 @@ public class PersistentConnectionManager<T> implements Dumpable {
/**
* Add a callback to the {@link ObservableServiceConnection}.
+ *
* @param callback The callback to add.
*/
public void addConnectionCallback(ObservableServiceConnection.Callback<T> callback) {
@@ -148,6 +168,7 @@ public class PersistentConnectionManager<T> implements Dumpable {
/**
* Remove a callback from the {@link ObservableServiceConnection}.
+ *
* @param callback The callback to remove.
*/
public void removeConnectionCallback(ObservableServiceConnection.Callback<T> callback) {
@@ -163,10 +184,10 @@ public class PersistentConnectionManager<T> implements Dumpable {
mConnection.dump(pw);
}
- private void initiateConnectionAttempt() {
+ private void initiateConnectionAttempt(String reason) {
+ mConnectionReasonLogger.log(reason);
// Reset attempts
mReconnectAttempts = 0;
-
// The first attempt is always a direct invocation rather than delayed.
mConnection.bind();
}
@@ -179,20 +200,15 @@ public class PersistentConnectionManager<T> implements Dumpable {
}
if (mReconnectAttempts >= mMaxReconnectAttempts) {
- if (DEBUG) {
- Log.d(TAG, "exceeded max connection attempts.");
- }
+ Log.d(TAG, "exceeded max connection attempts.");
return;
}
final long reconnectDelayMs =
(long) Math.scalb(mBaseReconnectDelayMs, mReconnectAttempts);
- if (DEBUG) {
- Log.d(TAG,
- "scheduling connection attempt in " + reconnectDelayMs + "milliseconds");
- }
-
+ Log.d(TAG,
+ "scheduling connection attempt in " + reconnectDelayMs + "milliseconds");
mCurrentReconnectCancelable = mBgExecutor.executeDelayed(mConnectRunnable,
reconnectDelayMs);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
index 42ab25fcd9bd..032794c43f08 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
@@ -56,6 +56,7 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
+import org.junit.Ignore
import org.junit.runner.RunWith
import org.mockito.Mockito.reset
import org.mockito.Mockito.spy
@@ -97,6 +98,7 @@ class FromDreamingTransitionInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ @Ignore("Until b/349837588 is fixed")
fun testTransitionToOccluded_ifDreamEnds_occludingActivityOnTop() =
testScope.runTest {
kosmos.fakeKeyguardRepository.setDreaming(true)
@@ -156,6 +158,7 @@ class FromDreamingTransitionInteractorTest : SysuiTestCase() {
reset(transitionRepository)
kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = false)
+ kosmos.fakeKeyguardRepository.setDreaming(false)
runCurrent()
assertThat(transitionRepository)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt
index 459e41d7dbc0..ea5a41f6fd5c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt
@@ -18,25 +18,22 @@ package com.android.systemui.keyguard.domain.interactor
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestModule
import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.inWindowLauncherUnlockAnimationRepository
+import com.android.systemui.keyguard.data.repository.keyguardSurfaceBehindRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.util.mockTopActivityClassName
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
import com.android.systemui.shared.system.ActivityManagerWrapper
-import com.android.systemui.user.domain.UserDomainLayerModule
-import dagger.BindsInstance
-import dagger.Component
+import com.android.systemui.shared.system.activityManagerWrapper
+import com.android.systemui.testKosmos
import junit.framework.Assert.assertEquals
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -49,10 +46,16 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
@kotlinx.coroutines.ExperimentalCoroutinesApi
class InWindowLauncherUnlockAnimationInteractorTest : SysuiTestCase() {
- private lateinit var underTest: InWindowLauncherUnlockAnimationInteractor
-
- private lateinit var testComponent: TestComponent
- private lateinit var testScope: TestScope
+ private val kosmos = testKosmos()
+ private val underTest =
+ InWindowLauncherUnlockAnimationInteractor(
+ kosmos.inWindowLauncherUnlockAnimationRepository,
+ kosmos.applicationCoroutineScope,
+ kosmos.keyguardTransitionInteractor,
+ { kosmos.keyguardSurfaceBehindRepository },
+ kosmos.activityManagerWrapper,
+ )
+ private val testScope = kosmos.testScope
private lateinit var transitionRepository: FakeKeyguardTransitionRepository
@Mock private lateinit var activityManagerWrapper: ActivityManagerWrapper
@@ -62,19 +65,9 @@ class InWindowLauncherUnlockAnimationInteractorTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
- testComponent =
- DaggerInWindowLauncherUnlockAnimationInteractorTest_TestComponent.factory()
- .create(
- test = this,
- mocks =
- TestMocksModule(
- activityManagerWrapper = activityManagerWrapper,
- ),
- )
- underTest = testComponent.underTest
- testScope = testComponent.testScope
- transitionRepository = testComponent.transitionRepository
+ transitionRepository = kosmos.fakeKeyguardTransitionRepository
+ activityManagerWrapper = kosmos.activityManagerWrapper
activityManagerWrapper.mockTopActivityClassName(launcherClassName)
}
@@ -92,7 +85,7 @@ class InWindowLauncherUnlockAnimationInteractorTest : SysuiTestCase() {
)
// Put launcher on top
- testComponent.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
+ kosmos.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
launcherClassName
)
activityManagerWrapper.mockTopActivityClassName(launcherClassName)
@@ -175,7 +168,7 @@ class InWindowLauncherUnlockAnimationInteractorTest : SysuiTestCase() {
)
// Put not launcher on top
- testComponent.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
+ kosmos.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
launcherClassName
)
activityManagerWrapper.mockTopActivityClassName("not_launcher")
@@ -252,7 +245,7 @@ class InWindowLauncherUnlockAnimationInteractorTest : SysuiTestCase() {
)
// Put launcher on top
- testComponent.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
+ kosmos.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
launcherClassName
)
activityManagerWrapper.mockTopActivityClassName(launcherClassName)
@@ -296,7 +289,7 @@ class InWindowLauncherUnlockAnimationInteractorTest : SysuiTestCase() {
)
// Put Launcher on top and begin transitioning to GONE.
- testComponent.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
+ kosmos.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
launcherClassName
)
activityManagerWrapper.mockTopActivityClassName(launcherClassName)
@@ -316,7 +309,7 @@ class InWindowLauncherUnlockAnimationInteractorTest : SysuiTestCase() {
values
)
- testComponent.surfaceBehindRepository.setSurfaceRemoteAnimationTargetAvailable(true)
+ kosmos.keyguardSurfaceBehindRepository.setSurfaceRemoteAnimationTargetAvailable(true)
runCurrent()
assertEquals(
@@ -360,7 +353,7 @@ class InWindowLauncherUnlockAnimationInteractorTest : SysuiTestCase() {
)
// Put Launcher on top and begin transitioning to GONE.
- testComponent.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
+ kosmos.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
launcherClassName
)
activityManagerWrapper.mockTopActivityClassName(launcherClassName)
@@ -402,7 +395,7 @@ class InWindowLauncherUnlockAnimationInteractorTest : SysuiTestCase() {
)
// Put Launcher on top and begin transitioning to GONE.
- testComponent.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
+ kosmos.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
launcherClassName
)
activityManagerWrapper.mockTopActivityClassName(launcherClassName)
@@ -427,7 +420,7 @@ class InWindowLauncherUnlockAnimationInteractorTest : SysuiTestCase() {
to = KeyguardState.AOD,
)
)
- testComponent.surfaceBehindRepository.setSurfaceRemoteAnimationTargetAvailable(true)
+ kosmos.keyguardSurfaceBehindRepository.setSurfaceRemoteAnimationTargetAvailable(true)
runCurrent()
assertEquals(
@@ -437,29 +430,4 @@ class InWindowLauncherUnlockAnimationInteractorTest : SysuiTestCase() {
values
)
}
-
- @SysUISingleton
- @Component(
- modules =
- [
- SysUITestModule::class,
- BiometricsDomainLayerModule::class,
- UserDomainLayerModule::class,
- ]
- )
- interface TestComponent {
- val underTest: InWindowLauncherUnlockAnimationInteractor
- val testScope: TestScope
- val transitionRepository: FakeKeyguardTransitionRepository
- val surfaceBehindRepository: FakeKeyguardSurfaceBehindRepository
- val inWindowLauncherUnlockAnimationRepository: InWindowLauncherUnlockAnimationRepository
-
- @Component.Factory
- interface Factory {
- fun create(
- @BindsInstance test: SysuiTestCase,
- mocks: TestMocksModule,
- ): TestComponent
- }
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
new file mode 100644
index 000000000000..22181f8fa568
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
@@ -0,0 +1,370 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import android.app.AlarmManager
+import android.app.admin.alarmManager
+import android.app.admin.devicePolicyManager
+import android.content.BroadcastReceiver
+import android.content.Intent
+import android.content.mockedContext
+import android.os.PowerManager
+import android.os.UserHandle
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.lockPatternUtils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.testKosmos
+import com.android.systemui.util.settings.fakeSettings
+import junit.framework.Assert.assertEquals
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardWakeDirectlyToGoneInteractorTest : SysuiTestCase() {
+
+ private var lastRegisteredBroadcastReceiver: BroadcastReceiver? = null
+ private val kosmos =
+ testKosmos().apply {
+ whenever(mockedContext.user).thenReturn(mock<UserHandle>())
+ doAnswer { invocation ->
+ lastRegisteredBroadcastReceiver = invocation.arguments[0] as BroadcastReceiver
+ }
+ .whenever(mockedContext)
+ .registerReceiver(any(), any(), any(), any(), any())
+ }
+
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.keyguardWakeDirectlyToGoneInteractor
+ private val lockPatternUtils = kosmos.lockPatternUtils
+ private val repository = kosmos.fakeKeyguardRepository
+ private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+ @Test
+ fun testCanWakeDirectlyToGone_keyguardServiceEnabledThenDisabled() =
+ testScope.runTest {
+ val canWake by collectValues(underTest.canWakeDirectlyToGone)
+
+ assertEquals(
+ listOf(
+ false, // Defaults to false.
+ ),
+ canWake
+ )
+
+ repository.setKeyguardEnabled(false)
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false, // Default to false.
+ true, // True now that keyguard service is disabled
+ ),
+ canWake
+ )
+
+ repository.setKeyguardEnabled(true)
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ true,
+ false,
+ ),
+ canWake
+ )
+ }
+
+ @Test
+ fun testCanWakeDirectlyToGone_lockscreenDisabledThenEnabled() =
+ testScope.runTest {
+ val canWake by collectValues(underTest.canWakeDirectlyToGone)
+
+ assertEquals(
+ listOf(
+ false, // Defaults to false.
+ ),
+ canWake
+ )
+
+ whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true)
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ // Still false - isLockScreenDisabled only causes canWakeDirectlyToGone to
+ // update on the next wake/sleep event.
+ false,
+ ),
+ canWake
+ )
+
+ kosmos.powerInteractor.setAsleepForTest()
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ // True since we slept after setting isLockScreenDisabled=true
+ true,
+ ),
+ canWake
+ )
+
+ kosmos.powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ kosmos.powerInteractor.setAsleepForTest()
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ true,
+ ),
+ canWake
+ )
+
+ whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false)
+ kosmos.powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ true,
+ false,
+ ),
+ canWake
+ )
+ }
+
+ @Test
+ fun testCanWakeDirectlyToGone_wakeAndUnlock() =
+ testScope.runTest {
+ val canWake by collectValues(underTest.canWakeDirectlyToGone)
+
+ assertEquals(
+ listOf(
+ false, // Defaults to false.
+ ),
+ canWake
+ )
+
+ repository.setBiometricUnlockState(BiometricUnlockMode.WAKE_AND_UNLOCK)
+ runCurrent()
+
+ assertEquals(listOf(false, true), canWake)
+
+ repository.setBiometricUnlockState(BiometricUnlockMode.NONE)
+ runCurrent()
+
+ assertEquals(listOf(false, true, false), canWake)
+ }
+
+ @Test
+ fun testCanWakeDirectlyToGone_andSetsAlarm_ifPowerButtonDoesNotLockImmediately() =
+ testScope.runTest {
+ val canWake by collectValues(underTest.canWakeDirectlyToGone)
+
+ assertEquals(
+ listOf(
+ false, // Defaults to false.
+ ),
+ canWake
+ )
+
+ repository.setCanIgnoreAuthAndReturnToGone(true)
+ runCurrent()
+
+ assertEquals(listOf(false, true), canWake)
+
+ repository.setCanIgnoreAuthAndReturnToGone(false)
+ runCurrent()
+
+ assertEquals(listOf(false, true, false), canWake)
+ }
+
+ @Test
+ fun testSetsCanIgnoreAuth_andSetsAlarm_whenTimingOut() =
+ testScope.runTest {
+ val canWake by collectValues(underTest.canWakeDirectlyToGone)
+
+ assertEquals(
+ listOf(
+ false, // Defaults to false.
+ ),
+ canWake
+ )
+
+ whenever(kosmos.devicePolicyManager.getMaximumTimeToLock(eq(null), anyInt()))
+ .thenReturn(-1)
+ kosmos.fakeSettings.putInt(Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT, 500)
+
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ testScope,
+ )
+
+ kosmos.powerInteractor.setAsleepForTest(
+ sleepReason = PowerManager.GO_TO_SLEEP_REASON_TIMEOUT
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ true,
+ ),
+ canWake
+ )
+
+ verify(kosmos.alarmManager)
+ .setExactAndAllowWhileIdle(
+ eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
+ anyLong(),
+ any(),
+ )
+ }
+
+ @Test
+ fun testCancelsFirstAlarm_onWake_withSecondAlarmSet() =
+ testScope.runTest {
+ val canWake by collectValues(underTest.canWakeDirectlyToGone)
+
+ assertEquals(
+ listOf(
+ false, // Defaults to false.
+ ),
+ canWake
+ )
+
+ whenever(kosmos.devicePolicyManager.getMaximumTimeToLock(eq(null), anyInt()))
+ .thenReturn(-1)
+ kosmos.fakeSettings.putInt(Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT, 500)
+
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ testScope,
+ )
+
+ kosmos.powerInteractor.setAsleepForTest(
+ sleepReason = PowerManager.GO_TO_SLEEP_REASON_TIMEOUT
+ )
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ testScope = testScope,
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ // Timed out, so we can ignore auth/return to GONE.
+ true,
+ ),
+ canWake
+ )
+
+ verify(kosmos.alarmManager)
+ .setExactAndAllowWhileIdle(
+ eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
+ anyLong(),
+ any(),
+ )
+
+ kosmos.powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.GONE,
+ testScope = testScope,
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ true,
+ // Should be canceled by the wakeup, but there would still be an
+ // alarm in flight that should be canceled.
+ false,
+ ),
+ canWake
+ )
+
+ kosmos.powerInteractor.setAsleepForTest(
+ sleepReason = PowerManager.GO_TO_SLEEP_REASON_TIMEOUT
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ true,
+ false,
+ // Back to sleep.
+ true,
+ ),
+ canWake
+ )
+
+ // Simulate the first sleep's alarm coming in.
+ lastRegisteredBroadcastReceiver?.onReceive(
+ kosmos.mockedContext,
+ Intent("com.android.internal.policy.impl.PhoneWindowManager.DELAYED_KEYGUARD")
+ )
+ runCurrent()
+
+ // It should not have any effect.
+ assertEquals(
+ listOf(
+ false,
+ true,
+ false,
+ true,
+ ),
+ canWake
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt
index c7f44164e582..0cfc20d7bbd8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt
@@ -18,17 +18,15 @@ package com.android.systemui.keyguard.ui.binder
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestModule
import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
-import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.inWindowLauncherUnlockAnimationInteractor
import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager
+import com.android.systemui.keyguard.ui.viewmodel.InWindowLauncherAnimationViewModel
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController
-import com.android.systemui.user.domain.UserDomainLayerModule
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
-import dagger.BindsInstance
-import dagger.Component
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -45,10 +43,9 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
@kotlinx.coroutines.ExperimentalCoroutinesApi
class InWindowLauncherUnlockAnimationManagerTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
private lateinit var underTest: InWindowLauncherUnlockAnimationManager
-
- private lateinit var testComponent: TestComponent
- private lateinit var testScope: TestScope
+ private val testScope = kosmos.testScope
@Mock private lateinit var launcherUnlockAnimationController: ILauncherUnlockAnimationController
@@ -56,14 +53,14 @@ class InWindowLauncherUnlockAnimationManagerTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
- testComponent =
- DaggerInWindowLauncherUnlockAnimationManagerTest_TestComponent.factory()
- .create(
- test = this,
- )
- underTest = testComponent.underTest
- testScope = testComponent.testScope
-
+ underTest =
+ InWindowLauncherUnlockAnimationManager(
+ kosmos.inWindowLauncherUnlockAnimationInteractor,
+ InWindowLauncherAnimationViewModel(
+ kosmos.inWindowLauncherUnlockAnimationInteractor
+ ),
+ kosmos.applicationCoroutineScope
+ )
underTest.setLauncherUnlockController("launcherClass", launcherUnlockAnimationController)
}
@@ -114,25 +111,4 @@ class InWindowLauncherUnlockAnimationManagerTest : SysuiTestCase() {
verifyNoMoreInteractions(launcherUnlockAnimationController)
}
-
- @SysUISingleton
- @Component(
- modules =
- [
- SysUITestModule::class,
- BiometricsDomainLayerModule::class,
- UserDomainLayerModule::class,
- ]
- )
- interface TestComponent {
- val underTest: InWindowLauncherUnlockAnimationManager
- val testScope: TestScope
-
- @Component.Factory
- interface Factory {
- fun create(
- @BindsInstance test: SysuiTestCase,
- ): TestComponent
- }
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
index 548366e3fc38..d183c7370713 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
@@ -79,7 +79,7 @@ class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() {
val overrideDisableSingleAppOption = false
setUpAndShowDialog(overrideDisableSingleAppOption)
- val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
+ val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
val secondOptionText =
spinner.adapter
.getDropDownView(1, null, spinner)
@@ -100,7 +100,7 @@ class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() {
val overrideDisableSingleAppOption = true
setUpAndShowDialog(overrideDisableSingleAppOption)
- val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
+ val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
val secondOptionText =
spinner.adapter
.getDropDownView(1, null, spinner)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
index db607fd56643..cc8d7d532bda 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
@@ -109,7 +109,7 @@ class ScreenRecordPermissionDialogDelegateTest : SysuiTestCase() {
fun testShowDialog_partialScreenSharingEnabled_optionsSpinnerIsVisible() {
showDialog()
- val visibility = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner).visibility
+ val visibility = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options).visibility
assertThat(visibility).isEqualTo(View.VISIBLE)
}
@@ -155,7 +155,7 @@ class ScreenRecordPermissionDialogDelegateTest : SysuiTestCase() {
fun showDialog_singleAppIsDefault() {
showDialog()
- val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
+ val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
val singleApp = context.getString(R.string.screen_share_permission_dialog_option_single_app)
assertEquals(spinner.adapter.getItem(0), singleApp)
}
@@ -217,7 +217,7 @@ class ScreenRecordPermissionDialogDelegateTest : SysuiTestCase() {
}
private fun onSpinnerItemSelected(position: Int) {
- val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
+ val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
checkNotNull(spinner.onItemSelectedListener)
.onItemSelected(spinner, mock(), position, /* id= */ 0)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 69e8f4737a5a..9e6a498b325a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -6,26 +6,23 @@ import android.testing.TestableLooper.RunWithLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.ExpandHelper
-import com.android.systemui.SysUITestModule
import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.classifier.FalsingManagerFake
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver
+import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
import com.android.systemui.plugins.qs.QS
-import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
import com.android.systemui.res.R
+import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
-import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
+import com.android.systemui.statusbar.disableflags.data.repository.disableFlagsRepository
+import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.NotificationTestHelper
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
@@ -33,17 +30,16 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.ScrimController
-import com.android.systemui.statusbar.policy.FakeConfigurationController
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
-import com.android.systemui.user.domain.UserDomainLayerModule
+import com.android.systemui.statusbar.policy.configurationController
+import com.android.systemui.statusbar.policy.fakeConfigurationController
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
-import dagger.BindsInstance
-import dagger.Component
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
@@ -65,8 +61,8 @@ import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
private fun <T> anyObject(): T {
return Mockito.anyObject<T>()
@@ -77,15 +73,14 @@ private fun <T> anyObject(): T {
@RunWith(AndroidJUnit4::class)
@OptIn(ExperimentalCoroutinesApi::class)
class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
-
+ private val kosmos =
+ testKosmos().apply {
+ fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
+ }
private lateinit var transitionController: LockscreenShadeTransitionController
- private lateinit var testComponent: TestComponent
- private val configurationController
- get() = testComponent.configurationController
- private val disableFlagsRepository
- get() = testComponent.disableFlagsRepository
- private val testScope
- get() = testComponent.testScope
+ private val configurationController = kosmos.fakeConfigurationController
+ private val disableFlagsRepository = kosmos.fakeDisableFlagsRepository
+ private val testScope = kosmos.testScope
private val qsSceneAdapter = FakeQSSceneAdapter({ mock() })
@@ -134,26 +129,6 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
whenever(keyguardBypassController.bypassEnabled).thenReturn(false)
whenever(naturalScrollingSettingObserver.isNaturalScrollingEnabled).thenReturn(true)
- testComponent =
- DaggerLockscreenShadeTransitionControllerTest_TestComponent.factory()
- .create(
- test = this,
- featureFlags =
- FakeFeatureFlagsClassicModule {
- set(Flags.FULL_SCREEN_USER_SWITCHER, false)
- },
- mocks =
- TestMocksModule(
- notificationShadeDepthController = depthController,
- keyguardBypassController = keyguardBypassController,
- mediaHierarchyManager = mediaHierarchyManager,
- notificationLockscreenUserManager = lockScreenUserManager,
- notificationStackScrollLayoutController = nsslController,
- scrimController = scrimController,
- statusBarStateController = statusbarStateController,
- )
- )
-
transitionController =
LockscreenShadeTransitionController(
statusBarStateController = statusbarStateController,
@@ -191,10 +166,10 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
falsingManager = FalsingManagerFake(),
dumpManager = mock(),
qsTransitionControllerFactory = { qsTransitionController },
- shadeRepository = testComponent.shadeRepository,
- shadeInteractor = testComponent.shadeInteractor,
+ shadeRepository = kosmos.shadeRepository,
+ shadeInteractor = kosmos.shadeInteractor,
splitShadeStateController = ResourcesSplitShadeStateController(),
- shadeLockscreenInteractorLazy = {shadeLockscreenInteractor},
+ shadeLockscreenInteractorLazy = { shadeLockscreenInteractor },
naturalScrollingSettingObserver = naturalScrollingSettingObserver,
lazyQSSceneAdapter = { qsSceneAdapter }
)
@@ -214,387 +189,424 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
}
@Test
- fun testCantDragDownWhenQSExpanded() {
- assertTrue("Can't drag down on keyguard", transitionController.canDragDown())
- whenever(qS.isFullyCollapsed).thenReturn(false)
- assertFalse("Can drag down when QS is expanded", transitionController.canDragDown())
- }
+ fun testCantDragDownWhenQSExpanded() =
+ testScope.runTest {
+ assertTrue("Can't drag down on keyguard", transitionController.canDragDown())
+ whenever(qS.isFullyCollapsed).thenReturn(false)
+ assertFalse("Can drag down when QS is expanded", transitionController.canDragDown())
+ }
@Test
- fun testCanDragDownInLockedDownShade() {
- whenever(statusbarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
- assertFalse("Can drag down in shade locked", transitionController.canDragDown())
- whenever(nsslController.isInLockedDownShade).thenReturn(true)
- assertTrue("Can't drag down in locked down shade", transitionController.canDragDown())
- }
+ fun testCanDragDownInLockedDownShade() =
+ testScope.runTest {
+ whenever(statusbarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
+ assertFalse("Can drag down in shade locked", transitionController.canDragDown())
+ whenever(nsslController.isInLockedDownShade).thenReturn(true)
+ assertTrue("Can't drag down in locked down shade", transitionController.canDragDown())
+ }
@Test
- fun testGoingToLockedShade() {
- transitionController.goToLockedShade(null)
- verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
- }
+ fun testGoingToLockedShade() =
+ testScope.runTest {
+ transitionController.goToLockedShade(null)
+ verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
+ }
@Test
- fun testWakingToShadeLockedWhenDozing() {
- whenever(statusbarStateController.isDozing).thenReturn(true)
- transitionController.goToLockedShade(null)
- verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
- assertTrue("Not waking to shade locked", transitionController.isWakingToShadeLocked)
- }
+ fun testWakingToShadeLockedWhenDozing() =
+ testScope.runTest {
+ whenever(statusbarStateController.isDozing).thenReturn(true)
+ transitionController.goToLockedShade(null)
+ verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
+ assertTrue("Not waking to shade locked", transitionController.isWakingToShadeLocked)
+ }
@Test
- fun testNotWakingToShadeLockedWhenNotDozing() {
- whenever(statusbarStateController.isDozing).thenReturn(false)
- transitionController.goToLockedShade(null)
- verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
- assertFalse(
- "Waking to shade locked when not dozing",
- transitionController.isWakingToShadeLocked
- )
- }
+ fun testNotWakingToShadeLockedWhenNotDozing() =
+ testScope.runTest {
+ whenever(statusbarStateController.isDozing).thenReturn(false)
+ transitionController.goToLockedShade(null)
+ verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
+ assertFalse(
+ "Waking to shade locked when not dozing",
+ transitionController.isWakingToShadeLocked
+ )
+ }
@Test
- fun testGoToLockedShadeOnlyOnKeyguard() {
- whenever(statusbarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
- transitionController.goToLockedShade(null)
- whenever(statusbarStateController.state).thenReturn(StatusBarState.SHADE)
- transitionController.goToLockedShade(null)
- verify(statusbarStateController, never()).setState(anyInt())
- }
+ fun testGoToLockedShadeOnlyOnKeyguard() =
+ testScope.runTest {
+ whenever(statusbarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
+ transitionController.goToLockedShade(null)
+ whenever(statusbarStateController.state).thenReturn(StatusBarState.SHADE)
+ transitionController.goToLockedShade(null)
+ verify(statusbarStateController, never()).setState(anyInt())
+ }
@Test
- fun testDontGoWhenShadeDisabled() {
- disableFlagsRepository.disableFlags.value =
- DisableFlagsModel(
- disable2 = DISABLE2_NOTIFICATION_SHADE,
- )
- testScope.runCurrent()
- transitionController.goToLockedShade(null)
- verify(statusbarStateController, never()).setState(anyInt())
- }
+ fun testDontGoWhenShadeDisabled() =
+ testScope.runTest {
+ disableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(
+ disable2 = DISABLE2_NOTIFICATION_SHADE,
+ )
+ testScope.runCurrent()
+ transitionController.goToLockedShade(null)
+ verify(statusbarStateController, never()).setState(anyInt())
+ }
@Test
- fun testUserExpandsViewOnGoingToFullShade() {
- assertFalse("Row shouldn't be user expanded yet", row.isUserExpanded)
- transitionController.goToLockedShade(row)
- assertTrue("Row wasn't user expanded on drag down", row.isUserExpanded)
- }
+ fun testUserExpandsViewOnGoingToFullShade() =
+ testScope.runTest {
+ assertFalse("Row shouldn't be user expanded yet", row.isUserExpanded)
+ transitionController.goToLockedShade(row)
+ assertTrue("Row wasn't user expanded on drag down", row.isUserExpanded)
+ }
@Test
- fun testTriggeringBouncerNoNotificationsOnLockscreen() {
- whenever(lockScreenUserManager.shouldShowLockscreenNotifications()).thenReturn(false)
- transitionController.goToLockedShade(null)
- verify(statusbarStateController, never()).setState(anyInt())
- verify(statusbarStateController).setLeaveOpenOnKeyguardHide(true)
- verify(centralSurfaces).showBouncerWithDimissAndCancelIfKeyguard(anyObject(), anyObject())
- }
+ fun testTriggeringBouncerNoNotificationsOnLockscreen() =
+ testScope.runTest {
+ whenever(lockScreenUserManager.shouldShowLockscreenNotifications()).thenReturn(false)
+ transitionController.goToLockedShade(null)
+ verify(statusbarStateController, never()).setState(anyInt())
+ verify(statusbarStateController).setLeaveOpenOnKeyguardHide(true)
+ verify(centralSurfaces)
+ .showBouncerWithDimissAndCancelIfKeyguard(anyObject(), anyObject())
+ }
@Test
- fun testGoToLockedShadeCreatesQSAnimation() {
- transitionController.goToLockedShade(null)
- verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
- verify(shadeLockscreenInteractor).transitionToExpandedShade(anyLong())
- assertNotNull(transitionController.dragDownAnimator)
- }
+ fun testGoToLockedShadeCreatesQSAnimation() =
+ testScope.runTest {
+ transitionController.goToLockedShade(null)
+ verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
+ verify(shadeLockscreenInteractor).transitionToExpandedShade(anyLong())
+ assertNotNull(transitionController.dragDownAnimator)
+ }
@Test
- fun testGoToLockedShadeDoesntCreateQSAnimation() {
- transitionController.goToLockedShade(null, needsQSAnimation = false)
- verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
- verify(shadeLockscreenInteractor).transitionToExpandedShade(anyLong())
- assertNull(transitionController.dragDownAnimator)
- }
+ fun testGoToLockedShadeDoesntCreateQSAnimation() =
+ testScope.runTest {
+ transitionController.goToLockedShade(null, needsQSAnimation = false)
+ verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
+ verify(shadeLockscreenInteractor).transitionToExpandedShade(anyLong())
+ assertNull(transitionController.dragDownAnimator)
+ }
@Test
- fun testGoToLockedShadeAlwaysCreatesQSAnimationInSplitShade() {
- enableSplitShade()
- transitionController.goToLockedShade(null, needsQSAnimation = true)
- verify(shadeLockscreenInteractor).transitionToExpandedShade(anyLong())
- assertNotNull(transitionController.dragDownAnimator)
- }
+ fun testGoToLockedShadeAlwaysCreatesQSAnimationInSplitShade() =
+ testScope.runTest {
+ enableSplitShade()
+ transitionController.goToLockedShade(null, needsQSAnimation = true)
+ verify(shadeLockscreenInteractor).transitionToExpandedShade(anyLong())
+ assertNotNull(transitionController.dragDownAnimator)
+ }
@Test
- fun testGoToLockedShadeCancelDoesntLeaveShadeOpenOnKeyguardHide() {
- whenever(lockScreenUserManager.shouldShowLockscreenNotifications()).thenReturn(false)
- whenever(lockScreenUserManager.isLockscreenPublicMode(any())).thenReturn(true)
- transitionController.goToLockedShade(null)
- val captor = argumentCaptor<Runnable>()
- verify(centralSurfaces).showBouncerWithDimissAndCancelIfKeyguard(isNull(), captor.capture())
- captor.value.run()
- verify(statusbarStateController).setLeaveOpenOnKeyguardHide(false)
- }
+ fun testGoToLockedShadeCancelDoesntLeaveShadeOpenOnKeyguardHide() =
+ testScope.runTest {
+ whenever(lockScreenUserManager.shouldShowLockscreenNotifications()).thenReturn(false)
+ whenever(lockScreenUserManager.isLockscreenPublicMode(any())).thenReturn(true)
+ transitionController.goToLockedShade(null)
+ val captor = argumentCaptor<Runnable>()
+ verify(centralSurfaces)
+ .showBouncerWithDimissAndCancelIfKeyguard(isNull(), captor.capture())
+ captor.value.run()
+ verify(statusbarStateController).setLeaveOpenOnKeyguardHide(false)
+ }
@Test
- fun testDragDownAmountDoesntCallOutInLockedDownShade() {
- whenever(nsslController.isInLockedDownShade).thenReturn(true)
- transitionController.dragDownAmount = 10f
- verify(nsslController, never()).setTransitionToFullShadeAmount(anyFloat())
- verify(mediaHierarchyManager, never()).setTransitionToFullShadeAmount(anyFloat())
- verify(scrimController, never()).setTransitionToFullShadeProgress(anyFloat(), anyFloat())
- verify(transitionControllerCallback, never())
- .setTransitionToFullShadeAmount(anyFloat(), anyBoolean(), anyLong())
- verify(qsTransitionController, never()).dragDownAmount = anyFloat()
- }
+ fun testDragDownAmountDoesntCallOutInLockedDownShade() =
+ testScope.runTest {
+ whenever(nsslController.isInLockedDownShade).thenReturn(true)
+ transitionController.dragDownAmount = 10f
+ verify(nsslController, never()).setTransitionToFullShadeAmount(anyFloat())
+ verify(mediaHierarchyManager, never()).setTransitionToFullShadeAmount(anyFloat())
+ verify(scrimController, never())
+ .setTransitionToFullShadeProgress(anyFloat(), anyFloat())
+ verify(transitionControllerCallback, never())
+ .setTransitionToFullShadeAmount(anyFloat(), anyBoolean(), anyLong())
+ verify(qsTransitionController, never()).dragDownAmount = anyFloat()
+ }
@Test
- fun testDragDownAmountCallsOut() {
- transitionController.dragDownAmount = 10f
- verify(nsslController).setTransitionToFullShadeAmount(anyFloat())
- verify(mediaHierarchyManager).setTransitionToFullShadeAmount(anyFloat())
- verify(scrimController).setTransitionToFullShadeProgress(anyFloat(), anyFloat())
- verify(transitionControllerCallback)
- .setTransitionToFullShadeAmount(anyFloat(), anyBoolean(), anyLong())
- verify(qsTransitionController).dragDownAmount = 10f
- verify(depthController).transitionToFullShadeProgress = anyFloat()
- }
+ fun testDragDownAmountCallsOut() =
+ testScope.runTest {
+ transitionController.dragDownAmount = 10f
+ verify(nsslController).setTransitionToFullShadeAmount(anyFloat())
+ verify(mediaHierarchyManager).setTransitionToFullShadeAmount(anyFloat())
+ verify(scrimController).setTransitionToFullShadeProgress(anyFloat(), anyFloat())
+ verify(transitionControllerCallback)
+ .setTransitionToFullShadeAmount(anyFloat(), anyBoolean(), anyLong())
+ verify(qsTransitionController).dragDownAmount = 10f
+ verify(depthController).transitionToFullShadeProgress = anyFloat()
+ }
@Test
- fun testDragDownAmount_depthDistanceIsZero_setsProgressToZero() {
- context
- .getOrCreateTestableResources()
- .addOverride(R.dimen.lockscreen_shade_depth_controller_transition_distance, 0)
- configurationController.notifyConfigurationChanged()
+ fun testDragDownAmount_depthDistanceIsZero_setsProgressToZero() =
+ testScope.runTest {
+ context
+ .getOrCreateTestableResources()
+ .addOverride(R.dimen.lockscreen_shade_depth_controller_transition_distance, 0)
+ configurationController.notifyConfigurationChanged()
- transitionController.dragDownAmount = 10f
+ transitionController.dragDownAmount = 10f
- verify(depthController).transitionToFullShadeProgress = 0f
- }
+ verify(depthController).transitionToFullShadeProgress = 0f
+ }
@Test
- fun testDragDownAmount_depthDistanceNonZero_setsProgressBasedOnDistance() {
- context
- .getOrCreateTestableResources()
- .addOverride(R.dimen.lockscreen_shade_depth_controller_transition_distance, 100)
- configurationController.notifyConfigurationChanged()
+ fun testDragDownAmount_depthDistanceNonZero_setsProgressBasedOnDistance() =
+ testScope.runTest {
+ context
+ .getOrCreateTestableResources()
+ .addOverride(R.dimen.lockscreen_shade_depth_controller_transition_distance, 100)
+ configurationController.notifyConfigurationChanged()
- transitionController.dragDownAmount = 10f
+ transitionController.dragDownAmount = 10f
- verify(depthController).transitionToFullShadeProgress = 0.1f
- }
+ verify(depthController).transitionToFullShadeProgress = 0.1f
+ }
@Test
- fun setDragAmount_setsKeyguardTransitionProgress() {
- transitionController.dragDownAmount = 10f
+ fun setDragAmount_setsKeyguardTransitionProgress() =
+ testScope.runTest {
+ transitionController.dragDownAmount = 10f
- verify(shadeLockscreenInteractor).setKeyguardTransitionProgress(anyFloat(), anyInt())
- }
+ verify(shadeLockscreenInteractor).setKeyguardTransitionProgress(anyFloat(), anyInt())
+ }
@Test
- fun setDragAmount_setsKeyguardAlphaBasedOnDistance() {
- val alphaDistance =
- context.resources.getDimensionPixelSize(
- R.dimen.lockscreen_shade_npvc_keyguard_content_alpha_transition_distance
- )
- transitionController.dragDownAmount = 10f
+ fun setDragAmount_setsKeyguardAlphaBasedOnDistance() =
+ testScope.runTest {
+ val alphaDistance =
+ context.resources.getDimensionPixelSize(
+ R.dimen.lockscreen_shade_npvc_keyguard_content_alpha_transition_distance
+ )
+ transitionController.dragDownAmount = 10f
- val expectedAlpha = 1 - 10f / alphaDistance
- verify(shadeLockscreenInteractor).setKeyguardTransitionProgress(eq(expectedAlpha), anyInt())
- }
+ val expectedAlpha = 1 - 10f / alphaDistance
+ verify(shadeLockscreenInteractor)
+ .setKeyguardTransitionProgress(eq(expectedAlpha), anyInt())
+ }
@Test
- fun setDragAmount_notInSplitShade_setsKeyguardTranslationToZero() {
- val mediaTranslationY = 123
- disableSplitShade()
- whenever(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).thenReturn(true)
- whenever(mediaHierarchyManager.getGuidedTransformationTranslationY())
- .thenReturn(mediaTranslationY)
+ fun setDragAmount_notInSplitShade_setsKeyguardTranslationToZero() =
+ testScope.runTest {
+ val mediaTranslationY = 123
+ disableSplitShade()
+ whenever(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).thenReturn(true)
+ whenever(mediaHierarchyManager.getGuidedTransformationTranslationY())
+ .thenReturn(mediaTranslationY)
- transitionController.dragDownAmount = 10f
+ transitionController.dragDownAmount = 10f
- verify(shadeLockscreenInteractor).setKeyguardTransitionProgress(anyFloat(), eq(0))
- }
+ verify(shadeLockscreenInteractor).setKeyguardTransitionProgress(anyFloat(), eq(0))
+ }
@Test
- fun setDragAmount_inSplitShade_setsKeyguardTranslationBasedOnMediaTranslation() {
- val mediaTranslationY = 123
- enableSplitShade()
- whenever(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).thenReturn(true)
- whenever(mediaHierarchyManager.getGuidedTransformationTranslationY())
- .thenReturn(mediaTranslationY)
+ fun setDragAmount_inSplitShade_setsKeyguardTranslationBasedOnMediaTranslation() =
+ testScope.runTest {
+ val mediaTranslationY = 123
+ enableSplitShade()
+ whenever(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).thenReturn(true)
+ whenever(mediaHierarchyManager.getGuidedTransformationTranslationY())
+ .thenReturn(mediaTranslationY)
- transitionController.dragDownAmount = 10f
+ transitionController.dragDownAmount = 10f
- verify(shadeLockscreenInteractor)
+ verify(shadeLockscreenInteractor)
.setKeyguardTransitionProgress(anyFloat(), eq(mediaTranslationY))
- }
+ }
@Test
- fun setDragAmount_inSplitShade_mediaNotShowing_setsKeyguardTranslationBasedOnDistance() {
- enableSplitShade()
- whenever(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).thenReturn(false)
- whenever(mediaHierarchyManager.getGuidedTransformationTranslationY()).thenReturn(123)
+ fun setDragAmount_inSplitShade_mediaNotShowing_setsKeyguardTranslationBasedOnDistance() =
+ testScope.runTest {
+ enableSplitShade()
+ whenever(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).thenReturn(false)
+ whenever(mediaHierarchyManager.getGuidedTransformationTranslationY()).thenReturn(123)
- transitionController.dragDownAmount = 10f
+ transitionController.dragDownAmount = 10f
- val distance =
- context.resources.getDimensionPixelSize(
- R.dimen.lockscreen_shade_keyguard_transition_distance
- )
- val offset =
- context.resources.getDimensionPixelSize(
- R.dimen.lockscreen_shade_keyguard_transition_vertical_offset
- )
- val expectedTranslation = 10f / distance * offset
- verify(shadeLockscreenInteractor)
- .setKeyguardTransitionProgress(anyFloat(), eq(expectedTranslation.toInt()))
- }
+ val distance =
+ context.resources.getDimensionPixelSize(
+ R.dimen.lockscreen_shade_keyguard_transition_distance
+ )
+ val offset =
+ context.resources.getDimensionPixelSize(
+ R.dimen.lockscreen_shade_keyguard_transition_vertical_offset
+ )
+ val expectedTranslation = 10f / distance * offset
+ verify(shadeLockscreenInteractor)
+ .setKeyguardTransitionProgress(anyFloat(), eq(expectedTranslation.toInt()))
+ }
@Test
- fun setDragDownAmount_setsValueOnMediaHierarchyManager() {
- transitionController.dragDownAmount = 10f
+ fun setDragDownAmount_setsValueOnMediaHierarchyManager() =
+ testScope.runTest {
+ transitionController.dragDownAmount = 10f
- verify(mediaHierarchyManager).setTransitionToFullShadeAmount(10f)
- }
+ verify(mediaHierarchyManager).setTransitionToFullShadeAmount(10f)
+ }
@Test
- fun setDragAmount_setsScrimProgressBasedOnScrimDistance() {
- val distance = 10
- context.orCreateTestableResources.addOverride(
- R.dimen.lockscreen_shade_scrim_transition_distance,
- distance
- )
- configurationController.notifyConfigurationChanged()
+ fun setDragAmount_setsScrimProgressBasedOnScrimDistance() =
+ testScope.runTest {
+ val distance = 10
+ context.orCreateTestableResources.addOverride(
+ R.dimen.lockscreen_shade_scrim_transition_distance,
+ distance
+ )
+ configurationController.notifyConfigurationChanged()
- transitionController.dragDownAmount = 5f
+ transitionController.dragDownAmount = 5f
- verify(scrimController)
- .transitionToFullShadeProgress(
- progress = eq(0.5f),
- lockScreenNotificationsProgress = anyFloat()
- )
- }
+ verify(scrimController)
+ .transitionToFullShadeProgress(
+ progress = eq(0.5f),
+ lockScreenNotificationsProgress = anyFloat()
+ )
+ }
@Test
- fun setDragAmount_setsNotificationsScrimProgressBasedOnNotificationsScrimDistanceAndDelay() {
- val distance = 100
- val delay = 10
- context.orCreateTestableResources.addOverride(
- R.dimen.lockscreen_shade_notifications_scrim_transition_distance,
- distance
- )
- context.orCreateTestableResources.addOverride(
- R.dimen.lockscreen_shade_notifications_scrim_transition_delay,
- delay
- )
- configurationController.notifyConfigurationChanged()
+ fun setDragAmount_setsNotificationsScrimProgressBasedOnNotificationsScrimDistanceAndDelay() =
+ testScope.runTest {
+ val distance = 100
+ val delay = 10
+ context.orCreateTestableResources.addOverride(
+ R.dimen.lockscreen_shade_notifications_scrim_transition_distance,
+ distance
+ )
+ context.orCreateTestableResources.addOverride(
+ R.dimen.lockscreen_shade_notifications_scrim_transition_delay,
+ delay
+ )
+ configurationController.notifyConfigurationChanged()
- transitionController.dragDownAmount = 20f
+ transitionController.dragDownAmount = 20f
- verify(scrimController)
- .transitionToFullShadeProgress(
- progress = anyFloat(),
- lockScreenNotificationsProgress = eq(0.1f)
- )
- }
+ verify(scrimController)
+ .transitionToFullShadeProgress(
+ progress = anyFloat(),
+ lockScreenNotificationsProgress = eq(0.1f)
+ )
+ }
@Test
- fun setDragAmount_dragAmountLessThanNotifDelayDistance_setsNotificationsScrimProgressToZero() {
- val distance = 100
- val delay = 50
- context.orCreateTestableResources.addOverride(
- R.dimen.lockscreen_shade_notifications_scrim_transition_distance,
- distance
- )
- context.orCreateTestableResources.addOverride(
- R.dimen.lockscreen_shade_notifications_scrim_transition_delay,
- delay
- )
- configurationController.notifyConfigurationChanged()
+ fun setDragAmount_dragAmountLessThanNotifDelayDistance_setsNotificationsScrimProgressToZero() =
+ testScope.runTest {
+ val distance = 100
+ val delay = 50
+ context.orCreateTestableResources.addOverride(
+ R.dimen.lockscreen_shade_notifications_scrim_transition_distance,
+ distance
+ )
+ context.orCreateTestableResources.addOverride(
+ R.dimen.lockscreen_shade_notifications_scrim_transition_delay,
+ delay
+ )
+ configurationController.notifyConfigurationChanged()
- transitionController.dragDownAmount = 20f
+ transitionController.dragDownAmount = 20f
- verify(scrimController)
- .transitionToFullShadeProgress(
- progress = anyFloat(),
- lockScreenNotificationsProgress = eq(0f)
- )
- }
+ verify(scrimController)
+ .transitionToFullShadeProgress(
+ progress = anyFloat(),
+ lockScreenNotificationsProgress = eq(0f)
+ )
+ }
@Test
- fun setDragAmount_dragAmountMoreThanTotalDistance_setsNotificationsScrimProgressToOne() {
- val distance = 100
- val delay = 50
- context.orCreateTestableResources.addOverride(
- R.dimen.lockscreen_shade_notifications_scrim_transition_distance,
- distance
- )
- context.orCreateTestableResources.addOverride(
- R.dimen.lockscreen_shade_notifications_scrim_transition_delay,
- delay
- )
- configurationController.notifyConfigurationChanged()
+ fun setDragAmount_dragAmountMoreThanTotalDistance_setsNotificationsScrimProgressToOne() =
+ testScope.runTest {
+ val distance = 100
+ val delay = 50
+ context.orCreateTestableResources.addOverride(
+ R.dimen.lockscreen_shade_notifications_scrim_transition_distance,
+ distance
+ )
+ context.orCreateTestableResources.addOverride(
+ R.dimen.lockscreen_shade_notifications_scrim_transition_delay,
+ delay
+ )
+ configurationController.notifyConfigurationChanged()
- transitionController.dragDownAmount = 999999f
+ transitionController.dragDownAmount = 999999f
- verify(scrimController)
- .transitionToFullShadeProgress(
- progress = anyFloat(),
- lockScreenNotificationsProgress = eq(1f)
- )
- }
+ verify(scrimController)
+ .transitionToFullShadeProgress(
+ progress = anyFloat(),
+ lockScreenNotificationsProgress = eq(1f)
+ )
+ }
@Test
- fun setDragDownAmount_inSplitShade_setsValueOnMediaHierarchyManager() {
- enableSplitShade()
+ fun setDragDownAmount_inSplitShade_setsValueOnMediaHierarchyManager() =
+ testScope.runTest {
+ enableSplitShade()
- transitionController.dragDownAmount = 10f
+ transitionController.dragDownAmount = 10f
- verify(mediaHierarchyManager).setTransitionToFullShadeAmount(10f)
- }
+ verify(mediaHierarchyManager).setTransitionToFullShadeAmount(10f)
+ }
@Test
- fun setDragAmount_notInSplitShade_forwardsToSingleShadeOverScroller() {
- disableSplitShade()
+ fun setDragAmount_notInSplitShade_forwardsToSingleShadeOverScroller() =
+ testScope.runTest {
+ disableSplitShade()
- transitionController.dragDownAmount = 10f
+ transitionController.dragDownAmount = 10f
- verify(singleShadeOverScroller).expansionDragDownAmount = 10f
- verifyZeroInteractions(splitShadeOverScroller)
- }
+ verify(singleShadeOverScroller).expansionDragDownAmount = 10f
+ verifyZeroInteractions(splitShadeOverScroller)
+ }
@Test
- fun setDragAmount_inSplitShade_forwardsToSplitShadeOverScroller() {
- enableSplitShade()
+ fun setDragAmount_inSplitShade_forwardsToSplitShadeOverScroller() =
+ testScope.runTest {
+ enableSplitShade()
- transitionController.dragDownAmount = 10f
+ transitionController.dragDownAmount = 10f
- verify(splitShadeOverScroller).expansionDragDownAmount = 10f
- verifyZeroInteractions(singleShadeOverScroller)
- }
+ verify(splitShadeOverScroller).expansionDragDownAmount = 10f
+ verifyZeroInteractions(singleShadeOverScroller)
+ }
@Test
- fun setDragDownAmount_inSplitShade_setsKeyguardStatusBarAlphaBasedOnDistance() {
- val alphaDistance =
- context.resources.getDimensionPixelSize(
- R.dimen.lockscreen_shade_npvc_keyguard_content_alpha_transition_distance
- )
- val dragDownAmount = 10f
- enableSplitShade()
+ fun setDragDownAmount_inSplitShade_setsKeyguardStatusBarAlphaBasedOnDistance() =
+ testScope.runTest {
+ val alphaDistance =
+ context.resources.getDimensionPixelSize(
+ R.dimen.lockscreen_shade_npvc_keyguard_content_alpha_transition_distance
+ )
+ val dragDownAmount = 10f
+ enableSplitShade()
- transitionController.dragDownAmount = dragDownAmount
+ transitionController.dragDownAmount = dragDownAmount
- val expectedAlpha = 1 - dragDownAmount / alphaDistance
- verify(shadeLockscreenInteractor).setKeyguardStatusBarAlpha(expectedAlpha)
- }
+ val expectedAlpha = 1 - dragDownAmount / alphaDistance
+ verify(shadeLockscreenInteractor).setKeyguardStatusBarAlpha(expectedAlpha)
+ }
@Test
- fun setDragDownAmount_notInSplitShade_setsKeyguardStatusBarAlphaToMinusOne() {
- disableSplitShade()
+ fun setDragDownAmount_notInSplitShade_setsKeyguardStatusBarAlphaToMinusOne() =
+ testScope.runTest {
+ disableSplitShade()
- transitionController.dragDownAmount = 10f
+ transitionController.dragDownAmount = 10f
- verify(shadeLockscreenInteractor).setKeyguardStatusBarAlpha(-1f)
- }
+ verify(shadeLockscreenInteractor).setKeyguardStatusBarAlpha(-1f)
+ }
@Test
- fun nullQs_canDragDownFromAdapter() {
- transitionController.qS = null
+ fun nullQs_canDragDownFromAdapter() =
+ testScope.runTest {
+ transitionController.qS = null
- qsSceneAdapter.isQsFullyCollapsed = true
- assertTrue("Can't drag down on keyguard", transitionController.canDragDown())
- qsSceneAdapter.isQsFullyCollapsed = false
- assertFalse("Can drag down when QS is expanded", transitionController.canDragDown())
- }
+ qsSceneAdapter.isQsFullyCollapsed = true
+ assertTrue("Can't drag down on keyguard", transitionController.canDragDown())
+ qsSceneAdapter.isQsFullyCollapsed = false
+ assertFalse("Can drag down when QS is expanded", transitionController.canDragDown())
+ }
private fun enableSplitShade() {
setSplitShadeEnabled(true)
@@ -619,32 +631,4 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
) {
setTransitionToFullShadeProgress(progress, lockScreenNotificationsProgress)
}
-
- @SysUISingleton
- @Component(
- modules =
- [
- SysUITestModule::class,
- UserDomainLayerModule::class,
- BiometricsDomainLayerModule::class,
- ]
- )
- interface TestComponent {
-
- val configurationController: FakeConfigurationController
- val disableFlagsRepository: FakeDisableFlagsRepository
- val powerInteractor: PowerInteractor
- val shadeInteractor: ShadeInteractor
- val shadeRepository: FakeShadeRepository
- val testScope: TestScope
-
- @Component.Factory
- interface Factory {
- fun create(
- @BindsInstance test: SysuiTestCase,
- featureFlags: FakeFeatureFlagsClassicModule,
- mocks: TestMocksModule,
- ): TestComponent
- }
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/domian/interactor/MediaRouterChipInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/domian/interactor/MediaRouterChipInteractorTest.kt
index 8a6a50dd13fc..ecb1a6d44b22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/domian/interactor/MediaRouterChipInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/domian/interactor/MediaRouterChipInteractorTest.kt
@@ -70,7 +70,7 @@ class MediaRouterChipInteractorTest : SysuiTestCase() {
}
@Test
- fun mediaRouterCastingState_connectingDevice_casting() =
+ fun mediaRouterCastingState_connectingDevice_casting_withName() =
testScope.runTest {
val latest by collectLastValue(underTest.mediaRouterCastingState)
@@ -79,17 +79,18 @@ class MediaRouterChipInteractorTest : SysuiTestCase() {
CastDevice(
state = CastDevice.CastState.Connecting,
id = "id",
- name = "name",
+ name = "My Favorite Device",
description = "desc",
origin = CastDevice.CastOrigin.MediaRouter,
)
)
- assertThat(latest).isEqualTo(MediaRouterCastModel.Casting)
+ assertThat(latest)
+ .isEqualTo(MediaRouterCastModel.Casting(deviceName = "My Favorite Device"))
}
@Test
- fun mediaRouterCastingState_connectedDevice_casting() =
+ fun mediaRouterCastingState_connectedDevice_casting_withName() =
testScope.runTest {
val latest by collectLastValue(underTest.mediaRouterCastingState)
@@ -98,13 +99,14 @@ class MediaRouterChipInteractorTest : SysuiTestCase() {
CastDevice(
state = CastDevice.CastState.Connected,
id = "id",
- name = "name",
+ name = "My Second Favorite Device",
description = "desc",
origin = CastDevice.CastOrigin.MediaRouter,
)
)
- assertThat(latest).isEqualTo(MediaRouterCastModel.Casting)
+ assertThat(latest)
+ .isEqualTo(MediaRouterCastModel.Casting(deviceName = "My Second Favorite Device"))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt
index e9d6f0e5537a..c8397bdf47c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt
@@ -19,8 +19,10 @@ package com.android.systemui.statusbar.chips.casttootherdevice.ui.view
import android.content.ComponentName
import android.content.DialogInterface
import android.content.Intent
+import android.content.applicationContext
import android.content.packageManager
import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.Kosmos
@@ -68,21 +70,87 @@ class EndCastScreenToOtherDeviceDialogDelegateTest : SysuiTestCase() {
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
- verify(sysuiDialog).setTitle(R.string.cast_screen_to_other_device_stop_dialog_title)
+ verify(sysuiDialog).setTitle(R.string.cast_to_other_device_stop_dialog_title)
}
@Test
- fun message_entireScreen() {
- createAndSetDelegate(ENTIRE_SCREEN)
+ fun message_entireScreen_unknownDevice() {
+ createAndSetDelegate(ENTIRE_SCREEN, deviceName = null)
+
+ underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+ verify(sysuiDialog)
+ .setMessage(
+ context.getString(R.string.cast_to_other_device_stop_dialog_message_entire_screen)
+ )
+ }
+
+ @Test
+ fun message_entireScreen_hasDevice() {
+ createAndSetDelegate(ENTIRE_SCREEN, deviceName = "My Favorite Device")
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
verify(sysuiDialog)
- .setMessage(context.getString(R.string.cast_screen_to_other_device_stop_dialog_message))
+ .setMessage(
+ context.getString(
+ R.string.cast_to_other_device_stop_dialog_message_entire_screen_with_device,
+ "My Favorite Device",
+ )
+ )
}
@Test
- fun message_singleTask() {
+ fun message_singleTask_unknownAppName_unknownDevice() {
+ val baseIntent =
+ Intent().apply { this.component = ComponentName("fake.task.package", "cls") }
+ whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>()))
+ .thenThrow(PackageManager.NameNotFoundException())
+
+ createAndSetDelegate(
+ MediaProjectionState.Projecting.SingleTask(
+ HOST_PACKAGE,
+ createTask(taskId = 1, baseIntent = baseIntent)
+ ),
+ deviceName = null,
+ )
+
+ underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+ verify(sysuiDialog)
+ .setMessage(
+ context.getString(R.string.cast_to_other_device_stop_dialog_message_generic)
+ )
+ }
+
+ @Test
+ fun message_singleTask_unknownAppName_hasDevice() {
+ val baseIntent =
+ Intent().apply { this.component = ComponentName("fake.task.package", "cls") }
+ whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>()))
+ .thenThrow(PackageManager.NameNotFoundException())
+
+ createAndSetDelegate(
+ MediaProjectionState.Projecting.SingleTask(
+ HOST_PACKAGE,
+ createTask(taskId = 1, baseIntent = baseIntent)
+ ),
+ deviceName = "My Favorite Device",
+ )
+
+ underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+ verify(sysuiDialog)
+ .setMessage(
+ context.getString(
+ R.string.cast_to_other_device_stop_dialog_message_generic_with_device,
+ "My Favorite Device",
+ )
+ )
+ }
+
+ @Test
+ fun message_singleTask_hasAppName_unknownDevice() {
val baseIntent =
Intent().apply { this.component = ComponentName("fake.task.package", "cls") }
val appInfo = mock<ApplicationInfo>()
@@ -94,16 +162,48 @@ class EndCastScreenToOtherDeviceDialogDelegateTest : SysuiTestCase() {
MediaProjectionState.Projecting.SingleTask(
HOST_PACKAGE,
createTask(taskId = 1, baseIntent = baseIntent)
+ ),
+ deviceName = null,
+ )
+
+ underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+ verify(sysuiDialog)
+ .setMessage(
+ context.getString(
+ R.string.cast_to_other_device_stop_dialog_message_specific_app,
+ "Fake Package",
+ )
)
+ }
+
+ @Test
+ fun message_singleTask_hasAppName_hasDevice() {
+ val baseIntent =
+ Intent().apply { this.component = ComponentName("fake.task.package", "cls") }
+ val appInfo = mock<ApplicationInfo>()
+ whenever(appInfo.loadLabel(kosmos.packageManager)).thenReturn("Fake Package")
+ whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>()))
+ .thenReturn(appInfo)
+
+ createAndSetDelegate(
+ MediaProjectionState.Projecting.SingleTask(
+ HOST_PACKAGE,
+ createTask(taskId = 1, baseIntent = baseIntent)
+ ),
+ deviceName = "My Favorite Device",
)
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
- // It'd be nice to use R.string.cast_screen_to_other_device_stop_dialog_message_specific_app
- // directly, but it includes the <b> tags which aren't in the returned string.
- val result = argumentCaptor<CharSequence>()
- verify(sysuiDialog).setMessage(result.capture())
- assertThat(result.firstValue.toString()).isEqualTo("You will stop casting Fake Package")
+ verify(sysuiDialog)
+ .setMessage(
+ context.getString(
+ R.string.cast_to_other_device_stop_dialog_message_specific_app_with_device,
+ "Fake Package",
+ "My Favorite Device",
+ )
+ )
}
@Test
@@ -140,14 +240,19 @@ class EndCastScreenToOtherDeviceDialogDelegateTest : SysuiTestCase() {
assertThat(kosmos.fakeMediaProjectionRepository.stopProjectingInvoked).isTrue()
}
- private fun createAndSetDelegate(state: MediaProjectionState.Projecting) {
+ private fun createAndSetDelegate(
+ state: MediaProjectionState.Projecting,
+ deviceName: String? = null,
+ ) {
underTest =
EndCastScreenToOtherDeviceDialogDelegate(
kosmos.endMediaProjectionDialogHelper,
+ kosmos.applicationContext,
stopAction = kosmos.mediaProjectionChipInteractor::stopProjecting,
ProjectionChipModel.Projecting(
ProjectionChipModel.Type.CAST_TO_OTHER_DEVICE,
state,
+ deviceName,
),
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt
index 0af423db7db4..e6101f500ad1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.chips.casttootherdevice.ui.view
import android.content.DialogInterface
+import android.content.applicationContext
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
@@ -65,12 +66,30 @@ class EndGenericCastToOtherDeviceDialogDelegateTest : SysuiTestCase() {
}
@Test
- fun message() {
- createAndSetDelegate()
+ fun message_unknownDevice() {
+ createAndSetDelegate(deviceName = null)
+
+ underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+ verify(sysuiDialog)
+ .setMessage(
+ context.getString(R.string.cast_to_other_device_stop_dialog_message_generic)
+ )
+ }
+
+ @Test
+ fun message_hasDevice() {
+ createAndSetDelegate(deviceName = "My Favorite Device")
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
- verify(sysuiDialog).setMessage(R.string.cast_to_other_device_stop_dialog_message)
+ verify(sysuiDialog)
+ .setMessage(
+ context.getString(
+ R.string.cast_to_other_device_stop_dialog_message_generic_with_device,
+ "My Favorite Device",
+ )
+ )
}
@Test
@@ -122,10 +141,12 @@ class EndGenericCastToOtherDeviceDialogDelegateTest : SysuiTestCase() {
assertThat(kosmos.fakeMediaRouterRepository.lastStoppedDevice).isEqualTo(device)
}
- private fun createAndSetDelegate() {
+ private fun createAndSetDelegate(deviceName: String? = null) {
underTest =
EndGenericCastToOtherDeviceDialogDelegate(
kosmos.endMediaProjectionDialogHelper,
+ kosmos.applicationContext,
+ deviceName,
stopAction = kosmos.mediaRouterChipInteractor::stopCasting,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt
index f9ad5ac46b6c..ab935fe9b631 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt
@@ -27,7 +27,6 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testCase
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
import com.google.common.truth.Truth.assertThat
@@ -56,19 +55,15 @@ class EndMediaProjectionDialogHelperTest : SysuiTestCase() {
}
@Test
- fun getDialogMessage_entireScreen_isGenericMessage() {
+ fun getAppName_stateVersion_entireScreen_returnsNull() {
val result =
- underTest.getDialogMessage(
- MediaProjectionState.Projecting.EntireScreen("host.package"),
- R.string.accessibility_home,
- R.string.cast_screen_to_other_device_stop_dialog_message_specific_app,
- )
+ underTest.getAppName(MediaProjectionState.Projecting.EntireScreen("host.package"))
- assertThat(result).isEqualTo(context.getString(R.string.accessibility_home))
+ assertThat(result).isNull()
}
@Test
- fun getDialogMessage_singleTask_cannotFindPackage_isGenericMessage() {
+ fun getAppName_stateVersion_singleTask_cannotFindPackage_returnsNull() {
val baseIntent =
Intent().apply { this.component = ComponentName("fake.task.package", "cls") }
whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>()))
@@ -77,21 +72,16 @@ class EndMediaProjectionDialogHelperTest : SysuiTestCase() {
val projectionState =
MediaProjectionState.Projecting.SingleTask(
"host.package",
- createTask(taskId = 1, baseIntent = baseIntent)
+ createTask(taskId = 1, baseIntent = baseIntent),
)
- val result =
- underTest.getDialogMessage(
- projectionState,
- R.string.accessibility_home,
- R.string.cast_screen_to_other_device_stop_dialog_message_specific_app,
- )
+ val result = underTest.getAppName(projectionState)
- assertThat(result).isEqualTo(context.getString(R.string.accessibility_home))
+ assertThat(result).isNull()
}
@Test
- fun getDialogMessage_singleTask_findsPackage_isSpecificMessageWithAppLabel() {
+ fun getAppName_stateVersion_singleTask_findsPackage_returnsName() {
val baseIntent =
Intent().apply { this.component = ComponentName("fake.task.package", "cls") }
val appInfo = mock<ApplicationInfo>()
@@ -102,93 +92,66 @@ class EndMediaProjectionDialogHelperTest : SysuiTestCase() {
val projectionState =
MediaProjectionState.Projecting.SingleTask(
"host.package",
- createTask(taskId = 1, baseIntent = baseIntent)
+ createTask(taskId = 1, baseIntent = baseIntent),
)
- val result =
- underTest.getDialogMessage(
- projectionState,
- R.string.accessibility_home,
- R.string.cast_screen_to_other_device_stop_dialog_message_specific_app,
- )
+ val result = underTest.getAppName(projectionState)
- // It'd be nice to use the R.string resources directly, but they include the <b> tags which
- // aren't in the returned string.
- assertThat(result.toString()).isEqualTo("You will stop casting Fake Package")
+ assertThat(result).isEqualTo("Fake Package")
}
@Test
- fun getDialogMessage_nullTask_isGenericMessage() {
- val result =
- underTest.getDialogMessage(
- specificTaskInfo = null,
- R.string.accessibility_home,
- R.string.cast_screen_to_other_device_stop_dialog_message_specific_app,
- )
+ fun getAppName_taskInfoVersion_null_returnsNull() {
+ val result = underTest.getAppName(specificTaskInfo = null)
- assertThat(result).isEqualTo(context.getString(R.string.accessibility_home))
+ assertThat(result).isNull()
}
@Test
- fun getDialogMessage_withTask_cannotFindPackage_isGenericMessage() {
+ fun getAppName_taskInfoVersion_cannotFindPackage_returnsNull() {
val baseIntent =
Intent().apply { this.component = ComponentName("fake.task.package", "cls") }
whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>()))
.thenThrow(PackageManager.NameNotFoundException())
- val task = createTask(taskId = 1, baseIntent = baseIntent)
- val result =
- underTest.getDialogMessage(
- task,
- R.string.accessibility_home,
- R.string.cast_screen_to_other_device_stop_dialog_message_specific_app,
- )
+ val result = underTest.getAppName(createTask(taskId = 1, baseIntent = baseIntent))
- assertThat(result).isEqualTo(context.getString(R.string.accessibility_home))
+ assertThat(result).isNull()
}
@Test
- fun getDialogMessage_withTask_findsPackage_isSpecificMessageWithAppLabel() {
+ fun getAppName_taskInfoVersion_findsPackage_returnsName() {
val baseIntent =
Intent().apply { this.component = ComponentName("fake.task.package", "cls") }
val appInfo = mock<ApplicationInfo>()
whenever(appInfo.loadLabel(kosmos.packageManager)).thenReturn("Fake Package")
whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>()))
.thenReturn(appInfo)
- val task = createTask(taskId = 1, baseIntent = baseIntent)
- val result =
- underTest.getDialogMessage(
- task,
- R.string.accessibility_home,
- R.string.cast_screen_to_other_device_stop_dialog_message_specific_app,
- )
+ val result = underTest.getAppName(createTask(taskId = 1, baseIntent = baseIntent))
- assertThat(result.toString()).isEqualTo("You will stop casting Fake Package")
+ assertThat(result).isEqualTo("Fake Package")
}
@Test
- fun getDialogMessage_appLabelHasSpecialCharacters_isEscaped() {
- val baseIntent =
- Intent().apply { this.component = ComponentName("fake.task.package", "cls") }
+ fun getAppName_packageNameVersion_cannotFindPackage_returnsNull() {
+ whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>()))
+ .thenThrow(PackageManager.NameNotFoundException())
+
+ val result = underTest.getAppName("fake.task.package")
+
+ assertThat(result).isNull()
+ }
+
+ @Test
+ fun getAppName_packageNameVersion_findsPackage_returnsName() {
val appInfo = mock<ApplicationInfo>()
- whenever(appInfo.loadLabel(kosmos.packageManager)).thenReturn("Fake & Package <Here>")
+ whenever(appInfo.loadLabel(kosmos.packageManager)).thenReturn("Fake Package")
whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>()))
.thenReturn(appInfo)
- val projectionState =
- MediaProjectionState.Projecting.SingleTask(
- "host.package",
- createTask(taskId = 1, baseIntent = baseIntent)
- )
-
- val result =
- underTest.getDialogMessage(
- projectionState,
- R.string.accessibility_home,
- R.string.cast_screen_to_other_device_stop_dialog_message_specific_app,
- )
+ val result = underTest.getAppName("fake.task.package")
- assertThat(result.toString()).isEqualTo("You will stop casting Fake & Package <Here>")
+ assertThat(result).isEqualTo("Fake Package")
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegateTest.kt
index 7e667de3619c..bfb63ac66d3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegateTest.kt
@@ -20,6 +20,7 @@ import android.app.ActivityManager
import android.content.ComponentName
import android.content.DialogInterface
import android.content.Intent
+import android.content.applicationContext
import android.content.packageManager
import android.content.pm.ApplicationInfo
import androidx.test.filters.SmallTest
@@ -95,11 +96,13 @@ class EndScreenRecordingDialogDelegateTest : SysuiTestCase() {
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
- // It'd be nice to use R.string.screenrecord_stop_dialog_message_specific_app directly, but
- // it includes the <b> tags which aren't in the returned string.
- val result = argumentCaptor<CharSequence>()
- verify(sysuiDialog).setMessage(result.capture())
- assertThat(result.firstValue.toString()).isEqualTo("You will stop recording Fake Package")
+ verify(sysuiDialog)
+ .setMessage(
+ context.getString(
+ R.string.screenrecord_stop_dialog_message_specific_app,
+ "Fake Package",
+ )
+ )
}
@Test
@@ -140,6 +143,7 @@ class EndScreenRecordingDialogDelegateTest : SysuiTestCase() {
underTest =
EndScreenRecordingDialogDelegate(
kosmos.endMediaProjectionDialogHelper,
+ kosmos.applicationContext,
stopAction = kosmos.screenRecordChipInteractor::stopRecording,
recordedTask,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt
index 63c29ac81b34..bfb57c51206a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt
@@ -19,8 +19,10 @@ package com.android.systemui.statusbar.chips.sharetoapp.ui.view
import android.content.ComponentName
import android.content.DialogInterface
import android.content.Intent
+import android.content.applicationContext
import android.content.packageManager
import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.Kosmos
@@ -65,6 +67,8 @@ class EndShareToAppDialogDelegateTest : SysuiTestCase() {
@Test
fun title() {
createAndSetDelegate(ENTIRE_SCREEN)
+ whenever(kosmos.packageManager.getApplicationInfo(eq(HOST_PACKAGE), any<Int>()))
+ .thenThrow(PackageManager.NameNotFoundException())
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
@@ -72,16 +76,60 @@ class EndShareToAppDialogDelegateTest : SysuiTestCase() {
}
@Test
- fun message_entireScreen() {
+ fun message_entireScreen_unknownHostPackage() {
createAndSetDelegate(ENTIRE_SCREEN)
+ whenever(kosmos.packageManager.getApplicationInfo(eq(HOST_PACKAGE), any<Int>()))
+ .thenThrow(PackageManager.NameNotFoundException())
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
- verify(sysuiDialog).setMessage(context.getString(R.string.share_to_app_stop_dialog_message))
+ verify(sysuiDialog)
+ .setMessage(context.getString(R.string.share_to_app_stop_dialog_message_entire_screen))
}
@Test
- fun message_singleTask() {
+ fun message_entireScreen_hasHostPackage() {
+ createAndSetDelegate(ENTIRE_SCREEN)
+ val hostAppInfo = mock<ApplicationInfo>()
+ whenever(hostAppInfo.loadLabel(kosmos.packageManager)).thenReturn("Host Package")
+ whenever(kosmos.packageManager.getApplicationInfo(eq(HOST_PACKAGE), any<Int>()))
+ .thenReturn(hostAppInfo)
+
+ underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+ verify(sysuiDialog)
+ .setMessage(
+ context.getString(
+ R.string.share_to_app_stop_dialog_message_entire_screen_with_host_app,
+ "Host Package",
+ )
+ )
+ }
+
+ @Test
+ fun message_singleTask_unknownAppName() {
+ val baseIntent =
+ Intent().apply { this.component = ComponentName("fake.task.package", "cls") }
+ whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>()))
+ .thenThrow(PackageManager.NameNotFoundException())
+
+ createAndSetDelegate(
+ MediaProjectionState.Projecting.SingleTask(
+ HOST_PACKAGE,
+ createTask(taskId = 1, baseIntent = baseIntent)
+ )
+ )
+
+ underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+ verify(sysuiDialog)
+ .setMessage(
+ context.getString(R.string.share_to_app_stop_dialog_message_single_app_generic)
+ )
+ }
+
+ @Test
+ fun message_singleTask_hasAppName() {
val baseIntent =
Intent().apply { this.component = ComponentName("fake.task.package", "cls") }
val appInfo = mock<ApplicationInfo>()
@@ -98,11 +146,13 @@ class EndShareToAppDialogDelegateTest : SysuiTestCase() {
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
- // It'd be nice to use R.string.share_to_app_stop_dialog_message_specific_app directly, but
- // it includes the <b> tags which aren't in the returned string.
- val result = argumentCaptor<CharSequence>()
- verify(sysuiDialog).setMessage(result.capture())
- assertThat(result.firstValue.toString()).isEqualTo("You will stop sharing Fake Package")
+ verify(sysuiDialog)
+ .setMessage(
+ context.getString(
+ R.string.share_to_app_stop_dialog_message_single_app_specific,
+ "Fake Package",
+ )
+ )
}
@Test
@@ -118,6 +168,8 @@ class EndShareToAppDialogDelegateTest : SysuiTestCase() {
fun positiveButton() =
kosmos.testScope.runTest {
createAndSetDelegate(ENTIRE_SCREEN)
+ whenever(kosmos.packageManager.getApplicationInfo(eq(HOST_PACKAGE), any<Int>()))
+ .thenThrow(PackageManager.NameNotFoundException())
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
@@ -143,8 +195,13 @@ class EndShareToAppDialogDelegateTest : SysuiTestCase() {
underTest =
EndShareToAppDialogDelegate(
kosmos.endMediaProjectionDialogHelper,
+ kosmos.applicationContext,
stopAction = kosmos.mediaProjectionChipInteractor::stopProjecting,
- ProjectionChipModel.Projecting(ProjectionChipModel.Type.SHARE_TO_APP, state),
+ ProjectionChipModel.Projecting(
+ ProjectionChipModel.Type.SHARE_TO_APP,
+ state,
+ deviceName = null,
+ ),
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
index 26f53707aba1..f07303ed32e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
@@ -17,21 +17,19 @@ package com.android.systemui.statusbar.notification.icon.domain.interactor
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
-import com.android.systemui.collectLastValue
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
-import com.android.systemui.runTest
-import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.data.repository.notificationListenerSettingsRepository
import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
-import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor
-import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.notificationsKeyguardViewStateRepository
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.headsUpNotificationIconInteractor
import com.android.systemui.statusbar.notification.shared.byIsAmbient
import com.android.systemui.statusbar.notification.shared.byIsLastMessageFromReply
import com.android.systemui.statusbar.notification.shared.byIsPulsing
@@ -39,15 +37,15 @@ import com.android.systemui.statusbar.notification.shared.byIsRowDismissed
import com.android.systemui.statusbar.notification.shared.byIsSilent
import com.android.systemui.statusbar.notification.shared.byIsSuppressedFromStatusBar
import com.android.systemui.statusbar.notification.shared.byKey
-import com.android.systemui.user.domain.UserDomainLayerModule
+import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
-import com.android.wm.shell.bubbles.Bubbles
+import com.android.wm.shell.bubbles.bubbles
+import com.android.wm.shell.bubbles.bubblesOptional
import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
-import java.util.Optional
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -55,29 +53,22 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
class NotificationIconsInteractorTest : SysuiTestCase() {
-
- private val bubbles: Bubbles = mock()
-
- @Component(modules = [SysUITestModule::class])
- @SysUISingleton
- interface TestComponent : SysUITestComponent<NotificationIconsInteractor> {
-
- val activeNotificationListRepository: ActiveNotificationListRepository
- val notificationsKeyguardInteractor: NotificationsKeyguardInteractor
-
- @Component.Factory
- interface Factory {
- fun create(@BindsInstance test: SysuiTestCase, mocks: TestMocksModule): TestComponent
- }
- }
-
- val testComponent: TestComponent =
- DaggerNotificationIconsInteractorTest_TestComponent.factory()
- .create(test = this, mocks = TestMocksModule(bubbles = Optional.of(bubbles)))
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val activeNotificationListRepository = kosmos.activeNotificationListRepository
+ private val notificationsKeyguardInteractor = kosmos.notificationsKeyguardInteractor
+
+ private val underTest =
+ NotificationIconsInteractor(
+ kosmos.activeNotificationsInteractor,
+ kosmos.bubblesOptional,
+ kosmos.headsUpNotificationIconInteractor,
+ kosmos.notificationsKeyguardViewStateRepository
+ )
@Before
fun setup() {
- testComponent.apply {
+ testScope.apply {
activeNotificationListRepository.activeNotifications.value =
ActiveNotificationsStore.Builder()
.apply { testIcons.forEach(::addIndividualNotif) }
@@ -87,22 +78,22 @@ class NotificationIconsInteractorTest : SysuiTestCase() {
@Test
fun filteredEntrySet() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.filteredNotifSet())
assertThat(filteredSet).containsExactlyElementsIn(testIcons)
}
@Test
fun filteredEntrySet_noExpandedBubbles() =
- testComponent.runTest {
- whenever(bubbles.isBubbleExpanded(eq("notif1"))).thenReturn(true)
+ testScope.runTest {
+ whenever(kosmos.bubbles.isBubbleExpanded(eq("notif1"))).thenReturn(true)
val filteredSet by collectLastValue(underTest.filteredNotifSet())
assertThat(filteredSet).comparingElementsUsing(byKey).doesNotContain("notif1")
}
@Test
fun filteredEntrySet_noAmbient() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.filteredNotifSet(showAmbient = false))
assertThat(filteredSet).comparingElementsUsing(byIsAmbient).doesNotContain(true)
assertThat(filteredSet)
@@ -112,21 +103,21 @@ class NotificationIconsInteractorTest : SysuiTestCase() {
@Test
fun filteredEntrySet_noLowPriority() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.filteredNotifSet(showLowPriority = false))
assertThat(filteredSet).comparingElementsUsing(byIsSilent).doesNotContain(true)
}
@Test
fun filteredEntrySet_noDismissed() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.filteredNotifSet(showDismissed = false))
assertThat(filteredSet).comparingElementsUsing(byIsRowDismissed).doesNotContain(true)
}
@Test
fun filteredEntrySet_noRepliedMessages() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by
collectLastValue(underTest.filteredNotifSet(showRepliedMessages = false))
assertThat(filteredSet)
@@ -136,7 +127,7 @@ class NotificationIconsInteractorTest : SysuiTestCase() {
@Test
fun filteredEntrySet_noPulsing_notifsNotFullyHidden() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.filteredNotifSet(showPulsing = false))
notificationsKeyguardInteractor.setNotificationsFullyHidden(false)
assertThat(filteredSet).comparingElementsUsing(byIsPulsing).doesNotContain(true)
@@ -144,65 +135,46 @@ class NotificationIconsInteractorTest : SysuiTestCase() {
@Test
fun filteredEntrySet_noPulsing_notifsFullyHidden() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.filteredNotifSet(showPulsing = false))
notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
}
}
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
- private val bubbles: Bubbles = mock()
-
- @Component(
- modules =
- [
- SysUITestModule::class,
- BiometricsDomainLayerModule::class,
- UserDomainLayerModule::class,
- ]
- )
- @SysUISingleton
- interface TestComponent : SysUITestComponent<AlwaysOnDisplayNotificationIconsInteractor> {
-
- val activeNotificationListRepository: ActiveNotificationListRepository
- val deviceEntryRepository: FakeDeviceEntryRepository
- val notificationsKeyguardInteractor: NotificationsKeyguardInteractor
-
- @Component.Factory
- interface Factory {
- fun create(@BindsInstance test: SysuiTestCase, mocks: TestMocksModule): TestComponent
- }
- }
-
- private val testComponent: TestComponent =
- DaggerAlwaysOnDisplayNotificationIconsInteractorTest_TestComponent.factory()
- .create(test = this, mocks = TestMocksModule(bubbles = Optional.of(bubbles)))
+ private val underTest =
+ AlwaysOnDisplayNotificationIconsInteractor(
+ kosmos.testDispatcher,
+ kosmos.deviceEntryInteractor,
+ kosmos.notificationIconsInteractor,
+ )
@Before
fun setup() {
- testComponent.apply {
- activeNotificationListRepository.activeNotifications.value =
- ActiveNotificationsStore.Builder()
- .apply { testIcons.forEach(::addIndividualNotif) }
- .build()
- }
+ kosmos.activeNotificationListRepository.activeNotifications.value =
+ ActiveNotificationsStore.Builder()
+ .apply { testIcons.forEach(::addIndividualNotif) }
+ .build()
}
@Test
fun filteredEntrySet_noExpandedBubbles() =
- testComponent.runTest {
- whenever(bubbles.isBubbleExpanded(eq("notif1"))).thenReturn(true)
+ testScope.runTest {
+ whenever(kosmos.bubbles.isBubbleExpanded(eq("notif1"))).thenReturn(true)
val filteredSet by collectLastValue(underTest.aodNotifs)
assertThat(filteredSet).comparingElementsUsing(byKey).doesNotContain("notif1")
}
@Test
fun filteredEntrySet_noAmbient() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.aodNotifs)
assertThat(filteredSet).comparingElementsUsing(byIsAmbient).doesNotContain(true)
assertThat(filteredSet)
@@ -212,14 +184,14 @@ class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() {
@Test
fun filteredEntrySet_noDismissed() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.aodNotifs)
assertThat(filteredSet).comparingElementsUsing(byIsRowDismissed).doesNotContain(true)
}
@Test
fun filteredEntrySet_noRepliedMessages() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.aodNotifs)
assertThat(filteredSet)
.comparingElementsUsing(byIsLastMessageFromReply)
@@ -228,37 +200,37 @@ class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() {
@Test
fun filteredEntrySet_showPulsing_notifsNotFullyHidden_bypassDisabled() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.aodNotifs)
- deviceEntryRepository.setBypassEnabled(false)
- notificationsKeyguardInteractor.setNotificationsFullyHidden(false)
+ kosmos.fakeDeviceEntryRepository.setBypassEnabled(false)
+ kosmos.notificationsKeyguardInteractor.setNotificationsFullyHidden(false)
assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
}
@Test
fun filteredEntrySet_showPulsing_notifsFullyHidden_bypassDisabled() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.aodNotifs)
- deviceEntryRepository.setBypassEnabled(false)
- notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
+ kosmos.fakeDeviceEntryRepository.setBypassEnabled(false)
+ kosmos.notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
}
@Test
fun filteredEntrySet_noPulsing_notifsNotFullyHidden_bypassEnabled() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.aodNotifs)
- deviceEntryRepository.setBypassEnabled(true)
- notificationsKeyguardInteractor.setNotificationsFullyHidden(false)
+ kosmos.fakeDeviceEntryRepository.setBypassEnabled(true)
+ kosmos.notificationsKeyguardInteractor.setNotificationsFullyHidden(false)
assertThat(filteredSet).comparingElementsUsing(byIsPulsing).doesNotContain(true)
}
@Test
fun filteredEntrySet_showPulsing_notifsFullyHidden_bypassEnabled() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.aodNotifs)
- deviceEntryRepository.setBypassEnabled(true)
- notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
+ kosmos.fakeDeviceEntryRepository.setBypassEnabled(true)
+ kosmos.notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
}
}
@@ -266,32 +238,19 @@ class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() {
@SmallTest
@RunWith(AndroidJUnit4::class)
class StatusBarNotificationIconsInteractorTest : SysuiTestCase() {
-
- private val bubbles: Bubbles = mock()
-
- @Component(modules = [SysUITestModule::class])
- @SysUISingleton
- interface TestComponent : SysUITestComponent<StatusBarNotificationIconsInteractor> {
-
- val activeNotificationListRepository: ActiveNotificationListRepository
- val headsUpIconsInteractor: HeadsUpNotificationIconInteractor
- val notificationsKeyguardInteractor: NotificationsKeyguardInteractor
- val notificationListenerSettingsRepository: NotificationListenerSettingsRepository
-
- @Component.Factory
- interface Factory {
- fun create(@BindsInstance test: SysuiTestCase, mocks: TestMocksModule): TestComponent
- }
- }
-
- val testComponent: TestComponent =
- DaggerStatusBarNotificationIconsInteractorTest_TestComponent.factory()
- .create(test = this, mocks = TestMocksModule(bubbles = Optional.of(bubbles)))
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val underTest =
+ StatusBarNotificationIconsInteractor(
+ kosmos.testDispatcher,
+ kosmos.notificationIconsInteractor,
+ kosmos.notificationListenerSettingsRepository,
+ )
@Before
fun setup() {
- testComponent.apply {
- activeNotificationListRepository.activeNotifications.value =
+ testScope.apply {
+ kosmos.activeNotificationListRepository.activeNotifications.value =
ActiveNotificationsStore.Builder()
.apply { testIcons.forEach(::addIndividualNotif) }
.build()
@@ -300,15 +259,15 @@ class StatusBarNotificationIconsInteractorTest : SysuiTestCase() {
@Test
fun filteredEntrySet_noExpandedBubbles() =
- testComponent.runTest {
- whenever(bubbles.isBubbleExpanded(eq("notif1"))).thenReturn(true)
+ testScope.runTest {
+ whenever(kosmos.bubbles.isBubbleExpanded(eq("notif1"))).thenReturn(true)
val filteredSet by collectLastValue(underTest.statusBarNotifs)
assertThat(filteredSet).comparingElementsUsing(byKey).doesNotContain("notif1")
}
@Test
fun filteredEntrySet_noAmbient() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.statusBarNotifs)
assertThat(filteredSet).comparingElementsUsing(byIsAmbient).doesNotContain(true)
assertThat(filteredSet)
@@ -318,30 +277,30 @@ class StatusBarNotificationIconsInteractorTest : SysuiTestCase() {
@Test
fun filteredEntrySet_noLowPriority_whenDontShowSilentIcons() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.statusBarNotifs)
- notificationListenerSettingsRepository.showSilentStatusIcons.value = false
+ kosmos.notificationListenerSettingsRepository.showSilentStatusIcons.value = false
assertThat(filteredSet).comparingElementsUsing(byIsSilent).doesNotContain(true)
}
@Test
fun filteredEntrySet_showLowPriority_whenShowSilentIcons() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.statusBarNotifs)
- notificationListenerSettingsRepository.showSilentStatusIcons.value = true
+ kosmos.notificationListenerSettingsRepository.showSilentStatusIcons.value = true
assertThat(filteredSet).comparingElementsUsing(byIsSilent).contains(true)
}
@Test
fun filteredEntrySet_noDismissed() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.statusBarNotifs)
assertThat(filteredSet).comparingElementsUsing(byIsRowDismissed).doesNotContain(true)
}
@Test
fun filteredEntrySet_noRepliedMessages() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.statusBarNotifs)
assertThat(filteredSet)
.comparingElementsUsing(byIsLastMessageFromReply)
@@ -350,9 +309,9 @@ class StatusBarNotificationIconsInteractorTest : SysuiTestCase() {
@Test
fun filteredEntrySet_includesIsolatedIcon() =
- testComponent.runTest {
+ testScope.runTest {
val filteredSet by collectLastValue(underTest.statusBarNotifs)
- headsUpIconsInteractor.setIsolatedIconNotificationKey("notif5")
+ kosmos.headsUpNotificationIconInteractor.setIsolatedIconNotificationKey("notif5")
assertThat(filteredSet).comparingElementsUsing(byKey).contains("notif5")
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
index 894e02e80997..1f4e80e48bb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
@@ -16,111 +16,81 @@
package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+import android.content.res.mainResources
import android.platform.test.annotations.DisableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
-import com.android.systemui.collectLastValue
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.power.shared.model.WakefulnessState
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController
-import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
-import com.android.systemui.user.domain.UserDomainLayerModule
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.notification.icon.domain.interactor.alwaysOnDisplayNotificationIconsInteractor
+import com.android.systemui.statusbar.phone.dozeParameters
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
-
- @SysUISingleton
- @Component(
- modules =
- [
- SysUITestModule::class,
- BiometricsDomainLayerModule::class,
- UserDomainLayerModule::class,
- ]
- )
- interface TestComponent :
- SysUITestComponent<NotificationIconContainerAlwaysOnDisplayViewModel> {
-
- val deviceProvisioningRepository: FakeDeviceProvisioningRepository
- val keyguardRepository: FakeKeyguardRepository
- val keyguardTransitionRepository: FakeKeyguardTransitionRepository
- val powerRepository: FakePowerRepository
-
- @Component.Factory
- interface Factory {
- fun create(
- @BindsInstance test: SysuiTestCase,
- mocks: TestMocksModule,
- featureFlags: FakeFeatureFlagsClassicModule,
- ): TestComponent
+ private val kosmos =
+ testKosmos().apply {
+ fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, value = false) }
}
- }
- private val dozeParams: DozeParameters = mock()
- private val screenOffAnimController: ScreenOffAnimationController = mock()
-
- private val testComponent: TestComponent =
- DaggerNotificationIconContainerAlwaysOnDisplayViewModelTest_TestComponent.factory()
- .create(
- test = this,
- featureFlags =
- FakeFeatureFlagsClassicModule {
- set(Flags.FULL_SCREEN_USER_SWITCHER, value = false)
- },
- mocks =
- TestMocksModule(
- dozeParameters = dozeParams,
- screenOffAnimationController = screenOffAnimController,
- ),
- )
+ val underTest =
+ NotificationIconContainerAlwaysOnDisplayViewModel(
+ kosmos.testDispatcher,
+ kosmos.alwaysOnDisplayNotificationIconsInteractor,
+ kosmos.keyguardInteractor,
+ kosmos.keyguardTransitionInteractor,
+ kosmos.mainResources,
+ kosmos.shadeInteractor,
+ )
+ val testScope = kosmos.testScope
+ val keyguardRepository = kosmos.fakeKeyguardRepository
+ val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ val powerRepository = kosmos.fakePowerRepository
@Before
fun setup() {
- testComponent.apply {
- keyguardRepository.setKeyguardShowing(true)
- keyguardRepository.setKeyguardOccluded(false)
- powerRepository.updateWakefulness(
- rawState = WakefulnessState.AWAKE,
- lastWakeReason = WakeSleepReason.OTHER,
- lastSleepReason = WakeSleepReason.OTHER,
- )
- }
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setKeyguardOccluded(false)
+ kosmos.fakePowerRepository.updateWakefulness(
+ rawState = WakefulnessState.AWAKE,
+ lastWakeReason = WakeSleepReason.OTHER,
+ lastSleepReason = WakeSleepReason.OTHER,
+ )
mSetFlagsRule.enableFlags(FLAG_NEW_AOD_TRANSITION)
}
@Test
fun animationsEnabled_isFalse_whenDeviceAsleepAndNotPulsing() =
- testComponent.runTest {
+ testScope.runTest {
powerRepository.updateWakefulness(
rawState = WakefulnessState.ASLEEP,
lastWakeReason = WakeSleepReason.POWER_BUTTON,
@@ -143,7 +113,7 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
@Test
fun animationsEnabled_isTrue_whenDeviceAsleepAndPulsing() =
- testComponent.runTest {
+ testScope.runTest {
powerRepository.updateWakefulness(
rawState = WakefulnessState.ASLEEP,
lastWakeReason = WakeSleepReason.POWER_BUTTON,
@@ -166,7 +136,7 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
@Test
fun animationsEnabled_isFalse_whenStartingToSleepAndNotControlScreenOff() =
- testComponent.runTest {
+ testScope.runTest {
powerRepository.updateWakefulness(
rawState = WakefulnessState.STARTING_TO_SLEEP,
lastWakeReason = WakeSleepReason.POWER_BUTTON,
@@ -179,7 +149,7 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
transitionState = TransitionState.STARTED,
)
)
- whenever(dozeParams.shouldControlScreenOff()).thenReturn(false)
+ whenever(kosmos.dozeParameters.shouldControlScreenOff()).thenReturn(false)
val animationsEnabled by collectLastValue(underTest.areContainerChangesAnimated)
runCurrent()
assertThat(animationsEnabled).isFalse()
@@ -187,7 +157,7 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
@Test
fun animationsEnabled_isTrue_whenStartingToSleepAndControlScreenOff() =
- testComponent.runTest {
+ testScope.runTest {
val animationsEnabled by collectLastValue(underTest.areContainerChangesAnimated)
assertThat(animationsEnabled).isTrue()
@@ -203,13 +173,13 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
transitionState = TransitionState.STARTED,
)
)
- whenever(dozeParams.shouldControlScreenOff()).thenReturn(true)
+ whenever(kosmos.dozeParameters.shouldControlScreenOff()).thenReturn(true)
assertThat(animationsEnabled).isTrue()
}
@Test
fun animationsEnabled_isTrue_whenNotAsleep() =
- testComponent.runTest {
+ testScope.runTest {
powerRepository.updateWakefulness(
rawState = WakefulnessState.AWAKE,
lastWakeReason = WakeSleepReason.POWER_BUTTON,
@@ -228,7 +198,7 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
@Test
@DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun animationsEnabled_isTrue_whenKeyguardIsShowing() =
- testComponent.runTest {
+ testScope.runTest {
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.STARTED,
@@ -257,7 +227,7 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
@Test
fun tintAlpha_isZero_whenNotOnAodOrDozing() =
- testComponent.runTest {
+ testScope.runTest {
val tintAlpha by collectLastValue(underTest.tintAlpha)
runCurrent()
keyguardTransitionRepository.sendTransitionSteps(
@@ -271,7 +241,7 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
@Test
fun tintAlpha_isOne_whenOnAod() =
- testComponent.runTest {
+ testScope.runTest {
val tintAlpha by collectLastValue(underTest.tintAlpha)
runCurrent()
keyguardTransitionRepository.sendTransitionSteps(
@@ -285,7 +255,7 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
@Test
fun tintAlpha_isOne_whenDozing() =
- testComponent.runTest {
+ testScope.runTest {
val tintAlpha by collectLastValue(underTest.tintAlpha)
runCurrent()
keyguardTransitionRepository.sendTransitionSteps(
@@ -298,7 +268,7 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
@Test
fun tintAlpha_isOne_whenTransitionFromAodToDoze() =
- testComponent.runTest {
+ testScope.runTest {
keyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.GONE,
to = KeyguardState.AOD,
@@ -332,7 +302,7 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
@Test
fun tintAlpha_isFraction_midTransitionToAod() =
- testComponent.runTest {
+ testScope.runTest {
val tintAlpha by collectLastValue(underTest.tintAlpha)
runCurrent()
@@ -361,7 +331,7 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
@Test
fun iconAnimationsEnabled_whenOnLockScreen() =
- testComponent.runTest {
+ testScope.runTest {
val iconAnimationsEnabled by collectLastValue(underTest.areIconAnimationsEnabled)
runCurrent()
@@ -376,7 +346,7 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
@Test
fun iconAnimationsDisabled_whenOnAod() =
- testComponent.runTest {
+ testScope.runTest {
val iconAnimationsEnabled by collectLastValue(underTest.areIconAnimationsEnabled)
runCurrent()
@@ -391,7 +361,7 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
@Test
fun iconAnimationsDisabled_whenDozing() =
- testComponent.runTest {
+ testScope.runTest {
val iconAnimationsEnabled by collectLastValue(underTest.areIconAnimationsEnabled)
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java
deleted file mode 100644
index ef10fdf63741..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2022 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.util.service;
-
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class PersistentConnectionManagerTest extends SysuiTestCase {
- private static final int MAX_RETRIES = 5;
- private static final int RETRY_DELAY_MS = 1000;
- private static final int CONNECTION_MIN_DURATION_MS = 5000;
- private static final String DUMPSYS_NAME = "dumpsys_name";
-
- private FakeSystemClock mFakeClock = new FakeSystemClock();
- private FakeExecutor mFakeExecutor = new FakeExecutor(mFakeClock);
-
- @Mock
- private ObservableServiceConnection<Proxy> mConnection;
-
- @Mock
- private ObservableServiceConnection.Callback<Proxy> mConnectionCallback;
-
- @Mock
- private Observer mObserver;
-
- @Mock
- private DumpManager mDumpManager;
-
- private static class Proxy {
- }
-
- private PersistentConnectionManager<Proxy> mConnectionManager;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
-
- mConnectionManager = new PersistentConnectionManager<>(
- mFakeClock,
- mFakeExecutor,
- mDumpManager,
- DUMPSYS_NAME,
- mConnection,
- MAX_RETRIES,
- RETRY_DELAY_MS,
- CONNECTION_MIN_DURATION_MS,
- mObserver);
- }
-
- private ObservableServiceConnection.Callback<Proxy> captureCallbackAndSend(
- ObservableServiceConnection<Proxy> mConnection, Proxy proxy) {
- ArgumentCaptor<ObservableServiceConnection.Callback<Proxy>> connectionCallbackCaptor =
- ArgumentCaptor.forClass(ObservableServiceConnection.Callback.class);
-
- verify(mConnection).addCallback(connectionCallbackCaptor.capture());
- verify(mConnection).bind();
- Mockito.clearInvocations(mConnection);
-
- final ObservableServiceConnection.Callback callback = connectionCallbackCaptor.getValue();
- if (proxy != null) {
- callback.onConnected(mConnection, proxy);
- } else {
- callback.onDisconnected(mConnection, 0);
- }
-
- return callback;
- }
-
- /**
- * Validates initial connection.
- */
- @Test
- public void testConnect() {
- mConnectionManager.start();
- captureCallbackAndSend(mConnection, Mockito.mock(Proxy.class));
- }
-
- /**
- * Ensures reconnection on disconnect.
- */
- @Test
- public void testRetryOnBindFailure() {
- mConnectionManager.start();
- ArgumentCaptor<ObservableServiceConnection.Callback<Proxy>> connectionCallbackCaptor =
- ArgumentCaptor.forClass(ObservableServiceConnection.Callback.class);
-
- verify(mConnection).addCallback(connectionCallbackCaptor.capture());
-
- // Verify attempts happen. Note that we account for the retries plus initial attempt, which
- // is not scheduled.
- for (int attemptCount = 0; attemptCount < MAX_RETRIES + 1; attemptCount++) {
- verify(mConnection).bind();
- Mockito.clearInvocations(mConnection);
- connectionCallbackCaptor.getValue().onDisconnected(mConnection, 0);
- mFakeExecutor.advanceClockToNext();
- mFakeExecutor.runAllReady();
- }
- }
-
- /**
- * Ensures manual unbind does not reconnect.
- */
- @Test
- public void testStopDoesNotReconnect() {
- mConnectionManager.start();
- ArgumentCaptor<ObservableServiceConnection.Callback<Proxy>> connectionCallbackCaptor =
- ArgumentCaptor.forClass(ObservableServiceConnection.Callback.class);
-
- verify(mConnection).addCallback(connectionCallbackCaptor.capture());
- verify(mConnection).bind();
- Mockito.clearInvocations(mConnection);
- mConnectionManager.stop();
- mFakeExecutor.advanceClockToNext();
- mFakeExecutor.runAllReady();
- verify(mConnection, never()).bind();
- }
-
- /**
- * Ensures rebind on package change.
- */
- @Test
- public void testAttemptOnPackageChange() {
- mConnectionManager.start();
- verify(mConnection).bind();
- ArgumentCaptor<Observer.Callback> callbackCaptor =
- ArgumentCaptor.forClass(Observer.Callback.class);
- captureCallbackAndSend(mConnection, Mockito.mock(Proxy.class));
-
- verify(mObserver).addCallback(callbackCaptor.capture());
-
- callbackCaptor.getValue().onSourceChanged();
- verify(mConnection).bind();
- }
-
- @Test
- public void testAddConnectionCallback() {
- mConnectionManager.addConnectionCallback(mConnectionCallback);
- verify(mConnection).addCallback(mConnectionCallback);
- }
-
- @Test
- public void testRemoveConnectionCallback() {
- mConnectionManager.removeConnectionCallback(mConnectionCallback);
- verify(mConnection).removeCallback(mConnectionCallback);
- }
-}
diff --git a/packages/SystemUI/tests/utils/src/android/app/admin/AlarmManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/app/admin/AlarmManagerKosmos.kt
new file mode 100644
index 000000000000..a7b5873a4798
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/app/admin/AlarmManagerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * 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 android.app.admin
+
+import android.app.AlarmManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.alarmManager by Kosmos.Fixture { mock<AlarmManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/app/admin/DevicePolicyManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/app/admin/DevicePolicyManagerKosmos.kt
new file mode 100644
index 000000000000..f51e122be214
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/app/admin/DevicePolicyManagerKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.app.admin
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.devicePolicyManager by Kosmos.Fixture { mock<android.app.admin.DevicePolicyManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/internal/widget/LockPatternUtilsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/internal/widget/LockPatternUtilsKosmos.kt
index d9ea5e92710c..b511270cdf09 100644
--- a/packages/SystemUI/tests/utils/src/com/android/internal/widget/LockPatternUtilsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/internal/widget/LockPatternUtilsKosmos.kt
@@ -16,7 +16,14 @@
package com.android.internal.widget
+import android.app.admin.devicePolicyManager
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
-var Kosmos.lockPatternUtils by Kosmos.Fixture { mock<LockPatternUtils>() }
+var Kosmos.lockPatternUtils by
+ Kosmos.Fixture {
+ mock<LockPatternUtils>().apply {
+ whenever(this.devicePolicyManager).thenReturn(this@Fixture.devicePolicyManager)
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 5bae6ec89b65..87143efb7f3c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -135,6 +135,9 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository {
private var isShowKeyguardWhenReenabled: Boolean = false
+ private val _canIgnoreAuthAndReturnToGone = MutableStateFlow(false)
+ override val canIgnoreAuthAndReturnToGone = _canIgnoreAuthAndReturnToGone.asStateFlow()
+
override fun setQuickSettingsVisible(isVisible: Boolean) {
_isQuickSettingsVisible.value = isVisible
}
@@ -278,6 +281,10 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository {
override fun isShowKeyguardWhenReenabled(): Boolean {
return isShowKeyguardWhenReenabled
}
+
+ override fun setCanIgnoreAuthAndReturnToGone(canWake: Boolean) {
+ _canIgnoreAuthAndReturnToGone.value = canWake
+ }
}
@Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
index ae138c8f930b..ef789d1b29ff 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
@@ -37,5 +37,6 @@ val Kosmos.fromAodTransitionInteractor by
powerInteractor = powerInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
deviceEntryRepository = deviceEntryRepository,
+ wakeToGoneInteractor = keyguardWakeDirectlyToGoneInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
index e7e007fd79fa..446652c7c6d8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
@@ -39,5 +39,6 @@ var Kosmos.fromDozingTransitionInteractor by
powerInteractor = powerInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
deviceEntryRepository = deviceEntryRepository,
+ wakeToGoneInteractor = keyguardWakeDirectlyToGoneInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
index a9be06d6387f..6c3de443be79 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
@@ -16,13 +16,16 @@
package com.android.systemui.keyguard.domain.interactor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+@OptIn(ExperimentalCoroutinesApi::class)
var Kosmos.fromDreamingTransitionInteractor by
Kosmos.Fixture {
FromDreamingTransitionInteractor(
@@ -36,5 +39,6 @@ var Kosmos.fromDreamingTransitionInteractor by
glanceableHubTransitions = glanceableHubTransitions,
powerInteractor = powerInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ deviceEntryInteractor = deviceEntryInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt
new file mode 100644
index 000000000000..63e168d018bf
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import android.app.admin.alarmManager
+import android.content.mockedContext
+import com.android.internal.widget.lockPatternUtils
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
+import com.android.systemui.util.settings.fakeSettings
+import com.android.systemui.util.time.systemClock
+
+val Kosmos.keyguardWakeDirectlyToGoneInteractor by
+ Kosmos.Fixture {
+ KeyguardWakeDirectlyToGoneInteractor(
+ applicationCoroutineScope,
+ mockedContext,
+ fakeKeyguardRepository,
+ systemClock,
+ alarmManager,
+ keyguardTransitionInteractor,
+ powerInteractor,
+ fakeSettings,
+ lockPatternUtils,
+ fakeSettings,
+ selectedUserInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
index bd9c0be6b0b7..8bb2fcecca58 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.statusbar.notification.domain.interactor.notificationLaunchAnimationInteractor
@@ -25,6 +26,7 @@ val Kosmos.windowManagerLockscreenVisibilityInteractor by
Kosmos.Fixture {
WindowManagerLockscreenVisibilityInteractor(
keyguardInteractor = keyguardInteractor,
+ transitionRepository = keyguardTransitionRepository,
transitionInteractor = keyguardTransitionInteractor,
surfaceBehindInteractor = keyguardSurfaceBehindInteractor,
fromLockscreenInteractor = fromLockscreenTransitionInteractor,
@@ -33,5 +35,6 @@ val Kosmos.windowManagerLockscreenVisibilityInteractor by
notificationLaunchAnimationInteractor = notificationLaunchAnimationInteractor,
sceneInteractor = { sceneInteractor },
deviceEntryInteractor = { deviceEntryInteractor },
+ wakeToGoneInteractor = keyguardWakeDirectlyToGoneInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
index a8de4607e4a2..144fe26ea230 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel
+import android.content.applicationContext
import com.android.systemui.animation.mockDialogTransitionAnimator
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
@@ -28,6 +29,7 @@ val Kosmos.castToOtherDeviceChipViewModel: CastToOtherDeviceChipViewModel by
Kosmos.Fixture {
CastToOtherDeviceChipViewModel(
scope = applicationCoroutineScope,
+ context = applicationContext,
mediaProjectionChipInteractor = mediaProjectionChipInteractor,
mediaRouterChipInteractor = mediaRouterChipInteractor,
systemClock = fakeSystemClock,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperKosmos.kt
index 4f82662fa673..1ed7a4702e2c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperKosmos.kt
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.chips.mediaprojection.ui.view
-import android.content.applicationContext
import android.content.packageManager
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
@@ -26,6 +25,5 @@ val Kosmos.endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper by
EndMediaProjectionDialogHelper(
dialogFactory = mockSystemUIDialogFactory,
packageManager = packageManager,
- context = applicationContext,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt
index 99b7ec9c9f1a..1d06947a40da 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel
+import android.content.applicationContext
import com.android.systemui.animation.mockDialogTransitionAnimator
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
@@ -27,6 +28,7 @@ val Kosmos.screenRecordChipViewModel: ScreenRecordChipViewModel by
Kosmos.Fixture {
ScreenRecordChipViewModel(
scope = applicationCoroutineScope,
+ context = applicationContext,
interactor = screenRecordChipInteractor,
endMediaProjectionDialogHelper = endMediaProjectionDialogHelper,
dialogTransitionAnimator = mockDialogTransitionAnimator,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt
index 535f81a7d63e..2e475a3c6885 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel
+import android.content.applicationContext
import com.android.systemui.animation.mockDialogTransitionAnimator
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
@@ -27,6 +28,7 @@ val Kosmos.shareToAppChipViewModel: ShareToAppChipViewModel by
Kosmos.Fixture {
ShareToAppChipViewModel(
scope = applicationCoroutineScope,
+ context = applicationContext,
mediaProjectionChipInteractor = mediaProjectionChipInteractor,
systemClock = fakeSystemClock,
endMediaProjectionDialogHelper = endMediaProjectionDialogHelper,
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index d3efa21a2311..9fc64a965f4b 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -26,6 +26,8 @@ import android.annotation.MainThread;
import android.annotation.NonNull;
import android.content.Context;
import android.graphics.Region;
+import android.hardware.input.InputManager;
+import android.os.Looper;
import android.os.PowerManager;
import android.os.SystemClock;
import android.provider.Settings;
@@ -54,6 +56,7 @@ import com.android.server.policy.WindowManagerPolicy;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Objects;
import java.util.StringJoiner;
/**
@@ -158,6 +161,13 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
*/
static final int FLAG_FEATURE_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP = 0x00001000;
+ /**
+ * Flag for enabling the Accessibility mouse key events feature.
+ *
+ * @see #setUserAndEnabledFeatures(int, int)
+ */
+ static final int FLAG_FEATURE_MOUSE_KEYS = 0x00002000;
+
static final int FEATURES_AFFECTING_MOTION_EVENTS =
FLAG_FEATURE_INJECT_MOTION_EVENTS
| FLAG_FEATURE_AUTOCLICK
@@ -189,6 +199,8 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
private KeyboardInterceptor mKeyboardInterceptor;
+ private MouseKeysInterceptor mMouseKeysInterceptor;
+
private boolean mInstalled;
private int mUserId;
@@ -733,6 +745,15 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
// default display.
addFirstEventHandler(Display.DEFAULT_DISPLAY, mKeyboardInterceptor);
}
+
+ if ((mEnabledFeatures & FLAG_FEATURE_MOUSE_KEYS) != 0) {
+ mMouseKeysInterceptor = new MouseKeysInterceptor(mAms,
+ Objects.requireNonNull(mContext.getSystemService(
+ InputManager.class)),
+ Looper.myLooper(),
+ Display.DEFAULT_DISPLAY);
+ addFirstEventHandler(Display.DEFAULT_DISPLAY, mMouseKeysInterceptor);
+ }
}
/**
@@ -816,6 +837,11 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
mKeyboardInterceptor.onDestroy();
mKeyboardInterceptor = null;
}
+
+ if (mMouseKeysInterceptor != null) {
+ mMouseKeysInterceptor.onDestroy();
+ mMouseKeysInterceptor = null;
+ }
}
private MagnificationGestureHandler createMagnificationGestureHandler(
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 32491b7eb0e0..b918d80fc63d 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -57,6 +57,7 @@ import static com.android.internal.accessibility.util.AccessibilityStatsLogUtils
import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import static com.android.server.accessibility.AccessibilityUserState.doesShortcutTargetsStringContain;
+import static com.android.hardware.input.Flags.keyboardA11yMouseKeys;
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import android.accessibilityservice.AccessibilityGestureEvent;
@@ -2936,6 +2937,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
if (combinedGenericMotionEventSources != 0) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_INTERCEPT_GENERIC_MOTION_EVENTS;
}
+ if (userState.isMouseKeysEnabled()) {
+ flags |= AccessibilityInputFilter.FLAG_FEATURE_MOUSE_KEYS;
+ }
if (flags != 0) {
if (!mHasInputFilter) {
mHasInputFilter = true;
@@ -3216,6 +3220,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
somethingChanged |= readMagnificationCapabilitiesLocked(userState);
somethingChanged |= readMagnificationFollowTypingLocked(userState);
somethingChanged |= readAlwaysOnMagnificationLocked(userState);
+ somethingChanged |= readMouseKeysEnabledLocked(userState);
return somethingChanged;
}
@@ -5476,6 +5481,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private final Uri mAlwaysOnMagnificationUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED);
+ private final Uri mMouseKeysUri = Settings.Secure.getUriFor(
+ Settings.Secure.ACCESSIBILITY_MOUSE_KEYS_ENABLED);
+
public AccessibilityContentObserver(Handler handler) {
super(handler);
}
@@ -5524,6 +5532,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mMagnificationFollowTypingUri, false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(
mAlwaysOnMagnificationUri, false, this, UserHandle.USER_ALL);
+ contentResolver.registerContentObserver(
+ mMouseKeysUri, false, this, UserHandle.USER_ALL);
}
@Override
@@ -5604,6 +5614,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
readMagnificationFollowTypingLocked(userState);
} else if (mAlwaysOnMagnificationUri.equals(uri)) {
readAlwaysOnMagnificationLocked(userState);
+ } else if (mMouseKeysUri.equals(uri)) {
+ if (readMouseKeysEnabledLocked(userState)) {
+ onUserStateChangedLocked(userState);
+ }
}
}
}
@@ -5742,6 +5756,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
return false;
}
+ boolean readMouseKeysEnabledLocked(AccessibilityUserState userState) {
+ if (!keyboardA11yMouseKeys()) {
+ return false;
+ }
+ final boolean isMouseKeysEnabled =
+ Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_MOUSE_KEYS_ENABLED, 0, userState.mUserId) == 1;
+ if (isMouseKeysEnabled != userState.isMouseKeysEnabled()) {
+ userState.setMouseKeysEnabled(isMouseKeysEnabled);
+ return true;
+ }
+ return false;
+ }
+
@Override
public void setGestureDetectionPassthroughRegion(int displayId, Region region) {
mMainHandler.sendMessage(
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 7bcbc2768a16..b061065d44a5 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -169,6 +169,8 @@ class AccessibilityUserState {
private final int mFocusStrokeWidthDefaultValue;
// The default value of the focus color.
private final int mFocusColorDefaultValue;
+ /** Whether mouse keys feature is enabled. */
+ private boolean mMouseKeysEnabled = false;
private final Map<ComponentName, ComponentName> mA11yServiceToTileService = new ArrayMap<>();
private final Map<ComponentName, ComponentName> mA11yActivityToTileService = new ArrayMap<>();
@@ -674,6 +676,14 @@ class AccessibilityUserState {
mIsFilterKeyEventsEnabled = enabled;
}
+ public void setMouseKeysEnabled(boolean enabled) {
+ mMouseKeysEnabled = enabled;
+ }
+
+ public boolean isMouseKeysEnabled() {
+ return mMouseKeysEnabled;
+ }
+
public int getInteractiveUiTimeoutLocked() {
return mInteractiveUiTimeout;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java b/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
new file mode 100644
index 000000000000..3f0f23f4a2f9
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
@@ -0,0 +1,498 @@
+/*
+ * 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.
+ */
+
+package com.android.server.accessibility;
+
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_INPUT_FILTER;
+import static android.util.MathUtils.sqrt;
+
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceParams;
+import android.hardware.input.InputManager;
+import android.hardware.input.VirtualMouse;
+import android.hardware.input.VirtualMouseButtonEvent;
+import android.hardware.input.VirtualMouseConfig;
+import android.hardware.input.VirtualMouseRelativeEvent;
+import android.hardware.input.VirtualMouseScrollEvent;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.KeyEvent;
+
+import com.android.server.LocalServices;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
+
+/**
+ * Implements the "mouse keys" accessibility feature for physical keyboards.
+ *
+ * If enabled, mouse keys will allow users to use a physical keyboard to
+ * control the mouse on the display.
+ * The following mouse functionality is supported by the mouse keys:
+ * <ul>
+ * <li> Move the mouse pointer in different directions (up, down, left, right and diagonally).
+ * <li> Click the mouse button (left, right and middle click).
+ * <li> Press and hold the mouse button.
+ * <li> Release the mouse button.
+ * <li> Scroll (up and down).
+ * </ul>
+ *
+ * The keys that are mapped to mouse keys are consumed by {@link AccessibilityInputFilter}.
+ * Non-mouse key {@link KeyEvent} will be passed to the parent handler to be handled as usual.
+ * A new {@link VirtualMouse} is created whenever the mouse keys feature is turned on in Settings.
+ * In case multiple physical keyboard are connected to a device,
+ * mouse keys of each physical keyboard will control a single (global) mouse pointer.
+ */
+public class MouseKeysInterceptor extends BaseEventStreamTransformation implements Handler.Callback,
+ InputManager.InputDeviceListener {
+ private static final String LOG_TAG = "MouseKeysInterceptor";
+
+ // To enable these logs, run: 'adb shell setprop log.tag.MouseKeysInterceptor DEBUG'
+ // (requires restart)
+ private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG);
+
+ private static final int MESSAGE_MOVE_MOUSE_POINTER = 1;
+ private static final int MESSAGE_SCROLL_MOUSE_POINTER = 2;
+ private static final float MOUSE_POINTER_MOVEMENT_STEP = 1.8f;
+ private static final int KEY_NOT_SET = -1;
+
+ /** Time interval after which mouse action will be repeated */
+ private static final int INTERVAL_MILLIS = 10;
+
+ private final AccessibilityManagerService mAms;
+ private final InputManager mInputManager;
+ private final Handler mHandler;
+
+ private final int mDisplayId;
+
+ VirtualDeviceManager.VirtualDevice mVirtualDevice = null;
+
+ private VirtualMouse mVirtualMouse = null;
+
+ /**
+ * State of the active directional mouse key.
+ * Multiple mouse keys will not be allowed to be used simultaneously i.e.,
+ * once a mouse key is pressed, other mouse key presses will be disregarded
+ * (except for when the "HOLD" key is pressed).
+ */
+ private int mActiveMoveKey = KEY_NOT_SET;
+
+ /** State of the active scroll mouse key. */
+ private int mActiveScrollKey = KEY_NOT_SET;
+
+ /** Last time the key action was performed */
+ private long mLastTimeKeyActionPerformed = 0;
+
+ // TODO (b/346706749): This is currently using the numpad key bindings for mouse keys.
+ // Decide the final mouse key bindings with UX input.
+ public enum MouseKeyEvent {
+ DIAGONAL_DOWN_LEFT_MOVE(KeyEvent.KEYCODE_NUMPAD_1),
+ DOWN_MOVE(KeyEvent.KEYCODE_NUMPAD_2),
+ DIAGONAL_DOWN_RIGHT_MOVE(KeyEvent.KEYCODE_NUMPAD_3),
+ LEFT_MOVE(KeyEvent.KEYCODE_NUMPAD_4),
+ RIGHT_MOVE(KeyEvent.KEYCODE_NUMPAD_6),
+ DIAGONAL_UP_LEFT_MOVE(KeyEvent.KEYCODE_NUMPAD_7),
+ UP_MOVE(KeyEvent.KEYCODE_NUMPAD_8),
+ DIAGONAL_UP_RIGHT_MOVE(KeyEvent.KEYCODE_NUMPAD_9),
+ LEFT_CLICK(KeyEvent.KEYCODE_NUMPAD_5),
+ RIGHT_CLICK(KeyEvent.KEYCODE_NUMPAD_DOT),
+ HOLD(KeyEvent.KEYCODE_NUMPAD_MULTIPLY),
+ RELEASE(KeyEvent.KEYCODE_NUMPAD_SUBTRACT),
+ SCROLL_UP(KeyEvent.KEYCODE_A),
+ SCROLL_DOWN(KeyEvent.KEYCODE_S);
+
+ private final int mKeyCode;
+ MouseKeyEvent(int enumValue) {
+ mKeyCode = enumValue;
+ }
+
+ private static final SparseArray<MouseKeyEvent> VALUE_TO_ENUM_MAP = new SparseArray<>();
+
+ static {
+ for (MouseKeyEvent type : MouseKeyEvent.values()) {
+ VALUE_TO_ENUM_MAP.put(type.mKeyCode, type);
+ }
+ }
+
+ public final int getKeyCodeValue() {
+ return mKeyCode;
+ }
+
+ /**
+ * Convert int value of the key code to corresponding MouseEvent enum. If no matching
+ * value is found, this will return {@code null}.
+ */
+ @Nullable
+ public static MouseKeyEvent from(int value) {
+ return VALUE_TO_ENUM_MAP.get(value);
+ }
+ }
+
+ /**
+ * Construct a new MouseKeysInterceptor.
+ *
+ * @param service The service to notify of key events
+ * @param inputManager InputManager to track changes to connected input devices
+ * @param looper Looper to use for callbacks and messages
+ * @param displayId Display ID to send mouse events to
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public MouseKeysInterceptor(AccessibilityManagerService service, InputManager inputManager,
+ Looper looper, int displayId) {
+ mAms = service;
+ mInputManager = inputManager;
+ mHandler = new Handler(looper, this);
+ mInputManager.registerInputDeviceListener(this, mHandler);
+ mDisplayId = displayId;
+ // Create the virtual mouse on a separate thread since virtual device creation
+ // should happen on an auxiliary thread, and not from the handler's thread.
+ new Thread(() -> {
+ mVirtualMouse = createVirtualMouse();
+ }).start();
+
+ }
+
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ private void sendVirtualMouseRelativeEvent(float x, float y) {
+ if (mVirtualMouse != null) {
+ mVirtualMouse.sendRelativeEvent(new VirtualMouseRelativeEvent.Builder()
+ .setRelativeX(x)
+ .setRelativeY(y)
+ .build()
+ );
+ }
+ }
+
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ private void sendVirtualMouseButtonEvent(int buttonCode, int actionCode) {
+ if (mVirtualMouse != null) {
+ mVirtualMouse.sendButtonEvent(new VirtualMouseButtonEvent.Builder()
+ .setAction(actionCode)
+ .setButtonCode(buttonCode)
+ .build()
+ );
+ }
+ }
+
+ /**
+ * Performs a mouse scroll action based on the provided key code.
+ * This method interprets the key code as a mouse scroll and sends
+ * the corresponding {@code VirtualMouseScrollEvent#mYAxisMovement}.
+
+ * @param keyCode The key code representing the mouse scroll action.
+ * Supported keys are:
+ * <ul>
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent SCROLL_UP}
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent SCROLL_DOWN}
+ * </ul>
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ private void performMouseScrollAction(int keyCode) {
+ MouseKeyEvent mouseKeyEvent = MouseKeyEvent.from(keyCode);
+ float y = switch (mouseKeyEvent) {
+ case SCROLL_UP -> 1.0f;
+ case SCROLL_DOWN -> -1.0f;
+ default -> 0.0f;
+ };
+ if (mVirtualMouse != null) {
+ mVirtualMouse.sendScrollEvent(new VirtualMouseScrollEvent.Builder()
+ .setYAxisMovement(y)
+ .build()
+ );
+ }
+ if (DEBUG) {
+ Slog.d(LOG_TAG, "Performed mouse key event: " + mouseKeyEvent.name()
+ + " for scroll action with axis movement (y=" + y + ")");
+ }
+ }
+
+ /**
+ * Performs a mouse button action based on the provided key code.
+ * This method interprets the key code as a mouse button press and sends
+ * the corresponding press and release events to the virtual mouse.
+
+ * @param keyCode The key code representing the mouse button action.
+ * Supported keys are:
+ * <ul>
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent LEFT_CLICK} (Primary Button)
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent RIGHT_CLICK} (Secondary
+ * Button)
+ * </ul>
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ private void performMouseButtonAction(int keyCode) {
+ MouseKeyEvent mouseKeyEvent = MouseKeyEvent.from(keyCode);
+ int buttonCode = switch (mouseKeyEvent) {
+ case LEFT_CLICK -> VirtualMouseButtonEvent.BUTTON_PRIMARY;
+ case RIGHT_CLICK -> VirtualMouseButtonEvent.BUTTON_SECONDARY;
+ default -> VirtualMouseButtonEvent.BUTTON_UNKNOWN;
+ };
+ if (buttonCode != VirtualMouseButtonEvent.BUTTON_UNKNOWN) {
+ sendVirtualMouseButtonEvent(buttonCode, VirtualMouseButtonEvent.ACTION_BUTTON_PRESS);
+ sendVirtualMouseButtonEvent(buttonCode, VirtualMouseButtonEvent.ACTION_BUTTON_RELEASE);
+ }
+ if (DEBUG) {
+ if (buttonCode == VirtualMouseButtonEvent.BUTTON_UNKNOWN) {
+ Slog.d(LOG_TAG, "Button code is unknown for mouse key event: "
+ + mouseKeyEvent.name());
+ } else {
+ Slog.d(LOG_TAG, "Performed mouse key event: " + mouseKeyEvent.name()
+ + " for button action");
+ }
+ }
+ }
+
+ /**
+ * Performs a mouse pointer action based on the provided key code.
+ * The method calculates the relative movement of the mouse pointer
+ * and sends the corresponding event to the virtual mouse.
+ *
+ * @param keyCode The key code representing the direction or button press.
+ * Supported keys are:
+ * <ul>
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent DIAGONAL_DOWN_LEFT}
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent DOWN}
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent DIAGONAL_DOWN_RIGHT}
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent LEFT}
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent RIGHT}
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent DIAGONAL_UP_LEFT}
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent UP}
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent DIAGONAL_UP_RIGHT}
+ * </ul>
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ private void performMousePointerAction(int keyCode) {
+ float x = 0f;
+ float y = 0f;
+ MouseKeyEvent mouseKeyEvent = MouseKeyEvent.from(keyCode);
+ switch (mouseKeyEvent) {
+ case DIAGONAL_DOWN_LEFT_MOVE -> {
+ x = -MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
+ y = MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
+ }
+ case DOWN_MOVE -> {
+ y = MOUSE_POINTER_MOVEMENT_STEP;
+ }
+ case DIAGONAL_DOWN_RIGHT_MOVE -> {
+ x = MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
+ y = MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
+ }
+ case LEFT_MOVE -> {
+ x = -MOUSE_POINTER_MOVEMENT_STEP;
+ }
+ case RIGHT_MOVE -> {
+ x = MOUSE_POINTER_MOVEMENT_STEP;
+ }
+ case DIAGONAL_UP_LEFT_MOVE -> {
+ x = -MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
+ y = -MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
+ }
+ case UP_MOVE -> {
+ y = -MOUSE_POINTER_MOVEMENT_STEP;
+ }
+ case DIAGONAL_UP_RIGHT_MOVE -> {
+ x = MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
+ y = -MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
+ }
+ default -> {
+ x = 0.0f;
+ y = 0.0f;
+ }
+ }
+ sendVirtualMouseRelativeEvent(x, y);
+ if (DEBUG) {
+ Slog.d(LOG_TAG, "Performed mouse key event: " + mouseKeyEvent.name()
+ + " for relative pointer movement (x=" + x + ", y=" + y + ")");
+ }
+ }
+
+ private boolean isMouseKey(int keyCode) {
+ return MouseKeyEvent.VALUE_TO_ENUM_MAP.contains(keyCode);
+ }
+
+ private boolean isMouseButtonKey(int keyCode) {
+ return keyCode == MouseKeyEvent.LEFT_CLICK.getKeyCodeValue()
+ || keyCode == MouseKeyEvent.RIGHT_CLICK.getKeyCodeValue();
+ }
+
+ private boolean isMouseScrollKey(int keyCode) {
+ return keyCode == MouseKeyEvent.SCROLL_UP.getKeyCodeValue()
+ || keyCode == MouseKeyEvent.SCROLL_DOWN.getKeyCodeValue();
+ }
+
+ /**
+ * Create a virtual mouse using the VirtualDeviceManagerInternal.
+ *
+ * @return The created VirtualMouse.
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ private VirtualMouse createVirtualMouse() {
+ final VirtualDeviceManagerInternal localVdm =
+ LocalServices.getService(VirtualDeviceManagerInternal.class);
+ mVirtualDevice = localVdm.createVirtualDevice(
+ new VirtualDeviceParams.Builder().setName("Mouse Keys Virtual Device").build());
+ VirtualMouse virtualMouse = mVirtualDevice.createVirtualMouse(
+ new VirtualMouseConfig.Builder()
+ .setInputDeviceName("Mouse Keys Virtual Mouse")
+ .setAssociatedDisplayId(mDisplayId)
+ .build());
+ return virtualMouse;
+ }
+
+ /**
+ * Handles key events and forwards mouse key events to the virtual mouse.
+ *
+ * @param event The key event to handle.
+ * @param policyFlags The policy flags associated with the key event.
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ @Override
+ public void onKeyEvent(KeyEvent event, int policyFlags) {
+ if (mAms.getTraceManager().isA11yTracingEnabledForTypes(FLAGS_INPUT_FILTER)) {
+ mAms.getTraceManager().logTrace(LOG_TAG + ".onKeyEvent",
+ FLAGS_INPUT_FILTER, "event=" + event + ";policyFlags=" + policyFlags);
+ }
+ boolean isDown = event.getAction() == KeyEvent.ACTION_DOWN;
+ int keyCode = event.getKeyCode();
+
+ if (!isMouseKey(keyCode)) {
+ // Pass non-mouse key events to the next handler
+ super.onKeyEvent(event, policyFlags);
+ } else if (keyCode == MouseKeyEvent.HOLD.getKeyCodeValue()) {
+ sendVirtualMouseButtonEvent(VirtualMouseButtonEvent.BUTTON_PRIMARY,
+ VirtualMouseButtonEvent.ACTION_BUTTON_PRESS);
+ } else if (keyCode == MouseKeyEvent.RELEASE.getKeyCodeValue()) {
+ sendVirtualMouseButtonEvent(VirtualMouseButtonEvent.BUTTON_PRIMARY,
+ VirtualMouseButtonEvent.ACTION_BUTTON_RELEASE);
+ } else if (isDown && isMouseButtonKey(keyCode)) {
+ performMouseButtonAction(keyCode);
+ } else if (isDown && isMouseScrollKey(keyCode)) {
+ // If the scroll key is pressed down and no other key is active,
+ // set it as the active key and send a message to scroll the pointer
+ if (mActiveScrollKey == KEY_NOT_SET) {
+ mActiveScrollKey = keyCode;
+ mLastTimeKeyActionPerformed = event.getDownTime();
+ mHandler.sendEmptyMessage(MESSAGE_SCROLL_MOUSE_POINTER);
+ }
+ } else if (isDown) {
+ // This is a directional key.
+ // If the key is pressed down and no other key is active,
+ // set it as the active key and send a message to move the pointer
+ if (mActiveMoveKey == KEY_NOT_SET) {
+ mActiveMoveKey = keyCode;
+ mLastTimeKeyActionPerformed = event.getDownTime();
+ mHandler.sendEmptyMessage(MESSAGE_MOVE_MOUSE_POINTER);
+ }
+ } else if (mActiveMoveKey == keyCode) {
+ // If the key is released, and it is the active key, stop moving the pointer
+ mActiveMoveKey = KEY_NOT_SET;
+ mHandler.removeMessages(MESSAGE_MOVE_MOUSE_POINTER);
+ } else if (mActiveScrollKey == keyCode) {
+ // If the key is released, and it is the active key, stop scrolling the pointer
+ mActiveScrollKey = KEY_NOT_SET;
+ mHandler.removeMessages(MESSAGE_SCROLL_MOUSE_POINTER);
+ } else {
+ Slog.i(LOG_TAG, "Dropping event with key code: '" + keyCode
+ + "', with no matching down event from deviceId = " + event.getDeviceId());
+ }
+ }
+
+ /**
+ * Handle messages for moving or scrolling the mouse pointer.
+ *
+ * @param msg The message to handle.
+ * @return True if the message was handled, false otherwise.
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ @Override
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_MOVE_MOUSE_POINTER ->
+ handleMouseMessage(msg.getWhen(), mActiveMoveKey, MESSAGE_MOVE_MOUSE_POINTER);
+ case MESSAGE_SCROLL_MOUSE_POINTER ->
+ handleMouseMessage(msg.getWhen(), mActiveScrollKey,
+ MESSAGE_SCROLL_MOUSE_POINTER);
+ default -> {
+ Slog.e(LOG_TAG, "Unexpected message type");
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Handles mouse-related messages for moving or scrolling the mouse pointer.
+ * This method checks if the specified time interval {@code INTERVAL_MILLIS} has passed since
+ * the last movement or scroll action and performs the corresponding action if necessary.
+ * If there is an active key, the message is rescheduled to be handled again
+ * after the specified {@code INTERVAL_MILLIS}.
+ *
+ * @param currentTime The current time when the message is being handled.
+ * @param activeKey The key code representing the active key. This determines
+ * the direction or type of action to be performed.
+ * @param messageType The type of message to be handled. It can be one of the
+ * following:
+ * <ul>
+ * <li>{@link #MESSAGE_MOVE_MOUSE_POINTER} - for moving the mouse pointer.
+ * <li>{@link #MESSAGE_SCROLL_MOUSE_POINTER} - for scrolling mouse pointer.
+ * </ul>
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void handleMouseMessage(long currentTime, int activeKey, int messageType) {
+ if (currentTime - mLastTimeKeyActionPerformed >= INTERVAL_MILLIS) {
+ if (messageType == MESSAGE_MOVE_MOUSE_POINTER) {
+ performMousePointerAction(activeKey);
+ } else if (messageType == MESSAGE_SCROLL_MOUSE_POINTER) {
+ performMouseScrollAction(activeKey);
+ }
+ mLastTimeKeyActionPerformed = currentTime;
+ }
+ if (activeKey != KEY_NOT_SET) {
+ // Reschedule the message if the key is still active
+ mHandler.sendEmptyMessageDelayed(messageType, INTERVAL_MILLIS);
+ }
+ }
+
+ @Override
+ public void onInputDeviceAdded(int deviceId) {
+ }
+
+ @Override
+ public void onInputDeviceRemoved(int deviceId) {
+ }
+
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ @Override
+ public void onDestroy() {
+ // Clear mouse state
+ mActiveMoveKey = KEY_NOT_SET;
+ mActiveScrollKey = KEY_NOT_SET;
+ mLastTimeKeyActionPerformed = 0;
+ mHandler.removeCallbacksAndMessages(null);
+
+ mVirtualDevice.close();
+ mInputManager.unregisterInputDeviceListener(this);
+ }
+
+ @Override
+ public void onInputDeviceChanged(int deviceId) {
+ }
+
+}
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index bacfd8f9960e..3633d0f9dd6f 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -2989,7 +2989,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
// Always redact location info from PhysicalChannelConfig if the registrant is from neither
// PHONE nor SYSTEM process. There is no user case that the registrant needs the location
// info (e.g. physicalCellId). This also remove the need for the location permissions check.
- return record.callerUid != Process.PHONE_UID && record.callerUid != Process.SYSTEM_UID;
+ return !TelephonyPermissions.isSystemOrPhone(record.callerUid);
}
/**
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 6d9b4f547be6..0d309eb6786d 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -977,7 +977,7 @@ public class AudioService extends IAudioService.Stub
private NotificationManager mNm;
private AudioManagerInternal.RingerModeDelegate mRingerModeDelegate;
- private VolumePolicy mVolumePolicy = VolumePolicy.DEFAULT;
+ private volatile VolumePolicy mVolumePolicy = VolumePolicy.DEFAULT;
private long mLoweredFromNormalToVibrateTime;
// Array of Uids of valid assistant services to check if caller is one of them
@@ -12101,6 +12101,11 @@ public class AudioService extends IAudioService.Stub
}
}
+ @Override
+ public VolumePolicy getVolumePolicy() {
+ return mVolumePolicy;
+ }
+
/** Interface used for enforcing the safe hearing standard. */
public interface ISafeHearingVolumeController {
/** Displays an instructional safeguard as required by the safe hearing standard. */
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
index eb78fe620c0b..a1184156f86e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
@@ -20,7 +20,6 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.biometrics.BiometricConstants;
-import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
@@ -28,11 +27,12 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.modules.expresslog.Counter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
-import java.util.function.BooleanSupplier;
+
/**
* Contains all the necessary information for a HAL operation.
@@ -89,8 +89,6 @@ public class BiometricSchedulerOperation {
private final BaseClientMonitor mClientMonitor;
@Nullable
private final ClientMonitorCallback mClientCallback;
- @NonNull
- private final BooleanSupplier mIsDebuggable;
@Nullable
private ClientMonitorCallback mOnStartCallback;
@OperationState
@@ -99,6 +97,7 @@ public class BiometricSchedulerOperation {
@NonNull
final Runnable mCancelWatchdog;
+ @VisibleForTesting
BiometricSchedulerOperation(
@NonNull BaseClientMonitor clientMonitor,
@Nullable ClientMonitorCallback callback
@@ -106,33 +105,14 @@ public class BiometricSchedulerOperation {
this(clientMonitor, callback, STATE_WAITING_IN_QUEUE);
}
- @VisibleForTesting
- BiometricSchedulerOperation(
- @NonNull BaseClientMonitor clientMonitor,
- @Nullable ClientMonitorCallback callback,
- @NonNull BooleanSupplier isDebuggable
- ) {
- this(clientMonitor, callback, STATE_WAITING_IN_QUEUE, isDebuggable);
- }
-
protected BiometricSchedulerOperation(
@NonNull BaseClientMonitor clientMonitor,
@Nullable ClientMonitorCallback callback,
@OperationState int state
) {
- this(clientMonitor, callback, state, Build::isDebuggable);
- }
-
- private BiometricSchedulerOperation(
- @NonNull BaseClientMonitor clientMonitor,
- @Nullable ClientMonitorCallback callback,
- @OperationState int state,
- @NonNull BooleanSupplier isDebuggable
- ) {
mClientMonitor = clientMonitor;
mClientCallback = callback;
mState = state;
- mIsDebuggable = isDebuggable;
mCancelWatchdog = () -> {
if (!isFinished()) {
Slog.e(TAG, "[Watchdog Triggered]: " + this);
@@ -187,9 +167,7 @@ public class BiometricSchedulerOperation {
if (mClientMonitor.getCookie() != 0) {
String err = "operation requires cookie";
- if (mIsDebuggable.getAsBoolean()) {
- throw new IllegalStateException(err);
- }
+ Counter.logIncrement("biometric.value_biometric_scheduler_operation_state_error_count");
Slog.e(TAG, err);
}
@@ -456,10 +434,9 @@ public class BiometricSchedulerOperation {
private boolean errorWhenOneOf(String op, @OperationState int... states) {
final boolean isError = ArrayUtils.contains(states, mState);
if (isError) {
- String err = op + ": mState must not be " + mState;
- if (mIsDebuggable.getAsBoolean()) {
- throw new IllegalStateException(err);
- }
+ Counter.logIncrement(
+ "biometric.value_biometric_scheduler_operation_state_error_count");
+ final String err = op + ": mState must not be " + mState;
Slog.e(TAG, err);
}
return isError;
@@ -468,10 +445,10 @@ public class BiometricSchedulerOperation {
private boolean errorWhenNoneOf(String op, @OperationState int... states) {
final boolean isError = !ArrayUtils.contains(states, mState);
if (isError) {
- String err = op + ": mState=" + mState + " must be one of " + Arrays.toString(states);
- if (mIsDebuggable.getAsBoolean()) {
- throw new IllegalStateException(err);
- }
+ Counter.logIncrement(
+ "biometric.value_biometric_scheduler_operation_state_error_count");
+ final String err = op + ": mState=" + mState + " must be one of "
+ + Arrays.toString(states);
Slog.e(TAG, err);
}
return isError;
diff --git a/services/core/java/com/android/server/location/altitude/AltitudeService.java b/services/core/java/com/android/server/location/altitude/AltitudeService.java
index 289d4a25f208..96540c225e23 100644
--- a/services/core/java/com/android/server/location/altitude/AltitudeService.java
+++ b/services/core/java/com/android/server/location/altitude/AltitudeService.java
@@ -25,6 +25,7 @@ import android.frameworks.location.altitude.IAltitudeService;
import android.location.Location;
import android.location.altitude.AltitudeConverter;
import android.os.RemoteException;
+import android.util.Log;
import com.android.server.SystemService;
@@ -38,6 +39,8 @@ import java.io.IOException;
*/
public class AltitudeService extends IAltitudeService.Stub {
+ private static final String TAG = "AltitudeService";
+
private final AltitudeConverter mAltitudeConverter = new AltitudeConverter();
private final Context mContext;
@@ -59,6 +62,7 @@ public class AltitudeService extends IAltitudeService.Stub {
try {
mAltitudeConverter.addMslAltitudeToLocation(mContext, location);
} catch (IOException e) {
+ Log.e(TAG, "", e);
response.success = false;
return response;
}
@@ -74,6 +78,7 @@ public class AltitudeService extends IAltitudeService.Stub {
try {
return mAltitudeConverter.getGeoidHeight(mContext, request);
} catch (IOException e) {
+ Log.e(TAG, "", e);
GetGeoidHeightResponse response = new GetGeoidHeightResponse();
response.success = false;
return response;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 1c40f44b7b78..9e53cc357ea4 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -8658,8 +8658,7 @@ public class NotificationManagerService extends SystemService {
mAttentionHelper.updateLightsLocked();
if (mShortcutHelper != null) {
mShortcutHelper.maybeListenForShortcutChangesForBubbles(r,
- true /* isRemoved */,
- mHandler);
+ true /* isRemoved */);
}
} else {
if (notificationForceGrouping()) {
@@ -9116,8 +9115,7 @@ public class NotificationManagerService extends SystemService {
if (mShortcutHelper != null) {
mShortcutHelper.maybeListenForShortcutChangesForBubbles(r,
- false /* isRemoved */,
- mHandler);
+ false /* isRemoved */);
}
maybeRecordInterruptionLocked(r);
diff --git a/services/core/java/com/android/server/notification/ShortcutHelper.java b/services/core/java/com/android/server/notification/ShortcutHelper.java
index 86dcecf9290a..857e3198d55b 100644
--- a/services/core/java/com/android/server/notification/ShortcutHelper.java
+++ b/services/core/java/com/android/server/notification/ShortcutHelper.java
@@ -27,7 +27,6 @@ import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutServiceInternal;
import android.os.Binder;
-import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager;
import android.text.TextUtils;
@@ -65,85 +64,34 @@ public class ShortcutHelper {
void onShortcutRemoved(String key);
}
+ private final ShortcutListener mShortcutListener;
private LauncherApps mLauncherAppsService;
- private ShortcutListener mShortcutListener;
private ShortcutServiceInternal mShortcutServiceInternal;
private UserManager mUserManager;
- // Key: packageName Value: <shortcutId, notifId>
- private HashMap<String, HashMap<String, String>> mActiveShortcutBubbles = new HashMap<>();
- private boolean mLauncherAppsCallbackRegistered;
+ // Key: packageName|userId Value: <shortcutId, notifId>
+ private final HashMap<String, HashMap<String, String>> mActiveShortcutBubbles = new HashMap<>();
+ private boolean mShortcutChangedCallbackRegistered;
// Bubbles can be created based on a shortcut, we need to listen for changes to
// that shortcut so that we may update the bubble appropriately.
- private final LauncherApps.Callback mLauncherAppsCallback = new LauncherApps.Callback() {
- @Override
- public void onPackageRemoved(String packageName, UserHandle user) {
- }
-
- @Override
- public void onPackageAdded(String packageName, UserHandle user) {
- }
-
- @Override
- public void onPackageChanged(String packageName, UserHandle user) {
- }
-
- @Override
- public void onPackagesAvailable(String[] packageNames, UserHandle user,
- boolean replacing) {
- }
-
- @Override
- public void onPackagesUnavailable(String[] packageNames, UserHandle user,
- boolean replacing) {
- }
+ private final LauncherApps.ShortcutChangeCallback mShortcutChangeCallback =
+ new LauncherApps.ShortcutChangeCallback() {
- @Override
- public void onShortcutsChanged(@NonNull String packageName,
- @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
- HashMap<String, String> shortcutBubbles = mActiveShortcutBubbles.get(packageName);
- ArrayList<String> bubbleKeysToRemove = new ArrayList<>();
- if (shortcutBubbles != null) {
- // Copy to avoid a concurrent modification exception when we remove bubbles from
- // shortcutBubbles.
- final Set<String> shortcutIds = new HashSet<>(shortcutBubbles.keySet());
-
- // If we can't find one of our bubbles in the shortcut list, that bubble needs
- // to be removed.
- for (String shortcutId : shortcutIds) {
- boolean foundShortcut = false;
- for (int i = 0; i < shortcuts.size(); i++) {
- if (shortcuts.get(i).getId().equals(shortcutId)) {
- foundShortcut = true;
- break;
- }
- }
- if (!foundShortcut) {
- bubbleKeysToRemove.add(shortcutBubbles.get(shortcutId));
- shortcutBubbles.remove(shortcutId);
- if (shortcutBubbles.isEmpty()) {
- mActiveShortcutBubbles.remove(packageName);
- if (mLauncherAppsCallbackRegistered
- && mActiveShortcutBubbles.isEmpty()) {
- mLauncherAppsService.unregisterCallback(mLauncherAppsCallback);
- mLauncherAppsCallbackRegistered = false;
- }
- }
- }
+ @Override
+ public void onShortcutsAddedOrUpdated(@NonNull String packageName,
+ @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
}
- }
- // Let NoMan know about the updates
- for (int i = 0; i < bubbleKeysToRemove.size(); i++) {
- // update flag bubble
- String bubbleKey = bubbleKeysToRemove.get(i);
- if (mShortcutListener != null) {
- mShortcutListener.onShortcutRemoved(bubbleKey);
+ public void onShortcutsRemoved(@NonNull String packageName,
+ @NonNull List<ShortcutInfo> removedShortcuts, @NonNull UserHandle user) {
+ final String packageUserKey = getPackageUserKey(packageName, user);
+ if (mActiveShortcutBubbles.get(packageUserKey) == null) return;
+ for (ShortcutInfo info : removedShortcuts) {
+ onShortcutRemoved(packageUserKey, info.getId());
+ }
}
- }
- }
- };
+ };
ShortcutHelper(LauncherApps launcherApps, ShortcutListener listener,
ShortcutServiceInternal shortcutServiceInternal, UserManager userManager) {
@@ -172,14 +120,14 @@ public class ShortcutHelper {
* Returns whether the given shortcut info is a conversation shortcut.
*/
public static boolean isConversationShortcut(
- ShortcutInfo shortcutInfo, ShortcutServiceInternal mShortcutServiceInternal,
+ ShortcutInfo shortcutInfo, ShortcutServiceInternal shortcutServiceInternal,
int callingUserId) {
if (shortcutInfo == null || !shortcutInfo.isLongLived() || !shortcutInfo.isEnabled()) {
return false;
}
// TODO (b/155016294) uncomment when sharing shortcuts are required
/*
- mShortcutServiceInternal.isSharingShortcut(callingUserId, "android",
+ shortcutServiceInternal.isSharingShortcut(callingUserId, "android",
shortcutInfo.getPackage(), shortcutInfo.getId(), shortcutInfo.getUserId(),
SHARING_FILTER);
*/
@@ -233,34 +181,30 @@ public class ShortcutHelper {
*
* @param r the notification record to check
* @param removedNotification true if this notification is being removed
- * @param handler handler to register the callback with
*/
void maybeListenForShortcutChangesForBubbles(NotificationRecord r,
- boolean removedNotification,
- Handler handler) {
+ boolean removedNotification) {
final String shortcutId = r.getNotification().getBubbleMetadata() != null
? r.getNotification().getBubbleMetadata().getShortcutId()
: null;
+ final String packageUserKey = getPackageUserKey(r.getSbn().getPackageName(), r.getUser());
if (!removedNotification
&& !TextUtils.isEmpty(shortcutId)
&& r.getShortcutInfo() != null
&& r.getShortcutInfo().getId().equals(shortcutId)) {
// Must track shortcut based bubbles in case the shortcut is removed
HashMap<String, String> packageBubbles = mActiveShortcutBubbles.get(
- r.getSbn().getPackageName());
+ packageUserKey);
if (packageBubbles == null) {
packageBubbles = new HashMap<>();
}
packageBubbles.put(shortcutId, r.getKey());
- mActiveShortcutBubbles.put(r.getSbn().getPackageName(), packageBubbles);
- if (!mLauncherAppsCallbackRegistered) {
- mLauncherAppsService.registerCallback(mLauncherAppsCallback, handler);
- mLauncherAppsCallbackRegistered = true;
- }
+ mActiveShortcutBubbles.put(packageUserKey, packageBubbles);
+ registerCallbackIfNeeded();
} else {
// No longer track shortcut
HashMap<String, String> packageBubbles = mActiveShortcutBubbles.get(
- r.getSbn().getPackageName());
+ packageUserKey);
if (packageBubbles != null) {
if (!TextUtils.isEmpty(shortcutId)) {
packageBubbles.remove(shortcutId);
@@ -278,20 +222,62 @@ public class ShortcutHelper {
}
}
if (packageBubbles.isEmpty()) {
- mActiveShortcutBubbles.remove(r.getSbn().getPackageName());
+ mActiveShortcutBubbles.remove(packageUserKey);
}
}
- if (mLauncherAppsCallbackRegistered && mActiveShortcutBubbles.isEmpty()) {
- mLauncherAppsService.unregisterCallback(mLauncherAppsCallback);
- mLauncherAppsCallbackRegistered = false;
+ unregisterCallbackIfNeeded();
+ }
+ }
+
+ private String getPackageUserKey(String packageName, UserHandle user) {
+ return packageName + "|" + user.getIdentifier();
+ }
+
+ private void onShortcutRemoved(String packageUserKey, String shortcutId) {
+ HashMap<String, String> shortcutBubbles = mActiveShortcutBubbles.get(packageUserKey);
+ ArrayList<String> bubbleKeysToRemove = new ArrayList<>();
+ if (shortcutBubbles != null) {
+ if (shortcutBubbles.containsKey(shortcutId)) {
+ bubbleKeysToRemove.add(shortcutBubbles.get(shortcutId));
+ shortcutBubbles.remove(shortcutId);
+ if (shortcutBubbles.isEmpty()) {
+ mActiveShortcutBubbles.remove(packageUserKey);
+ unregisterCallbackIfNeeded();
+ }
}
+ notifyNoMan(bubbleKeysToRemove);
+ }
+ }
+
+ private void registerCallbackIfNeeded() {
+ if (!mShortcutChangedCallbackRegistered) {
+ mShortcutChangedCallbackRegistered = true;
+ mShortcutServiceInternal.addShortcutChangeCallback(mShortcutChangeCallback);
+ }
+ }
+
+ private void unregisterCallbackIfNeeded() {
+ if (mShortcutChangedCallbackRegistered && mActiveShortcutBubbles.isEmpty()) {
+ mShortcutServiceInternal.removeShortcutChangeCallback(mShortcutChangeCallback);
+ mShortcutChangedCallbackRegistered = false;
}
}
void destroy() {
- if (mLauncherAppsCallbackRegistered) {
- mLauncherAppsService.unregisterCallback(mLauncherAppsCallback);
- mLauncherAppsCallbackRegistered = false;
+ if (mShortcutChangedCallbackRegistered) {
+ mShortcutServiceInternal.removeShortcutChangeCallback(mShortcutChangeCallback);
+ mShortcutChangedCallbackRegistered = false;
+ }
+ }
+
+ private void notifyNoMan(List<String> bubbleKeysToRemove) {
+ // Let NoMan know about the updates
+ for (int i = 0; i < bubbleKeysToRemove.size(); i++) {
+ // update flag bubble
+ String bubbleKey = bubbleKeysToRemove.get(i);
+ if (mShortcutListener != null) {
+ mShortcutListener.onShortcutRemoved(bubbleKey);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 1cd77ffcedaa..4a60e452919f 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -3443,6 +3443,14 @@ public class ShortcutService extends IShortcutService.Stub {
}
@Override
+ public void removeShortcutChangeCallback(
+ @NonNull LauncherApps.ShortcutChangeCallback callback) {
+ synchronized (mServiceLock) {
+ mShortcutChangeCallbacks.remove(Objects.requireNonNull(callback));
+ }
+ }
+
+ @Override
public int getShortcutIconResId(int launcherUserId, @NonNull String callingPackage,
@NonNull String packageName, @NonNull String shortcutId, int userId) {
Objects.requireNonNull(callingPackage, "callingPackage");
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index c9ba683a698a..9d0c0e9b36bb 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3568,24 +3568,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
Slog.wtf(TAG, "KEYCODE_VOICE_ASSIST should be handled in"
+ " interceptKeyBeforeQueueing");
return true;
- case KeyEvent.KEYCODE_VIDEO_APP_1:
- case KeyEvent.KEYCODE_VIDEO_APP_2:
- case KeyEvent.KEYCODE_VIDEO_APP_3:
- case KeyEvent.KEYCODE_VIDEO_APP_4:
- case KeyEvent.KEYCODE_VIDEO_APP_5:
- case KeyEvent.KEYCODE_VIDEO_APP_6:
- case KeyEvent.KEYCODE_VIDEO_APP_7:
- case KeyEvent.KEYCODE_VIDEO_APP_8:
- case KeyEvent.KEYCODE_FEATURED_APP_1:
- case KeyEvent.KEYCODE_FEATURED_APP_2:
- case KeyEvent.KEYCODE_FEATURED_APP_3:
- case KeyEvent.KEYCODE_FEATURED_APP_4:
- case KeyEvent.KEYCODE_DEMO_APP_1:
- case KeyEvent.KEYCODE_DEMO_APP_2:
- case KeyEvent.KEYCODE_DEMO_APP_3:
- case KeyEvent.KEYCODE_DEMO_APP_4:
- Slog.wtf(TAG, "KEYCODE_APP_X should be handled in interceptKeyBeforeQueueing");
- return true;
case KeyEvent.KEYCODE_BRIGHTNESS_UP:
case KeyEvent.KEYCODE_BRIGHTNESS_DOWN:
if (down) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index f5757dc7a4de..e81b440f6d6d 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -38,6 +38,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.Intent.ACTION_VIEW;
import static android.content.pm.PackageManager.NOTIFY_PACKAGE_USE_ACTIVITY;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -121,6 +122,7 @@ import android.content.res.Configuration;
import android.graphics.Rect;
import android.hardware.SensorPrivacyManager;
import android.hardware.SensorPrivacyManagerInternal;
+import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -142,6 +144,7 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.Display;
+import android.webkit.URLUtil;
import android.window.ActivityWindowInfo;
import com.android.internal.R;
@@ -158,6 +161,7 @@ import com.android.server.am.UserState;
import com.android.server.pm.SaferIntentUtils;
import com.android.server.utils.Slogf;
import com.android.server.wm.ActivityMetricsLogger.LaunchingState;
+import com.android.window.flags.Flags;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -2900,6 +2904,9 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
@Override
public void accept(ActivityRecord r) {
+ if (Flags.enableDesktopWindowingAppToWeb() && mInfo.capturedLink == null) {
+ setCapturedLink(r);
+ }
if (r.mLaunchCookie != null) {
mInfo.addLaunchCookie(r.mLaunchCookie);
}
@@ -2912,6 +2919,16 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
mTopRunning = r;
}
}
+
+ private void setCapturedLink(ActivityRecord r) {
+ final Uri uri = r.intent.getData();
+ if (uri == null || !ACTION_VIEW.equals(r.intent.getAction())
+ || !URLUtil.isNetworkUrl(uri.toString())) {
+ return;
+ }
+ mInfo.capturedLink = uri;
+ mInfo.capturedLinkTimestamp = r.lastLaunchTime;
+ }
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9a5f84cf7449..803312214fc3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -9299,7 +9299,8 @@ public class WindowManagerService extends IWindowManager.Stub
isTrustedOverlay);
final int sanitizedLpFlags =
- (flags & (FLAG_NOT_TOUCHABLE | FLAG_SLIPPERY | LayoutParams.FLAG_NOT_FOCUSABLE))
+ (flags & (FLAG_NOT_TOUCHABLE | FLAG_SLIPPERY | LayoutParams.FLAG_NOT_FOCUSABLE
+ | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH))
| LayoutParams.FLAG_NOT_TOUCH_MODAL;
h.layoutParamsType = type;
h.layoutParamsFlags = sanitizedLpFlags;
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index f83144f176d3..7c8f11908a36 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -49,6 +49,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
+import android.util.ArraySet;
import android.view.InputChannel;
import android.view.inputmethod.EditorInfo;
import android.window.ImeOnBackInvokedDispatcher;
@@ -134,6 +135,14 @@ public class InputMethodManagerServiceTestBase {
protected boolean mIsLargeScreen;
private InputManagerGlobal.TestSession mInputManagerGlobalSession;
+ private final ArraySet<Class<?>> mRegisteredLocalServices = new ArraySet<>();
+
+ protected <T> void addLocalServiceMock(Class<T> type, T service) {
+ mRegisteredLocalServices.add(type);
+ LocalServices.removeServiceForTest(type);
+ LocalServices.addService(type, service);
+ }
+
@BeforeClass
public static void setupClass() {
// Make sure DeviceConfig's lazy-initialized ContentProvider gets
@@ -148,7 +157,6 @@ public class InputMethodManagerServiceTestBase {
mockitoSession()
.initMocks(this)
.strictness(Strictness.LENIENT)
- .spyStatic(LocalServices.class)
.mockStatic(ServiceManager.class)
.mockStatic(SystemServerInitThreadPool.class)
.startMocking();
@@ -163,18 +171,13 @@ public class InputMethodManagerServiceTestBase {
mEditorInfo.packageName = TEST_EDITOR_PKG_NAME;
// Injecting and mocking local services.
- doReturn(mMockWindowManagerInternal)
- .when(() -> LocalServices.getService(WindowManagerInternal.class));
- doReturn(mMockActivityManagerInternal)
- .when(() -> LocalServices.getService(ActivityManagerInternal.class));
- doReturn(mMockPackageManagerInternal)
- .when(() -> LocalServices.getService(PackageManagerInternal.class));
- doReturn(mMockInputManagerInternal)
- .when(() -> LocalServices.getService(InputManagerInternal.class));
- doReturn(mMockUserManagerInternal)
- .when(() -> LocalServices.getService(UserManagerInternal.class));
- doReturn(mMockImeTargetVisibilityPolicy)
- .when(() -> LocalServices.getService(ImeTargetVisibilityPolicy.class));
+ addLocalServiceMock(WindowManagerInternal.class, mMockWindowManagerInternal);
+ addLocalServiceMock(ActivityManagerInternal.class, mMockActivityManagerInternal);
+ addLocalServiceMock(PackageManagerInternal.class, mMockPackageManagerInternal);
+ addLocalServiceMock(InputManagerInternal.class, mMockInputManagerInternal);
+ addLocalServiceMock(UserManagerInternal.class, mMockUserManagerInternal);
+ addLocalServiceMock(ImeTargetVisibilityPolicy.class, mMockImeTargetVisibilityPolicy);
+
doReturn(mMockIInputMethodManager)
.when(() -> ServiceManager.getServiceOrThrow(Context.INPUT_METHOD_SERVICE));
doReturn(mMockIPlatformCompat)
@@ -289,7 +292,7 @@ public class InputMethodManagerServiceTestBase {
if (mInputManagerGlobalSession != null) {
mInputManagerGlobalSession.close();
}
- LocalServices.removeServiceForTest(InputMethodManagerInternal.class);
+ mRegisteredLocalServices.forEach(LocalServices::removeServiceForTest);
}
protected void verifyShowSoftInput(boolean setVisible, boolean showSoftInput)
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java
index 9f46d0ba7df6..ffc4df8f2069 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java
@@ -50,7 +50,6 @@ import com.android.internal.inputmethod.InputBindResult;
import com.android.internal.inputmethod.InputMethodDebug;
import com.android.internal.inputmethod.StartInputFlags;
import com.android.internal.inputmethod.StartInputReason;
-import com.android.server.LocalServices;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.wm.WindowManagerInternal;
@@ -270,8 +269,7 @@ public class InputMethodManagerServiceWindowGainedFocusTest
@Test
public void startInputOrWindowGainedFocus_localeHintsOverride() throws RemoteException {
- doReturn(mMockVdmInternal).when(
- () -> LocalServices.getService(VirtualDeviceManagerInternal.class));
+ addLocalServiceMock(VirtualDeviceManagerInternal.class, mMockVdmInternal);
LocaleList overrideLocale = LocaleList.forLanguageTags("zh-CN");
doReturn(overrideLocale).when(mMockVdmInternal).getPreferredLocaleListForUid(anyInt());
mockHasImeFocusAndRestoreImeVisibility(false /* restoreImeVisibility */);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt b/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
new file mode 100644
index 000000000000..dc8d2390ef2d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
@@ -0,0 +1,272 @@
+/*
+ * 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.
+ */
+
+package com.android.server.accessibility
+
+import android.companion.virtual.VirtualDeviceManager
+import android.companion.virtual.VirtualDeviceParams
+import android.content.Context
+import android.hardware.input.IInputManager
+import android.hardware.input.InputManager
+import android.hardware.input.InputManagerGlobal
+import android.hardware.input.VirtualMouse
+import android.hardware.input.VirtualMouseButtonEvent
+import android.hardware.input.VirtualMouseConfig
+import android.hardware.input.VirtualMouseRelativeEvent
+import android.hardware.input.VirtualMouseScrollEvent
+import android.os.RemoteException
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import android.view.KeyEvent
+import androidx.test.core.app.ApplicationProvider
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal
+import com.android.server.LocalServices
+import com.android.server.testutils.OffsettableClock
+import junit.framework.Assert.assertEquals
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+import java.util.concurrent.TimeUnit
+import java.util.LinkedList
+import java.util.Queue
+import android.util.ArraySet
+
+/**
+ * Tests for {@link MouseKeysInterceptor}
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:MouseKeysInterceptorTest
+ */
+@Presubmit
+class MouseKeysInterceptorTest {
+ companion object {
+ const val DISPLAY_ID = 1
+ const val DEVICE_ID = 123
+ // This delay is required for key events to be sent and handled correctly.
+ // The handler only performs a move/scroll event if it receives the key event
+ // at INTERVAL_MILLIS (which happens in practice). Hence, we need this delay in the tests.
+ const val KEYBOARD_POST_EVENT_DELAY_MILLIS = 20L
+ }
+
+ private lateinit var mouseKeysInterceptor: MouseKeysInterceptor
+ private val clock = OffsettableClock()
+ private val testLooper = TestLooper { clock.now() }
+ private val nextInterceptor = TrackingInterceptor()
+
+ @Mock
+ private lateinit var mockAms: AccessibilityManagerService
+
+ @Mock
+ private lateinit var iInputManager: IInputManager
+ private lateinit var testSession: InputManagerGlobal.TestSession
+ private lateinit var mockInputManager: InputManager
+
+ @Mock
+ private lateinit var mockVirtualDeviceManagerInternal: VirtualDeviceManagerInternal
+ @Mock
+ private lateinit var mockVirtualDevice: VirtualDeviceManager.VirtualDevice
+ @Mock
+ private lateinit var mockVirtualMouse: VirtualMouse
+
+ @Mock
+ private lateinit var mockTraceManager: AccessibilityTraceManager
+
+ @Before
+ @Throws(RemoteException::class)
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ val context = ApplicationProvider.getApplicationContext<Context>()
+ testSession = InputManagerGlobal.createTestSession(iInputManager)
+ mockInputManager = InputManager(context)
+
+ Mockito.`when`(mockVirtualDeviceManagerInternal.getDeviceIdsForUid(Mockito.anyInt()))
+ .thenReturn(ArraySet(setOf(DEVICE_ID)))
+ LocalServices.removeServiceForTest(VirtualDeviceManagerInternal::class.java)
+ LocalServices.addService<VirtualDeviceManagerInternal>(
+ VirtualDeviceManagerInternal::class.java, mockVirtualDeviceManagerInternal
+ )
+
+ Mockito.`when`(mockVirtualDeviceManagerInternal.createVirtualDevice(
+ Mockito.any(VirtualDeviceParams::class.java)
+ )).thenReturn(mockVirtualDevice)
+ Mockito.`when`(mockVirtualDevice.createVirtualMouse(
+ Mockito.any(VirtualMouseConfig::class.java)
+ )).thenReturn(mockVirtualMouse)
+
+ Mockito.`when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID))
+ Mockito.`when`(mockAms.traceManager).thenReturn(mockTraceManager)
+
+ mouseKeysInterceptor = MouseKeysInterceptor(mockAms, mockInputManager,
+ testLooper.looper, DISPLAY_ID)
+ // VirtualMouse is created on a separate thread.
+ // Wait for VirtualMouse to be created before running tests
+ TimeUnit.MILLISECONDS.sleep(20L)
+ mouseKeysInterceptor.next = nextInterceptor
+ }
+
+ @After
+ fun tearDown() {
+ testLooper.dispatchAll()
+ if (this::testSession.isInitialized) {
+ testSession.close()
+ }
+ }
+
+ @Test
+ fun whenNonMouseKeyEventArrives_eventIsPassedToNextInterceptor() {
+ val downTime = clock.now()
+ val downEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_Q, 0, 0, DEVICE_ID, 0)
+ mouseKeysInterceptor.onKeyEvent(downEvent, 0)
+ testLooper.dispatchAll()
+
+ assertEquals(1, nextInterceptor.events.size)
+ assertEquals(downEvent, nextInterceptor.events.poll())
+ }
+
+ @Test
+ fun whenMouseDirectionalKeyIsPressed_relativeEventIsSent() {
+ // There should be some delay between the downTime of the key event and calling onKeyEvent
+ val downTime = clock.now() - KEYBOARD_POST_EVENT_DELAY_MILLIS
+ val keyCode = MouseKeysInterceptor.MouseKeyEvent.DOWN_MOVE.getKeyCodeValue()
+ val downEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+ keyCode, 0, 0, DEVICE_ID, 0)
+
+ mouseKeysInterceptor.onKeyEvent(downEvent, 0)
+ testLooper.dispatchAll()
+
+ // Verify the sendRelativeEvent method is called once and capture the arguments
+ verifyRelativeEvents(arrayOf<Float>(0f), arrayOf<Float>(1.8f))
+ }
+
+ @Test
+ fun whenClickKeyIsPressed_buttonEventIsSent() {
+ // There should be some delay between the downTime of the key event and calling onKeyEvent
+ val downTime = clock.now() - KEYBOARD_POST_EVENT_DELAY_MILLIS
+ val keyCode = MouseKeysInterceptor.MouseKeyEvent.LEFT_CLICK.getKeyCodeValue()
+ val downEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+ keyCode, 0, 0, DEVICE_ID, 0)
+ mouseKeysInterceptor.onKeyEvent(downEvent, 0)
+ testLooper.dispatchAll()
+
+ val actions = arrayOf<Int>(
+ VirtualMouseButtonEvent.ACTION_BUTTON_PRESS,
+ VirtualMouseButtonEvent.ACTION_BUTTON_RELEASE)
+ val buttons = arrayOf<Int>(
+ VirtualMouseButtonEvent.BUTTON_PRIMARY,
+ VirtualMouseButtonEvent.BUTTON_PRIMARY)
+ // Verify the sendButtonEvent method is called twice and capture the arguments
+ verifyButtonEvents(actions, buttons)
+ }
+
+ @Test
+ fun whenHoldKeyIsPressed_buttonEventIsSent() {
+ val downTime = clock.now() - KEYBOARD_POST_EVENT_DELAY_MILLIS
+ val keyCode = MouseKeysInterceptor.MouseKeyEvent.HOLD.getKeyCodeValue()
+ val downEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+ keyCode, 0, 0, DEVICE_ID, 0)
+ mouseKeysInterceptor.onKeyEvent(downEvent, 0)
+ testLooper.dispatchAll()
+
+ // Verify the sendButtonEvent method is called once and capture the arguments
+ verifyButtonEvents(
+ arrayOf<Int>( VirtualMouseButtonEvent.ACTION_BUTTON_PRESS),
+ arrayOf<Int>( VirtualMouseButtonEvent.BUTTON_PRIMARY)
+ )
+ }
+
+ @Test
+ fun whenReleaseKeyIsPressed_buttonEventIsSent() {
+ val downTime = clock.now() - KEYBOARD_POST_EVENT_DELAY_MILLIS
+ val keyCode = MouseKeysInterceptor.MouseKeyEvent.RELEASE.getKeyCodeValue()
+ val downEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+ keyCode, 0, 0, DEVICE_ID, 0)
+ mouseKeysInterceptor.onKeyEvent(downEvent, 0)
+ testLooper.dispatchAll()
+
+ // Verify the sendButtonEvent method is called once and capture the arguments
+ verifyButtonEvents(
+ arrayOf<Int>( VirtualMouseButtonEvent.ACTION_BUTTON_RELEASE),
+ arrayOf<Int>( VirtualMouseButtonEvent.BUTTON_PRIMARY)
+ )
+ }
+
+ @Test
+ fun whenScrollUpKeyIsPressed_scrollEventIsSent() {
+ // There should be some delay between the downTime of the key event and calling onKeyEvent
+ val downTime = clock.now() - KEYBOARD_POST_EVENT_DELAY_MILLIS
+ val keyCode = MouseKeysInterceptor.MouseKeyEvent.SCROLL_UP.getKeyCodeValue()
+ val downEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+ keyCode, 0, 0, DEVICE_ID, 0)
+
+ mouseKeysInterceptor.onKeyEvent(downEvent, 0)
+ testLooper.dispatchAll()
+
+ // Verify the sendScrollEvent method is called once and capture the arguments
+ verifyScrollEvents(arrayOf<Float>(0f), arrayOf<Float>(1.0f))
+ }
+
+ private fun verifyRelativeEvents(expectedX: Array<Float>, expectedY: Array<Float>) {
+ assertEquals(expectedX.size, expectedY.size)
+ val captor = ArgumentCaptor.forClass(VirtualMouseRelativeEvent::class.java)
+ Mockito.verify(mockVirtualMouse, Mockito.times(expectedX.size))
+ .sendRelativeEvent(captor.capture())
+
+ for (i in expectedX.indices) {
+ val captorEvent = captor.allValues[i]
+ assertEquals(expectedX[i], captorEvent.relativeX)
+ assertEquals(expectedY[i], captorEvent.relativeY)
+ }
+ }
+
+ private fun verifyButtonEvents(actions: Array<Int>, buttons: Array<Int>) {
+ assertEquals(actions.size, buttons.size)
+ val captor = ArgumentCaptor.forClass(VirtualMouseButtonEvent::class.java)
+ Mockito.verify(mockVirtualMouse, Mockito.times(actions.size))
+ .sendButtonEvent(captor.capture())
+
+ for (i in actions.indices) {
+ val captorEvent = captor.allValues[i]
+ assertEquals(actions[i], captorEvent.action)
+ assertEquals(buttons[i], captorEvent.buttonCode)
+ }
+ }
+
+ private fun verifyScrollEvents(xAxisMovements: Array<Float>, yAxisMovements: Array<Float>) {
+ assertEquals(xAxisMovements.size, yAxisMovements.size)
+ val captor = ArgumentCaptor.forClass(VirtualMouseScrollEvent::class.java)
+ Mockito.verify(mockVirtualMouse, Mockito.times(xAxisMovements.size))
+ .sendScrollEvent(captor.capture())
+
+ for (i in xAxisMovements.indices) {
+ val captorEvent = captor.allValues[i]
+ assertEquals(xAxisMovements[i], captorEvent.xAxisMovement)
+ assertEquals(yAxisMovements[i], captorEvent.yAxisMovement)
+ }
+ }
+
+ private class TrackingInterceptor : BaseEventStreamTransformation() {
+ val events: Queue<KeyEvent> = LinkedList()
+
+ override fun onKeyEvent(event: KeyEvent, policyFlags: Int) {
+ events.add(event)
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
index f6da41166403..ffc78110d496 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
@@ -28,7 +28,6 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import static org.testng.Assert.assertThrows;
import android.hardware.biometrics.BiometricConstants;
import android.os.Handler;
@@ -88,16 +87,14 @@ public class BiometricSchedulerOperationTest {
private Handler mHandler;
private BiometricSchedulerOperation mInterruptableOperation;
private BiometricSchedulerOperation mNonInterruptableOperation;
- private boolean mIsDebuggable;
@Before
public void setUp() {
mHandler = new Handler(TestableLooper.get(this).getLooper());
- mIsDebuggable = false;
mInterruptableOperation = new BiometricSchedulerOperation(mInterruptableClientMonitor,
- mClientCallback, () -> mIsDebuggable);
+ mClientCallback);
mNonInterruptableOperation = new BiometricSchedulerOperation(mNonInterruptableClientMonitor,
- mClientCallback, () -> mIsDebuggable);
+ mClientCallback);
when(mInterruptableClientMonitor.isInterruptable()).thenReturn(true);
when(mNonInterruptableClientMonitor.isInterruptable()).thenReturn(false);
@@ -143,32 +140,13 @@ public class BiometricSchedulerOperationTest {
}
@Test
- public void testSecondStartWithCookieCrashesWhenDebuggable() {
+ public void testSecondStartWithCookieFails() {
final int cookie = 5;
- mIsDebuggable = true;
when(mInterruptableClientMonitor.getCookie()).thenReturn(cookie);
when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
- final boolean started = mInterruptableOperation.startWithCookie(mOnStartCallback, cookie);
- assertThat(started).isTrue();
-
- assertThrows(IllegalStateException.class,
- () -> mInterruptableOperation.startWithCookie(mOnStartCallback, cookie));
- }
-
- @Test
- public void testSecondStartWithCookieFailsNicelyWhenNotDebuggable() {
- final int cookie = 5;
- mIsDebuggable = false;
- when(mInterruptableClientMonitor.getCookie()).thenReturn(cookie);
- when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
-
- final boolean started = mInterruptableOperation.startWithCookie(mOnStartCallback, cookie);
- assertThat(started).isTrue();
-
- final boolean startedAgain = mInterruptableOperation.startWithCookie(mOnStartCallback,
- cookie);
- assertThat(startedAgain).isFalse();
+ assertThat(mInterruptableOperation.startWithCookie(mOnStartCallback, cookie)).isTrue();
+ assertThat(mInterruptableOperation.startWithCookie(mOnStartCallback, cookie)).isFalse();
}
@Test
@@ -217,56 +195,23 @@ public class BiometricSchedulerOperationTest {
}
@Test
- public void secondStartCrashesWhenDebuggable() {
- mIsDebuggable = true;
+ public void secondStartFails() {
when(mInterruptableClientMonitor.getCookie()).thenReturn(0);
when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
- final boolean started = mInterruptableOperation.start(mOnStartCallback);
- assertThat(started).isTrue();
-
- assertThrows(IllegalStateException.class, () -> mInterruptableOperation.start(
- mOnStartCallback));
- }
-
- @Test
- public void secondStartFailsNicelyWhenNotDebuggable() {
- mIsDebuggable = false;
- when(mInterruptableClientMonitor.getCookie()).thenReturn(0);
- when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
-
- final boolean started = mInterruptableOperation.start(mOnStartCallback);
- assertThat(started).isTrue();
-
- final boolean startedAgain = mInterruptableOperation.start(mOnStartCallback);
- assertThat(startedAgain).isFalse();
+ assertThat(mInterruptableOperation.start(mOnStartCallback)).isTrue();
+ assertThat(mInterruptableOperation.start(mOnStartCallback)).isFalse();
}
@Test
public void doesNotStartWithCookie() {
- // This class only throws exceptions when debuggable.
- mIsDebuggable = true;
when(mInterruptableClientMonitor.getCookie()).thenReturn(9);
- assertThrows(IllegalStateException.class,
- () -> mInterruptableOperation.start(mock(ClientMonitorCallback.class)));
- }
- @Test
- public void cannotRestart() {
- // This class only throws exceptions when debuggable.
- mIsDebuggable = true;
- when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
-
- mInterruptableOperation.start(mOnStartCallback);
-
- assertThrows(IllegalStateException.class,
- () -> mInterruptableOperation.start(mock(ClientMonitorCallback.class)));
+ assertThat(mInterruptableOperation.start(mock(ClientMonitorCallback.class))).isFalse();
}
@Test
- public void abortsNotRunning() {
- // This class only throws exceptions when debuggable.
- mIsDebuggable = true;
+ public void abortSuccessfulIfOperationNotRunning() {
when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
mInterruptableOperation.abort();
@@ -274,28 +219,17 @@ public class BiometricSchedulerOperationTest {
assertThat(mInterruptableOperation.isFinished()).isTrue();
verify(mInterruptableClientMonitor).unableToStart();
verify(mInterruptableClientMonitor).destroy();
- assertThrows(IllegalStateException.class,
- () -> mInterruptableOperation.start(mock(ClientMonitorCallback.class)));
+ assertThat(mInterruptableOperation.start(mock(ClientMonitorCallback.class))).isFalse();
}
@Test
- public void abortCrashesWhenDebuggableIfOperationIsRunning() {
- mIsDebuggable = true;
+ public void abortFailsIfOperationIsRunning() {
when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
mInterruptableOperation.start(mOnStartCallback);
-
- assertThrows(IllegalStateException.class, () -> mInterruptableOperation.abort());
- }
-
- @Test
- public void abortFailsNicelyWhenNotDebuggableIfOperationIsRunning() {
- mIsDebuggable = false;
- when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
-
- mInterruptableOperation.start(mOnStartCallback);
-
mInterruptableOperation.abort();
+
+ assertThat(mInterruptableOperation.isFinished()).isFalse();
}
@Test
@@ -344,21 +278,7 @@ public class BiometricSchedulerOperationTest {
}
@Test
- public void cancelCrashesWhenDebuggableIfOperationIsFinished() {
- mIsDebuggable = true;
- when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
-
- mInterruptableOperation.abort();
- assertThat(mInterruptableOperation.isFinished()).isTrue();
-
- final ClientMonitorCallback cancelCb = mock(ClientMonitorCallback.class);
- assertThrows(IllegalStateException.class, () -> mInterruptableOperation.cancel(mHandler,
- cancelCb));
- }
-
- @Test
- public void cancelFailsNicelyWhenNotDebuggableIfOperationIsFinished() {
- mIsDebuggable = false;
+ public void cancelFailsIfOperationIsFinished() {
when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
mInterruptableOperation.abort();
@@ -366,6 +286,9 @@ public class BiometricSchedulerOperationTest {
final ClientMonitorCallback cancelCb = mock(ClientMonitorCallback.class);
mInterruptableOperation.cancel(mHandler, cancelCb);
+
+ verify(mInterruptableClientMonitor, never()).cancel();
+ verify(mInterruptableClientMonitor, never()).cancelWithoutStarting(any());
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 5d306e152ad7..c1f5a01a8c47 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -89,7 +89,6 @@ import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
-import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
import static android.service.notification.Adjustment.KEY_CONTEXTUAL_ACTIONS;
import static android.service.notification.Adjustment.KEY_IMPORTANCE;
@@ -838,13 +837,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// Pretend the shortcut exists
List<ShortcutInfo> shortcutInfos = new ArrayList<>();
- ShortcutInfo info = mock(ShortcutInfo.class);
- when(info.getPackage()).thenReturn(mPkg);
- when(info.getId()).thenReturn(VALID_CONVO_SHORTCUT_ID);
- when(info.getUserId()).thenReturn(USER_SYSTEM);
- when(info.isLongLived()).thenReturn(true);
- when(info.isEnabled()).thenReturn(true);
- shortcutInfos.add(info);
+ shortcutInfos.add(createMockConvoShortcut());
when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcutInfos);
when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(),
anyString(), anyInt(), any())).thenReturn(true);
@@ -11109,8 +11102,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
- ArgumentCaptor<LauncherApps.Callback> launcherAppsCallback =
- ArgumentCaptor.forClass(LauncherApps.Callback.class);
+ ArgumentCaptor<LauncherApps.ShortcutChangeCallback> shortcutChangeCallback =
+ ArgumentCaptor.forClass(LauncherApps.ShortcutChangeCallback.class);
// Messaging notification with shortcut info
Notification.BubbleMetadata metadata =
@@ -11131,7 +11124,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// Verify:
// Make sure we register the callback for shortcut changes
- verify(mLauncherApps, times(1)).registerCallback(launcherAppsCallback.capture(), any());
+ verify(mShortcutServiceInternal, times(1)).addShortcutChangeCallback(
+ shortcutChangeCallback.capture());
// yes allowed, yes messaging w/shortcut, yes bubble
Notification notif = mService.getNotificationRecord(nr.getSbn().getKey()).getNotification();
@@ -11144,14 +11138,17 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// Test: Remove the shortcut
when(mLauncherApps.getShortcuts(any(), any())).thenReturn(null);
- launcherAppsCallback.getValue().onShortcutsChanged(mPkg, emptyList(),
+ ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<>();
+ removedShortcuts.add(createMockConvoShortcut());
+ shortcutChangeCallback.getValue().onShortcutsRemoved(mPkg, removedShortcuts,
UserHandle.getUserHandleForUid(mUid));
waitForIdle();
// Verify:
// Make sure callback is unregistered
- verify(mLauncherApps, times(1)).unregisterCallback(launcherAppsCallback.getValue());
+ verify(mShortcutServiceInternal, times(1)).removeShortcutChangeCallback(
+ shortcutChangeCallback.getValue());
// We're no longer a bubble
NotificationRecord notif2 = mService.getNotificationRecord(
@@ -11169,8 +11166,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
- ArgumentCaptor<LauncherApps.Callback> launcherAppsCallback =
- ArgumentCaptor.forClass(LauncherApps.Callback.class);
+ ArgumentCaptor<LauncherApps.ShortcutChangeCallback> shortcutChangeCallback =
+ ArgumentCaptor.forClass(LauncherApps.ShortcutChangeCallback.class);
// Messaging notification with shortcut info
Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder(
@@ -11204,7 +11201,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// Verify:
// Make sure we register the callback for shortcut changes
- verify(mLauncherApps, times(1)).registerCallback(launcherAppsCallback.capture(), any());
+ verify(mShortcutServiceInternal, times(1)).addShortcutChangeCallback(
+ shortcutChangeCallback.capture());
// yes allowed, yes messaging w/shortcut, yes bubble
Notification notif = mService.getNotificationRecord(nr.getSbn().getKey()).getNotification();
@@ -11223,7 +11221,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// Verify:
// Make sure callback is unregistered
- verify(mLauncherApps, times(1)).unregisterCallback(launcherAppsCallback.getValue());
+ verify(mShortcutServiceInternal, times(1)).removeShortcutChangeCallback(
+ shortcutChangeCallback.getValue());
}
@Test
@@ -16263,4 +16262,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
assertThat(r.getChannel().getId()).isEqualTo(NEWS_ID);
}
+
+ private ShortcutInfo createMockConvoShortcut() {
+ ShortcutInfo info = mock(ShortcutInfo.class);
+ when(info.getPackage()).thenReturn(mPkg);
+ when(info.getId()).thenReturn(VALID_CONVO_SHORTCUT_ID);
+ when(info.getUserId()).thenReturn(USER_SYSTEM);
+ when(info.isLongLived()).thenReturn(true);
+ when(info.isEnabled()).thenReturn(true);
+ return info;
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java
index a4fb16dc1adc..f008cb671e78 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java
@@ -22,17 +22,22 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.PendingIntent;
import android.app.Person;
import android.content.pm.LauncherApps;
import android.content.pm.LauncherApps.ShortcutQuery;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutQueryWrapper;
import android.content.pm.ShortcutServiceInternal;
+import android.graphics.drawable.Icon;
import android.os.UserHandle;
import android.os.UserManager;
import android.service.notification.StatusBarNotification;
@@ -50,11 +55,9 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
-import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
@SmallTest
@@ -64,7 +67,6 @@ public class ShortcutHelperTest extends UiServiceTestCase {
private static final String SHORTCUT_ID = "shortcut";
private static final String PKG = "pkg";
- private static final String KEY = "key";
private static final Person PERSON = mock(Person.class);
@Mock
@@ -75,19 +77,11 @@ public class ShortcutHelperTest extends UiServiceTestCase {
UserManager mUserManager;
@Mock
ShortcutServiceInternal mShortcutServiceInternal;
- @Mock
- NotificationRecord mNr;
- @Mock
- Notification mNotif;
- @Mock
- StatusBarNotification mSbn;
- @Mock
- Notification.BubbleMetadata mBubbleMetadata;
- @Mock
- ShortcutInfo mShortcutInfo;
@Captor private ArgumentCaptor<ShortcutQuery> mShortcutQueryCaptor;
+ NotificationRecord mNr;
+
ShortcutHelper mShortcutHelper;
@Before
@@ -96,137 +90,186 @@ public class ShortcutHelperTest extends UiServiceTestCase {
mShortcutHelper = new ShortcutHelper(
mLauncherApps, mShortcutListener, mShortcutServiceInternal, mUserManager);
- when(mSbn.getPackageName()).thenReturn(PKG);
- when(mShortcutInfo.getId()).thenReturn(SHORTCUT_ID);
- when(mNotif.getBubbleMetadata()).thenReturn(mBubbleMetadata);
- when(mBubbleMetadata.getShortcutId()).thenReturn(SHORTCUT_ID);
when(mUserManager.isUserUnlocked(any(UserHandle.class))).thenReturn(true);
- setUpMockNotificationRecord(mNr, KEY);
+ mNr = setUpNotificationRecord(SHORTCUT_ID, PKG, UserHandle.of(UserHandle.USER_SYSTEM));
}
- private void setUpMockNotificationRecord(NotificationRecord mockRecord, String key) {
- when(mockRecord.getKey()).thenReturn(key);
- when(mockRecord.getSbn()).thenReturn(mSbn);
- when(mockRecord.getNotification()).thenReturn(mNotif);
- when(mockRecord.getShortcutInfo()).thenReturn(mShortcutInfo);
+ private NotificationRecord setUpNotificationRecord(String shortcutId,
+ String pkg,
+ UserHandle user) {
+ ShortcutInfo shortcutInfo = mock(ShortcutInfo.class);
+ when(shortcutInfo.getId()).thenReturn(shortcutId);
+ when(shortcutInfo.getUserHandle()).thenReturn(user);
+ when(shortcutInfo.isLongLived()).thenReturn(true);
+
+ Notification notification = new Notification.Builder(getContext())
+ .setContentTitle("title")
+ .setShortcutId(shortcutId)
+ .setBubbleMetadata(new Notification.BubbleMetadata.Builder(shortcutId).build())
+ .build();
+
+ StatusBarNotification sbn = new StatusBarNotification(pkg, pkg, 0, null,
+ 1000, 2000, notification, user, null, System.currentTimeMillis());
+ NotificationRecord record = new NotificationRecord(mContext, sbn,
+ mock(NotificationChannel.class));
+ record.setShortcutInfo(shortcutInfo);
+ return record;
}
- private LauncherApps.Callback addShortcutBubbleAndVerifyListener() {
- mShortcutHelper.maybeListenForShortcutChangesForBubbles(mNr,
- false /* removed */,
- null /* handler */);
+ private LauncherApps.ShortcutChangeCallback addShortcutBubbleAndVerifyListener(
+ NotificationRecord record) {
+ mShortcutHelper.maybeListenForShortcutChangesForBubbles(record, false /* removed */);
- ArgumentCaptor<LauncherApps.Callback> launcherAppsCallback =
- ArgumentCaptor.forClass(LauncherApps.Callback.class);
+ ArgumentCaptor<LauncherApps.ShortcutChangeCallback> launcherAppsCallback =
+ ArgumentCaptor.forClass(LauncherApps.ShortcutChangeCallback.class);
- verify(mLauncherApps, times(1)).registerCallback(
- launcherAppsCallback.capture(), any());
+ verify(mShortcutServiceInternal, times(1)).addShortcutChangeCallback(
+ launcherAppsCallback.capture());
return launcherAppsCallback.getValue();
}
@Test
public void testBubbleAdded_listenedAdded() {
- addShortcutBubbleAndVerifyListener();
+ addShortcutBubbleAndVerifyListener(mNr);
}
@Test
+ public void testListenerNotifiedOnShortcutRemoved() {
+ LauncherApps.ShortcutChangeCallback callback = addShortcutBubbleAndVerifyListener(mNr);
+
+ List<ShortcutInfo> removedShortcuts = new ArrayList<>();
+ removedShortcuts.add(mNr.getShortcutInfo());
+
+ callback.onShortcutsRemoved(PKG, removedShortcuts, mNr.getUser());
+ verify(mShortcutListener).onShortcutRemoved(mNr.getKey());
+ }
+
+ @Test
+ public void testListenerNotNotified_notMatchingPackage() {
+ LauncherApps.ShortcutChangeCallback callback = addShortcutBubbleAndVerifyListener(mNr);
+
+ List<ShortcutInfo> removedShortcuts = new ArrayList<>();
+ removedShortcuts.add(mNr.getShortcutInfo());
+
+ callback.onShortcutsRemoved("differentPackage", removedShortcuts, mNr.getUser());
+ verify(mShortcutListener, never()).onShortcutRemoved(anyString());
+ }
+
+ @Test
+ public void testListenerNotNotified_notMatchingUser() {
+ LauncherApps.ShortcutChangeCallback callback = addShortcutBubbleAndVerifyListener(mNr);
+
+ List<ShortcutInfo> removedShortcuts = new ArrayList<>();
+ removedShortcuts.add(mNr.getShortcutInfo());
+
+ callback.onShortcutsRemoved(PKG, removedShortcuts, UserHandle.of(10));
+ verify(mShortcutListener, never()).onShortcutRemoved(anyString());
+ }
+
+ @Test
+ public void testListenerNotifiedDifferentUser() {
+ LauncherApps.ShortcutChangeCallback callback = addShortcutBubbleAndVerifyListener(mNr);
+ NotificationRecord diffUserRecord = setUpNotificationRecord(SHORTCUT_ID, PKG,
+ UserHandle.of(10));
+ mShortcutHelper.maybeListenForShortcutChangesForBubbles(diffUserRecord,
+ false /* removed */);
+
+ List<ShortcutInfo> removedShortcuts = new ArrayList<>();
+ removedShortcuts.add(mNr.getShortcutInfo());
+
+ callback.onShortcutsRemoved(PKG, removedShortcuts, mNr.getUser());
+ verify(mShortcutListener).onShortcutRemoved(mNr.getKey());
+
+ reset(mShortcutListener);
+ removedShortcuts.clear();
+ removedShortcuts.add(diffUserRecord.getShortcutInfo());
+
+ callback.onShortcutsRemoved(PKG, removedShortcuts, diffUserRecord.getUser());
+ verify(mShortcutListener).onShortcutRemoved(diffUserRecord.getKey());
+ }
+
+
+ @Test
public void testBubbleRemoved_listenerRemoved() {
// First set it up to listen
- addShortcutBubbleAndVerifyListener();
+ addShortcutBubbleAndVerifyListener(mNr);
// Then remove the notif
mShortcutHelper.maybeListenForShortcutChangesForBubbles(mNr,
- true /* removed */,
- null /* handler */);
+ true /* removed */);
- verify(mLauncherApps, times(1)).unregisterCallback(any());
+ verify(mShortcutServiceInternal, times(1)).removeShortcutChangeCallback(any());
}
@Test
public void testBubbleNoLongerHasBubbleMetadata_listenerRemoved() {
// First set it up to listen
- addShortcutBubbleAndVerifyListener();
+ addShortcutBubbleAndVerifyListener(mNr);
// Then make it not a bubble
- when(mNotif.getBubbleMetadata()).thenReturn(null);
+ mNr.getNotification().setBubbleMetadata(null);
mShortcutHelper.maybeListenForShortcutChangesForBubbles(mNr,
- false /* removed */,
- null /* handler */);
+ false /* removed */);
- verify(mLauncherApps, times(1)).unregisterCallback(any());
+ verify(mShortcutServiceInternal, times(1)).removeShortcutChangeCallback(any());
}
@Test
public void testBubbleNoLongerHasShortcutId_listenerRemoved() {
// First set it up to listen
- addShortcutBubbleAndVerifyListener();
+ addShortcutBubbleAndVerifyListener(mNr);
// Clear out shortcutId
- when(mBubbleMetadata.getShortcutId()).thenReturn(null);
+ mNr.getNotification().setBubbleMetadata(new Notification.BubbleMetadata.Builder(
+ mock(PendingIntent.class), mock(Icon.class)).build());
mShortcutHelper.maybeListenForShortcutChangesForBubbles(mNr,
- false /* removed */,
- null /* handler */);
+ false /* removed */);
- verify(mLauncherApps, times(1)).unregisterCallback(any());
+ verify(mShortcutServiceInternal, times(1)).removeShortcutChangeCallback(any());
}
@Test
public void testNotifNoLongerHasShortcut_listenerRemoved() {
// First set it up to listen
- addShortcutBubbleAndVerifyListener();
-
- NotificationRecord validMock1 = Mockito.mock(NotificationRecord.class);
- setUpMockNotificationRecord(validMock1, "KEY1");
-
- NotificationRecord validMock2 = Mockito.mock(NotificationRecord.class);
- setUpMockNotificationRecord(validMock2, "KEY2");
+ addShortcutBubbleAndVerifyListener(mNr);
- NotificationRecord validMock3 = Mockito.mock(NotificationRecord.class);
- setUpMockNotificationRecord(validMock3, "KEY3");
+ NotificationRecord record1 = setUpNotificationRecord(SHORTCUT_ID, PKG,
+ UserHandle.of(UserHandle.USER_SYSTEM));
+ NotificationRecord record2 = setUpNotificationRecord(SHORTCUT_ID, PKG,
+ UserHandle.of(UserHandle.USER_SYSTEM));
+ NotificationRecord record3 = setUpNotificationRecord(SHORTCUT_ID, PKG,
+ UserHandle.of(UserHandle.USER_SYSTEM));
- mShortcutHelper.maybeListenForShortcutChangesForBubbles(validMock1,
- false /* removed */,
- null /* handler */);
+ mShortcutHelper.maybeListenForShortcutChangesForBubbles(record1,
+ false /* removed */);
- mShortcutHelper.maybeListenForShortcutChangesForBubbles(validMock2,
- false /* removed */,
- null /* handler */);
+ mShortcutHelper.maybeListenForShortcutChangesForBubbles(record2,
+ false /* removed */);
- mShortcutHelper.maybeListenForShortcutChangesForBubbles(validMock3,
- false /* removed */,
- null /* handler */);
+ mShortcutHelper.maybeListenForShortcutChangesForBubbles(record3,
+ false /* removed */);
- // Clear out shortcutId of the bubble in the middle, to double check that we don't hit a
+ // Clear out shortcutId of the bubble in the middle, to double-check that we don't hit a
// concurrent modification exception (removing the last bubble would sidestep that check).
- when(validMock2.getShortcutInfo()).thenReturn(null);
- mShortcutHelper.maybeListenForShortcutChangesForBubbles(validMock2,
- false /* removed */,
- null /* handler */);
+ record2.setShortcutInfo(null);
+ mShortcutHelper.maybeListenForShortcutChangesForBubbles(record2,
+ false /* removed */);
- verify(mLauncherApps, times(1)).unregisterCallback(any());
+ verify(mShortcutServiceInternal, times(1)).removeShortcutChangeCallback(any());
}
@Test
public void testOnShortcutsChanged_listenerRemoved() {
// First set it up to listen
- LauncherApps.Callback callback = addShortcutBubbleAndVerifyListener();
+ LauncherApps.ShortcutChangeCallback callback = addShortcutBubbleAndVerifyListener(mNr);
// App shortcuts are removed:
- callback.onShortcutsChanged(PKG, Collections.emptyList(), mock(UserHandle.class));
+ List<ShortcutInfo> removedShortcuts = new ArrayList<>();
+ removedShortcuts.add(mNr.getShortcutInfo());
+ callback.onShortcutsRemoved(PKG, removedShortcuts, mNr.getUser());
- verify(mLauncherApps, times(1)).unregisterCallback(any());
- }
-
- @Test
- public void testListenerNotifiedOnShortcutRemoved() {
- LauncherApps.Callback callback = addShortcutBubbleAndVerifyListener();
-
- List<ShortcutInfo> shortcutInfos = new ArrayList<>();
- when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcutInfos);
-
- callback.onShortcutsChanged(PKG, shortcutInfos, mock(UserHandle.class));
- verify(mShortcutListener).onShortcutRemoved(mNr.getKey());
+ verify(mShortcutServiceInternal, times(1)).removeShortcutChangeCallback(any());
}
@Test
@@ -321,7 +364,6 @@ public class ShortcutHelperTest extends UiServiceTestCase {
.isSameInstanceAs(si);
}
-
@Test
public void testGetValidShortcutInfo_isValidButUserLocked() {
ShortcutInfo si = mock(ShortcutInfo.class);