Merge "[Expressive design] Add BulletPreference" into main
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 569776e..bfddf4f 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -6822,6 +6822,17 @@
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.ModuleProperties> CREATOR;
}
+ @FlaggedApi("android.media.soundtrigger.manager_api") public static final class SoundTrigger.RecognitionConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getAudioCapabilities();
+ method @NonNull public byte[] getData();
+ method @NonNull public java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra> getKeyphrases();
+ method public boolean isAllowMultipleTriggers();
+ method public boolean isCaptureRequested();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.RecognitionConfig> CREATOR;
+ }
+
public static class SoundTrigger.RecognitionEvent {
method @Nullable public android.media.AudioFormat getCaptureFormat();
method public int getCaptureSession();
@@ -7772,10 +7783,16 @@
method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public void deleteModel(java.util.UUID);
method public int getDetectionServiceOperationsTimeout();
method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.media.soundtrigger.SoundTriggerManager.Model getModel(java.util.UUID);
+ method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getModelState(@NonNull java.util.UUID);
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModuleProperties getModuleProperties();
method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getParameter(@NonNull java.util.UUID, int);
+ method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public boolean isRecognitionActive(@NonNull java.util.UUID);
+ method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int loadSoundModel(@NonNull android.hardware.soundtrigger.SoundTrigger.SoundModel);
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModelParamRange queryParameter(@Nullable java.util.UUID, int);
method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int setParameter(@Nullable java.util.UUID, int, int);
+ method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int startRecognition(@NonNull java.util.UUID, @Nullable android.os.Bundle, @NonNull android.content.ComponentName, @NonNull android.hardware.soundtrigger.SoundTrigger.RecognitionConfig);
+ method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int stopRecognition(@NonNull java.util.UUID);
+ method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int unloadSoundModel(@NonNull java.util.UUID);
method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public void updateModel(android.media.soundtrigger.SoundTriggerManager.Model);
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 659222d..cc9e836 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1886,17 +1886,9 @@
ctor public SoundTrigger.ModuleProperties(int, @NonNull String, @NonNull String, @NonNull String, int, @NonNull String, int, int, int, int, boolean, int, boolean, int, boolean, int);
}
- public static final class SoundTrigger.RecognitionConfig implements android.os.Parcelable {
+ @FlaggedApi("android.media.soundtrigger.manager_api") public static final class SoundTrigger.RecognitionConfig implements android.os.Parcelable {
ctor public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[], int);
ctor public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[]);
- method public int describeContents();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.RecognitionConfig> CREATOR;
- field public final boolean allowMultipleTriggers;
- field public final int audioCapabilities;
- field public final boolean captureRequested;
- field @NonNull public final byte[] data;
- field @NonNull public final android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[] keyphrases;
}
public static class SoundTrigger.RecognitionEvent {
@@ -2217,7 +2209,7 @@
public class SoundTriggerInstrumentation.RecognitionSession {
method public void clearRecognitionCallback();
method public int getAudioSession();
- method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig getRecognitionConfig();
+ method @FlaggedApi("android.media.soundtrigger.manager_api") @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig getRecognitionConfig();
method public void setRecognitionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.RecognitionCallback);
method public void triggerAbortRecognition();
method public void triggerRecognitionEvent(@NonNull byte[], @Nullable java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>);
@@ -2228,7 +2220,6 @@
method @NonNull public android.media.soundtrigger.SoundTriggerManager createManagerForModule(@NonNull android.hardware.soundtrigger.SoundTrigger.ModuleProperties);
method @NonNull public android.media.soundtrigger.SoundTriggerManager createManagerForTestModule();
method @NonNull public static java.util.List<android.hardware.soundtrigger.SoundTrigger.ModuleProperties> listModuleProperties();
- method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int loadSoundModel(@NonNull android.hardware.soundtrigger.SoundTrigger.SoundModel);
}
public static class SoundTriggerManager.Model {
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 237780f..8a54b5d 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -258,7 +258,7 @@
@EnforcePermission(allOf={"INTERACT_ACROSS_USERS", "ACCESS_NOTIFICATIONS"})
void unregisterCallNotificationEventListener(String packageName, in UserHandle userHandle, in ICallNotificationEventCallback listener);
- void setCanBePromoted(String pkg, int uid, boolean promote);
+ void setCanBePromoted(String pkg, int uid, boolean promote, boolean fromUser);
boolean appCanBePromoted(String pkg, int uid);
boolean canBePromoted(String pkg);
}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 06f21b8..c7b84ae 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -976,7 +976,7 @@
public void setCanPostPromotedNotifications(@NonNull String pkg, int uid, boolean allowed) {
INotificationManager service = getService();
try {
- service.setCanBePromoted(pkg, uid, allowed);
+ service.setCanBePromoted(pkg, uid, allowed, true);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 108b5f4..b139017 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -31,6 +31,16 @@
}
flag {
+ name: "modes_ui_empty_shade"
+ namespace: "systemui"
+ description: "Shows mode that is currently blocking notifications in the empty shade; dependent on flags modes_api and modes_ui"
+ bug: "366003631"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "modes_ui_test"
namespace: "systemui"
description: "Guards new CTS tests for Modes; dependent on flags modes_api and modes_ui"
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 044178c..031380d 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -20,7 +20,6 @@
import static android.content.ContentProvider.maybeAddUserId;
import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
import static android.security.Flags.FLAG_FRP_ENFORCEMENT;
-import static android.security.Flags.preventIntentRedirect;
import android.Manifest;
import android.accessibilityservice.AccessibilityService;
@@ -7688,17 +7687,9 @@
/** @hide */
public static final int LOCAL_FLAG_FROM_SYSTEM = 1 << 5;
- /**
- * This flag indicates the creator token of this intent has been verified.
- *
- * @hide
- */
- public static final int LOCAL_FLAG_CREATOR_TOKEN_VERIFIED = 1 << 6;
-
/** @hide */
@IntDef(flag = true, prefix = { "EXTENDED_FLAG_" }, value = {
EXTENDED_FLAG_FILTER_MISMATCH,
- EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ExtendedFlags {}
@@ -7712,13 +7703,6 @@
@TestApi
public static final int EXTENDED_FLAG_FILTER_MISMATCH = 1 << 0;
- /**
- * This flag indicates the creator token of this intent is either missing or invalid.
- *
- * @hide
- */
- public static final int EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN = 1 << 1;
-
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// toUri() and parseUri() options.
@@ -7886,7 +7870,6 @@
this.mPackage = o.mPackage;
this.mComponent = o.mComponent;
this.mOriginalIntent = o.mOriginalIntent;
- this.mCreatorTokenInfo = o.mCreatorTokenInfo;
if (o.mCategories != null) {
this.mCategories = new ArraySet<>(o.mCategories);
@@ -12193,60 +12176,6 @@
return (mExtras != null) ? mExtras.describeContents() : 0;
}
- private static class CreatorTokenInfo {
- // Stores a creator token for an intent embedded as an extra intent in a top level intent,
- private IBinder mCreatorToken;
- // Stores all extra keys whose values are intents for a top level intent.
- private ArraySet<String> mExtraIntentKeys;
- }
-
- private @Nullable CreatorTokenInfo mCreatorTokenInfo;
-
- /** @hide */
- public void removeCreatorTokenInfo() {
- mCreatorTokenInfo = null;
- }
-
- /** @hide */
- public @Nullable IBinder getCreatorToken() {
- return mCreatorTokenInfo == null ? null : mCreatorTokenInfo.mCreatorToken;
- }
-
- /** @hide */
- public Set<String> getExtraIntentKeys() {
- return mCreatorTokenInfo == null ? null : mCreatorTokenInfo.mExtraIntentKeys;
- }
-
- /** @hide */
- public void setCreatorToken(@NonNull IBinder creatorToken) {
- if (mCreatorTokenInfo == null) {
- mCreatorTokenInfo = new CreatorTokenInfo();
- }
- mCreatorTokenInfo.mCreatorToken = creatorToken;
- }
-
- /**
- * Collects keys in the extra bundle whose value are intents.
- * @hide
- */
- public void collectExtraIntentKeys() {
- if (!preventIntentRedirect()) return;
-
- if (mExtras != null && !mExtras.isParcelled() && !mExtras.isEmpty()) {
- for (String key : mExtras.keySet()) {
- if (mExtras.get(key) instanceof Intent) {
- if (mCreatorTokenInfo == null) {
- mCreatorTokenInfo = new CreatorTokenInfo();
- }
- if (mCreatorTokenInfo.mExtraIntentKeys == null) {
- mCreatorTokenInfo.mExtraIntentKeys = new ArraySet<>();
- }
- mCreatorTokenInfo.mExtraIntentKeys.add(key);
- }
- }
- }
- }
-
public void writeToParcel(Parcel out, int flags) {
out.writeString8(mAction);
Uri.writeToParcel(out, mData);
@@ -12296,16 +12225,6 @@
} else {
out.writeInt(0);
}
-
- if (preventIntentRedirect()) {
- if (mCreatorTokenInfo == null) {
- out.writeInt(0);
- } else {
- out.writeInt(1);
- out.writeStrongBinder(mCreatorTokenInfo.mCreatorToken);
- out.writeArraySet(mCreatorTokenInfo.mExtraIntentKeys);
- }
- }
}
public static final @android.annotation.NonNull Parcelable.Creator<Intent> CREATOR
@@ -12363,14 +12282,6 @@
if (in.readInt() != 0) {
mOriginalIntent = new Intent(in);
}
-
- if (preventIntentRedirect()) {
- if (in.readInt() != 0) {
- mCreatorTokenInfo = new CreatorTokenInfo();
- mCreatorTokenInfo.mCreatorToken = in.readStrongBinder();
- mCreatorTokenInfo.mExtraIntentKeys = (ArraySet<String>) in.readArraySet(null);
- }
- }
}
/**
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 139ff65..60b409a 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -293,4 +293,12 @@
description: "Feature flag to provide the new methods within launcher apps class to get packages."
bug: "363324203"
is_fixed_read_only: true
+}
+
+flag {
+ name: "remove_cross_user_permission_hack"
+ namespace: "package_manager_service"
+ description: "Feature flag to remove hack code of using PackageManager.MATCH_ANY_USER flag without cross user permission."
+ bug: "332664521"
+ is_fixed_read_only: true
}
\ No newline at end of file
diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java
index bdbec55..5ee61bc 100644
--- a/core/java/android/hardware/input/KeyGestureEvent.java
+++ b/core/java/android/hardware/input/KeyGestureEvent.java
@@ -48,49 +48,57 @@
public static final int KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL = 8;
public static final int KEY_GESTURE_TYPE_TOGGLE_TASKBAR = 9;
public static final int KEY_GESTURE_TYPE_TAKE_SCREENSHOT = 10;
- public static final int KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER = 11;
- public static final int KEY_GESTURE_TYPE_BRIGHTNESS_UP = 12;
- public static final int KEY_GESTURE_TYPE_BRIGHTNESS_DOWN = 13;
- public static final int KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP = 14;
- public static final int KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN = 15;
- public static final int KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE = 16;
- public static final int KEY_GESTURE_TYPE_VOLUME_UP = 17;
- public static final int KEY_GESTURE_TYPE_VOLUME_DOWN = 18;
- public static final int KEY_GESTURE_TYPE_VOLUME_MUTE = 19;
- public static final int KEY_GESTURE_TYPE_ALL_APPS = 20;
- public static final int KEY_GESTURE_TYPE_LAUNCH_SEARCH = 21;
- public static final int KEY_GESTURE_TYPE_LANGUAGE_SWITCH = 22;
- public static final int KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS = 23;
- public static final int KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK = 24;
- public static final int KEY_GESTURE_TYPE_SYSTEM_MUTE = 25;
- public static final int KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT = 26;
- public static final int KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT = 27;
- public static final int KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT = 28;
- public static final int KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT = 29;
- public static final int KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT = 30;
- public static final int KEY_GESTURE_TYPE_LOCK_SCREEN = 31;
- public static final int KEY_GESTURE_TYPE_OPEN_NOTES = 32;
- public static final int KEY_GESTURE_TYPE_TOGGLE_POWER = 33;
- public static final int KEY_GESTURE_TYPE_SYSTEM_NAVIGATION = 34;
- public static final int KEY_GESTURE_TYPE_SLEEP = 35;
- public static final int KEY_GESTURE_TYPE_WAKEUP = 36;
- public static final int KEY_GESTURE_TYPE_MEDIA_KEY = 37;
- public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER = 38;
- public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL = 39;
- public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS = 40;
- public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR = 41;
- public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR = 42;
- public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC = 43;
- public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS = 44;
- public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING = 45;
- public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY = 46;
- public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES = 47;
- public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER = 48;
- public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS = 49;
- public static final int KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME = 50;
- public static final int KEY_GESTURE_TYPE_DESKTOP_MODE = 51;
- public static final int KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION = 52;
- public static final int KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER = 53;
+ public static final int KEY_GESTURE_TYPE_SCREENSHOT_CHORD = 11;
+ public static final int KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER = 12;
+ public static final int KEY_GESTURE_TYPE_BRIGHTNESS_UP = 13;
+ public static final int KEY_GESTURE_TYPE_BRIGHTNESS_DOWN = 14;
+ public static final int KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP = 15;
+ public static final int KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN = 16;
+ public static final int KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE = 17;
+ public static final int KEY_GESTURE_TYPE_VOLUME_UP = 18;
+ public static final int KEY_GESTURE_TYPE_VOLUME_DOWN = 19;
+ public static final int KEY_GESTURE_TYPE_VOLUME_MUTE = 20;
+ public static final int KEY_GESTURE_TYPE_ALL_APPS = 21;
+ public static final int KEY_GESTURE_TYPE_LAUNCH_SEARCH = 22;
+ public static final int KEY_GESTURE_TYPE_LANGUAGE_SWITCH = 23;
+ public static final int KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS = 24;
+ public static final int KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK = 25;
+ public static final int KEY_GESTURE_TYPE_SYSTEM_MUTE = 26;
+ public static final int KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT = 27;
+ public static final int KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT = 28;
+ public static final int KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT = 29;
+ public static final int KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT = 30;
+ public static final int KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT = 31;
+ public static final int KEY_GESTURE_TYPE_LOCK_SCREEN = 32;
+ public static final int KEY_GESTURE_TYPE_OPEN_NOTES = 33;
+ public static final int KEY_GESTURE_TYPE_TOGGLE_POWER = 34;
+ public static final int KEY_GESTURE_TYPE_SYSTEM_NAVIGATION = 35;
+ public static final int KEY_GESTURE_TYPE_SLEEP = 36;
+ public static final int KEY_GESTURE_TYPE_WAKEUP = 37;
+ public static final int KEY_GESTURE_TYPE_MEDIA_KEY = 38;
+ public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER = 39;
+ public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL = 40;
+ public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS = 41;
+ public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR = 42;
+ public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR = 43;
+ public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC = 44;
+ public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS = 45;
+ public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING = 46;
+ public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY = 47;
+ public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES = 48;
+ public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER = 49;
+ public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS = 50;
+ public static final int KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME = 51;
+ public static final int KEY_GESTURE_TYPE_DESKTOP_MODE = 52;
+ public static final int KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION = 53;
+ public static final int KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER = 54;
+ public static final int KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD = 55;
+ public static final int KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD = 56;
+ public static final int KEY_GESTURE_TYPE_GLOBAL_ACTIONS = 57;
+ public static final int KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD = 58;
+ public static final int KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT = 59;
+ public static final int KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT = 60;
+ public static final int KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS = 61;
public static final int FLAG_CANCELLED = 1;
@@ -116,6 +124,7 @@
KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
KEY_GESTURE_TYPE_TOGGLE_TASKBAR,
KEY_GESTURE_TYPE_TAKE_SCREENSHOT,
+ KEY_GESTURE_TYPE_SCREENSHOT_CHORD,
KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER,
KEY_GESTURE_TYPE_BRIGHTNESS_UP,
KEY_GESTURE_TYPE_BRIGHTNESS_DOWN,
@@ -158,7 +167,15 @@
KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME,
KEY_GESTURE_TYPE_DESKTOP_MODE,
KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION,
- KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER
+ KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER,
+ KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD,
+ KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD,
+ KEY_GESTURE_TYPE_GLOBAL_ACTIONS,
+ KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD,
+ KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT,
+ KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT,
+ KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS,
+
})
@Retention(RetentionPolicy.SOURCE)
public @interface KeyGestureType {
@@ -360,6 +377,7 @@
case KEY_GESTURE_TYPE_TOGGLE_TASKBAR:
return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_TASKBAR;
case KEY_GESTURE_TYPE_TAKE_SCREENSHOT:
+ case KEY_GESTURE_TYPE_SCREENSHOT_CHORD:
return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TAKE_SCREENSHOT;
case KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER:
return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__OPEN_SHORTCUT_HELPER;
@@ -472,6 +490,8 @@
return "KEY_GESTURE_TYPE_TOGGLE_TASKBAR";
case KEY_GESTURE_TYPE_TAKE_SCREENSHOT:
return "KEY_GESTURE_TYPE_TAKE_SCREENSHOT";
+ case KEY_GESTURE_TYPE_SCREENSHOT_CHORD:
+ return "KEY_GESTURE_TYPE_SCREENSHOT_CHORD";
case KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER:
return "KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER";
case KEY_GESTURE_TYPE_BRIGHTNESS_UP:
@@ -558,6 +578,20 @@
return "KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION";
case KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER:
return "KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER";
+ case KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD:
+ return "KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD";
+ case KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD:
+ return "KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD";
+ case KEY_GESTURE_TYPE_GLOBAL_ACTIONS:
+ return "KEY_GESTURE_TYPE_GLOBAL_ACTIONS";
+ case KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD:
+ return "KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD";
+ case KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT:
+ return "KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT";
+ case KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT:
+ return "KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT";
+ case KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS:
+ return "KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS";
default:
return Integer.toHexString(value);
}
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
index 5c07fa4..22ae676 100644
--- a/core/java/android/hardware/soundtrigger/ConversionUtil.java
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -155,16 +155,16 @@
public static RecognitionConfig api2aidlRecognitionConfig(
SoundTrigger.RecognitionConfig apiConfig) {
RecognitionConfig aidlConfig = new RecognitionConfig();
- aidlConfig.captureRequested = apiConfig.captureRequested;
- // apiConfig.allowMultipleTriggers is ignored by the lower layers.
+ aidlConfig.captureRequested = apiConfig.isCaptureRequested();
+ // apiConfig.isAllowMultipleTriggers() is ignored by the lower layers.
aidlConfig.phraseRecognitionExtras =
- new PhraseRecognitionExtra[apiConfig.keyphrases.length];
- for (int i = 0; i < apiConfig.keyphrases.length; ++i) {
+ new PhraseRecognitionExtra[apiConfig.getKeyphrases().size()];
+ for (int i = 0; i < apiConfig.getKeyphrases().size(); ++i) {
aidlConfig.phraseRecognitionExtras[i] = api2aidlPhraseRecognitionExtra(
- apiConfig.keyphrases[i]);
+ apiConfig.getKeyphrases().get(i));
}
- aidlConfig.data = Arrays.copyOf(apiConfig.data, apiConfig.data.length);
- aidlConfig.audioCapabilities = api2aidlAudioCapabilities(apiConfig.audioCapabilities);
+ aidlConfig.data = Arrays.copyOf(apiConfig.getData(), apiConfig.getData().length);
+ aidlConfig.audioCapabilities = api2aidlAudioCapabilities(apiConfig.getAudioCapabilities());
return aidlConfig;
}
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 9f3e3ad..79cbd19 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -63,6 +63,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.UUID;
@@ -1513,50 +1514,56 @@
* A RecognitionConfig is provided to
* {@link SoundTriggerModule#startRecognition(int, RecognitionConfig)} to configure the
* recognition request.
- *
- * @hide
*/
- @TestApi
+ @FlaggedApi(android.media.soundtrigger.Flags.FLAG_MANAGER_API)
public static final class RecognitionConfig implements Parcelable {
- /** True if the DSP should capture the trigger sound and make it available for further
- * capture. */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public final boolean captureRequested;
- /**
- * True if the service should restart listening after the DSP triggers.
- * Note: This config flag is currently used at the service layer rather than by the DSP.
- */
- public final boolean allowMultipleTriggers;
- /** List of all keyphrases in the sound model for which recognition should be performed with
- * options for each keyphrase. */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- @NonNull
- @SuppressLint("ArrayReturn")
- public final KeyphraseRecognitionExtra keyphrases[];
- /** Opaque data for use by system applications who know about voice engine internals,
- * typically during enrollment. */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- @NonNull
- public final byte[] data;
-
- /**
- * Bit field encoding of the AudioCapabilities
- * supported by the firmware.
- */
+ private final boolean mCaptureRequested;
+ private final boolean mAllowMultipleTriggers;
+ private final KeyphraseRecognitionExtra mKeyphrases[];
+ private final byte[] mData;
@ModuleProperties.AudioCapabilities
- public final int audioCapabilities;
+ private final int mAudioCapabilities;
+ /**
+ * Constructor for {@link RecognitionConfig} with {@code audioCapabilities} describes a
+ * config that can be used by
+ * {@link SoundTriggerModule#startRecognition(int, RecognitionConfig)}
+ *
+ * @param captureRequested Whether the DSP should capture the trigger sound.
+ * @param allowMultipleTriggers Whether the service should restart listening after the DSP
+ * triggers.
+ * @param keyphrases List of keyphrases in the sound model.
+ * @param data Opaque data for use by system applications who know about voice engine
+ * internals, typically during enrollment.
+ * @param audioCapabilities Bit field encoding of the AudioCapabilities.
+ *
+ * @hide
+ */
+ @TestApi
public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
@SuppressLint("ArrayReturn") @Nullable KeyphraseRecognitionExtra[] keyphrases,
@Nullable byte[] data, int audioCapabilities) {
- this.captureRequested = captureRequested;
- this.allowMultipleTriggers = allowMultipleTriggers;
- this.keyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0];
- this.data = data != null ? data : new byte[0];
- this.audioCapabilities = audioCapabilities;
+ this.mCaptureRequested = captureRequested;
+ this.mAllowMultipleTriggers = allowMultipleTriggers;
+ this.mKeyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0];
+ this.mData = data != null ? data : new byte[0];
+ this.mAudioCapabilities = audioCapabilities;
}
+ /**
+ * Constructor for {@link RecognitionConfig} without audioCapabilities. The
+ * audioCapabilities is set to 0.
+ *
+ * @param captureRequested Whether the DSP should capture the trigger sound.
+ * @param allowMultipleTriggers Whether the service should restart listening after the DSP
+ * triggers.
+ * @param keyphrases List of keyphrases in the sound model.
+ * @param data Opaque data for use by system applications.
+ *
+ * @hide
+ */
@UnsupportedAppUsage
+ @TestApi
public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
@SuppressLint("ArrayReturn") @Nullable KeyphraseRecognitionExtra[] keyphrases,
@Nullable byte[] data) {
@@ -1574,9 +1581,52 @@
}
};
+ /**
+ * Returns whether the DSP should capture the trigger sound and make it available for
+ * further capture.
+ */
+ public boolean isCaptureRequested() {
+ return mCaptureRequested;
+ }
+
+ /**
+ * Returns whether the service should restart listening after the DSP triggers.
+ *
+ * <p><b>Note:</b> This config flag is currently used at the service layer rather than by
+ * the DSP.
+ */
+ public boolean isAllowMultipleTriggers() {
+ return mAllowMultipleTriggers;
+ }
+
+ /**
+ * Gets all keyphrases in the sound model for which recognition should be performed with
+ * options for each keyphrase.
+ */
+ @NonNull
+ public List<KeyphraseRecognitionExtra> getKeyphrases() {
+ return Arrays.asList(mKeyphrases);
+ }
+
+ /**
+ * Opaque data.
+ *
+ * <p>For use by system applications who knows about voice engine internals, typically
+ * during enrollment.
+ */
+ @NonNull
+ public byte[] getData() {
+ return mData;
+ }
+
+ /** Bit field encoding of the AudioCapabilities supported by the firmware. */
+ public int getAudioCapabilities() {
+ return mAudioCapabilities;
+ }
+
private static RecognitionConfig fromParcel(Parcel in) {
- boolean captureRequested = in.readByte() == 1;
- boolean allowMultipleTriggers = in.readByte() == 1;
+ boolean captureRequested = in.readBoolean();
+ boolean allowMultipleTriggers = in.readBoolean();
KeyphraseRecognitionExtra[] keyphrases =
in.createTypedArray(KeyphraseRecognitionExtra.CREATOR);
byte[] data = in.readBlob();
@@ -1587,11 +1637,11 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeByte((byte) (captureRequested ? 1 : 0));
- dest.writeByte((byte) (allowMultipleTriggers ? 1 : 0));
- dest.writeTypedArray(keyphrases, flags);
- dest.writeBlob(data);
- dest.writeInt(audioCapabilities);
+ dest.writeBoolean(mCaptureRequested);
+ dest.writeBoolean(mAllowMultipleTriggers);
+ dest.writeTypedArray(mKeyphrases, flags);
+ dest.writeBlob(mData);
+ dest.writeInt(mAudioCapabilities);
}
@Override
@@ -1601,10 +1651,10 @@
@Override
public String toString() {
- return "RecognitionConfig [captureRequested=" + captureRequested
- + ", allowMultipleTriggers=" + allowMultipleTriggers + ", keyphrases="
- + Arrays.toString(keyphrases) + ", data=" + Arrays.toString(data)
- + ", audioCapabilities=" + Integer.toHexString(audioCapabilities) + "]";
+ return "RecognitionConfig [captureRequested=" + mCaptureRequested
+ + ", allowMultipleTriggers=" + mAllowMultipleTriggers + ", keyphrases="
+ + Arrays.toString(mKeyphrases) + ", data=" + Arrays.toString(mData)
+ + ", audioCapabilities=" + Integer.toHexString(mAudioCapabilities) + "]";
}
@Override
@@ -1616,19 +1666,19 @@
if (!(obj instanceof RecognitionConfig))
return false;
RecognitionConfig other = (RecognitionConfig) obj;
- if (captureRequested != other.captureRequested) {
+ if (mCaptureRequested != other.mCaptureRequested) {
return false;
}
- if (allowMultipleTriggers != other.allowMultipleTriggers) {
+ if (mAllowMultipleTriggers != other.mAllowMultipleTriggers) {
return false;
}
- if (!Arrays.equals(keyphrases, other.keyphrases)) {
+ if (!Arrays.equals(mKeyphrases, other.mKeyphrases)) {
return false;
}
- if (!Arrays.equals(data, other.data)) {
+ if (!Arrays.equals(mData, other.mData)) {
return false;
}
- if (audioCapabilities != other.audioCapabilities) {
+ if (mAudioCapabilities != other.mAudioCapabilities) {
return false;
}
return true;
@@ -1638,11 +1688,11 @@
public final int hashCode() {
final int prime = 31;
int result = 1;
- result = prime * result + (captureRequested ? 1 : 0);
- result = prime * result + (allowMultipleTriggers ? 1 : 0);
- result = prime * result + Arrays.hashCode(keyphrases);
- result = prime * result + Arrays.hashCode(data);
- result = prime * result + audioCapabilities;
+ result = prime * result + (mCaptureRequested ? 1 : 0);
+ result = prime * result + (mAllowMultipleTriggers ? 1 : 0);
+ result = prime * result + Arrays.hashCode(mKeyphrases);
+ result = prime * result + Arrays.hashCode(mData);
+ result = prime * result + mAudioCapabilities;
return result;
}
}
diff --git a/core/java/android/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig
index 45e9def..56d3669 100644
--- a/core/java/android/security/responsible_apis_flags.aconfig
+++ b/core/java/android/security/responsible_apis_flags.aconfig
@@ -52,11 +52,3 @@
description: "Opt the system into enforcement of BAL"
bug: "339403750"
}
-
-flag {
- name: "prevent_intent_redirect"
- namespace: "responsible_apis"
- description: "Prevent intent redirect attacks"
- bug: "361143368"
- is_fixed_read_only: true
-}
\ No newline at end of file
diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java
index c88048c..1f341ca 100644
--- a/core/java/android/view/WindowManagerPolicyConstants.java
+++ b/core/java/android/view/WindowManagerPolicyConstants.java
@@ -17,6 +17,7 @@
package android.view;
import static android.os.IInputConstants.POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY;
+import static android.os.IInputConstants.POLICY_FLAG_KEY_GESTURE_TRIGGERED;
import android.annotation.IntDef;
import android.os.PowerManager;
@@ -35,6 +36,7 @@
int FLAG_VIRTUAL = 0x00000002;
int FLAG_INJECTED_FROM_ACCESSIBILITY = POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY;
+ int FLAG_KEY_GESTURE_TRIGGERED = POLICY_FLAG_KEY_GESTURE_TRIGGERED;
int FLAG_INJECTED = 0x01000000;
int FLAG_TRUSTED = 0x02000000;
int FLAG_FILTERED = 0x04000000;
diff --git a/core/jni/android_util_XmlBlock.cpp b/core/jni/android_util_XmlBlock.cpp
index 5a444bb..c364451 100644
--- a/core/jni/android_util_XmlBlock.cpp
+++ b/core/jni/android_util_XmlBlock.cpp
@@ -83,7 +83,7 @@
return 0;
}
- ResXMLParser* st = new ResXMLParser(*osb);
+ ResXMLParser* st = new(std::nothrow) ResXMLParser(*osb);
if (st == NULL) {
jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
return 0;
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index e9ce712..d98836f 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -104,7 +104,6 @@
"mockito-target-extended-minus-junit4",
"TestParameterInjector",
"android.content.res.flags-aconfig-java",
- "android.security.flags-aconfig-java",
],
libs: [
diff --git a/core/tests/coretests/src/android/content/IntentTest.java b/core/tests/coretests/src/android/content/IntentTest.java
deleted file mode 100644
index d169ce3..0000000
--- a/core/tests/coretests/src/android/content/IntentTest.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertEquals;
-
-import android.net.Uri;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.security.Flags;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Build/Install/Run:
- * atest FrameworksCoreTests:IntentTest
- */
-@Presubmit
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class IntentTest {
- private static final String TEST_ACTION = "android.content.IntentTest_test";
- private static final String TEST_EXTRA_NAME = "testExtraName";
- private static final Uri TEST_URI = Uri.parse("content://com.example/people");
-
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_PREVENT_INTENT_REDIRECT)
- public void testReadFromParcelWithExtraIntentKeys() {
- Intent intent = new Intent("TEST_ACTION");
- intent.putExtra(TEST_EXTRA_NAME, new Intent(TEST_ACTION));
- intent.putExtra(TEST_EXTRA_NAME + "2", 1);
-
- intent.collectExtraIntentKeys();
- final Parcel parcel = Parcel.obtain();
- intent.writeToParcel(parcel, 0);
- parcel.setDataPosition(0);
- final Intent target = new Intent();
- target.readFromParcel(parcel);
-
- assertEquals(intent.getAction(), target.getAction());
- assertEquals(intent.getExtraIntentKeys(), target.getExtraIntentKeys());
- assertThat(intent.getExtraIntentKeys()).hasSize(1);
- }
-
- @Test
- public void testCreatorTokenInfo() {
- Intent intent = new Intent(TEST_ACTION);
- IBinder creatorToken = new Binder();
-
- intent.setCreatorToken(creatorToken);
- assertThat(intent.getCreatorToken()).isEqualTo(creatorToken);
-
- intent.removeCreatorTokenInfo();
- assertThat(intent.getCreatorToken()).isNull();
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_PREVENT_INTENT_REDIRECT)
- public void testCollectExtraIntentKeys() {
- Intent intent = new Intent(TEST_ACTION);
- Intent extraIntent = new Intent(TEST_ACTION, TEST_URI);
- intent.putExtra(TEST_EXTRA_NAME, extraIntent);
-
- intent.collectExtraIntentKeys();
-
- assertThat(intent.getExtraIntentKeys()).hasSize(1);
- assertThat(intent.getExtraIntentKeys()).contains(TEST_EXTRA_NAME);
- }
-
-}
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index 31f8996..ef4c3ef 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -16,6 +16,8 @@
package android.media.projection;
+import static android.view.Display.DEFAULT_DISPLAY;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.compat.CompatChanges;
@@ -29,6 +31,7 @@
import android.os.Build;
import android.os.Handler;
import android.os.RemoteException;
+import android.os.UserManager;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
@@ -70,6 +73,7 @@
private final DisplayManager mDisplayManager;
@NonNull
private final Map<Callback, CallbackRecord> mCallbacks = new ArrayMap<>();
+ private final int mDisplayId;
/** @hide */
public MediaProjection(Context context, IMediaProjection impl) {
@@ -88,6 +92,11 @@
throw new RuntimeException("Failed to start media projection", e);
}
mDisplayManager = displayManager;
+
+ final UserManager userManager = context.getSystemService(UserManager.class);
+ mDisplayId = userManager.isVisibleBackgroundUsersSupported()
+ ? userManager.getMainDisplayIdAssignedToUser()
+ : DEFAULT_DISPLAY;
}
/**
@@ -156,6 +165,7 @@
if (surface != null) {
builder.setSurface(surface);
}
+ builder.setDisplayIdToMirror(mDisplayId);
return createVirtualDisplay(builder, callback, handler);
}
@@ -234,6 +244,7 @@
if (surface != null) {
builder.setSurface(surface);
}
+ builder.setDisplayIdToMirror(mDisplayId);
return createVirtualDisplay(builder, callback, handler);
}
diff --git a/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java b/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java
index c376f25..8b28347 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java
@@ -17,6 +17,7 @@
package android.media.soundtrigger;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -328,6 +329,7 @@
* Get the recognition config used to start this recognition.
* @return - The config passed to the HAL for startRecognition.
*/
+ @FlaggedApi(Flags.FLAG_MANAGER_API)
public @NonNull SoundTrigger.RecognitionConfig getRecognitionConfig() {
return mRecognitionConfig;
}
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index 78352871..3d0c406 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -19,6 +19,7 @@
import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -42,7 +43,6 @@
import android.media.permission.Identity;
import android.media.permission.SafeCloseable;
import android.os.Binder;
-import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -463,13 +463,18 @@
public static final String EXTRA_STATUS = "android.media.soundtrigger.STATUS";
/**
- * Loads a given sound model into the sound trigger. Note the model will be unloaded if there is
- * an error/the system service is restarted.
- * @hide
+ * Loads a given sound model into the sound trigger.
+ *
+ * <p><b>Note:</b> the model will be unloaded if there is an error/the system service is
+ * restarted.
+ *
+ * @return {@link SoundTrigger#STATUS_OK} if the model was loaded successfully, error code
+ * otherwise
*/
+ @SuppressLint("AndroidFrameworkRequiresPermission")
@RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
@UnsupportedAppUsage
- @TestApi
+ @FlaggedApi(Flags.FLAG_MANAGER_API)
public int loadSoundModel(@NonNull SoundModel soundModel) {
if (mSoundTriggerSession == null) {
throw new IllegalStateException("No underlying SoundTriggerModule available");
@@ -508,11 +513,11 @@
*
* @return {@link SoundTrigger#STATUS_OK} if the recognition could be started, error code
* otherwise
- *
- * @hide
*/
+ @SuppressLint("AndroidFrameworkRequiresPermission")
@RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
@UnsupportedAppUsage
+ @FlaggedApi(Flags.FLAG_MANAGER_API)
public int startRecognition(@NonNull UUID soundModelId, @Nullable Bundle params,
@NonNull ComponentName detectionService, @NonNull RecognitionConfig config) {
Objects.requireNonNull(soundModelId);
@@ -531,11 +536,15 @@
/**
* Stops the given model's recognition.
- * @hide
+ *
+ * @return {@link SoundTrigger#STATUS_OK} if the recognition could be stopped, error code
+ * otherwise
*/
+ @SuppressLint("AndroidFrameworkRequiresPermission")
@RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public int stopRecognition(UUID soundModelId) {
+ @UnsupportedAppUsage
+ @FlaggedApi(Flags.FLAG_MANAGER_API)
+ public int stopRecognition(@NonNull UUID soundModelId) {
if (mSoundTriggerSession == null) {
throw new IllegalStateException("No underlying SoundTriggerModule available");
}
@@ -548,12 +557,18 @@
}
/**
- * Removes the given model from memory. Will also stop any pending recognitions.
- * @hide
+ * Removes the given model from memory.
+ *
+ * <p>Will also stop any pending recognitions.
+ *
+ * @return {@link SoundTrigger#STATUS_OK} if the model was unloaded successfully, error code
+ * otherwise
*/
+ @SuppressLint("AndroidFrameworkRequiresPermission")
@RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public int unloadSoundModel(UUID soundModelId) {
+ @UnsupportedAppUsage
+ @FlaggedApi(Flags.FLAG_MANAGER_API)
+ public int unloadSoundModel(@NonNull UUID soundModelId) {
if (mSoundTriggerSession == null) {
throw new IllegalStateException("No underlying SoundTriggerModule available");
}
@@ -566,12 +581,13 @@
}
/**
- * Returns true if the given model has had detection started on it.
- * @hide
+ * Returns whether the given model has had detection started on it.
*/
+ @SuppressLint("AndroidFrameworkRequiresPermission")
@RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
@UnsupportedAppUsage
- public boolean isRecognitionActive(UUID soundModelId) {
+ @FlaggedApi(Flags.FLAG_MANAGER_API)
+ public boolean isRecognitionActive(@NonNull UUID soundModelId) {
if (soundModelId == null || mSoundTriggerSession == null) {
return false;
}
@@ -599,14 +615,16 @@
}
/**
- * Asynchronously get state of the indicated model. The model state is returned as
- * a recognition event in the callback that was registered in the startRecognition
- * method.
- * @hide
+ * Asynchronously gets state of the indicated model.
+ *
+ * <p>The model state is returned as a recognition event in the callback that was registered
+ * in the {@link #startRecognition(UUID, Bundle, ComponentName, RecognitionConfig)} method.
*/
+ @SuppressLint("AndroidFrameworkRequiresPermission")
@RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
@UnsupportedAppUsage
- public int getModelState(UUID soundModelId) {
+ @FlaggedApi(Flags.FLAG_MANAGER_API)
+ public int getModelState(@NonNull UUID soundModelId) {
if (mSoundTriggerSession == null) {
throw new IllegalStateException("No underlying SoundTriggerModule available");
}
@@ -621,7 +639,7 @@
}
/**
- * Get the hardware sound trigger module properties currently loaded.
+ * Gets the hardware sound trigger module properties currently loaded.
*
* @return The properties currently loaded. Returns null if no supported hardware loaded.
*/
@@ -639,9 +657,11 @@
}
/**
- * Set a model specific {@link ModelParams} with the given value. This
- * parameter will keep its value for the duration the model is loaded regardless of starting and
- * stopping recognition. Once the model is unloaded, the value will be lost.
+ * Sets a model specific {@link ModelParams} with the given value.
+ *
+ * <p>This parameter will keep its value for the duration the model is loaded regardless of
+ * starting and stopping recognition. Once the model is unloaded, the value will be lost.
+ *
* {@link SoundTriggerManager#queryParameter} should be checked first before calling this
* method.
*
@@ -671,12 +691,15 @@
}
/**
- * Get a model specific {@link ModelParams}. This parameter will keep its value
- * for the duration the model is loaded regardless of starting and stopping recognition.
- * Once the model is unloaded, the value will be lost. If the value is not set, a default
- * value is returned. See {@link ModelParams} for parameter default values.
- * {@link SoundTriggerManager#queryParameter} should be checked first before
- * calling this method. Otherwise, an exception can be thrown.
+ * Gets a model specific {@link ModelParams}.
+ *
+ * <p>This parameter will keep its value for the duration the model is loaded regardless
+ * of starting and stopping recognition. Once the model is unloaded, the value will be lost.
+ * If the value is not set, a default value is returned. See {@link ModelParams} for
+ * parameter default values.
+ *
+ * {@link SoundTriggerManager#queryParameter} should be checked first before calling this
+ * method. Otherwise, an exception can be thrown.
*
* @param soundModelId UUID of model to get parameter
* @param modelParam {@link ModelParams}
@@ -697,9 +720,10 @@
}
/**
- * Determine if parameter control is supported for the given model handle.
- * This method should be checked prior to calling {@link SoundTriggerManager#setParameter} or
- * {@link SoundTriggerManager#getParameter}.
+ * Determines if parameter control is supported for the given model handle.
+ *
+ * <p>This method should be checked prior to calling {@link SoundTriggerManager#setParameter}
+ * or {@link SoundTriggerManager#getParameter}.
*
* @param soundModelId handle of model to get parameter
* @param modelParam {@link ModelParams}
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/drawable-v35/settingslib_expressive_icon_back.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/drawable-v35/settingslib_expressive_icon_back.xml
new file mode 100644
index 0000000..ccbe20e
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/drawable-v35/settingslib_expressive_icon_back.xml
@@ -0,0 +1,52 @@
+<?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.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:start="16dp"
+ android:end="16dp"
+ android:top="4dp"
+ android:bottom="4dp">
+ <shape>
+ <size android:width="32dp" android:height="40dp" />
+ <solid android:color="@color/settingslib_materialColorSurfaceContainerHighest" />
+ <corners
+ android:radius="100dp" />
+ </shape>
+ </item>
+
+ <item
+ android:width="24dp"
+ android:height="24dp"
+ android:gravity="center"
+ android:start="16dp"
+ android:end="16dp"
+ android:top="4dp"
+ android:bottom="4dp">
+ <vector
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="@color/settingslib_materialColorOnSurfaceVariant"
+ android:autoMirrored="true">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M313,520L537,744L480,800L160,480L480,160L537,216L313,440L800,440L800,520L313,520Z"/>
+ </vector>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v35/settingslib_expressive_collapsing_toolbar_base_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v35/settingslib_expressive_collapsing_toolbar_base_layout.xml
new file mode 100644
index 0000000..b881c57
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v35/settingslib_expressive_collapsing_toolbar_base_layout.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+
+<androidx.coordinatorlayout.widget.CoordinatorLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/content_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true">
+
+ <include layout="@layout/settingslib_expressive_collapsing_toolbar_content_layout"/>
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v35/settingslib_expressive_collapsing_toolbar_content_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v35/settingslib_expressive_collapsing_toolbar_content_layout.xml
new file mode 100644
index 0000000..6221659
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v35/settingslib_expressive_collapsing_toolbar_content_layout.xml
@@ -0,0 +1,56 @@
+<?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.
+-->
+<merge
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <com.google.android.material.appbar.AppBarLayout
+ android:id="@+id/app_bar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fitsSystemWindows="true"
+ android:outlineAmbientShadowColor="@android:color/transparent"
+ android:outlineSpotShadowColor="@android:color/transparent"
+ android:background="@android:color/transparent"
+ app:expanded="false"
+ android:theme="@style/SettingsLibTheme.CollapsingToolbar.Expressive">
+
+ <com.google.android.material.appbar.CollapsingToolbarLayout
+ android:id="@+id/collapsing_toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/settingslib_toolbar_layout_height"
+ app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
+ app:toolbarId="@id/action_bar"
+ style="@style/SettingsLibCollapsingToolbarLayoutStyle.Expressive">
+
+ <Toolbar
+ android:id="@+id/action_bar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ android:theme="?android:attr/actionBarTheme"
+ android:transitionName="shared_element_view"
+ app:layout_collapseMode="pin"/>
+
+ </com.google.android.material.appbar.CollapsingToolbarLayout>
+ </com.google.android.material.appbar.AppBarLayout>
+
+ <FrameLayout
+ android:id="@+id/content_frame"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
+</merge>
\ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/styles_expressive.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/styles_expressive.xml
new file mode 100644
index 0000000..d58c2c2
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/styles_expressive.xml
@@ -0,0 +1,44 @@
+<?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.
+-->
+<resources>
+ <style name="SettingsLibCollapsingToolbarLayoutStyle">
+ <item name="expandedTitleTextAppearance">@style/SettingsLibCollapsingToolbarTitle.Expanded</item>
+ <item name="collapsedTitleTextAppearance">@style/SettingsLibCollapsingToolbarTitle.Collapsed</item>
+ <item name="expandedTitleMarginStart">@dimen/settingslib_expressive_space_small4</item>
+ <item name="expandedTitleMarginEnd">@dimen/settingslib_expressive_space_small4</item>
+ <item name="expandedTitleMarginBottom">@dimen/settingslib_expressive_space_medium1</item>
+ <item name="maxLines">3</item>
+ <item name="scrimVisibleHeightTrigger">@dimen/settingslib_scrim_visible_height_trigger</item>
+ <item name="contentScrim">@color/settingslib_materialColorSurfaceVariant</item>
+ <item name="statusBarScrim">@null</item>
+ <item name="scrimAnimationDuration">50</item>
+ <item name="collapsedTitleTextColor">@color/settingslib_materialColorOnSurface</item>
+ <item name="expandedTitleTextColor">@color/settingslib_materialColorOnSurface</item>
+ </style>
+ <style name="SettingsLibCollapsingToolbarLayoutStyle.Expressive">
+ <item name="contentScrim">@color/settingslib_materialColorSurfaceContainer</item>
+ </style>
+
+ <style name="SettingsLibCollapsingToolbarTitle.Collapsed" parent="@android:style/TextAppearance.DeviceDefault.Headline">
+ <!--set dp because we don't want size adjust when font size change-->
+ <item name="android:textSize">20dp</item>
+ </style>
+
+ <style name="SettingsLibCollapsingToolbarTitle.Expanded" parent="CollapsingToolbarTitle.Collapsed">
+ <item name="android:textSize">36dp</item>
+ </style>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes_expressive.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes_expressive.xml
new file mode 100644
index 0000000..ca1904a
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes_expressive.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.
+-->
+<resources>
+ <style name="SettingsLibTheme.CollapsingToolbar.Expressive" parent="@style/Theme.MaterialComponents.DayNight"/>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java
index f46f110..feacecb 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java
@@ -27,6 +27,8 @@
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
+import com.android.settingslib.widget.SettingsThemeHelper;
+
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout;
import com.google.android.material.color.DynamicColors;
@@ -69,7 +71,10 @@
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
DynamicColors.applyToActivityIfAvailable(this);
}
- setTheme(com.android.settingslib.widget.theme.R.style.Theme_SubSettingsBase);
+ int resId = SettingsThemeHelper.isExpressiveTheme(this)
+ ? com.android.settingslib.widget.theme.R.style.Theme_SubSettingsBase_Expressive
+ : com.android.settingslib.widget.theme.R.style.Theme_SubSettingsBase;
+ setTheme(resId);
if (mCustomizeLayoutResId > 0 && Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
super.setContentView(mCustomizeLayoutResId);
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
index 16ed5a8..0f9d94e 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
@@ -28,6 +28,8 @@
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
+import com.android.settingslib.widget.SettingsThemeHelper;
+
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout;
@@ -59,6 +61,11 @@
protected void onCreate(@Nullable Bundle savedInstanceState) {
EdgeToEdgeUtils.enable(this);
super.onCreate(savedInstanceState);
+
+ if (SettingsThemeHelper.isExpressiveTheme(this)) {
+ setTheme(com.android.settingslib.widget.theme.R.style.Theme_SubSettingsBase_Expressive);
+ }
+
// for backward compatibility on R devices or wearable devices due to small device size.
if (mCustomizeLayoutResId > 0 && (Build.VERSION.SDK_INT < Build.VERSION_CODES.S
|| isWatch())) {
@@ -66,7 +73,7 @@
return;
}
- View view = getToolbarDelegate().onCreateView(getLayoutInflater(), null);
+ View view = getToolbarDelegate().onCreateView(getLayoutInflater(), null, this);
super.setContentView(view);
}
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
index 2ab2abd..01ecb66 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
@@ -20,6 +20,7 @@
import android.app.ActionBar;
import android.app.Activity;
+import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
@@ -37,6 +38,8 @@
import androidx.appcompat.app.AppCompatActivity;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
+import com.android.settingslib.widget.SettingsThemeHelper;
+
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout;
@@ -84,6 +87,8 @@
private boolean mUseCollapsingToolbar;
+ private boolean mIsExpressiveTheme;
+
public CollapsingToolbarDelegate(@NonNull HostCallback hostCallback,
boolean useCollapsingToolbar) {
mHostCallback = hostCallback;
@@ -103,11 +108,16 @@
int layoutId;
boolean useCollapsingToolbar =
mUseCollapsingToolbar || Build.VERSION.SDK_INT < Build.VERSION_CODES.S;
+ Context context = (activity != null) ? activity : inflater.getContext();
+ mIsExpressiveTheme = SettingsThemeHelper.isExpressiveTheme(context);
if (useCollapsingToolbar) {
- layoutId = R.layout.collapsing_toolbar_base_layout;
+ layoutId = mIsExpressiveTheme
+ ? R.layout.settingslib_expressive_collapsing_toolbar_base_layout
+ : R.layout.collapsing_toolbar_base_layout;
} else {
layoutId = R.layout.non_collapsing_toolbar_base_layout;
}
+
final View view = inflater.inflate(layoutId, container, false);
if (view instanceof CoordinatorLayout) {
mCoordinatorLayout = (CoordinatorLayout) view;
@@ -155,6 +165,9 @@
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeButtonEnabled(true);
+ if (mIsExpressiveTheme) {
+ actionBar.setHomeAsUpIndicator(R.drawable.settingslib_expressive_icon_back);
+ }
actionBar.setDisplayShowTitleEnabled(true);
}
}
@@ -174,6 +187,9 @@
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeButtonEnabled(true);
+ if (mIsExpressiveTheme) {
+ actionBar.setHomeAsUpIndicator(R.drawable.settingslib_expressive_icon_back);
+ }
actionBar.setDisplayShowTitleEnabled(true);
}
}
@@ -188,6 +204,9 @@
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeButtonEnabled(true);
+ if (mIsExpressiveTheme) {
+ actionBar.setHomeAsUpIndicator(R.drawable.settingslib_expressive_icon_back);
+ }
actionBar.setDisplayShowTitleEnabled(true);
}
}
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
index f70add9..51d7504 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
@@ -39,6 +39,7 @@
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import com.android.settingslib.collapsingtoolbar.R;
+import com.android.settingslib.widget.SettingsThemeHelper;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout;
@@ -105,7 +106,10 @@
}
private void init() {
- inflate(getContext(), R.layout.collapsing_toolbar_content_layout, this);
+ int resId = SettingsThemeHelper.isExpressiveTheme(getContext())
+ ? R.layout.settingslib_expressive_collapsing_toolbar_content_layout
+ : R.layout.collapsing_toolbar_content_layout;
+ inflate(getContext(), resId, this);
mCollapsingToolbarLayout = findViewById(R.id.collapsing_toolbar);
mAppBarLayout = findViewById(R.id.app_bar);
if (mCollapsingToolbarLayout != null) {
@@ -172,6 +176,9 @@
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayShowTitleEnabled(true);
+ if (SettingsThemeHelper.isExpressiveTheme(getContext())) {
+ actionBar.setHomeAsUpIndicator(R.drawable.settingslib_expressive_icon_back);
+ }
}
}
@@ -202,6 +209,9 @@
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayShowTitleEnabled(true);
+ if (SettingsThemeHelper.isExpressiveTheme(getContext())) {
+ actionBar.setHomeAsUpIndicator(R.drawable.settingslib_expressive_icon_back);
+ }
}
}
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable-v35/settingslib_expressive_switch_bar_bg.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable-v35/settingslib_expressive_switch_bar_bg.xml
new file mode 100644
index 0000000..3999e76
--- /dev/null
+++ b/packages/SettingsLib/MainSwitchPreference/res/drawable-v35/settingslib_expressive_switch_bar_bg.xml
@@ -0,0 +1,26 @@
+<?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.
+ -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:attr/colorControlHighlight">
+ <item>
+ <shape android:shape="rectangle">
+ <solid android:color="@color/settingslib_materialColorPrimaryContainer"/>
+ <corners android:radius="@dimen/settingslib_expressive_radius_extralarge3"/>
+ </shape>
+ </item>
+</ripple>
\ No newline at end of file
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v35/settingslib_expressive_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v35/settingslib_expressive_main_switch_bar.xml
new file mode 100644
index 0000000..4425ef0
--- /dev/null
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout-v35/settingslib_expressive_main_switch_bar.xml
@@ -0,0 +1,75 @@
+<?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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingVertical="@dimen/settingslib_expressive_space_small1"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/frame"
+ android:minHeight="@dimen/settingslib_expressive_space_large3"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:paddingStart="@dimen/settingslib_expressive_space_medium1"
+ android:paddingEnd="@dimen/settingslib_expressive_space_small3"
+ android:background="@drawable/settingslib_expressive_switch_bar_bg">
+
+ <RelativeLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginEnd="@dimen/settingslib_expressive_space_extrasmall4"
+ android:layout_marginVertical="@dimen/settingslib_expressive_space_small4"
+ android:layout_weight="1">
+
+ <TextView
+ android:id="@+id/switch_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start"
+ android:ellipsize="end"
+ android:textAlignment="viewStart"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:textColor="@color/settingslib_main_switch_text_color" />
+
+ <TextView
+ android:id="@+id/switch_summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignLeft="@id/switch_text"
+ android:layout_alignStart="@id/switch_text"
+ android:layout_below="@id/switch_text"
+ android:layout_gravity="start"
+ android:textAlignment="viewStart"
+ android:textAppearance="?android:attr/textAppearanceListItemSmall"
+ android:textColor="@color/settingslib_main_switch_text_color"
+ android:visibility="gone" />
+ </RelativeLayout>
+
+ <com.google.android.material.materialswitch.MaterialSwitch
+ android:theme="@style/Theme.Material3.DynamicColors.DayNight"
+ android:id="@android:id/switch_widget"
+ style="@style/SettingslibMainSwitchStyle.Expressive" />
+ </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
index e6f61a8..106802e 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
@@ -22,6 +22,7 @@
import android.os.Build.VERSION_CODES;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -31,6 +32,7 @@
import android.widget.TextView;
import androidx.annotation.ColorInt;
+import androidx.annotation.RequiresApi;
import com.android.settingslib.widget.mainswitch.R;
@@ -52,6 +54,7 @@
private int mBackgroundActivatedColor;
protected TextView mTextView;
+ protected TextView mSummaryView;
protected CompoundButton mSwitch;
private final View mFrameView;
@@ -71,7 +74,11 @@
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- LayoutInflater.from(context).inflate(R.layout.settingslib_main_switch_bar, this);
+ boolean isExpressive = SettingsThemeHelper.isExpressiveTheme(context);
+ int resId = isExpressive
+ ? R.layout.settingslib_expressive_main_switch_bar
+ : R.layout.settingslib_main_switch_bar;
+ LayoutInflater.from(context).inflate(resId, this);
if (Build.VERSION.SDK_INT < VERSION_CODES.S) {
TypedArray a;
@@ -93,6 +100,9 @@
mFrameView = findViewById(R.id.frame);
mTextView = findViewById(R.id.switch_text);
+ if (isExpressive) {
+ mSummaryView = findViewById(R.id.switch_summary);
+ }
mSwitch = findViewById(android.R.id.switch_widget);
addOnSwitchChangeListener((switchView, isChecked) -> setChecked(isChecked));
@@ -109,6 +119,12 @@
final CharSequence title = a.getText(
androidx.preference.R.styleable.Preference_android_title);
setTitle(title);
+ //TODO(b/369470034): update to next version
+ if (isExpressive && Build.VERSION.SDK_INT >= VERSION_CODES.VANILLA_ICE_CREAM) {
+ CharSequence summary = a.getText(
+ androidx.preference.R.styleable.Preference_android_summary);
+ setSummary(summary);
+ }
a.recycle();
}
@@ -153,6 +169,18 @@
}
/**
+ * Set the summary text
+ */
+ @RequiresApi(VERSION_CODES.VANILLA_ICE_CREAM)
+ //TODO(b/369470034): update to next version
+ public void setSummary(CharSequence text) {
+ if (mSummaryView != null) {
+ mSummaryView.setText(text);
+ mSummaryView.setVisibility(TextUtils.isEmpty(text) ? GONE : VISIBLE);
+ }
+ }
+
+ /**
* Set icon space reserved for title
*/
public void setIconSpaceReserved(boolean iconSpaceReserved) {
@@ -219,6 +247,12 @@
mFrameView.setEnabled(enabled);
mFrameView.setActivated(isChecked());
}
+
+ if (SettingsThemeHelper.isExpressiveTheme(getContext())) {
+ if (mSummaryView != null) {
+ mSummaryView.setEnabled(enabled);
+ }
+ }
}
private void propagateChecked(boolean isChecked) {
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
index b294d4e..d895c87 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.content.res.TypedArray;
+import android.os.Build;
import android.util.AttributeSet;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
@@ -89,6 +90,10 @@
androidx.preference.R.styleable.Preference_android_title);
setTitle(title);
+ CharSequence summary = a.getText(
+ androidx.preference.R.styleable.Preference_android_summary);
+ setSummary(summary);
+
final boolean bIconSpaceReserved = a.getBoolean(
androidx.preference.R.styleable.Preference_android_iconSpaceReserved, true);
setIconSpaceReserved(bIconSpaceReserved);
@@ -113,6 +118,15 @@
}
@Override
+ public void setSummary(CharSequence summary) {
+ super.setSummary(summary);
+ if (mMainSwitchBar != null
+ && Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ mMainSwitchBar.setSummary(summary);
+ }
+ }
+
+ @Override
public void setIconSpaceReserved(boolean iconSpaceReserved) {
super.setIconSpaceReserved(iconSpaceReserved);
if (mMainSwitchBar != null) {
diff --git a/packages/SettingsLib/Service/Android.bp b/packages/SettingsLib/Service/Android.bp
new file mode 100644
index 0000000..65d0e4c
--- /dev/null
+++ b/packages/SettingsLib/Service/Android.bp
@@ -0,0 +1,20 @@
+package {
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+ name: "SettingsLibService-srcs",
+ srcs: ["src/**/*.kt"],
+}
+
+android_library {
+ name: "SettingsLibService",
+ defaults: [
+ "SettingsLintDefaults",
+ ],
+ srcs: [":SettingsLibService-srcs"],
+ static_libs: [
+ "SettingsLibGraph",
+ ],
+ kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/packages/SettingsLib/Service/AndroidManifest.xml b/packages/SettingsLib/Service/AndroidManifest.xml
new file mode 100644
index 0000000..56d7818
--- /dev/null
+++ b/packages/SettingsLib/Service/AndroidManifest.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.settingslib.service">
+
+ <uses-sdk android:minSdkVersion="21" />
+</manifest>
diff --git a/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceGraphApi.kt b/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceGraphApi.kt
new file mode 100644
index 0000000..95661c9
--- /dev/null
+++ b/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceGraphApi.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.settingslib.service
+
+import android.app.Application
+import com.android.settingslib.graph.GetPreferenceGraphApiHandler
+import com.android.settingslib.graph.GetPreferenceGraphRequest
+
+/** Api to get preference graph. */
+internal class PreferenceGraphApi(activityClasses: Set<String>) :
+ GetPreferenceGraphApiHandler(activityClasses) {
+ override val id: Int
+ get() = API_GET_PREFERENCE_GRAPH
+
+ override fun hasPermission(
+ application: Application,
+ myUid: Int,
+ callingUid: Int,
+ request: GetPreferenceGraphRequest,
+ ): Boolean {
+ return true // TODO: add permission check
+ }
+}
diff --git a/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceService.kt b/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceService.kt
new file mode 100644
index 0000000..d382dad
--- /dev/null
+++ b/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceService.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.settingslib.service
+
+import com.android.settingslib.ipc.ApiHandler
+import com.android.settingslib.ipc.MessengerService
+import com.android.settingslib.ipc.PermissionChecker
+
+/**
+ * Preference service providing a bunch of APIs.
+ *
+ * In AndroidManifest.xml, the <service> must specify <intent-filter> with action
+ * [PREFERENCE_SERVICE_ACTION].
+ */
+open class PreferenceService(
+ permissionChecker: PermissionChecker,
+ name: String = "PreferenceService",
+) :
+ MessengerService(
+ listOf<ApiHandler<*, *>>(PreferenceGraphApi(setOf())),
+ permissionChecker,
+ name,
+ )
diff --git a/packages/SettingsLib/Service/src/com/android/settingslib/service/ServiceApiConstants.kt b/packages/SettingsLib/Service/src/com/android/settingslib/service/ServiceApiConstants.kt
new file mode 100644
index 0000000..8f03111
--- /dev/null
+++ b/packages/SettingsLib/Service/src/com/android/settingslib/service/ServiceApiConstants.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.settingslib.service
+
+const val PREFERENCE_SERVICE_ACTION = "com.android.settingslib.PREFERENCE_SERVICE"
+
+internal const val API_GET_PREFERENCE_GRAPH = 1
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_expressive_color_main_switch_track.xml b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_expressive_color_main_switch_track.xml
new file mode 100644
index 0000000..a6885a4
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_expressive_color_main_switch_track.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- State Disabled, Unchecked -->
+ <item
+ android:alpha="?android:attr/disabledAlpha"
+ android:color="@color/settingslib_materialColorSurfaceContainerHighest"
+ android:state_enabled="false" android:state_checked="false"/>
+ <!-- State Disabled, Checked -->
+ <item
+ android:alpha="?android:attr/disabledAlpha"
+ android:color="@color/settingslib_materialColorPrimary"
+ android:state_enabled="false" android:state_checked="true"/>
+ <!-- State Checked -->
+ <item
+ android:color="@color/settingslib_materialColorPrimary"
+ android:state_checked="true"/>
+
+ <!-- State Unchecked -->
+ <item
+ android:color="@color/settingslib_materialColorSurfaceContainerHighest"
+ android:state_hovered="true" android:state_checked="false"/>
+ <item
+ android:color="@color/settingslib_materialColorSurfaceContainerHighest"
+ android:state_focused="true" android:state_checked="false"/>
+ <item
+ android:color="@color/settingslib_materialColorSurfaceContainerHighest"
+ android:state_pressed="true" android:state_checked="false"/>
+ <item
+ android:color="@color/settingslib_materialColorSurfaceContainerHighest"
+ android:state_checked="false"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
index 442def9..816433c 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
@@ -188,4 +188,9 @@
<item name="iconTint">@color/settingslib_materialColorOnSecondaryContainer</item>
<item name="iconSize">@dimen/settingslib_expressive_space_small4</item>
</style>
+
+ <style name="SettingslibMainSwitchStyle.Expressive" parent="SettingslibSwitchStyle.Expressive">
+ <item name="android:layout_gravity">center</item>
+ <item name="trackTint">@color/settingslib_expressive_color_main_switch_track</item>
+ </style>
</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index d01c0b9..272dc2d 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,7 +15,7 @@
#
[versions]
-agp = "8.6.0"
+agp = "8.6.1"
compose-compiler = "1.5.11"
dexmaker-mockito = "2.28.3"
jvm = "17"
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.10-bin.zip b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.10-bin.zip
deleted file mode 100644
index 50432f3..0000000
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.10-bin.zip
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.10.2-bin.zip b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.10.2-bin.zip
new file mode 100644
index 0000000..45f0424
--- /dev/null
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.10.2-bin.zip
Binary files differ
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
index 9a7f4b6..1c25e974 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
@@ -16,6 +16,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=gradle-8.10-bin.zip
+distributionUrl=gradle-8.10.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index 790aa9f..8f8275b 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -58,12 +58,12 @@
api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
+ api("androidx.graphics:graphics-shapes-android:1.0.1")
api("androidx.lifecycle:lifecycle-livedata-ktx")
api("androidx.lifecycle:lifecycle-runtime-compose")
api("androidx.navigation:navigation-compose:2.8.1")
api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
- api("com.google.android.material:material:1.11.0")
- api("androidx.graphics:graphics-shapes-android:1.0.1")
+ api("com.google.android.material:material:1.12.0")
debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
implementation("com.airbnb.android:lottie-compose:6.4.0")
@@ -84,7 +84,6 @@
// Excludes files forked from AndroidX.
"com/android/settingslib/spa/widget/scaffold/CustomizedAppBar*",
- "com/android/settingslib/spa/widget/scaffold/TopAppBarColors*",
// Excludes files forked from Accompanist.
"com/android/settingslib/spa/framework/compose/DrawablePainter*",
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
index 712ddc8..5eeb49a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
@@ -167,6 +167,13 @@
return this;
}
+ public TestModeBuilder setVisualEffect(int effect, boolean allowed) {
+ ZenPolicy newPolicy = new ZenPolicy.Builder(mRule.getZenPolicy())
+ .showVisualEffect(effect, allowed).build();
+ setZenPolicy(newPolicy);
+ return this;
+ }
+
public TestModeBuilder setEnabled(boolean enabled) {
return setEnabled(enabled, /* byUser= */ false);
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index df101c5..dc9e267 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -126,17 +126,18 @@
awaitFirstDown(false)
viewModel.onSceneContainerUserInputStarted()
}
- },
+ }
) {
SceneTransitionLayout(
state = state,
modifier = modifier.fillMaxSize(),
swipeSourceDetector = viewModel.edgeDetector,
+ gestureFilter = viewModel::shouldFilterGesture,
) {
sceneByKey.forEach { (sceneKey, scene) ->
scene(
key = sceneKey,
- userActions = userActionsByContentKey.getOrDefault(sceneKey, emptyMap())
+ userActions = userActionsByContentKey.getOrDefault(sceneKey, emptyMap()),
) {
// Activate the scene.
LaunchedEffect(scene) { scene.activate() }
@@ -144,7 +145,7 @@
// Render the scene.
with(scene) {
this@scene.Content(
- modifier = Modifier.element(sceneKey.rootElementKey).fillMaxSize(),
+ modifier = Modifier.element(sceneKey.rootElementKey).fillMaxSize()
)
}
}
@@ -152,7 +153,7 @@
overlayByKey.forEach { (overlayKey, overlay) ->
overlay(
key = overlayKey,
- userActions = userActionsByContentKey.getOrDefault(overlayKey, emptyMap())
+ userActions = userActionsByContentKey.getOrDefault(overlayKey, emptyMap()),
) {
// Activate the overlay.
LaunchedEffect(overlay) { overlay.activate() }
@@ -164,12 +165,7 @@
}
BottomRightCornerRibbon(
- content = {
- Text(
- text = "flexi\uD83E\uDD43",
- color = Color.White,
- )
- },
+ content = { Text(text = "flexi\uD83E\uDD43", color = Color.White) },
modifier = Modifier.align(Alignment.BottomEnd),
)
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index f38a310..9891025 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -123,6 +123,10 @@
overSlop: Float,
pointersDown: Int,
): DragController {
+ if (startedPosition != null && layoutImpl.gestureFilter(startedPosition)) {
+ return NoOpDragController
+ }
+
if (overSlop == 0f) {
val oldDragController = dragController
check(oldDragController != null && oldDragController.isDrivingTransition) {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index cec8883..6e89814 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -47,6 +47,9 @@
* @param state the state of this layout.
* @param swipeSourceDetector the edge detector used to detect which edge a swipe is started from,
* if any.
+ * @param gestureFilter decides whether a drag gesture that started at the given start position
+ * should be filtered. If the lambda returns `true`, the drag gesture will be ignored. If it
+ * returns `false`, the drag gesture will be handled.
* @param transitionInterceptionThreshold used during a scene transition. For the scene to be
* intercepted, the progress value must be above the threshold, and below (1 - threshold).
* @param builder the configuration of the different scenes and overlays of this layout.
@@ -57,6 +60,7 @@
modifier: Modifier = Modifier,
swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
swipeDetector: SwipeDetector = DefaultSwipeDetector,
+ gestureFilter: (startedPosition: Offset) -> Boolean = DefaultGestureFilter,
@FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0.05f,
builder: SceneTransitionLayoutScope.() -> Unit,
) {
@@ -65,6 +69,7 @@
modifier,
swipeSourceDetector,
swipeDetector,
+ gestureFilter,
transitionInterceptionThreshold,
onLayoutImpl = null,
builder,
@@ -616,6 +621,7 @@
modifier: Modifier = Modifier,
swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
swipeDetector: SwipeDetector = DefaultSwipeDetector,
+ gestureFilter: (startedPosition: Offset) -> Boolean = DefaultGestureFilter,
transitionInterceptionThreshold: Float = 0f,
onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)? = null,
builder: SceneTransitionLayoutScope.() -> Unit,
@@ -632,6 +638,7 @@
transitionInterceptionThreshold = transitionInterceptionThreshold,
builder = builder,
animationScope = animationScope,
+ gestureFilter = gestureFilter,
)
.also { onLayoutImpl?.invoke(it) }
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 65c4043..9e7be37 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -31,6 +31,7 @@
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.layout.ApproachLayoutModifierNode
import androidx.compose.ui.layout.ApproachMeasureScope
import androidx.compose.ui.layout.LookaheadScope
@@ -70,6 +71,7 @@
* animations.
*/
internal val animationScope: CoroutineScope,
+ internal val gestureFilter: (startedPosition: Offset) -> Boolean,
) {
/**
* The map of [Scene]s.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeDetector.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeDetector.kt
index 54ee783..f758102 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeDetector.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeDetector.kt
@@ -17,6 +17,7 @@
package com.android.compose.animation.scene
import androidx.compose.runtime.Stable
+import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.PointerInputChange
/** {@link SwipeDetector} helps determine whether a swipe gestured has occurred. */
@@ -31,6 +32,8 @@
val DefaultSwipeDetector = PassthroughSwipeDetector()
+val DefaultGestureFilter = { _: Offset -> false }
+
/** An {@link SwipeDetector} implementation that recognizes a swipe on any input. */
class PassthroughSwipeDetector : SwipeDetector {
override fun detectSwipe(change: PointerInputChange): Boolean {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index fca92ca..b64b8be 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -108,6 +108,8 @@
val transitionInterceptionThreshold = 0.05f
+ var gestureFilter: (startedPosition: Offset) -> Boolean = DefaultGestureFilter
+
private val layoutImpl =
SceneTransitionLayoutImpl(
state = layoutState,
@@ -120,6 +122,7 @@
// Use testScope and not backgroundScope here because backgroundScope does not
// work well with advanceUntilIdle(), which is used by some tests.
animationScope = testScope,
+ gestureFilter = { startedPosition -> gestureFilter.invoke(startedPosition) },
)
.apply { setContentsAndLayoutTargetSizeForTest(LAYOUT_SIZE) }
@@ -317,6 +320,13 @@
}
@Test
+ fun onDragStarted_doesNotStartTransition_whenGestureFiltered() = runGestureTest {
+ gestureFilter = { _ -> true }
+ onDragStarted(overSlop = down(fractionOfScreen = 0.1f), expectedConsumedOverSlop = 0f)
+ assertIdle(currentScene = SceneA)
+ }
+
+ @Test
fun afterSceneTransitionIsStarted_interceptDragEvents() = runGestureTest {
val dragController = onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerGestureFilterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerGestureFilterTest.kt
new file mode 100644
index 0000000..efde1ec
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerGestureFilterTest.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.scene.ui.viewmodel
+
+import android.graphics.Region
+import android.view.setSystemGestureExclusionRegion
+import androidx.compose.ui.geometry.Offset
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.scene.sceneContainerGestureFilterFactory
+import com.android.systemui.settings.displayTracker
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SceneContainerGestureFilterTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val displayId = kosmos.displayTracker.defaultDisplayId
+
+ private val underTest = kosmos.sceneContainerGestureFilterFactory.create(displayId)
+ private val activationJob = Job()
+
+ @Test
+ fun shouldFilterGesture_whenNoRegion_returnsFalse() =
+ testScope.runTest {
+ activate()
+ setSystemGestureExclusionRegion(displayId, null)
+ runCurrent()
+
+ assertThat(underTest.shouldFilterGesture(Offset(100f, 100f))).isFalse()
+ }
+
+ @Test
+ fun shouldFilterGesture_whenOutsideRegion_returnsFalse() =
+ testScope.runTest {
+ activate()
+ setSystemGestureExclusionRegion(displayId, Region(0, 0, 200, 200))
+ runCurrent()
+
+ assertThat(underTest.shouldFilterGesture(Offset(300f, 100f))).isFalse()
+ }
+
+ @Test
+ fun shouldFilterGesture_whenInsideRegion_returnsTrue() =
+ testScope.runTest {
+ activate()
+ setSystemGestureExclusionRegion(displayId, Region(0, 0, 200, 200))
+ runCurrent()
+
+ assertThat(underTest.shouldFilterGesture(Offset(100f, 100f))).isTrue()
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun shouldFilterGesture_beforeActivation_throws() =
+ testScope.runTest {
+ setSystemGestureExclusionRegion(displayId, Region(0, 0, 200, 200))
+ runCurrent()
+
+ underTest.shouldFilterGesture(Offset(100f, 100f))
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun shouldFilterGesture_afterCancellation_throws() =
+ testScope.runTest {
+ activate()
+ setSystemGestureExclusionRegion(displayId, Region(0, 0, 200, 200))
+ runCurrent()
+
+ cancel()
+
+ underTest.shouldFilterGesture(Offset(100f, 100f))
+ }
+
+ private fun TestScope.activate() {
+ underTest.activateIn(testScope, activationJob)
+ runCurrent()
+ }
+
+ private fun TestScope.cancel() {
+ activationJob.cancel()
+ runCurrent()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index a0bb017..e60e742 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -36,10 +36,12 @@
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.fakeOverlaysByKeys
import com.android.systemui.scene.sceneContainerConfig
+import com.android.systemui.scene.sceneContainerGestureFilterFactory
import com.android.systemui.scene.shared.logger.sceneLogger
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
+import com.android.systemui.settings.displayTracker
import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.shared.flag.DualShade
@@ -86,6 +88,8 @@
shadeInteractor = kosmos.shadeInteractor,
splitEdgeDetector = kosmos.splitEdgeDetector,
logger = kosmos.sceneLogger,
+ gestureFilterFactory = kosmos.sceneContainerGestureFilterFactory,
+ displayId = kosmos.displayTracker.defaultDisplayId,
motionEventHandlerReceiver = { motionEventHandler ->
this@SceneContainerViewModelTest.motionEventHandler = motionEventHandler
},
@@ -283,10 +287,7 @@
fakeSceneDataSource.showOverlay(Overlays.NotificationsShade)
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
assertThat(currentOverlays)
- .containsExactly(
- Overlays.QuickSettingsShade,
- Overlays.NotificationsShade,
- )
+ .containsExactly(Overlays.QuickSettingsShade, Overlays.NotificationsShade)
val actionableContentKey =
underTest.getActionableContentKey(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt
new file mode 100644
index 0000000..f9b7769
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel
+
+import android.app.Flags
+import android.app.NotificationManager.Policy
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
+import android.provider.Settings
+import android.service.notification.ZenPolicy.VISUAL_EFFECT_NOTIFICATION_LIST
+import androidx.test.filters.SmallTest
+import com.android.settingslib.notification.data.repository.updateNotificationPolicy
+import com.android.settingslib.notification.modes.TestModeBuilder
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.andSceneContainer
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
+import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(ParameterizedAndroidJunit4::class)
+@SmallTest
+@EnableFlags(FooterViewRefactor.FLAG_NAME)
+class EmptyShadeViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val zenModeRepository = kosmos.zenModeRepository
+ private val activeNotificationListRepository = kosmos.activeNotificationListRepository
+
+ private val underTest = kosmos.emptyShadeViewModel
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
+ @Test
+ fun areNotificationsHiddenInShade_true() =
+ testScope.runTest {
+ val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
+
+ zenModeRepository.updateNotificationPolicy(
+ suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST
+ )
+ zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
+ runCurrent()
+
+ assertThat(hidden).isTrue()
+ }
+
+ @Test
+ fun areNotificationsHiddenInShade_false() =
+ testScope.runTest {
+ val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
+
+ zenModeRepository.updateNotificationPolicy(
+ suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST
+ )
+ zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_OFF)
+ runCurrent()
+
+ assertThat(hidden).isFalse()
+ }
+
+ @Test
+ fun hasFilteredOutSeenNotifications_true() =
+ testScope.runTest {
+ val hasFilteredNotifs by collectLastValue(underTest.hasFilteredOutSeenNotifications)
+
+ activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true
+ runCurrent()
+
+ assertThat(hasFilteredNotifs).isTrue()
+ }
+
+ @Test
+ fun hasFilteredOutSeenNotifications_false() =
+ testScope.runTest {
+ val hasFilteredNotifs by collectLastValue(underTest.hasFilteredOutSeenNotifications)
+
+ activeNotificationListRepository.hasFilteredOutSeenNotifications.value = false
+ runCurrent()
+
+ assertThat(hasFilteredNotifs).isFalse()
+ }
+
+ @Test
+ @EnableFlags(ModesEmptyShadeFix.FLAG_NAME)
+ @DisableFlags(Flags.FLAG_MODES_UI, Flags.FLAG_MODES_API)
+ fun text_changesWhenNotifsHiddenInShade() =
+ testScope.runTest {
+ val text by collectLastValue(underTest.text)
+
+ zenModeRepository.updateNotificationPolicy(
+ suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST
+ )
+ zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_OFF)
+ runCurrent()
+
+ assertThat(text).isEqualTo("No notifications")
+
+ zenModeRepository.updateNotificationPolicy(
+ suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST
+ )
+ zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
+ runCurrent()
+
+ assertThat(text).isEqualTo("Notifications paused by Do Not Disturb")
+ }
+
+ @Test
+ @EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI, Flags.FLAG_MODES_API)
+ fun text_reflectsModesHidingNotifications() =
+ testScope.runTest {
+ val text by collectLastValue(underTest.text)
+
+ assertThat(text).isEqualTo("No notifications")
+
+ zenModeRepository.addMode(
+ TestModeBuilder()
+ .setId("Do not disturb")
+ .setName("Do not disturb")
+ .setActive(true)
+ .setVisualEffect(VISUAL_EFFECT_NOTIFICATION_LIST, /* allowed= */ false)
+ .build()
+ )
+ runCurrent()
+ assertThat(text).isEqualTo("Notifications paused by Do not disturb")
+
+ zenModeRepository.addMode(
+ TestModeBuilder()
+ .setId("Work")
+ .setName("Work")
+ .setActive(true)
+ .setVisualEffect(VISUAL_EFFECT_NOTIFICATION_LIST, /* allowed= */ false)
+ .build()
+ )
+ runCurrent()
+ assertThat(text).isEqualTo("Notifications paused by Do not disturb and one other mode")
+
+ zenModeRepository.addMode(
+ TestModeBuilder()
+ .setId("Gym")
+ .setName("Gym")
+ .setActive(true)
+ .setVisualEffect(VISUAL_EFFECT_NOTIFICATION_LIST, /* allowed= */ false)
+ .build()
+ )
+ runCurrent()
+ assertThat(text).isEqualTo("Notifications paused by Do not disturb and 2 other modes")
+
+ zenModeRepository.deactivateMode("Do not disturb")
+ zenModeRepository.deactivateMode("Work")
+ runCurrent()
+ assertThat(text).isEqualTo("Notifications paused by Gym")
+ }
+
+ @Test
+ @EnableFlags(ModesEmptyShadeFix.FLAG_NAME)
+ fun footer_isVisibleWhenSeenNotifsAreFilteredOut() =
+ testScope.runTest {
+ val footerVisible by collectLastValue(underTest.footer.isVisible)
+
+ activeNotificationListRepository.hasFilteredOutSeenNotifications.value = false
+ runCurrent()
+
+ assertThat(footerVisible).isFalse()
+
+ activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true
+ runCurrent()
+
+ assertThat(footerVisible).isTrue()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index 26e1a4d..d12d6f6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -18,12 +18,9 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
-import android.app.NotificationManager.Policy
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
-import android.provider.Settings
import androidx.test.filters.SmallTest
-import com.android.settingslib.notification.data.repository.updateNotificationPolicy
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.DisableSceneContainer
@@ -46,7 +43,6 @@
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
import com.android.systemui.statusbar.policy.data.repository.fakeUserSetupRepository
-import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
import com.android.systemui.statusbar.policy.fakeConfigurationController
import com.android.systemui.testKosmos
import com.android.systemui.util.ui.isAnimating
@@ -79,7 +75,6 @@
private val fakeRemoteInputRepository = kosmos.fakeRemoteInputRepository
private val fakeUserSetupRepository = kosmos.fakeUserSetupRepository
private val headsUpRepository = kosmos.headsUpNotificationRepository
- private val zenModeRepository = kosmos.zenModeRepository
private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
@@ -266,56 +261,6 @@
}
@Test
- fun areNotificationsHiddenInShade_true() =
- testScope.runTest {
- val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
-
- zenModeRepository.updateNotificationPolicy(
- suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST
- )
- zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
- runCurrent()
-
- assertThat(hidden).isTrue()
- }
-
- @Test
- fun areNotificationsHiddenInShade_false() =
- testScope.runTest {
- val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
-
- zenModeRepository.updateNotificationPolicy(
- suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST
- )
- zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_OFF)
- runCurrent()
-
- assertThat(hidden).isFalse()
- }
-
- @Test
- fun hasFilteredOutSeenNotifications_true() =
- testScope.runTest {
- val hasFilteredNotifs by collectLastValue(underTest.hasFilteredOutSeenNotifications)
-
- activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true
- runCurrent()
-
- assertThat(hasFilteredNotifs).isTrue()
- }
-
- @Test
- fun hasFilteredOutSeenNotifications_false() =
- testScope.runTest {
- val hasFilteredNotifs by collectLastValue(underTest.hasFilteredOutSeenNotifications)
-
- activeNotificationListRepository.hasFilteredOutSeenNotifications.value = false
- runCurrent()
-
- assertThat(hasFilteredNotifs).isFalse()
- }
-
- @Test
fun shouldIncludeFooterView_trueWhenShade() =
testScope.runTest {
val shouldIncludeFooterView by collectFooterViewVisibility()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
index 0f6dc07..c5ccf9e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
@@ -25,6 +25,7 @@
import android.provider.Settings.Secure.ZEN_DURATION_FOREVER
import android.provider.Settings.Secure.ZEN_DURATION_PROMPT
import android.service.notification.SystemZenRules
+import android.service.notification.ZenPolicy.VISUAL_EFFECT_NOTIFICATION_LIST
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.R
@@ -34,6 +35,7 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.shared.settings.data.repository.secureSettingsRepository
+import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
import com.android.systemui.testKosmos
@@ -379,4 +381,46 @@
assertThat(dndMode!!.isActive).isTrue()
}
+
+ @Test
+ @EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI, Flags.FLAG_MODES_API)
+ fun modesHidingNotifications_onlyIncludesModesWithNotifListSuppression() =
+ testScope.runTest {
+ val modesHidingNotifications by collectLastValue(underTest.modesHidingNotifications)
+
+ zenModeRepository.addModes(
+ listOf(
+ TestModeBuilder()
+ .setName("Not active, no list suppression")
+ .setActive(false)
+ .setVisualEffect(VISUAL_EFFECT_NOTIFICATION_LIST, /* allowed= */ true)
+ .build(),
+ TestModeBuilder()
+ .setName("Not active, has list suppression")
+ .setActive(false)
+ .setVisualEffect(VISUAL_EFFECT_NOTIFICATION_LIST, /* allowed= */ false)
+ .build(),
+ TestModeBuilder()
+ .setName("No list suppression")
+ .setActive(true)
+ .setVisualEffect(VISUAL_EFFECT_NOTIFICATION_LIST, /* allowed= */ true)
+ .build(),
+ TestModeBuilder()
+ .setName("Has list suppression 1")
+ .setActive(true)
+ .setVisualEffect(VISUAL_EFFECT_NOTIFICATION_LIST, /* allowed= */ false)
+ .build(),
+ TestModeBuilder()
+ .setName("Has list suppression 2")
+ .setActive(true)
+ .setVisualEffect(VISUAL_EFFECT_NOTIFICATION_LIST, /* allowed= */ false)
+ .build(),
+ )
+ )
+ runCurrent()
+
+ assertThat(modesHidingNotifications?.map { it.name })
+ .containsExactly("Has list suppression 1", "Has list suppression 2")
+ .inOrder()
+ }
}
diff --git a/packages/SystemUI/res/layout/status_bar_no_notifications.xml b/packages/SystemUI/res/layout/status_bar_no_notifications.xml
index 248e611..e26b855 100644
--- a/packages/SystemUI/res/layout/status_bar_no_notifications.xml
+++ b/packages/SystemUI/res/layout/status_bar_no_notifications.xml
@@ -26,6 +26,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
+ android:paddingHorizontal="8dp"
>
<TextView
android:id="@+id/no_notifications"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 24b6579..75389b1 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1473,6 +1473,16 @@
<!-- The text to show in the notifications shade when dnd is suppressing notifications. [CHAR LIMIT=100] -->
<string name="dnd_suppressing_shade_text">Notifications paused by Do Not Disturb</string>
+ <!-- The text to show in the notifications shade when a mode is suppressing notifications. [CHAR LIMIT=100] -->
+ <string name="modes_suppressing_shade_text">
+ {count, plural, offset:1
+ =0 {No notifications}
+ =1 {Notifications paused by {mode}}
+ =2 {Notifications paused by {mode} and one other mode}
+ other {Notifications paused by {mode} and # other modes}
+ }
+ </string>
+
<!-- Media projection permission dialog action text. [CHAR LIMIT=60] -->
<string name="media_projection_action_text">Start now</string>
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 0b775ab..820c102 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -35,6 +35,8 @@
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag
+import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor
import com.android.systemui.statusbar.notification.shared.NotificationAvalancheSuppression
import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
@@ -56,6 +58,8 @@
NotificationAvalancheSuppression.token dependsOn VisualInterruptionRefactor.token
PriorityPeopleSection.token dependsOn SortBySectionTimeFlag.token
NotificationMinimalismPrototype.token dependsOn NotificationThrottleHun.token
+ ModesEmptyShadeFix.token dependsOn FooterViewRefactor.token
+ ModesEmptyShadeFix.token dependsOn modesUi
// SceneContainer dependencies
SceneContainerFlag.getFlagDependencies().forEach { (alpha, beta) -> alpha dependsOn beta }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepository.kt
new file mode 100644
index 0000000..a8d0777
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepository.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.scene.data.repository
+
+import android.graphics.Region
+import android.view.ISystemGestureExclusionListener
+import android.view.IWindowManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+@SysUISingleton
+class SystemGestureExclusionRepository
+@Inject
+constructor(private val windowManager: IWindowManager) {
+
+ /**
+ * Returns [Flow] of the [Region] in which system gestures should be excluded on the display
+ * identified with [displayId].
+ */
+ fun exclusionRegion(displayId: Int): Flow<Region?> {
+ return conflatedCallbackFlow {
+ val listener =
+ object : ISystemGestureExclusionListener.Stub() {
+ override fun onSystemGestureExclusionChanged(
+ displayId: Int,
+ restrictedRegion: Region?,
+ unrestrictedRegion: Region?,
+ ) {
+ trySend(restrictedRegion)
+ }
+ }
+ windowManager.registerSystemGestureExclusionListener(listener, displayId)
+
+ awaitClose {
+ windowManager.unregisterSystemGestureExclusionListener(listener, displayId)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractor.kt
new file mode 100644
index 0000000..4cee874
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractor.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.scene.domain.interactor
+
+import android.graphics.Region
+import com.android.systemui.scene.data.repository.SystemGestureExclusionRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+class SystemGestureExclusionInteractor
+@Inject
+constructor(private val repository: SystemGestureExclusionRepository) {
+
+ /**
+ * Returns [Flow] of the [Region] in which system gestures should be excluded on the display
+ * identified with [displayId].
+ */
+ fun exclusionRegion(displayId: Int): Flow<Region?> {
+ return repository.exclusionRegion(displayId)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
index 7f35d73..a7e7d8b 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -107,7 +107,9 @@
view.viewModel(
traceName = "SceneWindowRootViewBinder",
minWindowLifecycleState = WindowLifecycleState.ATTACHED,
- factory = { viewModelFactory.create(motionEventHandlerReceiver) },
+ factory = {
+ viewModelFactory.create(view.context.displayId, motionEventHandlerReceiver)
+ },
) { viewModel ->
try {
view.setViewTreeOnBackPressedDispatcherOwner(
@@ -184,7 +186,7 @@
PlatformTheme {
ScreenDecorProvider(
displayCutout = displayCutoutFromWindowInsets(scope, context, windowInsets),
- screenCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
+ screenCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context),
) {
SceneContainer(
viewModel = viewModel,
@@ -205,9 +207,7 @@
): ComposeView {
return ComposeView(context).apply {
setContent {
- AlternateBouncer(
- alternateBouncerDependencies = alternateBouncerDependencies,
- )
+ AlternateBouncer(alternateBouncerDependencies = alternateBouncerDependencies)
}
}
}
@@ -234,14 +234,7 @@
else -> CutoutLocation.CENTER
}
val viewDisplayCutout = it?.displayCutout
- DisplayCutout(
- left,
- top,
- right,
- bottom,
- location,
- viewDisplayCutout,
- )
+ DisplayCutout(left, top, right, bottom, location, viewDisplayCutout)
}
.stateIn(scope, SharingStarted.WhileSubscribed(), DisplayCutout())
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerGestureFilter.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerGestureFilter.kt
new file mode 100644
index 0000000..a1d915a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerGestureFilter.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.scene.ui.viewmodel
+
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.geometry.Offset
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
+import com.android.systemui.scene.domain.interactor.SystemGestureExclusionInteractor
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlin.math.roundToInt
+
+/** Decides whether drag gestures should be filtered out in the scene container framework. */
+class SceneContainerGestureFilter
+@AssistedInject
+constructor(interactor: SystemGestureExclusionInteractor, @Assisted displayId: Int) :
+ ExclusiveActivatable() {
+
+ private val hydrator = Hydrator("SceneContainerGestureFilter.hydrator")
+ private val exclusionRegion by
+ hydrator.hydratedStateOf(
+ traceName = "exclusionRegion",
+ initialValue = null,
+ source = interactor.exclusionRegion(displayId),
+ )
+
+ override suspend fun onActivated(): Nothing {
+ hydrator.activate()
+ }
+
+ /**
+ * Returns `true` if a drag gesture starting at [startPosition] should be filtered out (e.g.
+ * ignored, `false` otherwise.
+ *
+ * Invoke this and pass in the position of the `ACTION_DOWN` pointer event that began the
+ * gesture.
+ */
+ fun shouldFilterGesture(startPosition: Offset): Boolean {
+ check(isActive) { "Must be activated to use!" }
+
+ return exclusionRegion?.contains(startPosition.x.roundToInt(), startPosition.y.roundToInt())
+ ?: false
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(displayId: Int): SceneContainerGestureFilter
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index af1f5a7..0bf2d49 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -18,6 +18,7 @@
import android.view.MotionEvent
import androidx.compose.runtime.getValue
+import androidx.compose.ui.geometry.Offset
import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.DefaultEdgeDetector
import com.android.compose.animation.scene.ObservableTransitionState
@@ -41,9 +42,12 @@
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
/** Models UI state for the scene container. */
class SceneContainerViewModel
@@ -55,6 +59,8 @@
shadeInteractor: ShadeInteractor,
private val splitEdgeDetector: SplitEdgeDetector,
private val logger: SceneLogger,
+ gestureFilterFactory: SceneContainerGestureFilter.Factory,
+ @Assisted displayId: Int,
@Assisted private val motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
) : ExclusiveActivatable() {
@@ -80,6 +86,8 @@
},
)
+ private val gestureFilter: SceneContainerGestureFilter = gestureFilterFactory.create(displayId)
+
override suspend fun onActivated(): Nothing {
try {
// Sends a MotionEventHandler to the owner of the view-model so they can report
@@ -96,7 +104,11 @@
}
)
- hydrator.activate()
+ coroutineScope {
+ launch { hydrator.activate() }
+ launch { gestureFilter.activate() }
+ }
+ awaitCancellation()
} finally {
// Clears the previously-sent MotionEventHandler so the owner of the view-model releases
// their reference to it.
@@ -243,6 +255,17 @@
}
}
+ /**
+ * Returns `true` if a drag gesture starting at [startPosition] should be filtered out (e.g.
+ * ignored, `false` otherwise.
+ *
+ * Invoke this and pass in the position of the `ACTION_DOWN` pointer event that began the
+ * gesture.
+ */
+ fun shouldFilterGesture(startPosition: Offset): Boolean {
+ return gestureFilter.shouldFilterGesture(startPosition)
+ }
+
/** Defines interface for classes that can handle externally-reported [MotionEvent]s. */
interface MotionEventHandler {
/** Notifies that a [MotionEvent] has occurred. */
@@ -258,7 +281,8 @@
@AssistedFactory
interface Factory {
fun create(
- motionEventHandlerReceiver: (MotionEventHandler?) -> Unit
+ displayId: Int,
+ motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
): SceneContainerViewModel
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.kt
similarity index 60%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.kt
index ec3c7d0..0f93b5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -13,37 +13,36 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.systemui.statusbar.notification
-package com.android.systemui.statusbar.notification;
-
-import android.content.Intent;
-import android.view.View;
-
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import android.content.Intent
+import android.view.View
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
/**
* Component responsible for handling actions on a notification which cause activites to start.
* (e.g. clicking on a notification, tapping on the settings icon in the notification guts)
*/
-public interface NotificationActivityStarter {
+interface NotificationActivityStarter {
/** Called when the user clicks on the notification bubble icon. */
- void onNotificationBubbleIconClicked(NotificationEntry entry);
+ fun onNotificationBubbleIconClicked(entry: NotificationEntry?)
/** Called when the user clicks on the surface of a notification. */
- void onNotificationClicked(NotificationEntry entry, ExpandableNotificationRow row);
+ fun onNotificationClicked(entry: NotificationEntry?, row: ExpandableNotificationRow?)
/** Called when the user clicks on a button in the notification guts which fires an intent. */
- void startNotificationGutsIntent(Intent intent, int appUid,
- ExpandableNotificationRow row);
+ fun startNotificationGutsIntent(intent: Intent?, appUid: Int, row: ExpandableNotificationRow?)
- /** Called when the user clicks "Manage" or "History" in the Shade. */
- void startHistoryIntent(View view, boolean showHistory);
+ /**
+ * Called when the user clicks "Manage" or "History" in the Shade, or the "No notifications"
+ * text.
+ */
+ fun startHistoryIntent(view: View?, showHistory: Boolean)
/** Called when the user succeed to drop notification to proper target view. */
- void onDragSuccess(NotificationEntry entry);
+ fun onDragSuccess(entry: NotificationEntry?)
- default boolean isCollapsingToShowActivityOverLockscreen() {
- return false;
- }
+ val isCollapsingToShowActivityOverLockscreen: Boolean
+ get() = false
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
index 0bbde21..82ce31b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
@@ -25,7 +25,6 @@
import androidx.annotation.NonNull;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.Flags;
import com.android.systemui.media.controls.util.MediaFeatureFlag;
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -61,8 +60,27 @@
return false;
}
- if (!Flags.notificationsBackgroundIcons()) {
- inflateOrUpdateIcons(entry);
+ switch (mIconsState.getOrDefault(entry, STATE_ICONS_UNINFLATED)) {
+ case STATE_ICONS_UNINFLATED:
+ try {
+ mIconManager.createIcons(entry);
+ mIconsState.put(entry, STATE_ICONS_INFLATED);
+ } catch (InflationException e) {
+ reportInflationError(entry, e);
+ mIconsState.put(entry, STATE_ICONS_ERROR);
+ }
+ break;
+ case STATE_ICONS_INFLATED:
+ try {
+ mIconManager.updateIcons(entry, /* usingCache = */ false);
+ } catch (InflationException e) {
+ reportInflationError(entry, e);
+ mIconsState.put(entry, STATE_ICONS_ERROR);
+ }
+ break;
+ case STATE_ICONS_ERROR:
+ // do nothing
+ break;
}
return true;
@@ -72,19 +90,7 @@
private final NotifCollectionListener mCollectionListener = new NotifCollectionListener() {
@Override
public void onEntryInit(@NonNull NotificationEntry entry) {
- // We default to STATE_ICONS_UNINFLATED anyway, so there's no need to initialize it.
- if (!Flags.notificationsBackgroundIcons()) {
- mIconsState.put(entry, STATE_ICONS_UNINFLATED);
- }
- }
-
- @Override
- public void onEntryAdded(@NonNull NotificationEntry entry) {
- if (Flags.notificationsBackgroundIcons()) {
- if (isMediaNotification(entry.getSbn())) {
- inflateOrUpdateIcons(entry);
- }
- }
+ mIconsState.put(entry, STATE_ICONS_UNINFLATED);
}
@Override
@@ -93,12 +99,6 @@
// The update may have fixed the inflation error, so give it another chance.
mIconsState.put(entry, STATE_ICONS_UNINFLATED);
}
-
- if (Flags.notificationsBackgroundIcons()) {
- if (isMediaNotification(entry.getSbn())) {
- inflateOrUpdateIcons(entry);
- }
- }
}
@Override
@@ -107,31 +107,6 @@
}
};
- private void inflateOrUpdateIcons(NotificationEntry entry) {
- switch (mIconsState.getOrDefault(entry, STATE_ICONS_UNINFLATED)) {
- case STATE_ICONS_UNINFLATED:
- try {
- mIconManager.createIcons(entry);
- mIconsState.put(entry, STATE_ICONS_INFLATED);
- } catch (InflationException e) {
- reportInflationError(entry, e);
- mIconsState.put(entry, STATE_ICONS_ERROR);
- }
- break;
- case STATE_ICONS_INFLATED:
- try {
- mIconManager.updateIcons(entry, /* usingCache = */ false);
- } catch (InflationException e) {
- reportInflationError(entry, e);
- mIconsState.put(entry, STATE_ICONS_ERROR);
- }
- break;
- case STATE_ICONS_ERROR:
- // do nothing
- break;
- }
- }
-
private void reportInflationError(NotificationEntry entry, Exception e) {
// This is the same logic as in PreparationCoordinator; it doesn't handle media
// notifications when the media feature is enabled since they aren't displayed in the shade,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/shared/ModesEmptyShadeFix.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/shared/ModesEmptyShadeFix.kt
new file mode 100644
index 0000000..f1fc275
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/shared/ModesEmptyShadeFix.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.emptyshade.shared
+
+import android.app.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the modes_ui_empty_shade flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object ModesEmptyShadeFix {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_MODES_UI_EMPTY_SHADE
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.modesUiEmptyShade()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is not enabled to ensure that the refactor author catches issues in testing.
+ * Caution!! Using this check incorrectly will cause crashes in nextfood builds!
+ */
+ @JvmStatic
+ inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/view/EmptyShadeView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/view/EmptyShadeView.java
index 850e944..73477da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/view/EmptyShadeView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/view/EmptyShadeView.java
@@ -22,6 +22,7 @@
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
@@ -29,26 +30,71 @@
import androidx.annotation.NonNull;
+import com.android.systemui.animation.LaunchableView;
+import com.android.systemui.animation.LaunchableViewDelegate;
import com.android.systemui.res.R;
+import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix;
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
-public class EmptyShadeView extends StackScrollerDecorView {
+import kotlin.Unit;
+
+import java.util.Objects;
+
+public class EmptyShadeView extends StackScrollerDecorView implements LaunchableView {
private TextView mEmptyText;
private TextView mEmptyFooterText;
- private @StringRes int mText = R.string.empty_shade_text;
+ private @StringRes int mTextId = R.string.empty_shade_text;
+ private String mTextString;
- private @DrawableRes int mFooterIcon = R.drawable.ic_friction_lock_closed;
- private @StringRes int mFooterText = R.string.unlock_to_see_notif_text;
+ private @DrawableRes int mFooterIcon;
+ private @StringRes int mFooterText;
+ // This view is initially gone in the xml.
private @Visibility int mFooterVisibility = View.GONE;
private int mSize;
+ private LaunchableViewDelegate mLaunchableViewDelegate = new LaunchableViewDelegate(this,
+ visibility -> {
+ super.setVisibility(visibility);
+ return Unit.INSTANCE;
+ });
+
public EmptyShadeView(Context context, AttributeSet attrs) {
super(context, attrs);
mSize = getResources().getDimensionPixelSize(
R.dimen.notifications_unseen_footer_icon_size);
+ if (ModesEmptyShadeFix.isEnabled()) {
+ mTextString = getContext().getString(R.string.empty_shade_text);
+ } else {
+ // These will be set by the binder when appropriate if ModesEmptyShadeFix is on.
+ mFooterIcon = R.drawable.ic_friction_lock_closed;
+ mFooterText = R.string.unlock_to_see_notif_text;
+ }
+ }
+
+ @Override
+ public void setVisibility(int visibility) {
+ mLaunchableViewDelegate.setVisibility(visibility);
+ }
+
+ @Override
+ public void setShouldBlockVisibilityChanges(boolean block) {
+ /* check if */ ModesEmptyShadeFix.isUnexpectedlyInLegacyMode();
+ mLaunchableViewDelegate.setShouldBlockVisibilityChanges(block);
+ }
+
+ @Override
+ public void onActivityLaunchAnimationEnd() {
+ /* check if */ ModesEmptyShadeFix.isUnexpectedlyInLegacyMode();
+ }
+
+ @Override
+ @NonNull
+ public Rect getPaddingForLaunchAnimation() {
+ /* check if */ ModesEmptyShadeFix.isUnexpectedlyInLegacyMode();
+ return new Rect();
}
@Override
@@ -56,7 +102,11 @@
super.onConfigurationChanged(newConfig);
mSize = getResources().getDimensionPixelSize(
R.dimen.notifications_unseen_footer_icon_size);
- mEmptyText.setText(mText);
+ if (ModesEmptyShadeFix.isEnabled()) {
+ mEmptyText.setText(mTextString);
+ } else {
+ mEmptyText.setText(mTextId);
+ }
mEmptyFooterText.setVisibility(mFooterVisibility);
setFooterText(mFooterText);
setFooterIcon(mFooterIcon);
@@ -72,25 +122,45 @@
return findViewById(R.id.no_notifications_footer);
}
+ /** Update view colors. */
public void setTextColors(@ColorInt int onSurface, @ColorInt int onSurfaceVariant) {
mEmptyText.setTextColor(onSurfaceVariant);
mEmptyFooterText.setTextColor(onSurface);
mEmptyFooterText.setCompoundDrawableTintList(ColorStateList.valueOf(onSurface));
}
+ /** Set the resource ID for the main text shown by the view. */
public void setText(@StringRes int text) {
- mText = text;
- mEmptyText.setText(mText);
+ ModesEmptyShadeFix.assertInLegacyMode();
+ mTextId = text;
+ mEmptyText.setText(mTextId);
}
+ /** Set the string for the main text shown by the view. */
+ public void setText(String text) {
+ if (ModesEmptyShadeFix.isUnexpectedlyInLegacyMode() || Objects.equals(mTextString, text)) {
+ return;
+ }
+ mTextString = text;
+ mEmptyText.setText(text);
+ }
+
+ /** Visibility for the footer (the additional icon+text shown below the main text). */
public void setFooterVisibility(@Visibility int visibility) {
+ if (ModesEmptyShadeFix.isEnabled() && mFooterVisibility == visibility) {
+ return; // nothing to change
+ }
mFooterVisibility = visibility;
setSecondaryVisible(/* visible = */ visibility == View.VISIBLE,
/* animate = */false,
/* onAnimationEnded = */ null);
}
+ /** Text resource ID for the footer (the additional icon+text shown below the main text). */
public void setFooterText(@StringRes int text) {
+ if (ModesEmptyShadeFix.isEnabled() && mFooterText == text) {
+ return; // nothing to change
+ }
mFooterText = text;
if (text != 0) {
mEmptyFooterText.setText(mFooterText);
@@ -99,7 +169,11 @@
}
}
+ /** Icon resource ID for the footer (the additional icon+text shown below the main text). */
public void setFooterIcon(@DrawableRes int icon) {
+ if (ModesEmptyShadeFix.isEnabled() && mFooterIcon == icon) {
+ return; // nothing to change
+ }
mFooterIcon = icon;
Drawable drawable;
if (icon == 0) {
@@ -111,18 +185,24 @@
mEmptyFooterText.setCompoundDrawablesRelative(drawable, null, null, null);
}
+ /** Get resource ID for main text. */
@StringRes
public int getTextResource() {
- return mText;
+ ModesEmptyShadeFix.assertInLegacyMode();
+ return mTextId;
}
+ /** Get resource ID for footer text. */
@StringRes
public int getFooterTextResource() {
+ ModesEmptyShadeFix.assertInLegacyMode();
return mFooterText;
}
+ /** Get resource ID for footer icon. */
@DrawableRes
public int getFooterIconResource() {
+ ModesEmptyShadeFix.assertInLegacyMode();
return mFooterIcon;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewbinder/EmptyShadeViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewbinder/EmptyShadeViewBinder.kt
new file mode 100644
index 0000000..102a11c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewbinder/EmptyShadeViewBinder.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.emptyshade.ui.viewbinder
+
+import android.view.View
+import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView
+import com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel.EmptyShadeViewModel
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+
+object EmptyShadeViewBinder {
+ suspend fun bind(
+ view: EmptyShadeView,
+ viewModel: EmptyShadeViewModel,
+ launchNotificationSettings: View.OnClickListener,
+ launchNotificationHistory: View.OnClickListener,
+ ) = coroutineScope {
+ launch { viewModel.text.collect { view.setText(it) } }
+
+ launch {
+ viewModel.tappingShouldLaunchHistory.collect { shouldLaunchHistory ->
+ if (shouldLaunchHistory) {
+ view.setOnClickListener(launchNotificationHistory)
+ } else {
+ view.setOnClickListener(launchNotificationSettings)
+ }
+ }
+ }
+
+ launch { bindFooter(view, viewModel) }
+ }
+
+ private suspend fun bindFooter(view: EmptyShadeView, viewModel: EmptyShadeViewModel) =
+ coroutineScope {
+ // Bind the resource IDs
+ view.setFooterText(viewModel.footer.messageId)
+ view.setFooterIcon(viewModel.footer.iconId)
+
+ launch {
+ viewModel.footer.isVisible.collect { visible ->
+ view.setFooterVisibility(if (visible) View.VISIBLE else View.GONE)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt
new file mode 100644
index 0000000..d5417e7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel
+
+import android.content.Context
+import android.icu.text.MessageFormat
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.modes.shared.ModesUi
+import com.android.systemui.res.R
+import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
+import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
+import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterMessageViewModel
+import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
+import com.android.systemui.util.kotlin.FlowDumperImpl
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.Locale
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+/**
+ * ViewModel for the empty shade (aka the "No notifications" text shown when there are no
+ * notifications.
+ */
+class EmptyShadeViewModel
+@AssistedInject
+constructor(
+ private val context: Context,
+ zenModeInteractor: ZenModeInteractor,
+ seenNotificationsInteractor: SeenNotificationsInteractor,
+ notificationSettingsInteractor: NotificationSettingsInteractor,
+ dumpManager: DumpManager,
+) : FlowDumperImpl(dumpManager) {
+ val areNotificationsHiddenInShade: Flow<Boolean> by lazy {
+ if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
+ flowOf(false)
+ } else {
+ zenModeInteractor.areNotificationsHiddenInShade.dumpWhileCollecting(
+ "areNotificationsHiddenInShade"
+ )
+ }
+ }
+
+ val hasFilteredOutSeenNotifications: StateFlow<Boolean> by lazy {
+ if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
+ MutableStateFlow(false)
+ } else {
+ seenNotificationsInteractor.hasFilteredOutSeenNotifications.dumpValue(
+ "hasFilteredOutSeenNotifications"
+ )
+ }
+ }
+
+ val text: Flow<String> by lazy {
+ if (ModesEmptyShadeFix.isUnexpectedlyInLegacyMode()) {
+ flowOf(context.getString(R.string.empty_shade_text))
+ } else {
+ // Note: Flag modes_ui_empty_shade includes two pieces: refactoring the empty shade to
+ // recommended architecture, and making it so it reacts to changes for the new Modes.
+ // The former does not depend on the modes flags being on, but the latter does.
+ if (ModesUi.isEnabled) {
+ zenModeInteractor.modesHidingNotifications.map { modes ->
+ // Create a string that is either "No notifications" if no modes are filtering
+ // them
+ // out, or something like "Notifications paused by SomeMode" otherwise.
+ val msgFormat =
+ MessageFormat(
+ context.getString(R.string.modes_suppressing_shade_text),
+ Locale.getDefault(),
+ )
+ val count = modes.count()
+ val args: MutableMap<String, Any> = HashMap()
+ args["count"] = count
+ if (count >= 1) {
+ args["mode"] = modes[0].name
+ }
+ msgFormat.format(args)
+ }
+ } else {
+ areNotificationsHiddenInShade.map { areNotificationsHiddenInShade ->
+ if (areNotificationsHiddenInShade) {
+ context.getString(R.string.dnd_suppressing_shade_text)
+ } else {
+ context.getString(R.string.empty_shade_text)
+ }
+ }
+ }
+ }
+ }
+
+ val footer: FooterMessageViewModel by lazy {
+ ModesEmptyShadeFix.assertInNewMode()
+ FooterMessageViewModel(
+ messageId = R.string.unlock_to_see_notif_text,
+ iconId = R.drawable.ic_friction_lock_closed,
+ isVisible = hasFilteredOutSeenNotifications,
+ )
+ }
+
+ val tappingShouldLaunchHistory by lazy {
+ ModesEmptyShadeFix.assertInNewMode()
+ notificationSettingsInteractor.isNotificationHistoryEnabled
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): EmptyShadeViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index 291dc13..cd228e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -72,14 +72,24 @@
}
/**
+ * See {@link #setVisible(boolean, boolean, Consumer)}.
+ */
+ public void setVisible(boolean visible, boolean animate) {
+ setVisible(visible, animate, null /* onAnimationEnded */);
+ }
+
+ /**
* Make this view visible. If {@code false} is passed, the view will fade out its content
* and set the view Visibility to GONE. If only the content should be changed,
* {@link #setContentVisibleAnimated(boolean)} can be used.
*
* @param visible True if the contents should be visible.
* @param animate True if we should fade to new visibility.
+ * @param onAnimationEnded Callback to run after visibility updates, takes a boolean as a
+ * parameter that represents whether the animation was cancelled.
*/
- public void setVisible(boolean visible, boolean animate) {
+ public void setVisible(boolean visible, boolean animate,
+ Consumer<Boolean> onAnimationEnded) {
if (mIsVisible != visible) {
mIsVisible = visible;
if (animate) {
@@ -90,10 +100,10 @@
} else {
setWillBeGone(true);
}
- setContentVisible(visible, true /* animate */, null /* onAnimationEnded */);
+ setContentVisible(visible, true /* animate */, onAnimationEnded);
} else {
setVisibility(visible ? VISIBLE : GONE);
- setContentVisible(visible, false /* animate */, null /* onAnimationEnded */);
+ setContentVisible(visible, false /* animate */, onAnimationEnded);
setWillBeGone(false);
notifyHeightChanged(false /* needsAnimation */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index cd3516d..b2b2c2a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -104,6 +104,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix;
import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView;
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
@@ -686,7 +687,9 @@
protected void onFinishInflate() {
super.onFinishInflate();
- inflateEmptyShadeView();
+ if (!ModesEmptyShadeFix.isEnabled()) {
+ inflateEmptyShadeView();
+ }
if (!FooterViewRefactor.isEnabled()) {
inflateFooterView();
}
@@ -729,7 +732,9 @@
inflateFooterView();
updateFooter();
}
- inflateEmptyShadeView();
+ if (!ModesEmptyShadeFix.isEnabled()) {
+ inflateEmptyShadeView();
+ }
mSectionsManager.reinflateViews();
}
@@ -4835,6 +4840,8 @@
/** Trigger an update for the empty shade resources and visibility. */
public void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade,
boolean hasFilteredOutSeenNotifications) {
+ ModesEmptyShadeFix.assertInLegacyMode();
+
mEmptyShadeView.setVisible(visible, mIsExpanded && mAnimationsEnabled);
if (areNotificationsHiddenInShade) {
@@ -4853,6 +4860,8 @@
@StringRes int newTextRes,
@StringRes int newFooterTextRes,
@DrawableRes int newFooterIconRes) {
+ ModesEmptyShadeFix.assertInLegacyMode();
+
int oldTextRes = mEmptyShadeView.getTextResource();
if (oldTextRes != newTextRes) {
mEmptyShadeView.setText(newTextRes);
@@ -4874,6 +4883,9 @@
public boolean isEmptyShadeViewVisible() {
SceneContainerFlag.assertInLegacyMode();
+ if (mEmptyShadeView == null) {
+ return false;
+ }
return mEmptyShadeView.isVisible();
}
@@ -5361,7 +5373,7 @@
public float getOpeningHeight() {
SceneContainerFlag.assertInLegacyMode();
- if (mEmptyShadeView.getVisibility() == GONE) {
+ if (mEmptyShadeView == null || mEmptyShadeView.getVisibility() == GONE) {
return getMinExpansionHeight();
} else {
return FooterViewRefactor.isEnabled() ? getAppearEndPosition()
@@ -5710,6 +5722,8 @@
}
private void inflateEmptyShadeView() {
+ ModesEmptyShadeFix.assertInLegacyMode();
+
EmptyShadeView oldView = mEmptyShadeView;
EmptyShadeView view = (EmptyShadeView) LayoutInflater.from(mContext).inflate(
R.layout.status_bar_no_notifications, this, false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index dc9615c..3dad326 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.stack.ui.viewbinder
import android.view.LayoutInflater
+import android.view.View
import androidx.lifecycle.lifecycleScope
import com.android.app.tracing.TraceUtils.traceAsync
import com.android.internal.logging.MetricsLogger
@@ -25,6 +26,7 @@
import com.android.systemui.common.ui.view.setImportantForAccessibilityYesNo
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.lifecycle.repeatWhenAttachedToWindow
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -32,6 +34,10 @@
import com.android.systemui.statusbar.notification.NotificationActivityStarter
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController
import com.android.systemui.statusbar.notification.dagger.SilentHeader
+import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
+import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView
+import com.android.systemui.statusbar.notification.emptyshade.ui.viewbinder.EmptyShadeViewBinder
+import com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel.EmptyShadeViewModel
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder
@@ -49,6 +55,7 @@
import com.android.systemui.util.kotlin.awaitCancellationThenDispose
import com.android.systemui.util.kotlin.getOrNull
import com.android.systemui.util.ui.isAnimating
+import com.android.systemui.util.ui.stopAnimating
import com.android.systemui.util.ui.value
import java.util.Optional
import javax.inject.Inject
@@ -84,7 +91,7 @@
fun bindWhileAttached(
view: NotificationStackScrollLayout,
- viewController: NotificationStackScrollLayoutController
+ viewController: NotificationStackScrollLayoutController,
) {
val shelf =
LayoutInflater.from(view.context)
@@ -103,7 +110,13 @@
val hasNonClearableSilentNotifications: StateFlow<Boolean> =
viewModel.hasNonClearableSilentNotifications.stateIn(this)
launch { reinflateAndBindFooter(view, hasNonClearableSilentNotifications) }
- launch { bindEmptyShade(view) }
+ launch {
+ if (ModesEmptyShadeFix.isEnabled) {
+ reinflateAndBindEmptyShade(view)
+ } else {
+ bindEmptyShadeLegacy(viewModel.emptyShadeViewFactory.create(), view)
+ }
+ }
launch {
bindSilentHeaderClickListener(view, hasNonClearableSilentNotifications)
}
@@ -121,17 +134,12 @@
}
private suspend fun bindShelf(shelf: NotificationShelf) {
- NotificationShelfViewBinder.bind(
- shelf,
- viewModel.shelf,
- falsingManager,
- nicBinder,
- )
+ NotificationShelfViewBinder.bind(shelf, viewModel.shelf, falsingManager, nicBinder)
}
private suspend fun reinflateAndBindFooter(
parentView: NotificationStackScrollLayout,
- hasNonClearableSilentNotifications: StateFlow<Boolean>
+ hasNonClearableSilentNotifications: StateFlow<Boolean>,
) {
viewModel.footer.getOrNull()?.let { footerViewModel ->
// The footer needs to be re-inflated every time the theme or the font size changes.
@@ -149,7 +157,7 @@
footerView,
footerViewModel,
parentView,
- hasNonClearableSilentNotifications
+ hasNonClearableSilentNotifications,
)
}
}
@@ -163,13 +171,13 @@
footerView: FooterView,
footerViewModel: FooterViewModel,
parentView: NotificationStackScrollLayout,
- hasNonClearableSilentNotifications: StateFlow<Boolean>
+ hasNonClearableSilentNotifications: StateFlow<Boolean>,
): Unit = coroutineScope {
val disposableHandle =
FooterViewBinder.bindWhileAttached(
footerView,
footerViewModel,
- clearAllNotifications = {
+ {
clearAllNotifications(
parentView,
// Hide the silent section header (if present) if there will be
@@ -177,16 +185,8 @@
hideSilentSection = !hasNonClearableSilentNotifications.value,
)
},
- launchNotificationSettings = { view ->
- notificationActivityStarter
- .get()
- .startHistoryIntent(view, /* showHistory= */ false)
- },
- launchNotificationHistory = { view ->
- notificationActivityStarter
- .get()
- .startHistoryIntent(view, /* showHistory= */ true)
- },
+ launchNotificationSettings,
+ launchNotificationHistory,
)
if (SceneContainerFlag.isEnabled) {
launch {
@@ -194,7 +194,9 @@
footerView.setVisible(
/* visible = */ animatedVisibility.value,
/* animate = */ animatedVisibility.isAnimating,
- )
+ ) {
+ animatedVisibility.stopAnimating()
+ }
}
}
} else {
@@ -211,20 +213,71 @@
disposableHandle.awaitCancellationThenDispose()
}
- private suspend fun bindEmptyShade(parentView: NotificationStackScrollLayout) {
+ private val launchNotificationSettings: (View) -> Unit = { view: View ->
+ notificationActivityStarter.get().startHistoryIntent(view, /* showHistory= */ false)
+ }
+
+ private val launchNotificationHistory: (View) -> Unit = { view ->
+ notificationActivityStarter.get().startHistoryIntent(view, /* showHistory= */ true)
+ }
+
+ private suspend fun reinflateAndBindEmptyShade(parentView: NotificationStackScrollLayout) {
+ ModesEmptyShadeFix.assertInNewMode()
+ // The empty shade needs to be re-inflated every time the theme or the font size
+ // changes.
+ configuration
+ .inflateLayout<EmptyShadeView>(
+ R.layout.status_bar_no_notifications,
+ parentView,
+ attachToRoot = false,
+ )
+ .flowOn(backgroundDispatcher)
+ .collectLatest { emptyShadeView: EmptyShadeView ->
+ traceAsync("bind EmptyShadeView") {
+ parentView.setEmptyShadeView(emptyShadeView)
+ bindEmptyShade(emptyShadeView, viewModel.emptyShadeViewFactory.create())
+ }
+ }
+ }
+
+ private suspend fun bindEmptyShadeLegacy(
+ emptyShadeViewModel: EmptyShadeViewModel,
+ parentView: NotificationStackScrollLayout,
+ ) {
+ ModesEmptyShadeFix.assertInLegacyMode()
combine(
viewModel.shouldShowEmptyShadeView,
- viewModel.areNotificationsHiddenInShade,
- viewModel.hasFilteredOutSeenNotifications,
- ::Triple
+ emptyShadeViewModel.areNotificationsHiddenInShade,
+ emptyShadeViewModel.hasFilteredOutSeenNotifications,
+ ::Triple,
)
.collect { (shouldShow, areNotifsHidden, hasFilteredNotifs) ->
- parentView.updateEmptyShadeView(
- shouldShow,
- areNotifsHidden,
- hasFilteredNotifs,
+ parentView.updateEmptyShadeView(shouldShow, areNotifsHidden, hasFilteredNotifs)
+ }
+ }
+
+ private suspend fun bindEmptyShade(
+ emptyShadeView: EmptyShadeView,
+ emptyShadeViewModel: EmptyShadeViewModel,
+ ): Unit = coroutineScope {
+ ModesEmptyShadeFix.assertInNewMode()
+ launch {
+ emptyShadeView.repeatWhenAttachedToWindow {
+ EmptyShadeViewBinder.bind(
+ emptyShadeView,
+ emptyShadeViewModel,
+ launchNotificationSettings,
+ launchNotificationHistory,
)
}
+ }
+ launch {
+ viewModel.shouldShowEmptyShadeViewAnimated.collect { shouldShow ->
+ emptyShadeView.setVisible(shouldShow.value, shouldShow.isAnimating) {
+ shouldShow.stopAnimating()
+ }
+ }
+ }
}
private suspend fun bindSilentHeaderClickListener(
@@ -261,7 +314,7 @@
private fun clearSilentNotifications(
view: NotificationStackScrollLayout,
closeShade: Boolean,
- hideSilentSection: Boolean
+ hideSilentSection: Boolean,
) {
view.clearSilentNotifications(closeShade, hideSilentSection)
}
@@ -270,11 +323,7 @@
if (NotificationsLiveDataStoreRefactor.isEnabled) {
viewModel.logger.getOrNull()?.let { viewModel ->
loggerOptional.getOrNull()?.let { logger ->
- NotificationStatsLoggerBinder.bindLogger(
- view,
- logger,
- viewModel,
- )
+ NotificationStatsLoggerBinder.bindLogger(view, logger, viewModel)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index 4e2a46d..935e2a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -23,14 +23,14 @@
import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
-import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
+import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
+import com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel.EmptyShadeViewModel
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel
import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey
import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackInteractor
import com.android.systemui.statusbar.policy.domain.interactor.UserSetupInteractor
-import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
import com.android.systemui.util.kotlin.FlowDumperImpl
import com.android.systemui.util.kotlin.combine
import com.android.systemui.util.kotlin.sample
@@ -48,22 +48,24 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
-/** ViewModel for the list of notifications. */
+/**
+ * ViewModel for the list of notifications, including child elements like the Clear all/Manage
+ * button at the bottom (the footer) and the "No notifications" text (the empty shade).
+ */
class NotificationListViewModel
@Inject
constructor(
val shelf: NotificationShelfViewModel,
val hideListViewModel: HideListViewModel,
val footer: Optional<FooterViewModel>,
+ val emptyShadeViewFactory: EmptyShadeViewModel.Factory,
val logger: Optional<NotificationLoggerViewModel>,
activeNotificationsInteractor: ActiveNotificationsInteractor,
notificationStackInteractor: NotificationStackInteractor,
private val headsUpNotificationInteractor: HeadsUpNotificationInteractor,
remoteInputInteractor: RemoteInputInteractor,
- seenNotificationsInteractor: SeenNotificationsInteractor,
shadeInteractor: ShadeInteractor,
userSetupInteractor: UserSetupInteractor,
- zenModeInteractor: ZenModeInteractor,
@Background bgDispatcher: CoroutineDispatcher,
dumpManager: DumpManager,
) : FlowDumperImpl(dumpManager) {
@@ -90,6 +92,7 @@
}
val shouldShowEmptyShadeView: Flow<Boolean> by lazy {
+ ModesEmptyShadeFix.assertInLegacyMode()
if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
flowOf(false)
} else {
@@ -114,6 +117,45 @@
}
}
+ val shouldShowEmptyShadeViewAnimated: Flow<AnimatedValue<Boolean>> by lazy {
+ if (ModesEmptyShadeFix.isUnexpectedlyInLegacyMode()) {
+ flowOf(AnimatedValue.NotAnimating(false))
+ } else {
+ combine(
+ activeNotificationsInteractor.areAnyNotificationsPresent,
+ shadeInteractor.isQsFullscreen,
+ notificationStackInteractor.isShowingOnLockscreen,
+ ) { hasNotifications, isQsFullScreen, isShowingOnLockscreen ->
+ when {
+ hasNotifications -> false
+ isQsFullScreen -> false
+ // Do not show the empty shade if the lockscreen is visible (including AOD
+ // b/228790482 and bouncer b/267060171), except if the shade is opened on
+ // top.
+ isShowingOnLockscreen -> false
+ else -> true
+ }
+ }
+ .distinctUntilChanged()
+ .sample(
+ // TODO(b/322167853): This check is currently duplicated in FooterViewModel
+ // but instead it should be a field in ShadeAnimationInteractor.
+ combine(
+ shadeInteractor.isShadeFullyExpanded,
+ shadeInteractor.isShadeTouchable,
+ ::Pair,
+ )
+ .onStart { emit(Pair(false, false)) }
+ ) { visible, (isShadeFullyExpanded, animationsEnabled) ->
+ val shouldAnimate = isShadeFullyExpanded && animationsEnabled
+ AnimatableEvent(visible, shouldAnimate)
+ }
+ .toAnimatedValueFlow()
+ .dumpWhileCollecting("shouldShowEmptyShadeViewAnimated")
+ .flowOn(bgDispatcher)
+ }
+ }
+
/**
* Whether the footer should not be visible for the user, even if it's present in the list (as
* per [shouldIncludeFooterView] below).
@@ -154,7 +196,7 @@
userSetupInteractor.isUserSetUp,
notificationStackInteractor.isShowingOnLockscreen,
shadeInteractor.isQsFullscreen,
- remoteInputInteractor.isRemoteInputActive
+ remoteInputInteractor.isRemoteInputActive,
) {
hasNotifications,
isUserSetUp,
@@ -193,7 +235,7 @@
combine(
shadeInteractor.isShadeFullyExpanded,
shadeInteractor.isShadeTouchable,
- ::Pair
+ ::Pair,
)
.onStart { emit(Pair(false, false)) }
) { visibilityChange, (isShadeFullyExpanded, animationsEnabled) ->
@@ -263,7 +305,7 @@
combine(
shadeInteractor.isShadeFullyExpanded,
shadeInteractor.isShadeTouchable,
- ::Pair
+ ::Pair,
)
.onStart { emit(Pair(false, false)) }
) { visibilityChange, (isShadeFullyExpanded, animationsEnabled) ->
@@ -283,29 +325,7 @@
enum class VisibilityChange(val visible: Boolean, val canAnimate: Boolean) {
DISAPPEAR_WITHOUT_ANIMATION(visible = false, canAnimate = false),
DISAPPEAR_WITH_ANIMATION(visible = false, canAnimate = true),
- APPEAR_WITH_ANIMATION(visible = true, canAnimate = true)
- }
-
- // TODO(b/308591475): This should be tracked separately by the empty shade.
- val areNotificationsHiddenInShade: Flow<Boolean> by lazy {
- if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
- flowOf(false)
- } else {
- zenModeInteractor.areNotificationsHiddenInShade.dumpWhileCollecting(
- "areNotificationsHiddenInShade"
- )
- }
- }
-
- // TODO(b/308591475): This should be tracked separately by the empty shade.
- val hasFilteredOutSeenNotifications: Flow<Boolean> by lazy {
- if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
- flowOf(false)
- } else {
- seenNotificationsInteractor.hasFilteredOutSeenNotifications.dumpWhileCollecting(
- "hasFilteredOutSeenNotifications"
- )
- }
+ APPEAR_WITH_ANIMATION(visible = true, canAnimate = true),
}
val hasClearableAlertingNotifications: Flow<Boolean> by lazy {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
index ba45942..daba109 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
@@ -20,6 +20,7 @@
import android.provider.Settings
import android.provider.Settings.Secure.ZEN_DURATION_FOREVER
import android.provider.Settings.Secure.ZEN_DURATION_PROMPT
+import android.service.notification.ZenPolicy.VISUAL_EFFECT_NOTIFICATION_LIST
import android.util.Log
import androidx.concurrent.futures.await
import com.android.settingslib.notification.data.repository.ZenModeRepository
@@ -29,6 +30,7 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.modes.shared.ModesUi
import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
+import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository
import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
import com.android.systemui.statusbar.policy.domain.model.ActiveZenModes
@@ -39,6 +41,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
@@ -54,8 +57,8 @@
private val notificationSettingsRepository: NotificationSettingsRepository,
@Background private val bgDispatcher: CoroutineDispatcher,
private val iconLoader: ZenIconLoader,
- private val deviceProvisioningRepository: DeviceProvisioningRepository,
- private val userSetupRepository: UserSetupRepository,
+ deviceProvisioningRepository: DeviceProvisioningRepository,
+ userSetupRepository: UserSetupRepository,
) {
val isZenAvailable: Flow<Boolean> =
combine(
@@ -126,6 +129,25 @@
val mainActiveMode: Flow<ZenModeInfo?> =
activeModes.map { a -> a.mainMode }.distinctUntilChanged()
+ val modesHidingNotifications: Flow<List<ZenMode>> by lazy {
+ if (ModesEmptyShadeFix.isUnexpectedlyInLegacyMode() || !ModesUi.isEnabled) {
+ flowOf(listOf())
+ } else {
+ modes
+ .map { modes ->
+ modes.filter { mode ->
+ mode.isActive &&
+ !mode.policy.isVisualEffectAllowed(
+ /* effect = */ VISUAL_EFFECT_NOTIFICATION_LIST,
+ /* defaultVal = */ true,
+ )
+ }
+ }
+ .flowOn(bgDispatcher)
+ .distinctUntilChanged()
+ }
+ }
+
suspend fun getModeIcon(mode: ZenMode): ZenIcon {
return iconLoader.getIcon(context, mode).await()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt
index 71afa62..5cc6454 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt
@@ -62,6 +62,7 @@
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.domain.startable.sceneContainerStartable
+import com.android.systemui.scene.sceneContainerGestureFilterFactory
import com.android.systemui.scene.shared.logger.sceneLogger
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
@@ -70,6 +71,7 @@
import com.android.systemui.scene.ui.composable.SceneContainer
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import com.android.systemui.scene.ui.viewmodel.splitEdgeDetector
+import com.android.systemui.settings.displayTracker
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.testKosmos
import kotlin.time.Duration.Companion.seconds
@@ -133,6 +135,8 @@
shadeInteractor = kosmos.shadeInteractor,
splitEdgeDetector = kosmos.splitEdgeDetector,
logger = kosmos.sceneLogger,
+ gestureFilterFactory = kosmos.sceneContainerGestureFilterFactory,
+ displayId = kosmos.displayTracker.defaultDisplayId,
motionEventHandlerReceiver = {},
)
.apply { setTransitionState(transitionState) }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
index 07c29a0..0c65c9c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
@@ -20,7 +20,6 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
@@ -30,15 +29,12 @@
import android.app.Notification.MediaStyle;
import android.media.session.MediaSession;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
import android.service.notification.NotificationListenerService;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.media.controls.util.MediaFeatureFlag;
import com.android.systemui.statusbar.notification.InflationException;
@@ -158,8 +154,7 @@
}
@Test
- @DisableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS)
- public void inflateMediaNotificationIconsMediaEnabled_old() throws InflationException {
+ public void inflateMediaNotificationIconsMediaEnabled() throws InflationException {
finishSetupWithMediaFeatureFlagEnabled(true);
mListener.onEntryInit(mMediaEntry);
@@ -187,37 +182,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS)
- public void inflateMediaNotificationIconsMediaEnabled_new() throws InflationException {
- finishSetupWithMediaFeatureFlagEnabled(true);
-
- mListener.onEntryInit(mMediaEntry);
- mListener.onEntryAdded(mMediaEntry);
- verify(mIconManager).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
- clearInvocations(mIconManager);
-
- mFilter.shouldFilterOut(mMediaEntry, 0);
- verify(mIconManager, never()).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
-
- mListener.onEntryUpdated(mMediaEntry);
- verify(mIconManager, never()).createIcons(eq(mMediaEntry));
- verify(mIconManager).updateIcons(eq(mMediaEntry), /* usingCache = */ eq(false));
-
- mListener.onEntryRemoved(mMediaEntry, NotificationListenerService.REASON_CANCEL);
- mListener.onEntryCleanUp(mMediaEntry);
- clearInvocations(mIconManager);
-
- mListener.onEntryInit(mMediaEntry);
- mListener.onEntryAdded(mMediaEntry);
- verify(mIconManager).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
- }
-
- @Test
- @DisableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS)
- public void inflationException_old() throws InflationException {
+ public void inflationException() throws InflationException {
finishSetupWithMediaFeatureFlagEnabled(true);
mListener.onEntryInit(mMediaEntry);
@@ -244,31 +209,6 @@
verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
}
- @Test
- @EnableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS)
- public void inflationException_new() throws InflationException {
- finishSetupWithMediaFeatureFlagEnabled(true);
-
- doThrow(InflationException.class).when(mIconManager).createIcons(eq(mMediaEntry));
-
- mListener.onEntryInit(mMediaEntry);
- mListener.onEntryAdded(mMediaEntry);
- verify(mIconManager).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
- clearInvocations(mIconManager);
-
- mListener.onEntryUpdated(mMediaEntry);
- verify(mIconManager).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
- clearInvocations(mIconManager);
-
- doNothing().when(mIconManager).createIcons(eq(mMediaEntry));
-
- mListener.onEntryUpdated(mMediaEntry);
- verify(mIconManager).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
- }
-
private void finishSetupWithMediaFeatureFlagEnabled(boolean mediaFeatureFlagEnabled) {
when(mMediaFeatureFlag.getEnabled()).thenReturn(mediaFeatureFlagEnabled);
mCoordinator = new MediaCoordinator(mMediaFeatureFlag, mStatusBarService, mIconManager);
diff --git a/packages/SystemUI/tests/utils/src/android/view/WindowManagerServiceKosmos.kt b/packages/SystemUI/tests/utils/src/android/view/WindowManagerServiceKosmos.kt
new file mode 100644
index 0000000..cd681a1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/view/WindowManagerServiceKosmos.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.view
+
+import android.graphics.Region
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mockito.mock
+import org.mockito.kotlin.any
+import org.mockito.kotlin.whenever
+
+val Kosmos.mockWindowManagerService: IWindowManager by
+ Kosmos.Fixture {
+ mock(IWindowManager::class.java).apply {
+ whenever(registerSystemGestureExclusionListener(any(), anyInt())).then { answer ->
+ val listener = answer.arguments[0] as ISystemGestureExclusionListener
+ val displayId = answer.arguments[1] as Int
+ exclusionListeners.getOrPut(displayId) { mutableListOf() }.add(listener)
+ listener.onSystemGestureExclusionChanged(
+ displayId,
+ restrictedRegionByDisplayId[displayId],
+ null,
+ )
+ }
+
+ whenever(unregisterSystemGestureExclusionListener(any(), anyInt())).then { answer ->
+ val listener = answer.arguments[0] as ISystemGestureExclusionListener
+ val displayId = answer.arguments[1] as Int
+ exclusionListeners[displayId]?.remove(listener)
+ }
+ }
+ }
+
+var Kosmos.windowManagerService: IWindowManager by Kosmos.Fixture { mockWindowManagerService }
+
+private var restrictedRegionByDisplayId = mutableMapOf<Int, Region?>()
+private var exclusionListeners = mutableMapOf<Int, MutableList<ISystemGestureExclusionListener>>()
+
+fun setSystemGestureExclusionRegion(displayId: Int, restrictedRegion: Region?) {
+ restrictedRegionByDisplayId[displayId] = restrictedRegion
+ exclusionListeners[displayId]?.forEach { listener ->
+ listener.onSystemGestureExclusionChanged(displayId, restrictedRegion, null)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
index 8744638..737aaf2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
@@ -6,13 +6,16 @@
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.domain.interactor.systemGestureExclusionInteractor
import com.android.systemui.scene.shared.logger.sceneLogger
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.FakeOverlay
+import com.android.systemui.scene.ui.viewmodel.SceneContainerGestureFilter
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import com.android.systemui.scene.ui.viewmodel.splitEdgeDetector
+import com.android.systemui.settings.displayTracker
import com.android.systemui.shade.domain.interactor.shadeInteractor
import kotlinx.coroutines.flow.MutableStateFlow
@@ -30,10 +33,7 @@
val Kosmos.initialSceneKey by Fixture { Scenes.Lockscreen }
var Kosmos.overlayKeys by Fixture {
- listOf(
- Overlays.NotificationsShade,
- Overlays.QuickSettingsShade,
- )
+ listOf(Overlays.NotificationsShade, Overlays.QuickSettingsShade)
}
val Kosmos.fakeOverlaysByKeys by Fixture { overlayKeys.associateWith { FakeOverlay(it) } }
@@ -74,8 +74,21 @@
powerInteractor = powerInteractor,
shadeInteractor = shadeInteractor,
splitEdgeDetector = splitEdgeDetector,
+ gestureFilterFactory = sceneContainerGestureFilterFactory,
+ displayId = displayTracker.defaultDisplayId,
motionEventHandlerReceiver = {},
- logger = sceneLogger
+ logger = sceneLogger,
)
.apply { setTransitionState(transitionState) }
}
+
+val Kosmos.sceneContainerGestureFilterFactory by Fixture {
+ object : SceneContainerGestureFilter.Factory {
+ override fun create(displayId: Int): SceneContainerGestureFilter {
+ return SceneContainerGestureFilter(
+ interactor = systemGestureExclusionInteractor,
+ displayId = displayId,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
new file mode 100644
index 0000000..15ed1b3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.scene.data.repository
+
+import android.view.windowManagerService
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.systemGestureExclusionRepository by Fixture {
+ SystemGestureExclusionRepository(windowManager = windowManagerService)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractorKosmos.kt
new file mode 100644
index 0000000..3e46c3f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractorKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.scene.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.scene.data.repository.systemGestureExclusionRepository
+
+val Kosmos.systemGestureExclusionInteractor by Fixture {
+ SystemGestureExclusionInteractor(repository = systemGestureExclusionRepository)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelKosmos.kt
new file mode 100644
index 0000000..8fdb948
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelKosmos.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.shared.notifications.domain.interactor.notificationSettingsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
+
+val Kosmos.emptyShadeViewModel by
+ Kosmos.Fixture {
+ EmptyShadeViewModel(
+ applicationContext,
+ zenModeInteractor,
+ seenNotificationsInteractor,
+ notificationSettingsInteractor,
+ dumpManager,
+ )
+ }
+
+val Kosmos.emptyShadeViewModelFactory: EmptyShadeViewModel.Factory by
+ Kosmos.Fixture {
+ object : EmptyShadeViewModel.Factory {
+ override fun create() = emptyShadeViewModel
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
index de8b350..c3bc744 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
@@ -23,13 +23,12 @@
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
-import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
+import com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel.emptyShadeViewModelFactory
import com.android.systemui.statusbar.notification.footer.ui.viewmodel.footerViewModel
import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.notificationShelfViewModel
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackInteractor
import com.android.systemui.statusbar.policy.domain.interactor.userSetupInteractor
-import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
import java.util.Optional
val Kosmos.notificationListViewModel by Fixture {
@@ -37,15 +36,14 @@
notificationShelfViewModel,
hideListViewModel,
Optional.of(footerViewModel),
+ emptyShadeViewModelFactory,
Optional.of(notificationListLoggerViewModel),
activeNotificationsInteractor,
notificationStackInteractor,
headsUpNotificationInteractor,
remoteInputInteractor,
- seenNotificationsInteractor,
shadeInteractor,
userSetupInteractor,
- zenModeInteractor,
testDispatcher,
dumpManager,
)
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 35323d6..ad72941 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -262,7 +262,6 @@
import android.content.AttributionSource;
import android.content.AutofillOptions;
import android.content.BroadcastReceiver;
-import android.content.ClipData;
import android.content.ComponentCallbacks2;
import android.content.ComponentName;
import android.content.ContentCaptureOptions;
@@ -420,6 +419,7 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.MemInfoReader;
import com.android.internal.util.Preconditions;
+import com.android.server.crashrecovery.CrashRecoveryHelper;
import com.android.server.AlarmManagerInternal;
import com.android.server.BootReceiver;
import com.android.server.DeviceIdleInternal;
@@ -439,7 +439,6 @@
import com.android.server.appop.AppOpsService;
import com.android.server.compat.PlatformCompat;
import com.android.server.contentcapture.ContentCaptureManagerInternal;
-import com.android.server.crashrecovery.CrashRecoveryHelper;
import com.android.server.criticalevents.CriticalEventLog;
import com.android.server.firewall.IntentFirewall;
import com.android.server.graphics.fonts.FontManagerInternal;
@@ -484,7 +483,6 @@
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
@@ -502,7 +500,6 @@
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
@@ -19100,87 +19097,4 @@
Freezer getFreezer() {
return mFreezer;
}
-
- // Set of IntentCreatorToken objects that are currently active.
- private static final Map<IntentCreatorToken.Key, WeakReference<IntentCreatorToken>>
- sIntentCreatorTokenCache = new ConcurrentHashMap<>();
-
- /**
- * A binder token used to keep track of which app created the intent. This token can be used to
- * defend against intent redirect attacks. It stores uid of the intent creator and key fields of
- * the intent to make it impossible for attacker to fake uid with a malicious intent.
- *
- * @hide
- */
- public static final class IntentCreatorToken extends Binder {
- @NonNull
- private final Key mKeyFields;
-
- public IntentCreatorToken(int creatorUid, Intent intent) {
- super();
- this.mKeyFields = new Key(creatorUid, intent);
- }
-
- public int getCreatorUid() {
- return mKeyFields.mCreatorUid;
- }
-
- /** {@hide} */
- public static boolean isValid(@NonNull Intent intent) {
- IBinder binder = intent.getCreatorToken();
- IntentCreatorToken token = null;
- if (binder instanceof IntentCreatorToken) {
- token = (IntentCreatorToken) binder;
- }
- return token != null && token.mKeyFields.equals(
- new Key(token.mKeyFields.mCreatorUid, intent));
- }
-
- private static class Key {
- private Key(int creatorUid, Intent intent) {
- this.mCreatorUid = creatorUid;
- this.mAction = intent.getAction();
- this.mData = intent.getData();
- this.mType = intent.getType();
- this.mPackage = intent.getPackage();
- this.mComponent = intent.getComponent();
- this.mFlags = intent.getFlags() & Intent.IMMUTABLE_FLAGS;
- ClipData clipData = intent.getClipData();
- if (clipData != null) {
- this.mClipDataUris = new ArrayList<>(clipData.getItemCount());
- for (int i = 0; i < clipData.getItemCount(); i++) {
- this.mClipDataUris.add(clipData.getItemAt(i).getUri());
- }
- }
- }
-
- private final int mCreatorUid;
- private final String mAction;
- private final Uri mData;
- private final String mType;
- private final String mPackage;
- private final ComponentName mComponent;
- private final int mFlags;
- private List<Uri> mClipDataUris;
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- Key key = (Key) o;
- return mCreatorUid == key.mCreatorUid && mFlags == key.mFlags && Objects.equals(
- mAction, key.mAction) && Objects.equals(mData, key.mData)
- && Objects.equals(mType, key.mType) && Objects.equals(mPackage,
- key.mPackage) && Objects.equals(mComponent, key.mComponent)
- && Objects.equals(mClipDataUris, key.mClipDataUris);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mCreatorUid, mAction, mData, mType, mPackage, mComponent,
- mFlags,
- mClipDataUris);
- }
- }
- }
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index dc263c5..af9c9ac 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -1080,7 +1080,7 @@
*/
public float[] getNits() {
if (mEvenDimmerBrightnessData != null) {
- return mEvenDimmerBrightnessData.mNits;
+ return mEvenDimmerBrightnessData.nits;
}
return mNits;
}
@@ -1093,7 +1093,7 @@
@VisibleForTesting
public float[] getBacklight() {
if (mEvenDimmerBrightnessData != null) {
- return mEvenDimmerBrightnessData.mBacklight;
+ return mEvenDimmerBrightnessData.backlight;
}
return mBacklight;
}
@@ -1107,7 +1107,7 @@
*/
public float getBacklightFromBrightness(float brightness) {
if (mEvenDimmerBrightnessData != null) {
- return mEvenDimmerBrightnessData.mBrightnessToBacklight.interpolate(brightness);
+ return mEvenDimmerBrightnessData.brightnessToBacklight.interpolate(brightness);
}
return mBrightnessToBacklightSpline.interpolate(brightness);
}
@@ -1120,7 +1120,7 @@
*/
public float getBrightnessFromBacklight(float backlight) {
if (mEvenDimmerBrightnessData != null) {
- return mEvenDimmerBrightnessData.mBacklightToBrightness.interpolate(backlight);
+ return mEvenDimmerBrightnessData.backlightToBrightness.interpolate(backlight);
}
return mBacklightToBrightnessSpline.interpolate(backlight);
}
@@ -1131,7 +1131,7 @@
*/
private Spline getBacklightToBrightnessSpline() {
if (mEvenDimmerBrightnessData != null) {
- return mEvenDimmerBrightnessData.mBacklightToBrightness;
+ return mEvenDimmerBrightnessData.backlightToBrightness;
}
return mBacklightToBrightnessSpline;
}
@@ -1144,11 +1144,11 @@
*/
public float getNitsFromBacklight(float backlight) {
if (mEvenDimmerBrightnessData != null) {
- if (mEvenDimmerBrightnessData.mBacklightToNits == null) {
+ if (mEvenDimmerBrightnessData.backlightToNits == null) {
return INVALID_NITS;
}
backlight = Math.max(backlight, mBacklightMinimum);
- return mEvenDimmerBrightnessData.mBacklightToNits.interpolate(backlight);
+ return mEvenDimmerBrightnessData.backlightToNits.interpolate(backlight);
}
if (mBacklightToNitsSpline == null) {
@@ -1165,14 +1165,14 @@
*/
public float getBacklightFromNits(float nits) {
if (mEvenDimmerBrightnessData != null) {
- return mEvenDimmerBrightnessData.mNitsToBacklight.interpolate(nits);
+ return mEvenDimmerBrightnessData.nitsToBacklight.interpolate(nits);
}
return mNitsToBacklightSpline.interpolate(nits);
}
private Spline getNitsToBacklightSpline() {
if (mEvenDimmerBrightnessData != null) {
- return mEvenDimmerBrightnessData.mNitsToBacklight;
+ return mEvenDimmerBrightnessData.nitsToBacklight;
}
return mNitsToBacklightSpline;
}
@@ -1186,7 +1186,7 @@
if (mEvenDimmerBrightnessData == null) {
return INVALID_NITS;
}
- return mEvenDimmerBrightnessData.mMinLuxToNits.interpolate(lux);
+ return mEvenDimmerBrightnessData.minLuxToNits.interpolate(lux);
}
/**
@@ -1197,7 +1197,7 @@
if (mEvenDimmerBrightnessData == null) {
return PowerManager.BRIGHTNESS_MIN;
}
- return mEvenDimmerBrightnessData.mTransitionPoint;
+ return mEvenDimmerBrightnessData.transitionPoint;
}
/**
@@ -1268,7 +1268,7 @@
*/
public float[] getBrightness() {
if (mEvenDimmerBrightnessData != null) {
- return mEvenDimmerBrightnessData.mBrightness;
+ return mEvenDimmerBrightnessData.brightness;
}
return mBrightness;
}
@@ -2617,13 +2617,13 @@
List<NonNegativeFloatToFloatPoint> points = map.getMap().getPoint();
for (NonNegativeFloatToFloatPoint point : points) {
float lux = point.getFirst().floatValue();
- float maxBrightness = point.getSecond().floatValue();
- if (maxBrightness > hbmTransitionPoint) {
+ float maxBacklight = point.getSecond().floatValue();
+ if (maxBacklight > hbmTransitionPoint) {
Slog.wtf(TAG,
- "Invalid NBM config: maxBrightness is greater than hbm"
+ "Invalid NBM config: maxBacklight is greater than hbm"
+ ".transitionPoint. type="
- + type + "; lux=" + lux + "; maxBrightness="
- + maxBrightness);
+ + type + "; lux=" + lux + "; maxBacklight="
+ + maxBacklight);
continue;
}
if (luxToTransitionPointMap.containsKey(lux)) {
@@ -2632,8 +2632,7 @@
+ lux);
continue;
}
- luxToTransitionPointMap.put(lux,
- getBrightnessFromBacklight(maxBrightness));
+ luxToTransitionPointMap.put(lux, getBrightnessFromBacklight(maxBacklight));
}
if (!luxToTransitionPointMap.isEmpty()) {
mLuxThrottlingData.put(mappedType, luxToTransitionPointMap);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 711bc79..62d8761 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -22,6 +22,7 @@
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
+import static com.android.server.display.brightness.BrightnessEvent.FLAG_EVEN_DIMMER;
import static com.android.server.display.config.DisplayBrightnessMappingConfig.autoBrightnessPresetToString;
import android.animation.Animator;
@@ -1755,6 +1756,8 @@
final float brightnessOnAvailableScale = MathUtils.constrainedMap(0.0f, 1.0f,
clampedState.getMinBrightness(), clampedMax,
brightnessState);
+ final boolean evenDimmerModeOn =
+ mCdsi != null && mCdsi.getReduceBrightColorsActivatedForEvenDimmer();
mTempBrightnessEvent.setPercent(Math.round(
1000.0f * com.android.internal.display.BrightnessUtils.convertLinearToGamma(
brightnessOnAvailableScale) / 10)); // rounded to one dp
@@ -1769,7 +1772,8 @@
mTempBrightnessEvent.setHbmMode(mBrightnessRangeController.getHighBrightnessMode());
mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags()
| (mIsRbcActive ? BrightnessEvent.FLAG_RBC : 0)
- | (mPowerRequest.lowPowerMode ? BrightnessEvent.FLAG_LOW_POWER_MODE : 0));
+ | (mPowerRequest.lowPowerMode ? BrightnessEvent.FLAG_LOW_POWER_MODE : 0)
+ | (evenDimmerModeOn ? FLAG_EVEN_DIMMER : 0));
mTempBrightnessEvent.setRbcStrength(mCdsi != null
? mCdsi.getReduceBrightColorsStrength() : -1);
mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor);
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 0570b2a..5edea0a 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -37,9 +37,9 @@
import android.os.Trace;
import android.util.DisplayUtils;
import android.util.LongSparseArray;
-import android.util.MathUtils;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.Spline;
import android.view.Display;
import android.view.DisplayAddress;
import android.view.DisplayCutout;
@@ -81,10 +81,6 @@
private static final String UNIQUE_ID_PREFIX = "local:";
private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.boot.emulator.circular";
- // Min and max strengths for even dimmer feature.
- private static final float EVEN_DIMMER_MIN_STRENGTH = 0.0f;
- private static final float EVEN_DIMMER_MAX_STRENGTH = 90.0f;
- private static final float BRIGHTNESS_MIN = 0.0f;
private final LongSparseArray<LocalDisplayDevice> mDevices = new LongSparseArray<>();
@@ -99,7 +95,9 @@
private Context mOverlayContext;
private int mEvenDimmerStrength = -1;
+ private boolean mEvenDimmerEnabled = false;
private ColorDisplayService.ColorDisplayServiceInternal mCdsi;
+ private Spline mNitsToEvenDimmerStrength;
// Called with SyncRoot lock held.
LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context,
@@ -279,7 +277,7 @@
mIsFirstDisplay = isFirstDisplay;
updateDisplayPropertiesLocked(staticDisplayInfo, dynamicInfo, modeSpecs);
mSidekickInternal = LocalServices.getService(SidekickInternal.class);
- mBacklightAdapter = new BacklightAdapter(displayToken, isFirstDisplay,
+ mBacklightAdapter = mInjector.getBacklightAdapter(displayToken, isFirstDisplay,
mSurfaceControlProxy);
mActiveSfDisplayModeAtStartId = dynamicInfo.activeDisplayModeId;
}
@@ -998,26 +996,36 @@
}
private void applyColorMatrixBasedDimming(float brightnessState) {
- int strength = (int) (MathUtils.constrainedMap(
- // to this range:
- EVEN_DIMMER_MAX_STRENGTH, EVEN_DIMMER_MIN_STRENGTH,
- // from this range:
- BRIGHTNESS_MIN, mDisplayDeviceConfig.getEvenDimmerTransitionPoint(),
- // map this (+ rounded up):
- brightnessState) + 0.5);
-
- if (mEvenDimmerStrength < 0 // uninitialised
- || MathUtils.abs(mEvenDimmerStrength - strength) > 1
- || strength <= 1) {
- mEvenDimmerStrength = strength;
- }
- boolean enabled = mEvenDimmerStrength > 0.0f;
-
if (mCdsi == null) {
mCdsi = LocalServices.getService(
ColorDisplayService.ColorDisplayServiceInternal.class);
}
- if (mCdsi != null) {
+ if (mCdsi == null) {
+ return;
+ }
+
+ final float minHardwareNits = backlightToNits(brightnessToBacklight(
+ mDisplayDeviceConfig.getEvenDimmerTransitionPoint()));
+ final float requestedNits =
+ backlightToNits(brightnessToBacklight(brightnessState));
+ mNitsToEvenDimmerStrength =
+ mCdsi.fetchEvenDimmerSpline(minHardwareNits);
+
+ if (mNitsToEvenDimmerStrength == null) {
+ return;
+ }
+
+ // Find required dimming strength, rounded up.
+ int strength = Math.round(mNitsToEvenDimmerStrength
+ .interpolate(requestedNits));
+ boolean enabled = strength > 0.0f;
+ if (mEvenDimmerEnabled != enabled) {
+ Slog.i(TAG, "Setting Extra Dim; strength: " + strength
+ + ", " + (enabled ? "enabled" : "disabled"));
+ }
+ if (mEvenDimmerStrength != strength || mEvenDimmerEnabled != enabled) {
+ mEvenDimmerEnabled = enabled;
+ mEvenDimmerStrength = strength;
mCdsi.applyEvenDimmerColorChanges(enabled, strength);
}
}
@@ -1290,6 +1298,9 @@
pw.println("DisplayDeviceConfig: ");
pw.println("---------------------");
pw.println(mDisplayDeviceConfig);
+ pw.println("mEvenDimmerEnabled=" + mEvenDimmerEnabled);
+ pw.println("mEvenDimmerStrength=" + mEvenDimmerStrength);
+ pw.println("mNitsToEvenDimmerStrength=" + mNitsToEvenDimmerStrength);
}
private int findSfDisplayModeIdLocked(int displayModeId, int modeGroup) {
@@ -1461,6 +1472,12 @@
long physicalDisplayId, boolean isFirstDisplay, DisplayManagerFlags flags) {
return DisplayDeviceConfig.create(context, physicalDisplayId, isFirstDisplay, flags);
}
+
+ public BacklightAdapter getBacklightAdapter(IBinder displayToken, boolean isFirstDisplay,
+ SurfaceControlProxy surfaceControlProxy) {
+ return new BacklightAdapter(displayToken, isFirstDisplay, surfaceControlProxy);
+
+ }
}
public interface DisplayEventListener {
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
index ad57ebf..9e9b899 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
@@ -44,6 +44,7 @@
public static final int FLAG_DOZE_SCALE = 0x4;
public static final int FLAG_USER_SET = 0x8;
public static final int FLAG_LOW_POWER_MODE = 0x20;
+ public static final int FLAG_EVEN_DIMMER = 0x40;
private static final SimpleDateFormat FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
@@ -492,6 +493,7 @@
+ ((mFlags & FLAG_RBC) != 0 ? "rbc " : "")
+ ((mFlags & FLAG_INVALID_LUX) != 0 ? "invalid_lux " : "")
+ ((mFlags & FLAG_DOZE_SCALE) != 0 ? "doze_scale " : "")
- + ((mFlags & FLAG_LOW_POWER_MODE) != 0 ? "low_power_mode " : "");
+ + ((mFlags & FLAG_LOW_POWER_MODE) != 0 ? "low_power_mode " : "")
+ + ((mFlags & FLAG_EVEN_DIMMER) != 0 ? "even_dimmer " : "");
}
}
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index bd759f3..dc59e66 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -70,6 +70,7 @@
import android.util.MathUtils;
import android.util.Slog;
import android.util.SparseIntArray;
+import android.util.Spline;
import android.view.Display;
import android.view.SurfaceControl;
import android.view.accessibility.AccessibilityManager;
@@ -114,6 +115,8 @@
Matrix.setIdentityM(MATRIX_IDENTITY, 0);
}
+ private static final int EVEN_DIMMER_MAX_PERCENT_ALLOWED = 100;
+
private static final int MSG_USER_CHANGED = 0;
private static final int MSG_SET_UP = 1;
private static final int MSG_APPLY_NIGHT_DISPLAY_IMMEDIATE = 2;
@@ -193,6 +196,9 @@
private final boolean mVisibleBackgroundUsersEnabled;
private final UserManagerService mUserManager;
+ private Spline mEvenDimmerSpline;
+ private boolean mEvenDimmerActivated;
+
public ColorDisplayService(Context context) {
super(context);
mHandler = new TintHandler(DisplayThread.get().getLooper());
@@ -1625,6 +1631,16 @@
}
/**
+ * Gets the adjusted nits, given a strength and nits.
+ * @param strength of reduce bright colors
+ * @param nits target nits
+ * @return the actual nits that would be output, given input nits and rbc strength.
+ */
+ public float getAdjustedNitsForStrength(float nits, int strength) {
+ return mReduceBrightColorsTintController.getAdjustedNitsForStrength(nits, strength);
+ }
+
+ /**
* Sets the listener and returns whether reduce bright colors is currently enabled.
*/
public boolean setReduceBrightColorsListener(ReduceBrightColorsListener listener) {
@@ -1644,6 +1660,14 @@
}
/**
+ *
+ * @return whether reduce bright colors is on, due to even dimmer being activated
+ */
+ public boolean getReduceBrightColorsActivatedForEvenDimmer() {
+ return mEvenDimmerActivated;
+ }
+
+ /**
* Gets the computed brightness, in nits, when the reduce bright colors feature is applied
* at the current strength.
*
@@ -1667,10 +1691,42 @@
* Applies tint changes for even dimmer feature.
*/
public void applyEvenDimmerColorChanges(boolean enabled, int strength) {
+ mEvenDimmerActivated = enabled;
mReduceBrightColorsTintController.setActivated(enabled);
mReduceBrightColorsTintController.setMatrix(strength);
mHandler.sendEmptyMessage(MSG_APPLY_REDUCE_BRIGHT_COLORS);
}
+
+ /**
+ * Get spline to map between requested nits, and required even dimmer strength.
+ * @return nits to strength spline
+ */
+ public Spline fetchEvenDimmerSpline(float nits) {
+ if (mEvenDimmerSpline == null) {
+ mEvenDimmerSpline = createNitsToStrengthSpline(nits);
+ }
+ return mEvenDimmerSpline;
+ }
+
+ /**
+ * Creates a spline mapping requested nits values, for each resulting strength value
+ * (in percent) for even dimmer.
+ * Returns null if coefficients are not initialised.
+ * @return spline from nits to strength
+ */
+ private Spline createNitsToStrengthSpline(float nits) {
+ final float[] requestedNits = new float[EVEN_DIMMER_MAX_PERCENT_ALLOWED + 1];
+ final float[] resultingStrength = new float[EVEN_DIMMER_MAX_PERCENT_ALLOWED + 1];
+ for (int i = 0; i <= EVEN_DIMMER_MAX_PERCENT_ALLOWED; i++) {
+ resultingStrength[EVEN_DIMMER_MAX_PERCENT_ALLOWED - i] = i;
+ requestedNits[EVEN_DIMMER_MAX_PERCENT_ALLOWED - i] =
+ getAdjustedNitsForStrength(nits, i);
+ if (requestedNits[EVEN_DIMMER_MAX_PERCENT_ALLOWED - i] == 0) {
+ return null;
+ }
+ }
+ return new Spline.LinearSpline(requestedNits, resultingStrength);
+ }
}
/**
diff --git a/services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java b/services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java
index f529c4c..4f6fbd0 100644
--- a/services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java
+++ b/services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java
@@ -123,6 +123,14 @@
return computeComponentValue(mStrength) * nits;
}
+ /**
+ * Returns the effective brightness (in nits), which has been adjusted to account for the effect
+ * of the bright color reduction.
+ */
+ public float getAdjustedNitsForStrength(float nits, int strength) {
+ return computeComponentValue(strength) * nits;
+ }
+
private float computeComponentValue(int strengthLevel) {
final float percentageStrength = strengthLevel / 100f;
final float squaredPercentageStrength = percentageStrength * percentageStrength;
diff --git a/services/core/java/com/android/server/display/config/EvenDimmerBrightnessData.java b/services/core/java/com/android/server/display/config/EvenDimmerBrightnessData.java
index 7e2e10a..9a590a2 100644
--- a/services/core/java/com/android/server/display/config/EvenDimmerBrightnessData.java
+++ b/services/core/java/com/android/server/display/config/EvenDimmerBrightnessData.java
@@ -34,72 +34,76 @@
/**
* Brightness value at which even dimmer methods are used.
*/
- public final float mTransitionPoint;
+ public final float transitionPoint;
/**
* Nits array, maps to mBacklight
*/
- public final float[] mNits;
+ public final float[] nits;
/**
* Backlight array, maps to mBrightness and mNits
*/
- public final float[] mBacklight;
+ public final float[] backlight;
/**
* Brightness array, maps to mBacklight
*/
- public final float[] mBrightness;
+ public final float[] brightness;
+
/**
* Spline, mapping between backlight and nits
*/
- public final Spline mBacklightToNits;
+ public final Spline backlightToNits;
+
/**
* Spline, mapping between nits and backlight
*/
- public final Spline mNitsToBacklight;
+ public final Spline nitsToBacklight;
+
/**
* Spline, mapping between brightness and backlight
*/
- public final Spline mBrightnessToBacklight;
+ public final Spline brightnessToBacklight;
+
/**
* Spline, mapping between backlight and brightness
*/
- public final Spline mBacklightToBrightness;
+ public final Spline backlightToBrightness;
/**
* Spline, mapping the minimum nits for each lux condition.
*/
- public final Spline mMinLuxToNits;
+ public final Spline minLuxToNits;
@VisibleForTesting
public EvenDimmerBrightnessData(float transitionPoint, float[] nits,
float[] backlight, float[] brightness, Spline backlightToNits,
Spline nitsToBacklight, Spline brightnessToBacklight, Spline backlightToBrightness,
Spline minLuxToNits) {
- mTransitionPoint = transitionPoint;
- mNits = nits;
- mBacklight = backlight;
- mBrightness = brightness;
- mBacklightToNits = backlightToNits;
- mNitsToBacklight = nitsToBacklight;
- mBrightnessToBacklight = brightnessToBacklight;
- mBacklightToBrightness = backlightToBrightness;
- mMinLuxToNits = minLuxToNits;
+ this.transitionPoint = transitionPoint;
+ this.nits = nits;
+ this.backlight = backlight;
+ this.brightness = brightness;
+ this.backlightToNits = backlightToNits;
+ this.nitsToBacklight = nitsToBacklight;
+ this.brightnessToBacklight = brightnessToBacklight;
+ this.backlightToBrightness = backlightToBrightness;
+ this.minLuxToNits = minLuxToNits;
}
@Override
public String toString() {
return "EvenDimmerBrightnessData {"
- + "mTransitionPoint: " + mTransitionPoint
- + ", mNits: " + Arrays.toString(mNits)
- + ", mBacklight: " + Arrays.toString(mBacklight)
- + ", mBrightness: " + Arrays.toString(mBrightness)
- + ", mBacklightToNits: " + mBacklightToNits
- + ", mNitsToBacklight: " + mNitsToBacklight
- + ", mBrightnessToBacklight: " + mBrightnessToBacklight
- + ", mBacklightToBrightness: " + mBacklightToBrightness
- + ", mMinLuxToNits: " + mMinLuxToNits
+ + "transitionPoint: " + transitionPoint
+ + ", nits: " + Arrays.toString(nits)
+ + ", backlight: " + Arrays.toString(backlight)
+ + ", brightness: " + Arrays.toString(brightness)
+ + ", backlightToNits: " + backlightToNits
+ + ", nitsToBacklight: " + nitsToBacklight
+ + ", brightnessToBacklight: " + brightnessToBacklight
+ + ", backlightToBrightness: " + backlightToBrightness
+ + ", minLuxToNits: " + minLuxToNits
+ "} ";
}
@@ -122,7 +126,6 @@
if (map == null) {
return null;
}
- String interpolation = map.getInterpolation();
List<BrightnessPoint> brightnessPoints = map.getBrightnessPoint();
if (brightnessPoints.isEmpty()) {
@@ -169,22 +172,11 @@
++i;
}
- // Explicitly choose linear interpolation.
- if ("linear".equals(interpolation)) {
- return new EvenDimmerBrightnessData(transitionPoint, nits, backlight, brightness,
- new Spline.LinearSpline(backlight, nits),
- new Spline.LinearSpline(nits, backlight),
- new Spline.LinearSpline(brightness, backlight),
- new Spline.LinearSpline(backlight, brightness),
- new Spline.LinearSpline(minLux, minNits)
- );
- }
-
return new EvenDimmerBrightnessData(transitionPoint, nits, backlight, brightness,
- Spline.createSpline(backlight, nits),
- Spline.createSpline(nits, backlight),
- Spline.createSpline(brightness, backlight),
- Spline.createSpline(backlight, brightness),
+ new Spline.LinearSpline(backlight, nits),
+ new Spline.LinearSpline(nits, backlight),
+ new Spline.LinearSpline(brightness, backlight),
+ new Spline.LinearSpline(backlight, brightness),
Spline.createSpline(minLux, minNits)
);
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index fd7479e..909c47b 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -25,6 +25,7 @@
import static com.android.hardware.input.Flags.touchpadVisualizer;
import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
+import static com.android.server.policy.WindowManagerPolicy.ACTION_PASS_TO_USER;
import android.Manifest;
import android.annotation.EnforcePermission;
@@ -117,6 +118,7 @@
import android.view.VerifiedInputEvent;
import android.view.ViewConfiguration;
import android.view.WindowManager;
+import android.view.WindowManagerPolicyConstants;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
@@ -325,6 +327,9 @@
// Manages Sticky modifier state
private final StickyModifierStateController mStickyModifierStateController;
private final KeyGestureController mKeyGestureController;
+ /** Fallback actions by key code */
+ private final SparseArray<KeyCharacterMap.FallbackAction> mFallbackActions =
+ new SparseArray<>();
// Manages Keyboard microphone mute led
private final KeyboardLedController mKeyboardLedController;
@@ -611,6 +616,7 @@
mKeyRemapper.systemRunning();
mPointerIconCache.systemRunning();
mKeyboardGlyphManager.systemRunning();
+ mKeyGestureController.systemRunning();
initKeyGestures();
}
@@ -2469,6 +2475,14 @@
mFocusEventDebugView.reportKeyEvent(event);
}
}
+ if (useKeyGestureEventHandler() && mKeyGestureController.interceptKeyBeforeQueueing(event,
+ policyFlags)) {
+ // If key gesture gets triggered, we send the event to policy with KEY_GESTURE flag
+ // indicating, the event is used in triggering a key gesture. We can't block event
+ // like Power or volume keys since policy might still want to handle it to change
+ // certain states.
+ policyFlags |= WindowManagerPolicyConstants.FLAG_KEY_GESTURE_TRIGGERED;
+ }
return mWindowManagerCallbacks.interceptKeyBeforeQueueing(event, policyFlags);
}
@@ -2511,6 +2525,74 @@
null, null, null) == PERMISSION_GRANTED;
}
+ // Native callback.
+ @SuppressWarnings("unused")
+ private KeyEvent dispatchUnhandledKey(IBinder focus, KeyEvent event, int policyFlags) {
+ if (interceptUnhandledKey(event, focus)) {
+ return null;
+ }
+ // TODO(b/358569822): Move fallback logic to KeyGestureController
+ if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) != 0) {
+ return null;
+ }
+ final KeyCharacterMap kcm = event.getKeyCharacterMap();
+ final int keyCode = event.getKeyCode();
+ final int metaState = event.getMetaState();
+ final boolean initialDown = event.getAction() == KeyEvent.ACTION_DOWN
+ && event.getRepeatCount() == 0;
+
+ // Check for fallback actions specified by the key character map.
+ final KeyCharacterMap.FallbackAction fallbackAction;
+ if (initialDown) {
+ fallbackAction = kcm.getFallbackAction(keyCode, metaState);
+ } else {
+ fallbackAction = mFallbackActions.get(keyCode);
+ }
+
+ if (fallbackAction == null) {
+ return null;
+ }
+ KeyEvent fallbackEvent = null;
+ final int flags = event.getFlags() | KeyEvent.FLAG_FALLBACK;
+ fallbackEvent = KeyEvent.obtain(
+ event.getDownTime(), event.getEventTime(),
+ event.getAction(), fallbackAction.keyCode,
+ event.getRepeatCount(), fallbackAction.metaState,
+ event.getDeviceId(), event.getScanCode(),
+ flags, event.getSource(), event.getDisplayId(), null);
+
+ if (!interceptFallback(focus, fallbackEvent, policyFlags)) {
+ fallbackEvent.recycle();
+ fallbackEvent = null;
+ }
+
+ if (initialDown) {
+ mFallbackActions.put(keyCode, fallbackAction);
+ } else if (event.getAction() == KeyEvent.ACTION_UP) {
+ mFallbackActions.remove(keyCode);
+ fallbackAction.recycle();
+ }
+ return fallbackEvent;
+ }
+
+ private boolean interceptFallback(IBinder focusedToken, KeyEvent fallbackEvent,
+ int policyFlags) {
+ int actions = interceptKeyBeforeQueueing(fallbackEvent, policyFlags);
+ if ((actions & ACTION_PASS_TO_USER) == 0) {
+ return false;
+ }
+ long delayMillis = interceptKeyBeforeDispatching(focusedToken, fallbackEvent, policyFlags);
+ return delayMillis == 0 && !interceptUnhandledKey(fallbackEvent, focusedToken);
+ }
+
+ private boolean interceptUnhandledKey(KeyEvent event, IBinder focus) {
+ if (useKeyGestureEventHandler() && mKeyGestureController.interceptUnhandledKey(event,
+ focus)) {
+ return true;
+ }
+ return mWindowManagerCallbacks.interceptUnhandledKey(event, focus);
+ }
+
private void initKeyGestures() {
if (!useKeyGestureEventHandler()) {
return;
@@ -2566,12 +2648,6 @@
// Native callback.
@SuppressWarnings("unused")
- private KeyEvent dispatchUnhandledKey(IBinder focus, KeyEvent event, int policyFlags) {
- return mWindowManagerCallbacks.dispatchUnhandledKey(focus, event, policyFlags);
- }
-
- // Native callback.
- @SuppressWarnings("unused")
private void onPointerDownOutsideFocus(IBinder touchedToken) {
mWindowManagerCallbacks.onPointerDownOutsideFocus(touchedToken);
}
@@ -2986,9 +3062,9 @@
long interceptKeyBeforeDispatching(IBinder token, KeyEvent event, int policyFlags);
/**
- * Dispatch unhandled key
+ * Intercept unhandled key
*/
- KeyEvent dispatchUnhandledKey(IBinder token, KeyEvent event, int policyFlags);
+ boolean interceptUnhandledKey(KeyEvent event, IBinder token);
int getPointerLayer();
diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java
index 4538b49..b488db5 100644
--- a/services/core/java/com/android/server/input/KeyGestureController.java
+++ b/services/core/java/com/android/server/input/KeyGestureController.java
@@ -16,14 +16,23 @@
package com.android.server.input;
+import static android.content.pm.PackageManager.FEATURE_LEANBACK;
+import static android.content.pm.PackageManager.FEATURE_WATCH;
+import static android.view.WindowManagerPolicyConstants.FLAG_INTERACTIVE;
+
+import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
+import static com.android.hardware.input.Flags.useKeyGestureEventHandlerMultiPressGestures;
import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
import android.annotation.BinderThread;
import android.annotation.MainThread;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
+import android.content.ContentResolver;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
+import android.database.ContentObserver;
import android.hardware.input.AidlKeyGestureEvent;
import android.hardware.input.IKeyGestureEventListener;
import android.hardware.input.IKeyGestureHandler;
@@ -35,18 +44,23 @@
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.provider.Settings;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.InputDevice;
+import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.policy.KeyCombinationManager;
import java.util.ArrayDeque;
import java.util.HashSet;
@@ -82,20 +96,36 @@
private static final int SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY = 1;
private static final int LAST_SEARCH_KEY_BEHAVIOR = SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY;
+ // must match: config_keyChordPowerVolumeUp in config.xml
+ static final int POWER_VOLUME_UP_BEHAVIOR_NOTHING = 0;
+ static final int POWER_VOLUME_UP_BEHAVIOR_MUTE = 1;
+ static final int POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS = 2;
+
private final Context mContext;
private final Handler mHandler;
private final int mSystemPid;
+ private final KeyCombinationManager mKeyCombinationManager;
+ private final SettingsObserver mSettingsObserver;
// Pending actions
private boolean mPendingMetaAction;
private boolean mPendingCapsLockToggle;
private boolean mPendingHideRecentSwitcher;
- // Key behaviors
+ // Platform behaviors
private boolean mEnableBugReportKeyboardShortcut;
+ private boolean mHasFeatureWatch;
+ private boolean mHasFeatureLeanback;
+
+ // Key behaviors
private int mSearchKeyBehavior;
private int mSettingsKeyBehavior;
+ // Settings behaviors
+ private int mRingerToggleChord = Settings.Secure.VOLUME_HUSH_OFF;
+ private int mPowerVolUpBehavior;
+
+
// List of currently registered key gesture event listeners keyed by process pid
@GuardedBy("mKeyGestureEventListenerRecords")
private final SparseArray<KeyGestureEventListenerRecord>
@@ -128,12 +158,19 @@
return Integer.compare(p1, p2);
}
});
+ mKeyCombinationManager = new KeyCombinationManager(mHandler);
+ mSettingsObserver = new SettingsObserver(mHandler);
initBehaviors();
+ initKeyCombinationRules();
}
private void initBehaviors() {
mEnableBugReportKeyboardShortcut = "1".equals(SystemProperties.get("ro.debuggable"));
+ PackageManager pm = mContext.getPackageManager();
+ mHasFeatureWatch = pm.hasSystemFeature(FEATURE_WATCH);
+ mHasFeatureLeanback = pm.hasSystemFeature(FEATURE_LEANBACK);
+
Resources res = mContext.getResources();
mSearchKeyBehavior = res.getInteger(R.integer.config_searchKeyBehavior);
if (mSearchKeyBehavior < SEARCH_KEY_BEHAVIOR_DEFAULT_SEARCH
@@ -145,6 +182,251 @@
|| mSettingsKeyBehavior > LAST_SETTINGS_KEY_BEHAVIOR) {
mSettingsKeyBehavior = SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY;
}
+
+ mHandler.post(this::initBehaviorsFromSettings);
+ }
+
+ private void initBehaviorsFromSettings() {
+ ContentResolver resolver = mContext.getContentResolver();
+ mRingerToggleChord = Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.VOLUME_HUSH_GESTURE, Settings.Secure.VOLUME_HUSH_OFF,
+ UserHandle.USER_CURRENT);
+
+ mPowerVolUpBehavior = Settings.Global.getInt(resolver,
+ Settings.Global.KEY_CHORD_POWER_VOLUME_UP,
+ mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_keyChordPowerVolumeUp));
+ }
+
+ private void initKeyCombinationRules() {
+ if (!useKeyGestureEventHandler() || !useKeyGestureEventHandlerMultiPressGestures()) {
+ return;
+ }
+ // TODO(b/358569822): Handle Power, Back key properly since key combination gesture is
+ // captured here and rest of the Power, Back key behaviors are handled in PWM
+ final boolean screenshotChordEnabled = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_enableScreenshotChord);
+
+ if (screenshotChordEnabled) {
+ mKeyCombinationManager.addRule(
+ new KeyCombinationManager.TwoKeysCombinationRule(KeyEvent.KEYCODE_VOLUME_DOWN,
+ KeyEvent.KEYCODE_POWER) {
+ @Override
+ public boolean preCondition() {
+ return isKeyGestureSupported(
+ KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD);
+ }
+
+ @Override
+ public void execute() {
+ handleKeyGesture(
+ new int[]{KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_POWER},
+ KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD,
+ KeyGestureEvent.ACTION_GESTURE_START, 0);
+ }
+
+ @Override
+ public void cancel() {
+ handleKeyGesture(
+ new int[]{KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_POWER},
+ KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+ KeyGestureEvent.FLAG_CANCELLED);
+ }
+ });
+
+ if (mHasFeatureWatch) {
+ mKeyCombinationManager.addRule(
+ new KeyCombinationManager.TwoKeysCombinationRule(KeyEvent.KEYCODE_POWER,
+ KeyEvent.KEYCODE_STEM_PRIMARY) {
+ @Override
+ public boolean preCondition() {
+ return isKeyGestureSupported(
+ KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD);
+ }
+
+ @Override
+ public void execute() {
+ handleKeyGesture(new int[]{KeyEvent.KEYCODE_POWER,
+ KeyEvent.KEYCODE_STEM_PRIMARY},
+ KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD,
+ KeyGestureEvent.ACTION_GESTURE_START, 0);
+ }
+ @Override
+ public void cancel() {
+ handleKeyGesture(new int[]{KeyEvent.KEYCODE_POWER,
+ KeyEvent.KEYCODE_STEM_PRIMARY},
+ KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+ KeyGestureEvent.FLAG_CANCELLED);
+ }
+ });
+ }
+ }
+
+ mKeyCombinationManager.addRule(
+ new KeyCombinationManager.TwoKeysCombinationRule(KeyEvent.KEYCODE_VOLUME_DOWN,
+ KeyEvent.KEYCODE_VOLUME_UP) {
+ @Override
+ public boolean preCondition() {
+ return isKeyGestureSupported(
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD);
+ }
+
+ @Override
+ public void execute() {
+ handleKeyGesture(
+ new int[]{KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP},
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD,
+ KeyGestureEvent.ACTION_GESTURE_START, 0);
+ }
+
+ @Override
+ public void cancel() {
+ handleKeyGesture(
+ new int[]{KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP},
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+ KeyGestureEvent.FLAG_CANCELLED);
+ }
+ });
+
+ // Volume up + power can either be the "ringer toggle chord" or as another way to
+ // launch GlobalActions. This behavior can change at runtime so we must check behavior
+ // inside the TwoKeysCombinationRule.
+ mKeyCombinationManager.addRule(
+ new KeyCombinationManager.TwoKeysCombinationRule(KeyEvent.KEYCODE_VOLUME_UP,
+ KeyEvent.KEYCODE_POWER) {
+ @Override
+ public boolean preCondition() {
+ if (!isKeyGestureSupported(getGestureType())) {
+ return false;
+ }
+ switch (mPowerVolUpBehavior) {
+ case POWER_VOLUME_UP_BEHAVIOR_MUTE:
+ return mRingerToggleChord != Settings.Secure.VOLUME_HUSH_OFF;
+ default:
+ return true;
+ }
+ }
+ @Override
+ public void execute() {
+ int gestureType = getGestureType();
+ if (gestureType == KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED) {
+ return;
+ }
+ handleKeyGesture(
+ new int[]{KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_POWER},
+ gestureType, KeyGestureEvent.ACTION_GESTURE_START, 0);
+ }
+ @Override
+ public void cancel() {
+ int gestureType = getGestureType();
+ if (gestureType == KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED) {
+ return;
+ }
+ handleKeyGesture(
+ new int[]{KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_POWER},
+ gestureType, KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+ KeyGestureEvent.FLAG_CANCELLED);
+ }
+
+ @KeyGestureEvent.KeyGestureType
+ private int getGestureType() {
+ switch (mPowerVolUpBehavior) {
+ case POWER_VOLUME_UP_BEHAVIOR_MUTE -> {
+ return KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD;
+ }
+ case POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS -> {
+ return KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS;
+ }
+ default -> {
+ return KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED;
+ }
+ }
+ }
+ });
+
+ if (mHasFeatureLeanback) {
+ mKeyCombinationManager.addRule(
+ new KeyCombinationManager.TwoKeysCombinationRule(KeyEvent.KEYCODE_BACK,
+ KeyEvent.KEYCODE_DPAD_DOWN) {
+ @Override
+ public boolean preCondition() {
+ return isKeyGestureSupported(
+ KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD);
+ }
+
+ @Override
+ public void execute() {
+ handleKeyGesture(
+ new int[]{KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN},
+ KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD,
+ KeyGestureEvent.ACTION_GESTURE_START, 0);
+ }
+
+ @Override
+ public void cancel() {
+ handleKeyGesture(
+ new int[]{KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN},
+ KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+ KeyGestureEvent.FLAG_CANCELLED);
+ }
+ @Override
+ public long getKeyInterceptDelayMs() {
+ // Use a timeout of 0 to prevent additional latency in processing of
+ // this key. This will potentially cause some unwanted UI actions if the
+ // user does end up triggering the key combination later, but in most
+ // cases, the user will simply hit a single key, and this will allow us
+ // to process it without first waiting to see if the combination is
+ // going to be triggered.
+ return 0;
+ }
+ });
+
+ mKeyCombinationManager.addRule(
+ new KeyCombinationManager.TwoKeysCombinationRule(KeyEvent.KEYCODE_BACK,
+ KeyEvent.KEYCODE_DPAD_CENTER) {
+ @Override
+ public boolean preCondition() {
+ return isKeyGestureSupported(
+ KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT);
+ }
+
+ @Override
+ public void execute() {
+ handleKeyGesture(
+ new int[]{KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_CENTER},
+ KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT,
+ KeyGestureEvent.ACTION_GESTURE_START, 0);
+ }
+ @Override
+ public void cancel() {
+ handleKeyGesture(
+ new int[]{KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_CENTER},
+ KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+ KeyGestureEvent.FLAG_CANCELLED);
+ }
+ @Override
+ public long getKeyInterceptDelayMs() {
+ return 0;
+ }
+ });
+ }
+ }
+
+ public void systemRunning() {
+ mSettingsObserver.observe();
+ }
+
+ public boolean interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
+ final boolean interactive = (policyFlags & FLAG_INTERACTIVE) != 0;
+ if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
+ return mKeyCombinationManager.interceptKey(event, interactive);
+ }
+ return false;
}
public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event,
@@ -153,9 +435,22 @@
// KeyGestureHandler (PWM is one of the handlers)
final int keyCode = event.getKeyCode();
final int deviceId = event.getDeviceId();
+ final int flags = event.getFlags();
final long keyConsumed = -1;
final long keyNotConsumed = 0;
+ if (mKeyCombinationManager.isKeyConsumed(event)) {
+ return keyConsumed;
+ }
+
+ if ((flags & KeyEvent.FLAG_FALLBACK) == 0) {
+ final long now = SystemClock.uptimeMillis();
+ final long interceptTimeout = mKeyCombinationManager.getKeyInterceptTimeout(keyCode);
+ if (now < interceptTimeout) {
+ return interceptTimeout - now;
+ }
+ }
+
Set<Integer> consumedKeys = mConsumedKeysForDevice.get(deviceId);
if (consumedKeys == null) {
consumedKeys = new HashSet<>();
@@ -577,10 +872,71 @@
return false;
}
+ boolean interceptUnhandledKey(KeyEvent event, IBinder focusedToken) {
+ final int keyCode = event.getKeyCode();
+ final int repeatCount = event.getRepeatCount();
+ final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
+ final int metaState = event.getModifiers();
+ final int deviceId = event.getDeviceId();
+ final int displayId = event.getDisplayId();
+
+ switch(keyCode) {
+ case KeyEvent.KEYCODE_SPACE:
+ if (down && repeatCount == 0) {
+ // Handle keyboard layout switching. (CTRL + SPACE)
+ if (KeyEvent.metaStateHasModifiers(metaState & ~KeyEvent.META_SHIFT_MASK,
+ KeyEvent.META_CTRL_ON)) {
+ return handleKeyGesture(deviceId, new int[]{keyCode},
+ KeyEvent.META_CTRL_ON | (event.isShiftPressed()
+ ? KeyEvent.META_SHIFT_ON : 0),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+ focusedToken, /* flags = */0);
+ }
+ }
+ break;
+ case KeyEvent.KEYCODE_Z:
+ if (down && KeyEvent.metaStateHasModifiers(metaState,
+ KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON)) {
+ // Intercept the Accessibility keychord (CTRL + ALT + Z) for keyboard users.
+ return handleKeyGesture(deviceId, new int[]{keyCode},
+ KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+ focusedToken, /* flags = */0);
+ }
+ break;
+ case KeyEvent.KEYCODE_SYSRQ:
+ if (down && repeatCount == 0) {
+ return handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+ focusedToken, /* flags = */0);
+ }
+ break;
+ case KeyEvent.KEYCODE_ESCAPE:
+ if (down && KeyEvent.metaStateHasNoModifiers(metaState) && repeatCount == 0) {
+ return handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
+ KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+ focusedToken, /* flags = */0);
+ }
+ break;
+ }
+
+ return false;
+ }
+
+ private boolean handleKeyGesture(int[] keycodes,
+ @KeyGestureEvent.KeyGestureType int gestureType, int action, int flags) {
+ return handleKeyGesture(KeyCharacterMap.VIRTUAL_KEYBOARD, keycodes, /* modifierState= */0,
+ gestureType, action, Display.DEFAULT_DISPLAY, /* focusedToken = */null, flags);
+ }
+
@VisibleForTesting
boolean handleKeyGesture(int deviceId, int[] keycodes, int modifierState,
@KeyGestureEvent.KeyGestureType int gestureType, int action, int displayId,
- IBinder focusedToken, int flags) {
+ @Nullable IBinder focusedToken, int flags) {
return handleKeyGesture(createKeyGestureEvent(deviceId, keycodes,
modifierState, gestureType, action, displayId, flags), focusedToken);
}
@@ -628,7 +984,7 @@
@MainThread
private void notifyKeyGestureEvent(AidlKeyGestureEvent event) {
InputDevice device = getInputDevice(event.deviceId);
- if (device == null || device.isVirtual()) {
+ if (device == null) {
return;
}
if (event.action == KeyGestureEvent.ACTION_GESTURE_COMPLETE) {
@@ -822,6 +1178,27 @@
}
}
+ private class SettingsObserver extends ContentObserver {
+ private SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ private void observe() {
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.VOLUME_HUSH_GESTURE), false, this,
+ UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.KEY_CHORD_POWER_VOLUME_UP), false, this,
+ UserHandle.USER_ALL);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ initBehaviorsFromSettings();
+ }
+ }
+
@Nullable
private InputDevice getInputDevice(int deviceId) {
InputManager inputManager = mContext.getSystemService(InputManager.class);
@@ -851,6 +1228,8 @@
ipw.println("mPendingHideRecentSwitcher = " + mPendingHideRecentSwitcher);
ipw.println("mSearchKeyBehavior = " + mSearchKeyBehavior);
ipw.println("mSettingsKeyBehavior = " + mSettingsKeyBehavior);
+ ipw.println("mRingerToggleChord = " + mRingerToggleChord);
+ ipw.println("mPowerVolUpBehavior = " + mPowerVolUpBehavior);
ipw.print("mKeyGestureEventListenerRecords = {");
synchronized (mKeyGestureEventListenerRecords) {
int size = mKeyGestureEventListenerRecords.size();
@@ -881,5 +1260,6 @@
ipw.println(ev);
}
ipw.decreaseIndent();
+ mKeyCombinationManager.dump("", ipw);
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index bcc2019..c3a714b 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -4136,14 +4136,18 @@
}
+ /**
+ * Any changes from SystemUI or Settings should be fromUser == true. Any changes the
+ * allowlist should be fromUser == false.
+ */
@Override
@FlaggedApi(android.app.Flags.FLAG_API_RICH_ONGOING)
- public void setCanBePromoted(String pkg, int uid, boolean promote) {
+ public void setCanBePromoted(String pkg, int uid, boolean promote, boolean fromUser) {
checkCallerIsSystemOrSystemUiOrShell();
if (!android.app.Flags.apiRichOngoing()) {
return;
}
- boolean changed = mPreferencesHelper.setCanBePromoted(pkg, uid, promote);
+ boolean changed = mPreferencesHelper.setCanBePromoted(pkg, uid, promote, fromUser);
if (changed) {
// check for pending/posted notifs from this app and update the flag
synchronized (mNotificationLock) {
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index fdb9f67..85c3957 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -40,6 +40,7 @@
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
+import static com.android.server.notification.PreferencesHelper.LockableAppFields.USER_LOCKED_PROMOTABLE;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
@@ -192,10 +193,13 @@
/**
* All user-lockable fields for a given application.
*/
- @IntDef({LockableAppFields.USER_LOCKED_IMPORTANCE})
+ @IntDef({LockableAppFields.USER_LOCKED_IMPORTANCE,
+ LockableAppFields.USER_LOCKED_BUBBLE,
+ LockableAppFields.USER_LOCKED_PROMOTABLE})
public @interface LockableAppFields {
int USER_LOCKED_IMPORTANCE = 0x00000001;
int USER_LOCKED_BUBBLE = 0x00000002;
+ int USER_LOCKED_PROMOTABLE = 0x00000004;
}
private final Object mLock = new Object();
@@ -858,13 +862,19 @@
}
@FlaggedApi(android.app.Flags.FLAG_API_RICH_ONGOING)
- public boolean setCanBePromoted(String packageName, int uid, boolean promote) {
+ public boolean setCanBePromoted(String packageName, int uid, boolean promote,
+ boolean fromUser) {
boolean changed = false;
synchronized (mLock) {
PackagePreferences pkgPrefs = getOrCreatePackagePreferencesLocked(packageName, uid);
- if (pkgPrefs.canHavePromotedNotifs != promote) {
- pkgPrefs.canHavePromotedNotifs = promote;
- changed = true;
+ if (fromUser || ((pkgPrefs.lockedAppFields & USER_LOCKED_PROMOTABLE) == 0)) {
+ if (pkgPrefs.canHavePromotedNotifs != promote) {
+ pkgPrefs.canHavePromotedNotifs = promote;
+ if (fromUser) {
+ pkgPrefs.lockedAppFields |= USER_LOCKED_PROMOTABLE;
+ }
+ changed = true;
+ }
}
}
// no need to send a ranking update because we need to update the flag value on all pending
diff --git a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
index 8410cff..fe9a859 100644
--- a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
@@ -171,7 +171,6 @@
static final int MAX_DATA_BLOCK_SIZE = 1024 * 100;
public static final int DIGEST_SIZE_BYTES = 32;
- private static final String OEM_UNLOCK_PROP = "sys.oem_unlock_allowed";
private static final String FLASH_LOCK_PROP = "ro.boot.flash.locked";
private static final String FLASH_LOCK_LOCKED = "1";
private static final String FLASH_LOCK_UNLOCKED = "0";
@@ -275,7 +274,6 @@
enforceChecksumValidity();
if (mFrpEnforced) {
automaticallyDeactivateFrpIfPossible();
- setOemUnlockEnabledProperty(doGetOemUnlockEnabled());
setOldSettingForBackworkCompatibility(mFrpActive);
} else {
formatIfOemUnlockEnabled();
@@ -303,10 +301,6 @@
}
}
- private void setOemUnlockEnabledProperty(boolean oemUnlockEnabled) {
- setProperty(OEM_UNLOCK_PROP, oemUnlockEnabled ? "1" : "0");
- }
-
@Override
public void onBootPhase(int phase) {
// Wait for initialization in onStart to finish
@@ -342,7 +336,6 @@
formatPartitionLocked(true);
}
}
- setOemUnlockEnabledProperty(enabled);
}
private void enforceOemUnlockReadPermission() {
@@ -814,17 +807,9 @@
channel.force(true);
} catch (IOException e) {
Slog.e(TAG, "unable to access persistent partition", e);
- return;
- } finally {
- setOemUnlockEnabledProperty(enabled);
}
}
- @VisibleForTesting
- void setProperty(String name, String value) {
- SystemProperties.set(name, value);
- }
-
private boolean doGetOemUnlockEnabled() {
DataInputStream inputStream;
try {
diff --git a/services/core/java/com/android/server/policy/KeyCombinationManager.java b/services/core/java/com/android/server/policy/KeyCombinationManager.java
index 9dfaca8..1592ef3 100644
--- a/services/core/java/com/android/server/policy/KeyCombinationManager.java
+++ b/services/core/java/com/android/server/policy/KeyCombinationManager.java
@@ -65,21 +65,21 @@
* };
* </pre>
*/
- abstract static class TwoKeysCombinationRule {
+ public abstract static class TwoKeysCombinationRule {
private int mKeyCode1;
private int mKeyCode2;
- TwoKeysCombinationRule(int keyCode1, int keyCode2) {
+ public TwoKeysCombinationRule(int keyCode1, int keyCode2) {
mKeyCode1 = keyCode1;
mKeyCode2 = keyCode2;
}
- boolean preCondition() {
+ public boolean preCondition() {
return true;
}
boolean shouldInterceptKey(int keyCode) {
- return preCondition() && (keyCode == mKeyCode1 || keyCode == mKeyCode2);
+ return (keyCode == mKeyCode1 || keyCode == mKeyCode2) && preCondition();
}
boolean shouldInterceptKeys(SparseLongArray downTimes) {
@@ -94,12 +94,12 @@
}
// The excessive delay before it dispatching to client.
- long getKeyInterceptDelayMs() {
+ public long getKeyInterceptDelayMs() {
return COMBINE_KEY_DELAY_MILLIS;
}
- abstract void execute();
- abstract void cancel();
+ public abstract void execute();
+ public abstract void cancel();
@Override
public String toString() {
@@ -128,18 +128,18 @@
}
}
- KeyCombinationManager(Handler handler) {
+ public KeyCombinationManager(Handler handler) {
mHandler = handler;
}
- void addRule(TwoKeysCombinationRule rule) {
+ public void addRule(TwoKeysCombinationRule rule) {
if (mRules.contains(rule)) {
throw new IllegalArgumentException("Rule : " + rule + " already exists.");
}
mRules.add(rule);
}
- void removeRule(TwoKeysCombinationRule rule) {
+ public void removeRule(TwoKeysCombinationRule rule) {
mRules.remove(rule);
}
@@ -148,7 +148,7 @@
* to a window.
* Return true if any active rule could be triggered by the key event, otherwise false.
*/
- boolean interceptKey(KeyEvent event, boolean interactive) {
+ public boolean interceptKey(KeyEvent event, boolean interactive) {
synchronized (mLock) {
return interceptKeyLocked(event, interactive);
}
@@ -226,7 +226,7 @@
/**
* Return the interceptTimeout to tell InputDispatcher when is ready to deliver to window.
*/
- long getKeyInterceptTimeout(int keyCode) {
+ public long getKeyInterceptTimeout(int keyCode) {
synchronized (mLock) {
if (mDownTimes.get(keyCode) == 0) {
return 0;
@@ -246,7 +246,7 @@
/**
* True if the key event had been handled.
*/
- boolean isKeyConsumed(KeyEvent event) {
+ public boolean isKeyConsumed(KeyEvent event) {
synchronized (mLock) {
if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) != 0) {
return false;
@@ -258,7 +258,7 @@
/**
* True if power key is the candidate.
*/
- boolean isPowerKeyIntercepted() {
+ public boolean isPowerKeyIntercepted() {
synchronized (mLock) {
if (forAllActiveRules((rule) -> rule.shouldInterceptKey(KEYCODE_POWER))) {
// return false if only if power key pressed.
@@ -294,7 +294,7 @@
return false;
}
- void dump(String prefix, PrintWriter pw) {
+ public void dump(String prefix, PrintWriter pw) {
pw.println(prefix + "KeyCombination rules:");
forAllRules(mRules, (rule)-> {
pw.println(prefix + " " + rule);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index ca60518..2284050 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -86,6 +86,7 @@
import static com.android.hardware.input.Flags.emojiAndScreenshotKeycodesAvailable;
import static com.android.hardware.input.Flags.modifierShortcutDump;
import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
+import static com.android.hardware.input.Flags.useKeyGestureEventHandlerMultiPressGestures;
import static com.android.server.flags.Flags.modifierShortcutManagerMultiuser;
import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY;
@@ -198,7 +199,6 @@
import android.view.IDisplayFoldListener;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
-import android.view.KeyCharacterMap.FallbackAction;
import android.view.KeyEvent;
import android.view.KeyboardShortcutGroup;
import android.view.MotionEvent;
@@ -698,10 +698,6 @@
// Maps global key codes to the components that will handle them.
private GlobalKeyManager mGlobalKeyManager;
- // Fallback actions by key code.
- private final SparseArray<KeyCharacterMap.FallbackAction> mFallbackActions =
- new SparseArray<KeyCharacterMap.FallbackAction>();
-
private final com.android.internal.policy.LogDecelerateInterpolator mLogDecelerateInterpolator
= new LogDecelerateInterpolator(100, 0);
private final DeferredKeyActionExecutor mDeferredKeyActionExecutor =
@@ -1056,7 +1052,8 @@
return handled;
}
- private void interceptPowerKeyDown(KeyEvent event, boolean interactive) {
+ private void interceptPowerKeyDown(KeyEvent event, boolean interactive,
+ boolean isKeyGestureTriggered) {
// Hold a wake lock until the power key is released.
if (!mPowerKeyWakeLock.isHeld()) {
mPowerKeyWakeLock.acquire();
@@ -1089,7 +1086,8 @@
// If the power key has still not yet been handled, then detect short
// press, long press, or multi press and decide what to do.
mPowerKeyHandled = mPowerKeyHandled || hungUp
- || handledByPowerManager || mKeyCombinationManager.isPowerKeyIntercepted();
+ || handledByPowerManager || isKeyGestureTriggered
+ || mKeyCombinationManager.isPowerKeyIntercepted();
if (!mPowerKeyHandled) {
if (!interactive) {
wakeUpFromWakeKey(event);
@@ -2465,6 +2463,9 @@
private void initKeyCombinationRules() {
mKeyCombinationManager = new KeyCombinationManager(mHandler);
+ if (useKeyGestureEventHandler() && useKeyGestureEventHandlerMultiPressGestures()) {
+ return;
+ }
final boolean screenshotChordEnabled = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_enableScreenshotChord);
@@ -2472,13 +2473,13 @@
mKeyCombinationManager.addRule(
new TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN, KEYCODE_POWER) {
@Override
- void execute() {
+ public void execute() {
mPowerKeyHandled = true;
interceptScreenshotChord(
SCREENSHOT_KEY_CHORD, getScreenshotChordLongPressDelay());
}
@Override
- void cancel() {
+ public void cancel() {
cancelPendingScreenshotChordAction();
}
});
@@ -2487,13 +2488,13 @@
mKeyCombinationManager.addRule(
new TwoKeysCombinationRule(KEYCODE_POWER, KEYCODE_STEM_PRIMARY) {
@Override
- void execute() {
+ public void execute() {
mPowerKeyHandled = true;
interceptScreenshotChord(SCREENSHOT_KEY_CHORD,
getScreenshotChordLongPressDelay());
}
@Override
- void cancel() {
+ public void cancel() {
cancelPendingScreenshotChordAction();
}
});
@@ -2503,16 +2504,16 @@
mKeyCombinationManager.addRule(
new TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN, KEYCODE_VOLUME_UP) {
@Override
- boolean preCondition() {
+ public boolean preCondition() {
return mAccessibilityShortcutController
.isAccessibilityShortcutAvailable(isKeyguardLocked());
}
@Override
- void execute() {
+ public void execute() {
interceptAccessibilityShortcutChord();
}
@Override
- void cancel() {
+ public void cancel() {
cancelPendingAccessibilityShortcutAction();
}
});
@@ -2523,7 +2524,7 @@
mKeyCombinationManager.addRule(
new TwoKeysCombinationRule(KEYCODE_VOLUME_UP, KEYCODE_POWER) {
@Override
- boolean preCondition() {
+ public boolean preCondition() {
switch (mPowerVolUpBehavior) {
case POWER_VOLUME_UP_BEHAVIOR_MUTE:
return mRingerToggleChord != VOLUME_HUSH_OFF;
@@ -2532,7 +2533,7 @@
}
}
@Override
- void execute() {
+ public void execute() {
switch (mPowerVolUpBehavior) {
case POWER_VOLUME_UP_BEHAVIOR_MUTE:
// no haptic feedback here since
@@ -2551,7 +2552,7 @@
}
}
@Override
- void cancel() {
+ public void cancel() {
switch (mPowerVolUpBehavior) {
case POWER_VOLUME_UP_BEHAVIOR_MUTE:
cancelPendingRingerToggleChordAction();
@@ -2567,16 +2568,16 @@
mKeyCombinationManager.addRule(
new TwoKeysCombinationRule(KEYCODE_BACK, KEYCODE_DPAD_DOWN) {
@Override
- void execute() {
+ public void execute() {
mBackKeyHandled = true;
interceptAccessibilityGestureTv();
}
@Override
- void cancel() {
+ public void cancel() {
cancelAccessibilityGestureTv();
}
@Override
- long getKeyInterceptDelayMs() {
+ public long getKeyInterceptDelayMs() {
// Use a timeout of 0 to prevent additional latency in processing of
// this key. This will potentially cause some unwanted UI actions if the
// user does end up triggering the key combination later, but in most
@@ -2590,16 +2591,16 @@
mKeyCombinationManager.addRule(
new TwoKeysCombinationRule(KEYCODE_DPAD_CENTER, KEYCODE_BACK) {
@Override
- void execute() {
+ public void execute() {
mBackKeyHandled = true;
interceptBugreportGestureTv();
}
@Override
- void cancel() {
+ public void cancel() {
cancelBugreportGestureTv();
}
@Override
- long getKeyInterceptDelayMs() {
+ public long getKeyInterceptDelayMs() {
return 0;
}
});
@@ -3403,15 +3404,18 @@
+ keyguardOn() + " canceled=" + event.isCanceled());
}
- if (mKeyCombinationManager.isKeyConsumed(event)) {
- return keyConsumed;
- }
+ if (!useKeyGestureEventHandler()) {
+ if (mKeyCombinationManager.isKeyConsumed(event)) {
+ return keyConsumed;
+ }
- if ((flags & KeyEvent.FLAG_FALLBACK) == 0) {
- final long now = SystemClock.uptimeMillis();
- final long interceptTimeout = mKeyCombinationManager.getKeyInterceptTimeout(keyCode);
- if (now < interceptTimeout) {
- return interceptTimeout - now;
+ if ((flags & KeyEvent.FLAG_FALLBACK) == 0) {
+ final long now = SystemClock.uptimeMillis();
+ final long interceptTimeout = mKeyCombinationManager.getKeyInterceptTimeout(
+ keyCode);
+ if (now < interceptTimeout) {
+ return interceptTimeout - now;
+ }
}
}
@@ -3453,8 +3457,16 @@
// NOTE: Please try not to add new Shortcut combinations here and instead use KeyGestureEvents.
// Add shortcut trigger logic in {@code KeyGestureController} and add handling logic in
// {@link handleKeyGesture()}
- @SuppressLint("MissingPermission")
private boolean interceptSystemKeysAndShortcuts(IBinder focusedToken, KeyEvent event) {
+ if (useKeyGestureEventHandler()) {
+ return interceptSystemKeysAndShortcutsNew(focusedToken, event);
+ } else {
+ return interceptSystemKeysAndShortcutsOld(focusedToken, event);
+ }
+ }
+
+ @SuppressLint("MissingPermission")
+ private boolean interceptSystemKeysAndShortcutsOld(IBinder focusedToken, KeyEvent event) {
final boolean keyguardOn = keyguardOn();
final int keyCode = event.getKeyCode();
final int repeatCount = event.getRepeatCount();
@@ -3878,6 +3890,59 @@
return (metaState & KeyEvent.META_META_ON) != 0;
}
+ private boolean interceptSystemKeysAndShortcutsNew(IBinder focusedToken, KeyEvent event) {
+ final int keyCode = event.getKeyCode();
+ final int metaState = event.getMetaState();
+ final boolean keyguardOn = keyguardOn();
+
+ if (isUserSetupComplete() && !keyguardOn) {
+ if (mModifierShortcutManager.interceptKey(event)) {
+ dismissKeyboardShortcutsMenu();
+ return true;
+ }
+ }
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_HOME:
+ return handleHomeShortcuts(focusedToken, event);
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ case KeyEvent.KEYCODE_VOLUME_MUTE:
+ if (mUseTvRouting || mHandleVolumeKeysInWM) {
+ // On TVs or when the configuration is enabled, volume keys never
+ // go to the foreground app.
+ dispatchDirectAudioEvent(event);
+ return true;
+ }
+
+ // If the device is in VR mode and keys are "internal" (e.g. on the side of the
+ // device), then drop the volume keys and don't forward it to the
+ // application/dispatch the audio event.
+ if (mDefaultDisplayPolicy.isPersistentVrModeEnabled()) {
+ final InputDevice d = event.getDevice();
+ if (d != null && !d.isExternal()) {
+ return true;
+ }
+ }
+ break;
+ case KeyEvent.KEYCODE_STEM_PRIMARY:
+ if (prepareToSendSystemKeyToApplication(focusedToken, event)) {
+ // Send to app.
+ return false;
+ } else {
+ // Intercepted.
+ sendSystemKeyToStatusBarAsync(event);
+ return true;
+ }
+ }
+ if (isValidGlobalKey(keyCode)
+ && mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) {
+ return true;
+ }
+
+ // Reserve all the META modifier combos for system behavior
+ return (metaState & KeyEvent.META_META_ON) != 0;
+ }
+
@SuppressLint("MissingPermission")
private void initKeyGestures() {
if (!useKeyGestureEventHandler()) {
@@ -3887,7 +3952,13 @@
@Override
public boolean handleKeyGestureEvent(@NonNull KeyGestureEvent event,
@Nullable IBinder focusedToken) {
- return PhoneWindowManager.this.handleKeyGestureEvent(event, focusedToken);
+ boolean handled = PhoneWindowManager.this.handleKeyGestureEvent(event,
+ focusedToken);
+ if (handled && Arrays.stream(event.getKeycodes()).anyMatch(
+ (keycode) -> keycode == KeyEvent.KEYCODE_POWER)) {
+ mPowerKeyHandled = true;
+ }
+ return handled;
}
@Override
@@ -3917,7 +3988,20 @@
case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS:
case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH:
case KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH:
+ case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT:
+ case KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS:
return true;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD:
+ case KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD:
+ case KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS:
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT:
+ return mDefaultDisplayPolicy.isAwake();
+ case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD:
+ return mDefaultDisplayPolicy.isAwake() && mAccessibilityShortcutController
+ .isAccessibilityShortcutAvailable(isKeyguardLocked());
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD:
+ return mDefaultDisplayPolicy.isAwake() && mAccessibilityShortcutController
+ .isAccessibilityShortcutAvailable(false);
default:
return false;
}
@@ -4075,6 +4159,66 @@
sendSwitchKeyboardLayout(displayId, focusedToken, direction);
}
return true;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD:
+ if (start) {
+ // Screenshot chord is pressed: Wait for long press delay before taking
+ // screenshot
+ interceptScreenshotChord(SCREENSHOT_KEY_CHORD,
+ getScreenshotChordLongPressDelay());
+ } else {
+ cancelPendingScreenshotChordAction();
+ }
+ return true;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD:
+ if (start) {
+ interceptAccessibilityShortcutChord();
+ } else {
+ cancelPendingAccessibilityShortcutAction();
+ }
+ return true;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD:
+ if (start) {
+ interceptRingerToggleChord();
+ } else {
+ cancelPendingRingerToggleChordAction();
+ }
+ return true;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS:
+ if (start) {
+ performHapticFeedback(
+ HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON,
+ "KEY_GESTURE_TYPE_GLOBAL_ACTIONS - Global Actions");
+ showGlobalActions();
+ } else {
+ cancelGlobalActionsAction();
+ }
+ return true;
+ // TODO (b/358569822): Consolidate TV and non-TV gestures into same KeyGestureEvent
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD:
+ if (start) {
+ interceptAccessibilityGestureTv();
+ } else {
+ cancelAccessibilityGestureTv();
+ }
+ return true;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT:
+ if (start) {
+ interceptBugreportGestureTv();
+ } else {
+ cancelBugreportGestureTv();
+ }
+ return true;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT:
+ if (complete && mAccessibilityShortcutController.isAccessibilityShortcutAvailable(
+ isKeyguardLocked())) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_ACCESSIBILITY_SHORTCUT));
+ }
+ return true;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS:
+ if (complete) {
+ mContext.closeSystemDialogs();
+ }
+ return true;
}
return false;
}
@@ -4246,7 +4390,8 @@
mHandler.removeMessages(MSG_ACCESSIBILITY_TV);
}
- private void requestBugreportForTv() {
+ @VisibleForTesting
+ void requestBugreportForTv() {
try {
if (!ActivityManager.getService().launchBugReportHandlerApp()) {
ActivityManager.getService().requestInteractiveBugReport();
@@ -4259,7 +4404,7 @@
// TODO(b/117479243): handle it in InputPolicy
/** {@inheritDoc} */
@Override
- public KeyEvent dispatchUnhandledKey(IBinder focusedToken, KeyEvent event, int policyFlags) {
+ public boolean interceptUnhandledKey(KeyEvent event, IBinder focusedToken) {
// Note: This method is only called if the initial down was unhandled.
if (DEBUG_INPUT) {
final KeyInterceptionInfo info =
@@ -4272,75 +4417,26 @@
+ ", keyCode=" + event.getKeyCode()
+ ", scanCode=" + event.getScanCode()
+ ", metaState=" + event.getMetaState()
- + ", repeatCount=" + event.getRepeatCount()
- + ", policyFlags=" + policyFlags);
+ + ", repeatCount=" + event.getRepeatCount());
}
- if (interceptUnhandledKey(event, focusedToken)) {
- return null;
- }
-
- KeyEvent fallbackEvent = null;
- if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
- final KeyCharacterMap kcm = event.getKeyCharacterMap();
- final int keyCode = event.getKeyCode();
- final int metaState = event.getMetaState();
- final boolean initialDown = event.getAction() == KeyEvent.ACTION_DOWN
- && event.getRepeatCount() == 0;
-
- // Check for fallback actions specified by the key character map.
- final FallbackAction fallbackAction;
- if (initialDown) {
- fallbackAction = kcm.getFallbackAction(keyCode, metaState);
- } else {
- fallbackAction = mFallbackActions.get(keyCode);
- }
-
- if (fallbackAction != null) {
- if (DEBUG_INPUT) {
- Slog.d(TAG, "Fallback: keyCode=" + fallbackAction.keyCode
- + " metaState=" + Integer.toHexString(fallbackAction.metaState));
- }
-
- final int flags = event.getFlags() | KeyEvent.FLAG_FALLBACK;
- fallbackEvent = KeyEvent.obtain(
- event.getDownTime(), event.getEventTime(),
- event.getAction(), fallbackAction.keyCode,
- event.getRepeatCount(), fallbackAction.metaState,
- event.getDeviceId(), event.getScanCode(),
- flags, event.getSource(), event.getDisplayId(), null);
-
- if (!interceptFallback(focusedToken, fallbackEvent, policyFlags)) {
- fallbackEvent.recycle();
- fallbackEvent = null;
- }
-
- if (initialDown) {
- mFallbackActions.put(keyCode, fallbackAction);
- } else if (event.getAction() == KeyEvent.ACTION_UP) {
- mFallbackActions.remove(keyCode);
- fallbackAction.recycle();
- }
- }
- }
-
- if (DEBUG_INPUT) {
- if (fallbackEvent == null) {
- Slog.d(TAG, "No fallback.");
- } else {
- Slog.d(TAG, "Performing fallback: " + fallbackEvent);
- }
- }
- return fallbackEvent;
- }
-
- private boolean interceptUnhandledKey(KeyEvent event, IBinder focusedToken) {
final int keyCode = event.getKeyCode();
final int repeatCount = event.getRepeatCount();
final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
final int metaState = event.getModifiers();
- switch(keyCode) {
+ // TODO(b/358569822): Shift to KeyGestureEvent based handling
+ if (keyCode == KeyEvent.KEYCODE_STEM_PRIMARY) {
+ handleUnhandledSystemKey(event);
+ sendSystemKeyToStatusBarAsync(event);
+ return true;
+ }
+
+ if (useKeyGestureEventHandler()) {
+ return false;
+ }
+
+ switch (keyCode) {
case KeyEvent.KEYCODE_SPACE:
if (down && repeatCount == 0) {
// Handle keyboard layout switching. (CTRL + SPACE)
@@ -4377,10 +4473,6 @@
return true;
}
break;
- case KeyEvent.KEYCODE_STEM_PRIMARY:
- handleUnhandledSystemKey(event);
- sendSystemKeyToStatusBarAsync(event);
- return true;
}
return false;
@@ -4419,19 +4511,6 @@
targetWindowToken);
}
- private boolean interceptFallback(IBinder focusedToken, KeyEvent fallbackEvent,
- int policyFlags) {
- int actions = interceptKeyBeforeQueueing(fallbackEvent, policyFlags);
- if ((actions & ACTION_PASS_TO_USER) != 0) {
- long delayMillis = interceptKeyBeforeDispatching(
- focusedToken, fallbackEvent, policyFlags);
- if (delayMillis == 0 && !interceptUnhandledKey(fallbackEvent, focusedToken)) {
- return true;
- }
- }
- return false;
- }
-
@Override
public void setTopFocusedDisplay(int displayId) {
mTopFocusedDisplayId = displayId;
@@ -4888,6 +4967,7 @@
final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
boolean isWakeKey = (policyFlags & WindowManagerPolicy.FLAG_WAKE) != 0
|| event.isWakeKey();
+ boolean isKeyGestureTriggered = (policyFlags & FLAG_KEY_GESTURE_TRIGGERED) != 0;
// There are key events that perform the operation as the current user,
// and these should be ignored for visible background users.
@@ -5014,8 +5094,13 @@
final boolean isDefaultDisplayOn = Display.isOnState(mDefaultDisplay.getState());
final boolean isDefaultDisplayAwake = mDefaultDisplayPolicy.isAwake();
final boolean interactiveAndAwake = interactive && isDefaultDisplayAwake;
- if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
- handleKeyGesture(event, interactiveAndAwake, isDefaultDisplayOn);
+ if (isKeyGestureTriggered) {
+ // If key gesture is triggered outside policy, reset gesture handlers here
+ mSingleKeyGestureDetector.reset();
+ } else {
+ if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
+ handleKeyGesture(event, interactiveAndAwake, isDefaultDisplayOn);
+ }
}
// Enable haptics if down and virtual key without multiple repetitions. If this is a hard
@@ -5178,7 +5263,7 @@
result &= ~ACTION_PASS_TO_USER;
isWakeKey = false; // wake-up will be handled separately
if (down) {
- interceptPowerKeyDown(event, interactiveAndAwake);
+ interceptPowerKeyDown(event, interactiveAndAwake, isKeyGestureTriggered);
} else {
interceptPowerKeyUp(event, canceled);
}
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 892af6b..ad11657 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -754,11 +754,9 @@
* @param focusedToken Client window token that currently has focus. This is where the key
* event will normally go.
* @param event The key event.
- * @param policyFlags The policy flags associated with the key.
- * @return Returns an alternate key event to redispatch as a fallback, or null to give up.
- * The caller is responsible for recycling the key event.
+ * @return true if the unhandled key is intercepted by the policy.
*/
- KeyEvent dispatchUnhandledKey(IBinder focusedToken, KeyEvent event, int policyFlags);
+ boolean interceptUnhandledKey(KeyEvent event, IBinder focusedToken);
/**
* Called when the top focused display is changed.
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 232c3b6..dcf0319 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -188,9 +188,8 @@
* the application did not handle.
*/
@Override
- public KeyEvent dispatchUnhandledKey(
- IBinder focusedToken, KeyEvent event, int policyFlags) {
- return mService.mPolicy.dispatchUnhandledKey(focusedToken, event, policyFlags);
+ public boolean interceptUnhandledKey(KeyEvent event, IBinder focusedToken) {
+ return mService.mPolicy.interceptUnhandledKey(event, focusedToken);
}
/** Callback to get pointer layer. */
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 9da848a..bf623b2 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -24,6 +24,8 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -2040,10 +2042,15 @@
final boolean isOtherUndefinedMode = otherWindowingMode == WINDOWING_MODE_UNDEFINED;
// An activity type and windowing mode is compatible if they are the exact same type/mode,
- // or if one of the type/modes is undefined
+ // or if one of the type/modes is undefined. This is with the exception of
+ // freeform/fullscreen where both modes are assumed to be compatible with each other.
final boolean isCompatibleType = activityType == otherActivityType
|| isUndefinedType || isOtherUndefinedType;
final boolean isCompatibleMode = windowingMode == otherWindowingMode
+ || (windowingMode == WINDOWING_MODE_FREEFORM
+ && otherWindowingMode == WINDOWING_MODE_FULLSCREEN)
+ || (windowingMode == WINDOWING_MODE_FULLSCREEN
+ && otherWindowingMode == WINDOWING_MODE_FREEFORM)
|| isUndefinedMode || isOtherUndefinedMode;
return isCompatibleType && isCompatibleMode;
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 0eafb59..a07facf 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -512,8 +512,6 @@
<xs:annotation name="final"/>
</xs:element>
</xs:sequence>
- <!-- valid value of interpolation if specified: linear -->
- <xs:attribute name="interpolation" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="brightnessPoint">
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 355b0ab..5309263 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -91,8 +91,6 @@
public class ComprehensiveBrightnessMap {
ctor public ComprehensiveBrightnessMap();
method @NonNull public final java.util.List<com.android.server.display.config.BrightnessPoint> getBrightnessPoint();
- method public String getInterpolation();
- method public void setInterpolation(String);
}
public class Density {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index fd05b26..8e1be9a 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -392,7 +392,7 @@
public void testInvalidLuxThrottling() throws Exception {
setupDisplayDeviceConfigFromDisplayConfigFile(
getContent(getInvalidLuxThrottling(), getValidProxSensor(),
- /* includeIdleMode= */ true, /* enableEvenDimmer */ false));
+ /* includeIdleMode= */ true, /* enableEvenDimmer= */ false));
Map<DisplayDeviceConfig.BrightnessLimitMapType, Map<Float, Float>> luxThrottlingData =
mDisplayDeviceConfig.getLuxThrottlingData();
@@ -600,7 +600,7 @@
public void testProximitySensorWithEmptyValuesFromDisplayConfig() throws IOException {
setupDisplayDeviceConfigFromDisplayConfigFile(
getContent(getValidLuxThrottling(), getProxSensorWithEmptyValues(),
- /* includeIdleMode= */ true, /* enableEvenDimmer */ false));
+ /* includeIdleMode= */ true, /* enableEvenDimmer= */ false));
assertNull(mDisplayDeviceConfig.getProximitySensor());
}
@@ -608,7 +608,7 @@
public void testProximitySensorWithRefreshRatesFromDisplayConfig() throws IOException {
setupDisplayDeviceConfigFromDisplayConfigFile(
getContent(getValidLuxThrottling(), getValidProxSensorWithRefreshRateAndVsyncRate(),
- /* includeIdleMode= */ true, /* enableEvenDimmer */ false));
+ /* includeIdleMode= */ true, /* enableEvenDimmer= */ false));
assertEquals("test_proximity_sensor",
mDisplayDeviceConfig.getProximitySensor().type);
assertEquals("Test Proximity Sensor",
@@ -803,7 +803,7 @@
@Test
public void testBrightnessRamps_IdleFallsBackToConfigInteractive() throws IOException {
setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(),
- getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer */ false));
+ getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer= */ false));
assertEquals(mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis(), 3000);
assertEquals(mDisplayDeviceConfig.getBrightnessRampIncreaseMaxMillis(), 2000);
@@ -820,14 +820,14 @@
@Test
public void testBrightnessCapForWearBedtimeMode() throws IOException {
setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(),
- getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer */ false));
+ getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer= */ false));
assertEquals(0.1f, mDisplayDeviceConfig.getBrightnessCapForWearBedtimeMode(), ZERO_DELTA);
}
@Test
public void testAutoBrightnessBrighteningLevels() throws IOException {
setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(),
- getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer */ false));
+ getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer= */ false));
assertArrayEquals(new float[]{0.0f, 80},
mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
@@ -890,7 +890,7 @@
when(mFlags.areAutoBrightnessModesEnabled()).thenReturn(false);
setupDisplayDeviceConfigFromConfigResourceFile();
setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(),
- getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer */ false));
+ getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer= */ false));
assertArrayEquals(new float[]{brightnessIntToFloat(50), brightnessIntToFloat(100),
brightnessIntToFloat(150)},
@@ -929,7 +929,7 @@
when(mFlags.isEvenDimmerEnabled()).thenReturn(true);
when(mResources.getBoolean(R.bool.config_evenDimmerEnabled)).thenReturn(true);
setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(),
- getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer */ true));
+ getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer= */ true));
assertTrue(mDisplayDeviceConfig.isEvenDimmerAvailable());
assertEquals(0.01f, mDisplayDeviceConfig.getBacklightFromBrightness(0.002f), ZERO_DELTA);
@@ -1365,7 +1365,7 @@
private String getContent() {
return getContent(getValidLuxThrottling(), getValidProxSensor(),
- /* includeIdleMode= */ true, false);
+ /* includeIdleMode= */ true, /* enableEvenDimmer= */ false);
}
private String getContent(String brightnessCapConfig, String proxSensor,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 120cc84..f5bed99 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -29,8 +29,10 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -45,6 +47,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.util.Spline;
import android.view.Display;
import android.view.DisplayAddress;
import android.view.SurfaceControl;
@@ -59,6 +62,7 @@
import com.android.internal.R;
import com.android.server.LocalServices;
import com.android.server.display.LocalDisplayAdapter.BacklightAdapter;
+import com.android.server.display.color.ColorDisplayService;
import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.mode.DisplayModeDirector;
import com.android.server.display.notifications.DisplayNotificationManager;
@@ -119,6 +123,8 @@
private DisplayManagerFlags mFlags;
@Mock
private DisplayPowerController mMockedDisplayPowerController;
+ @Mock
+ private ColorDisplayService.ColorDisplayServiceInternal mMockedColorDisplayServiceInternal;
private Handler mHandler;
@@ -133,6 +139,11 @@
private Injector mInjector;
@Mock
+ private DisplayDeviceConfig mMockDisplayDeviceConfig;
+ @Mock
+ private BacklightAdapter mMockBacklightAdapter;
+
+ @Mock
private LocalDisplayAdapter.SurfaceControlProxy mSurfaceControlProxy;
private static final float[] DISPLAY_RANGE_NITS = { 2.685f, 478.5f };
private static final int[] BACKLIGHT_RANGE = { 1, 255 };
@@ -150,6 +161,9 @@
doReturn(mMockedResources).when(mMockedContext).getResources();
LocalServices.removeServiceForTest(LightsManager.class);
LocalServices.addService(LightsManager.class, mMockedLightsManager);
+ LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
+ LocalServices.addService(ColorDisplayService.ColorDisplayServiceInternal.class,
+ mMockedColorDisplayServiceInternal);
mInjector = new Injector();
when(mSurfaceControlProxy.getBootDisplayModeSupport()).thenReturn(true);
mAdapter = new LocalDisplayAdapter(mMockedSyncRoot, mMockedContext, mHandler,
@@ -211,7 +225,15 @@
when(mMockedResources.getIntArray(
com.android.internal.R.array.config_autoBrightnessLcdBacklightValues))
.thenReturn(new int[]{});
+
+ when(mMockedColorDisplayServiceInternal.fetchEvenDimmerSpline(3)).thenReturn(
+ new Spline.LinearSpline(
+ new float[]{2f, 3.0f, 500f, 2000f},
+ new float[]{100, 0, 0, 0}));
+ when(mMockDisplayDeviceConfig.isEvenDimmerAvailable()).thenReturn(true);
+
doReturn(true).when(mFlags).isDisplayOffloadEnabled();
+ doReturn(true).when(mFlags).isEvenDimmerEnabled();
initDisplayOffloadSession();
}
@@ -222,6 +244,122 @@
}
}
+ @Test
+ public void testEvenDimmer() throws InterruptedException {
+ // Set up
+ FakeDisplay display = new FakeDisplay(PORT_A);
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+ // brightness|backlight| nits | strength
+ // 0.5 | 0.45 | 600 | 0 // initial setup value
+ // 0.4 | 0.35 | 500 | 0 // normal range value
+ // 0.31 | 0.2 | 3 | 0 // transition point
+ // 0.16 | 0.125 | 2.5 | 50 // mid point of even dimmer
+ // 0.1 | 0.05 | 2 | 100 // bottom of even dimmer range
+ // 0.05 | 0.01 | 1 | 100+ // beyond strength=100 range (should still return 100)
+ when(mMockDisplayDeviceConfig.getEvenDimmerTransitionPoint()).thenReturn(0.31f);
+ when(mMockDisplayDeviceConfig.getBacklightFromBrightness(0.5f)).thenReturn(0.45f);
+ when(mMockDisplayDeviceConfig.getBacklightFromBrightness(0.4f)).thenReturn(0.35f);
+ when(mMockDisplayDeviceConfig.getBacklightFromBrightness(0.31f)).thenReturn(0.2f);
+ when(mMockDisplayDeviceConfig.getBacklightFromBrightness(0.16f)).thenReturn(0.125f);
+ when(mMockDisplayDeviceConfig.getBacklightFromBrightness(0.1f)).thenReturn(0.05f);
+ when(mMockDisplayDeviceConfig.getBacklightFromBrightness(0.05f)).thenReturn(0.01f);
+ when(mMockDisplayDeviceConfig.getNitsFromBacklight(0.45f)).thenReturn(600f);
+ when(mMockDisplayDeviceConfig.getNitsFromBacklight(0.35f)).thenReturn(500f);
+ when(mMockDisplayDeviceConfig.getNitsFromBacklight(0.2f)).thenReturn(3f);
+ when(mMockDisplayDeviceConfig.getNitsFromBacklight(0.125f)).thenReturn(2.5f);
+ when(mMockDisplayDeviceConfig.getNitsFromBacklight(0.05f)).thenReturn(2f);
+ when(mMockDisplayDeviceConfig.getNitsFromBacklight(0.01f)).thenReturn(1f);
+
+ // initialise brightness to 0.5
+ Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON,
+ 0.5f, 0.5f, null);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ changeStateRunnable.run();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ verify(mSurfaceControlProxy).setDisplayPowerMode(any(), anyInt());
+ verify(mMockBacklightAdapter).setBacklight(anyFloat(), anyFloat(), anyFloat(), anyFloat());
+ verify(mMockedColorDisplayServiceInternal).applyEvenDimmerColorChanges(eq(false), eq(0));
+ verify(mMockedColorDisplayServiceInternal).fetchEvenDimmerSpline(eq(3.0f));
+
+ // set up normal brightness range
+ changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0.4f, 0.4f,
+ null);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ changeStateRunnable.run();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ // verify normal brightness range
+ verify(mMockBacklightAdapter).setBacklight(0.35f, 500f, 0.35f, 500f);
+ verify(mMockedColorDisplayServiceInternal,
+ times(1)) // no more, since the strength is the same
+ .applyEvenDimmerColorChanges(eq(false), eq(0));
+ verify(mMockedColorDisplayServiceInternal, times(2)).fetchEvenDimmerSpline(eq(3.0f));
+
+ // set up even dimmer edge range
+ changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0.31f,
+ 0.31f, null);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ changeStateRunnable.run();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ // verify even dimmer edge range
+ verify(mMockBacklightAdapter).setBacklight(0.2f, 3f, 0.2f, 3f);
+ // verify no more times, since the strength and enabled-ness is the same
+ verify(mMockedColorDisplayServiceInternal, times(1)).applyEvenDimmerColorChanges(eq(false),
+ eq(0));
+ verify(mMockedColorDisplayServiceInternal, times(3)).fetchEvenDimmerSpline(eq(3.0f));
+
+ // set up mid point of even dimmer range
+ changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0.16f,
+ 0.16f, null);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ changeStateRunnable.run();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ // verify within even dimmer range
+ verify(mMockBacklightAdapter).setBacklight(0.125f, 2.5f, 0.125f, 2.5f);
+ verify(mMockedColorDisplayServiceInternal).applyEvenDimmerColorChanges(eq(true), eq(50));
+ verify(mMockedColorDisplayServiceInternal, times(4)).fetchEvenDimmerSpline(eq(3.0f));
+
+ // set up within even dimmer range
+ changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0.1f, 0.1f,
+ null);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ changeStateRunnable.run();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ // verify within even dimmer range
+ verify(mMockBacklightAdapter).setBacklight(0.05f, 2f, 0.05f, 2f);
+ verify(mMockedColorDisplayServiceInternal).applyEvenDimmerColorChanges(eq(true), eq(100));
+ verify(mMockedColorDisplayServiceInternal, times(5)).fetchEvenDimmerSpline(eq(3.0f));
+
+ // set up below even dimmer range
+ changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0.05f,
+ 0.05f, null);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ changeStateRunnable.run();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ // verify within even dimmer range
+ verify(mMockBacklightAdapter).setBacklight(0.01f, 1f, 0.01f, 1f);
+ // ensure no greater than 100 strength is returned, therefore not called again.
+ verify(mMockedColorDisplayServiceInternal).applyEvenDimmerColorChanges(eq(true), eq(100));
+ verify(mMockedColorDisplayServiceInternal, times(6)).fetchEvenDimmerSpline(eq(3.0f));
+
+ // set up return to normal range
+ changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0.4f, 0.4f,
+ null);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ changeStateRunnable.run();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ // verify return to normal range
+ verify(mMockBacklightAdapter, times(2)).setBacklight(0.35f, 500f, 0.35f, 500f);
+ verify(mMockedColorDisplayServiceInternal, times(2)).applyEvenDimmerColorChanges(eq(false),
+ anyInt());
+ verify(mMockedColorDisplayServiceInternal, times(7)).fetchEvenDimmerSpline(eq(3.0f));
+ }
+
/**
* Confirm that display is marked as private when it is listed in
* com.android.internal.R.array.config_localPrivateDisplayPorts.
@@ -1461,15 +1599,16 @@
return mSurfaceControlProxy;
}
- // Instead of using DisplayDeviceConfig.create(context, physicalDisplayId, isFirstDisplay)
- // we should use DisplayDeviceConfig.create(context, isFirstDisplay) for the test to ensure
- // that real device DisplayDeviceConfig is not loaded for FakeDisplay and we are getting
- // consistent behaviour. Please also note that context passed to this method, is
- // mMockContext and values will be loaded from mMockResources.
@Override
public DisplayDeviceConfig createDisplayDeviceConfig(Context context,
long physicalDisplayId, boolean isFirstDisplay, DisplayManagerFlags flags) {
- return DisplayDeviceConfig.create(context, isFirstDisplay, flags);
+ return mMockDisplayDeviceConfig;
+ }
+
+ @Override
+ public BacklightAdapter getBacklightAdapter(IBinder displayToken, boolean isFirstDisplay,
+ LocalDisplayAdapter.SurfaceControlProxy surfaceControlProxy) {
+ return mMockBacklightAdapter;
}
}
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
index 4645156..3e2949d6 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
@@ -16,7 +16,9 @@
package com.android.server.audio;
+import static com.android.media.audio.Flags.FLAG_ABS_VOLUME_INDEX_FIX;
import static com.android.media.audio.Flags.FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME;
+import static com.android.media.audio.Flags.absVolumeIndexFix;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.mock;
@@ -116,7 +118,7 @@
}
@Test
- @RequiresFlagsDisabled(FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME)
+ @RequiresFlagsDisabled({FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME, FLAG_ABS_VOLUME_INDEX_FIX})
public void configurablePreScaleAbsoluteVolume_checkIndex() throws Exception {
AudioManager am = mContext.getSystemService(AudioManager.class);
final int minIndex = am.getStreamMinVolume(AudioManager.STREAM_MUSIC);
@@ -177,6 +179,7 @@
final AudioDeviceAttributes bleDevice = new AudioDeviceAttributes(
/*native type*/ AudioSystem.DEVICE_OUT_BLE_HEADSET, /*address*/ "bla");
final int maxPreScaleIndex = 3;
+ int passedIndex = maxIndex;
for (int i = 0; i < maxPreScaleIndex; i++) {
final VolumeInfo volCur = new VolumeInfo.Builder(volMedia)
@@ -185,9 +188,12 @@
mAudioService.setDeviceVolume(volCur, bleDevice, mPackageName);
mTestLooper.dispatchAll();
+ if (absVolumeIndexFix()) {
+ passedIndex = i + 1;
+ }
// Stream volume changes
verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
- AudioManager.STREAM_MUSIC, maxIndex,
+ AudioManager.STREAM_MUSIC, passedIndex,
AudioSystem.DEVICE_OUT_BLE_HEADSET);
}
@@ -197,8 +203,11 @@
mAudioService.setDeviceVolume(volIndex4, bleDevice, mPackageName);
mTestLooper.dispatchAll();
+ if (absVolumeIndexFix()) {
+ passedIndex = 4;
+ }
verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
- AudioManager.STREAM_MUSIC, maxIndex,
+ AudioManager.STREAM_MUSIC, passedIndex,
AudioSystem.DEVICE_OUT_BLE_HEADSET);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
index beed0a3d..c305fd9 100644
--- a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
@@ -39,8 +39,9 @@
import static android.view.KeyEvent.ACTION_DOWN;
import static android.view.KeyEvent.KEYCODE_VOLUME_UP;
-import static com.android.media.audio.Flags.FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME;
import static com.android.media.audio.Flags.FLAG_ABS_VOLUME_INDEX_FIX;
+import static com.android.media.audio.Flags.FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME;
+import static com.android.media.audio.Flags.absVolumeIndexFix;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -627,7 +628,6 @@
@Test
@RequiresFlagsEnabled(FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME)
- @RequiresFlagsDisabled(FLAG_ABS_VOLUME_INDEX_FIX)
public void disablePreScaleAbsoluteVolume_checkIndex() throws Exception {
final int minIndex = mAm.getStreamMinVolume(STREAM_MUSIC);
final int maxIndex = mAm.getStreamMaxVolume(STREAM_MUSIC);
@@ -638,6 +638,7 @@
final AudioDeviceAttributes bleDevice = new AudioDeviceAttributes(
/*native type*/ AudioSystem.DEVICE_OUT_BLE_HEADSET, /*address*/ "bla");
final int maxPreScaleIndex = 3;
+ int passedIndex = maxIndex;
for (int i = 0; i < maxPreScaleIndex; i++) {
final VolumeInfo volCur = new VolumeInfo.Builder(volMedia)
@@ -646,9 +647,12 @@
mAudioService.setDeviceVolume(volCur, bleDevice, mContext.getOpPackageName());
mTestLooper.dispatchAll();
+ if (absVolumeIndexFix()) {
+ passedIndex = i + 1;
+ }
// Stream volume changes
verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
- STREAM_MUSIC, maxIndex,
+ STREAM_MUSIC, passedIndex,
AudioSystem.DEVICE_OUT_BLE_HEADSET);
}
@@ -658,8 +662,11 @@
mAudioService.setDeviceVolume(volIndex4, bleDevice, mContext.getOpPackageName());
mTestLooper.dispatchAll();
+ if (absVolumeIndexFix()) {
+ passedIndex = 4;
+ }
verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
- STREAM_MUSIC, maxIndex,
+ STREAM_MUSIC, passedIndex,
AudioSystem.DEVICE_OUT_BLE_HEADSET);
}
diff --git a/services/tests/servicestests/src/com/android/server/pdb/PersistentDataBlockServiceTest.java b/services/tests/servicestests/src/com/android/server/pdb/PersistentDataBlockServiceTest.java
index f91f77a..cdfc521 100644
--- a/services/tests/servicestests/src/com/android/server/pdb/PersistentDataBlockServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pdb/PersistentDataBlockServiceTest.java
@@ -86,7 +86,6 @@
private File mDataBlockFile;
private File mFrpSecretFile;
private File mFrpSecretTmpFile;
- private String mOemUnlockPropertyValue;
private boolean mIsUpgradingFromPreV = false;
@Mock private UserManager mUserManager;
@@ -105,13 +104,6 @@
}
@Override
- void setProperty(String key, String value) {
- // Override to capture the value instead of actually setting the property.
- assertThat(key).isEqualTo("sys.oem_unlock_allowed");
- mOemUnlockPropertyValue = value;
- }
-
- @Override
boolean isUpgradingFromPreVRelease() {
return mIsUpgradingFromPreV;
}
@@ -598,7 +590,6 @@
mInterface.setOemUnlockEnabled(true);
assertThat(mInterface.getOemUnlockEnabled()).isTrue();
- assertThat(mOemUnlockPropertyValue).isEqualTo("1");
}
@Test
@@ -635,7 +626,6 @@
// The current implementation does not check digest before set or get the oem unlock bit.
tamperWithDigest();
mInterface.setOemUnlockEnabled(true);
- assertThat(mOemUnlockPropertyValue).isEqualTo("1");
tamperWithDigest();
assertThat(mInterface.getOemUnlockEnabled()).isTrue();
}
@@ -676,7 +666,6 @@
mInternalInterface.forceOemUnlockEnabled(true);
- assertThat(mOemUnlockPropertyValue).isEqualTo("1");
assertThat(readBackingFile(mPdbService.getOemUnlockDataOffset(), 1).array())
.isEqualTo(new byte[] { 1 });
}
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 d69d678..6c9015d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -16611,7 +16611,7 @@
mService.addNotification(r);
mService.addEnqueuedNotification(r1);
- mBinderService.setCanBePromoted(mPkg, mUid, true);
+ mBinderService.setCanBePromoted(mPkg, mUid, true, true);
waitForIdle();
@@ -16651,9 +16651,9 @@
mService.addNotification(r);
- mBinderService.setCanBePromoted(mPkg, mUid, true);
+ mBinderService.setCanBePromoted(mPkg, mUid, true, true);
waitForIdle();
- mBinderService.setCanBePromoted(mPkg, mUid, true);
+ mBinderService.setCanBePromoted(mPkg, mUid, true, true);
waitForIdle();
ArgumentCaptor<NotificationRecord> captor =
@@ -16668,7 +16668,7 @@
mContext.getTestablePermissions().setPermission(
android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
// start from true state
- mBinderService.setCanBePromoted(mPkg, mUid, true);
+ mBinderService.setCanBePromoted(mPkg, mUid, true, true);
// qualifying posted notification
Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
@@ -16709,7 +16709,7 @@
mService.addNotification(r);
mService.addEnqueuedNotification(r1);
- mBinderService.setCanBePromoted(mPkg, mUid, false);
+ mBinderService.setCanBePromoted(mPkg, mUid, false, true);
waitForIdle();
@@ -16733,7 +16733,7 @@
mContext.getTestablePermissions().setPermission(
android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
// start from true state
- mBinderService.setCanBePromoted(mPkg, mUid, true);
+ mBinderService.setCanBePromoted(mPkg, mUid, true, true);
// qualifying posted notification
Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
@@ -16751,9 +16751,9 @@
mService.addNotification(r);
- mBinderService.setCanBePromoted(mPkg, mUid, false);
+ mBinderService.setCanBePromoted(mPkg, mUid, false, true);
waitForIdle();
- mBinderService.setCanBePromoted(mPkg, mUid, false);
+ mBinderService.setCanBePromoted(mPkg, mUid, false, true);
waitForIdle();
ArgumentCaptor<NotificationRecord> captor =
@@ -16765,7 +16765,7 @@
@Test
@EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
public void testPostPromotableNotification() throws Exception {
- mBinderService.setCanBePromoted(mPkg, mUid, true);
+ mBinderService.setCanBePromoted(mPkg, mUid, true, true);
assertThat(mBinderService.appCanBePromoted(mPkg, mUid)).isTrue();
mContext.getTestablePermissions().setPermission(
android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
@@ -16823,7 +16823,7 @@
@Test
@EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
public void testPostPromotableNotification_unimportantNotification() throws Exception {
- mBinderService.setCanBePromoted(mPkg, mUid, true);
+ mBinderService.setCanBePromoted(mPkg, mUid, true, true);
mContext.getTestablePermissions().setPermission(
android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
Notification n = new Notification.Builder(mContext, mMinChannel.getId())
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 7d63062..a0c0df8 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -62,6 +62,7 @@
import static com.android.server.notification.Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA;
import static com.android.server.notification.NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_UPDATED_BY_USER;
import static com.android.server.notification.PreferencesHelper.DEFAULT_BUBBLE_PREFERENCE;
+import static com.android.server.notification.PreferencesHelper.LockableAppFields.USER_LOCKED_PROMOTABLE;
import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_COUNT_LIMIT;
import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT;
import static com.android.server.notification.PreferencesHelper.UNKNOWN_UID;
@@ -643,7 +644,7 @@
mHelper.setShowBadge(PKG_N_MR1, UID_N_MR1, true);
if (android.app.Flags.uiRichOngoing()) {
- mHelper.setCanBePromoted(PKG_N_MR1, UID_N_MR1, true);
+ mHelper.setCanBePromoted(PKG_N_MR1, UID_N_MR1, true, true);
}
ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, false,
@@ -657,6 +658,8 @@
assertTrue(mXmlHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
if (android.app.Flags.uiRichOngoing()) {
assertThat(mXmlHelper.canBePromoted(PKG_N_MR1, UID_N_MR1)).isTrue();
+ assertThat(mXmlHelper.getAppLockedFields(PKG_N_MR1, UID_N_MR1) & USER_LOCKED_PROMOTABLE)
+ .isNotEqualTo(0);
}
assertEquals(channel1,
mXmlHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1.getId(), false));
@@ -6311,20 +6314,30 @@
}
@Test
- @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
public void testNoAppHasPermissionToPromoteByDefault() {
mHelper.setShowBadge(PKG_P, UID_P, true);
assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isFalse();
}
@Test
- @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
public void testSetCanBePromoted() {
- mHelper.setCanBePromoted(PKG_P, UID_P, true);
+ mHelper.setCanBePromoted(PKG_P, UID_P, true, true);
assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isTrue();
- mHelper.setCanBePromoted(PKG_P, UID_P, false);
+ mHelper.setCanBePromoted(PKG_P, UID_P, false, true);
assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isFalse();
verify(mHandler, never()).requestSort();
}
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
+ public void testSetCanBePromoted_allowlistNotOverrideUser() {
+ mHelper.setCanBePromoted(PKG_P, UID_P, true, true);
+ assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isTrue();
+
+ mHelper.setCanBePromoted(PKG_P, UID_P, false, false);
+ assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isTrue();
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java b/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java
index 1c33116..6f9c890 100644
--- a/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java
@@ -22,6 +22,7 @@
import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS;
import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAVIOR_MUTE;
+import android.platform.test.annotations.DisableFlags;
import android.view.ViewConfiguration;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -39,6 +40,7 @@
*/
@MediumTest
@RunWith(AndroidJUnit4.class)
+@DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER_MULTI_PRESS_GESTURES)
public class CombinationKeyTests extends ShortcutKeyTestBase {
private static final long A11Y_KEY_HOLD_MILLIS = 3500;
diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java
index 487390d..8b5f68a 100644
--- a/services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java
@@ -71,12 +71,12 @@
new KeyCombinationManager.TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN,
KEYCODE_POWER) {
@Override
- void execute() {
+ public void execute() {
mAction1Triggered.countDown();
}
@Override
- void cancel() {
+ public void cancel() {
}
});
@@ -85,21 +85,21 @@
new KeyCombinationManager.TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN,
KEYCODE_VOLUME_UP) {
@Override
- boolean preCondition() {
+ public boolean preCondition() {
return mPreCondition;
}
@Override
- void execute() {
+ public void execute() {
mAction2Triggered.countDown();
}
@Override
- void cancel() {
+ public void cancel() {
}
@Override
- long getKeyInterceptDelayMs() {
+ public long getKeyInterceptDelayMs() {
return 0;
}
});
@@ -115,12 +115,12 @@
};
@Override
- void execute() {
+ public void execute() {
mHandler.postDelayed(mAction, SCHEDULE_TIME);
}
@Override
- void cancel() {
+ public void cancel() {
mHandler.removeCallbacks(mAction);
}
});
@@ -235,12 +235,12 @@
new KeyCombinationManager.TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN,
KEYCODE_POWER) {
@Override
- void execute() {
+ public void execute() {
mAction1Triggered.countDown();
}
@Override
- void cancel() {
+ public void cancel() {
}
};
diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
index 3d978e4..cdb4542 100644
--- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
@@ -22,10 +22,13 @@
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_ALL_APPS;
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_ASSIST;
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_NOTIFICATION_PANEL;
+import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS;
+import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAVIOR_MUTE;
import static com.android.server.policy.PhoneWindowManager.SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL;
import android.hardware.input.KeyGestureEvent;
import android.os.RemoteException;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.view.KeyEvent;
@@ -56,88 +59,15 @@
private static final int CTRL_ON = MODIFIER.get(KeyEvent.KEYCODE_CTRL_LEFT);
@Keep
- private static Object[][] shortcutTestArguments() {
+ private static Object[][] shortcutTestArgumentsNotMigratedToKeyGestureController() {
// testName, testKeys, expectedKeyGestureType, expectedKey, expectedModifierState
return new Object[][]{
- {"Meta + H -> Open Home", new int[]{META_KEY, KeyEvent.KEYCODE_H},
- KeyGestureEvent.KEY_GESTURE_TYPE_HOME, KeyEvent.KEYCODE_H, META_ON},
- {"Meta + Enter -> Open Home", new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
- KeyGestureEvent.KEY_GESTURE_TYPE_HOME, KeyEvent.KEYCODE_ENTER,
- META_ON},
{"HOME key -> Open Home", new int[]{KeyEvent.KEYCODE_HOME},
KeyGestureEvent.KEY_GESTURE_TYPE_HOME,
KeyEvent.KEYCODE_HOME, 0},
- {"RECENT_APPS key -> Open Overview", new int[]{KeyEvent.KEYCODE_RECENT_APPS},
- KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS,
- KeyEvent.KEYCODE_RECENT_APPS, 0},
- {"Meta + Tab -> Open Overview", new int[]{META_KEY, KeyEvent.KEYCODE_TAB},
- KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS, KeyEvent.KEYCODE_TAB,
- META_ON},
- {"Alt + Tab -> Open Overview", new int[]{ALT_KEY, KeyEvent.KEYCODE_TAB},
- KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS, KeyEvent.KEYCODE_TAB,
- ALT_ON},
{"BACK key -> Go back", new int[]{KeyEvent.KEYCODE_BACK},
KeyGestureEvent.KEY_GESTURE_TYPE_BACK,
KeyEvent.KEYCODE_BACK, 0},
- {"Meta + Escape -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_ESCAPE},
- KeyGestureEvent.KEY_GESTURE_TYPE_BACK, KeyEvent.KEYCODE_ESCAPE,
- META_ON},
- {"Meta + Left arrow -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DPAD_LEFT},
- KeyGestureEvent.KEY_GESTURE_TYPE_BACK, KeyEvent.KEYCODE_DPAD_LEFT,
- META_ON},
- {"Meta + Del -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DEL},
- KeyGestureEvent.KEY_GESTURE_TYPE_BACK, KeyEvent.KEYCODE_DEL, META_ON},
- {"APP_SWITCH key -> Open App switcher", new int[]{KeyEvent.KEYCODE_APP_SWITCH},
- KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH,
- KeyEvent.KEYCODE_APP_SWITCH, 0},
- {"ASSIST key -> Launch assistant", new int[]{KeyEvent.KEYCODE_ASSIST},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT,
- KeyEvent.KEYCODE_ASSIST, 0},
- {"Meta + A -> Launch assistant", new int[]{META_KEY, KeyEvent.KEYCODE_A},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT, KeyEvent.KEYCODE_A,
- META_ON},
- {"VOICE_ASSIST key -> Launch Voice Assistant",
- new int[]{KeyEvent.KEYCODE_VOICE_ASSIST},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT,
- KeyEvent.KEYCODE_VOICE_ASSIST, 0},
- {"Meta + I -> Launch System Settings", new int[]{META_KEY, KeyEvent.KEYCODE_I},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS,
- KeyEvent.KEYCODE_I, META_ON},
- {"Meta + N -> Toggle Notification panel", new int[]{META_KEY, KeyEvent.KEYCODE_N},
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
- KeyEvent.KEYCODE_N, META_ON},
- {"NOTIFICATION key -> Toggle Notification Panel",
- new int[]{KeyEvent.KEYCODE_NOTIFICATION},
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
- KeyEvent.KEYCODE_NOTIFICATION,
- 0},
- {"Meta + Ctrl + S -> Take Screenshot",
- new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_S},
- KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT, KeyEvent.KEYCODE_S,
- META_ON | CTRL_ON},
- {"Meta + / -> Open Shortcut Helper", new int[]{META_KEY, KeyEvent.KEYCODE_SLASH},
- KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER,
- KeyEvent.KEYCODE_SLASH, META_ON},
- {"BRIGHTNESS_UP key -> Increase Brightness",
- new int[]{KeyEvent.KEYCODE_BRIGHTNESS_UP},
- KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP,
- KeyEvent.KEYCODE_BRIGHTNESS_UP, 0},
- {"BRIGHTNESS_DOWN key -> Decrease Brightness",
- new int[]{KeyEvent.KEYCODE_BRIGHTNESS_DOWN},
- KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN,
- KeyEvent.KEYCODE_BRIGHTNESS_DOWN, 0},
- {"KEYBOARD_BACKLIGHT_UP key -> Increase Keyboard Backlight",
- new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP},
- KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP,
- KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP, 0},
- {"KEYBOARD_BACKLIGHT_DOWN key -> Decrease Keyboard Backlight",
- new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN},
- KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN,
- KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN, 0},
- {"KEYBOARD_BACKLIGHT_TOGGLE key -> Toggle Keyboard Backlight",
- new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE},
- KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE,
- KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE, 0},
{"VOLUME_UP key -> Increase Volume", new int[]{KeyEvent.KEYCODE_VOLUME_UP},
KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_UP,
KeyEvent.KEYCODE_VOLUME_UP, 0},
@@ -147,53 +77,9 @@
{"VOLUME_MUTE key -> Mute Volume", new int[]{KeyEvent.KEYCODE_VOLUME_MUTE},
KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_MUTE,
KeyEvent.KEYCODE_VOLUME_MUTE, 0},
- {"ALL_APPS key -> Open App Drawer",
- new int[]{KeyEvent.KEYCODE_ALL_APPS},
- KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS,
- KeyEvent.KEYCODE_ALL_APPS, 0},
- {"SEARCH key -> Launch Search Activity", new int[]{KeyEvent.KEYCODE_SEARCH},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH,
- KeyEvent.KEYCODE_SEARCH, 0},
- {"LANGUAGE_SWITCH key -> Switch Keyboard Language",
- new int[]{KeyEvent.KEYCODE_LANGUAGE_SWITCH},
- KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
- KeyEvent.KEYCODE_LANGUAGE_SWITCH, 0},
- {"META key -> Open App Drawer", new int[]{META_KEY},
- KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS, META_KEY,
- META_ON},
- {"Meta + Alt -> Toggle CapsLock", new int[]{META_KEY, ALT_KEY},
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK, ALT_KEY,
- META_ON | ALT_ON},
- {"Alt + Meta -> Toggle CapsLock", new int[]{ALT_KEY, META_KEY},
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK, META_KEY,
- META_ON | ALT_ON},
- {"CAPS_LOCK key -> Toggle CapsLock", new int[]{KeyEvent.KEYCODE_CAPS_LOCK},
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
- KeyEvent.KEYCODE_CAPS_LOCK, 0},
{"MUTE key -> Mute System Microphone", new int[]{KeyEvent.KEYCODE_MUTE},
KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_MUTE, KeyEvent.KEYCODE_MUTE,
0},
- {"Meta + Ctrl + DPAD_UP -> Split screen navigation",
- new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_UP},
- KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION,
- KeyEvent.KEYCODE_DPAD_UP,
- META_ON | CTRL_ON},
- {"Meta + Ctrl + DPAD_LEFT -> Split screen navigation",
- new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_LEFT},
- KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT,
- KeyEvent.KEYCODE_DPAD_LEFT,
- META_ON | CTRL_ON},
- {"Meta + Ctrl + DPAD_RIGHT -> Split screen navigation",
- new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_RIGHT},
- KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT,
- KeyEvent.KEYCODE_DPAD_RIGHT,
- META_ON | CTRL_ON},
- {"Meta + L -> Lock Homescreen", new int[]{META_KEY, KeyEvent.KEYCODE_L},
- KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN, KeyEvent.KEYCODE_L,
- META_ON},
- {"Meta + Ctrl + N -> Open Notes", new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_N},
- KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES, KeyEvent.KEYCODE_N,
- META_ON | CTRL_ON},
{"POWER key -> Toggle Power", new int[]{KeyEvent.KEYCODE_POWER},
KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_POWER, KeyEvent.KEYCODE_POWER,
0},
@@ -279,7 +165,130 @@
{"Meta + S -> Launch Default Messaging App",
new int[]{META_KEY, KeyEvent.KEYCODE_S},
KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING,
- KeyEvent.KEYCODE_S, META_ON},
+ KeyEvent.KEYCODE_S, META_ON}};
+ }
+
+ @Keep
+ private static Object[][] shortcutTestArgumentsMigratedToKeyGestureController() {
+ // testName, testKeys, expectedKeyGestureType, expectedKey, expectedModifierState
+ return new Object[][]{
+ {"Meta + H -> Open Home", new int[]{META_KEY, KeyEvent.KEYCODE_H},
+ KeyGestureEvent.KEY_GESTURE_TYPE_HOME, KeyEvent.KEYCODE_H, META_ON},
+ {"Meta + Enter -> Open Home", new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
+ KeyGestureEvent.KEY_GESTURE_TYPE_HOME, KeyEvent.KEYCODE_ENTER,
+ META_ON},
+ {"RECENT_APPS key -> Open Overview", new int[]{KeyEvent.KEYCODE_RECENT_APPS},
+ KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS,
+ KeyEvent.KEYCODE_RECENT_APPS, 0},
+ {"Meta + Tab -> Open Overview", new int[]{META_KEY, KeyEvent.KEYCODE_TAB},
+ KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS, KeyEvent.KEYCODE_TAB,
+ META_ON},
+ {"Alt + Tab -> Open Overview", new int[]{ALT_KEY, KeyEvent.KEYCODE_TAB},
+ KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS, KeyEvent.KEYCODE_TAB,
+ ALT_ON},
+ {"Meta + Escape -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_ESCAPE},
+ KeyGestureEvent.KEY_GESTURE_TYPE_BACK, KeyEvent.KEYCODE_ESCAPE,
+ META_ON},
+ {"Meta + Left arrow -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DPAD_LEFT},
+ KeyGestureEvent.KEY_GESTURE_TYPE_BACK, KeyEvent.KEYCODE_DPAD_LEFT,
+ META_ON},
+ {"Meta + Del -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DEL},
+ KeyGestureEvent.KEY_GESTURE_TYPE_BACK, KeyEvent.KEYCODE_DEL, META_ON},
+ {"APP_SWITCH key -> Open App switcher", new int[]{KeyEvent.KEYCODE_APP_SWITCH},
+ KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH,
+ KeyEvent.KEYCODE_APP_SWITCH, 0},
+ {"ASSIST key -> Launch assistant", new int[]{KeyEvent.KEYCODE_ASSIST},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT,
+ KeyEvent.KEYCODE_ASSIST, 0},
+ {"Meta + A -> Launch assistant", new int[]{META_KEY, KeyEvent.KEYCODE_A},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT, KeyEvent.KEYCODE_A,
+ META_ON},
+ {"VOICE_ASSIST key -> Launch Voice Assistant",
+ new int[]{KeyEvent.KEYCODE_VOICE_ASSIST},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT,
+ KeyEvent.KEYCODE_VOICE_ASSIST, 0},
+ {"Meta + I -> Launch System Settings", new int[]{META_KEY, KeyEvent.KEYCODE_I},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS,
+ KeyEvent.KEYCODE_I, META_ON},
+ {"Meta + N -> Toggle Notification panel", new int[]{META_KEY, KeyEvent.KEYCODE_N},
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
+ KeyEvent.KEYCODE_N, META_ON},
+ {"NOTIFICATION key -> Toggle Notification Panel",
+ new int[]{KeyEvent.KEYCODE_NOTIFICATION},
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
+ KeyEvent.KEYCODE_NOTIFICATION,
+ 0},
+ {"Meta + Ctrl + S -> Take Screenshot",
+ new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_S},
+ KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT, KeyEvent.KEYCODE_S,
+ META_ON | CTRL_ON},
+ {"Meta + / -> Open Shortcut Helper", new int[]{META_KEY, KeyEvent.KEYCODE_SLASH},
+ KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER,
+ KeyEvent.KEYCODE_SLASH, META_ON},
+ {"BRIGHTNESS_UP key -> Increase Brightness",
+ new int[]{KeyEvent.KEYCODE_BRIGHTNESS_UP},
+ KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP,
+ KeyEvent.KEYCODE_BRIGHTNESS_UP, 0},
+ {"BRIGHTNESS_DOWN key -> Decrease Brightness",
+ new int[]{KeyEvent.KEYCODE_BRIGHTNESS_DOWN},
+ KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN,
+ KeyEvent.KEYCODE_BRIGHTNESS_DOWN, 0},
+ {"KEYBOARD_BACKLIGHT_UP key -> Increase Keyboard Backlight",
+ new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP},
+ KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP,
+ KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP, 0},
+ {"KEYBOARD_BACKLIGHT_DOWN key -> Decrease Keyboard Backlight",
+ new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN},
+ KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN,
+ KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN, 0},
+ {"KEYBOARD_BACKLIGHT_TOGGLE key -> Toggle Keyboard Backlight",
+ new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE},
+ KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE,
+ KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE, 0},
+ {"ALL_APPS key -> Open App Drawer",
+ new int[]{KeyEvent.KEYCODE_ALL_APPS},
+ KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS,
+ KeyEvent.KEYCODE_ALL_APPS, 0},
+ {"SEARCH key -> Launch Search Activity", new int[]{KeyEvent.KEYCODE_SEARCH},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH,
+ KeyEvent.KEYCODE_SEARCH, 0},
+ {"LANGUAGE_SWITCH key -> Switch Keyboard Language",
+ new int[]{KeyEvent.KEYCODE_LANGUAGE_SWITCH},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
+ KeyEvent.KEYCODE_LANGUAGE_SWITCH, 0},
+ {"META key -> Open App Drawer", new int[]{META_KEY},
+ KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS, META_KEY,
+ META_ON},
+ {"Meta + Alt -> Toggle CapsLock", new int[]{META_KEY, ALT_KEY},
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK, ALT_KEY,
+ META_ON | ALT_ON},
+ {"Alt + Meta -> Toggle CapsLock", new int[]{ALT_KEY, META_KEY},
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK, META_KEY,
+ META_ON | ALT_ON},
+ {"CAPS_LOCK key -> Toggle CapsLock", new int[]{KeyEvent.KEYCODE_CAPS_LOCK},
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
+ KeyEvent.KEYCODE_CAPS_LOCK, 0},
+ {"Meta + Ctrl + DPAD_UP -> Split screen navigation",
+ new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_UP},
+ KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION,
+ KeyEvent.KEYCODE_DPAD_UP,
+ META_ON | CTRL_ON},
+ {"Meta + Ctrl + DPAD_LEFT -> Split screen navigation",
+ new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_LEFT},
+ KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT,
+ KeyEvent.KEYCODE_DPAD_LEFT,
+ META_ON | CTRL_ON},
+ {"Meta + Ctrl + DPAD_RIGHT -> Split screen navigation",
+ new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_RIGHT},
+ KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT,
+ KeyEvent.KEYCODE_DPAD_RIGHT,
+ META_ON | CTRL_ON},
+ {"Meta + L -> Lock Homescreen", new int[]{META_KEY, KeyEvent.KEYCODE_L},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN, KeyEvent.KEYCODE_L,
+ META_ON},
+ {"Meta + Ctrl + N -> Open Notes", new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_N},
+ KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES, KeyEvent.KEYCODE_N,
+ META_ON | CTRL_ON},
{"Meta + Ctrl + DPAD_DOWN -> Enter desktop mode",
new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_DOWN},
KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE,
@@ -296,72 +305,14 @@
new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_NOTIFICATION_PANEL,
KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
KeyEvent.KEYCODE_HOME, 0},
- {"Long press META + ENTER -> Toggle Notification panel",
- new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
- LONG_PRESS_HOME_NOTIFICATION_PANEL,
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
- KeyEvent.KEYCODE_ENTER,
- META_ON},
- {"Long press META + H -> Toggle Notification panel",
- new int[]{META_KEY, KeyEvent.KEYCODE_H}, LONG_PRESS_HOME_NOTIFICATION_PANEL,
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
- KeyEvent.KEYCODE_H, META_ON},
{"Long press HOME key -> Launch assistant",
new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_ASSIST,
KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT,
KeyEvent.KEYCODE_HOME, 0},
- {"Long press META + ENTER -> Launch assistant",
- new int[]{META_KEY, KeyEvent.KEYCODE_ENTER}, LONG_PRESS_HOME_ASSIST,
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT,
- KeyEvent.KEYCODE_ENTER, META_ON},
- {"Long press META + H -> Launch assistant",
- new int[]{META_KEY, KeyEvent.KEYCODE_H}, LONG_PRESS_HOME_ASSIST,
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT, KeyEvent.KEYCODE_H,
- META_ON},
{"Long press HOME key -> Open App Drawer",
new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_ALL_APPS,
KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS,
- KeyEvent.KEYCODE_HOME, 0},
- {"Long press META + ENTER -> Open App Drawer",
- new int[]{META_KEY, KeyEvent.KEYCODE_ENTER}, LONG_PRESS_HOME_ALL_APPS,
- KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS,
- KeyEvent.KEYCODE_ENTER, META_ON},
- {"Long press META + H -> Open App Drawer",
- new int[]{META_KEY, KeyEvent.KEYCODE_H},
- LONG_PRESS_HOME_ALL_APPS,
- KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS,
- KeyEvent.KEYCODE_H, META_ON}};
- }
-
- @Keep
- private static Object[][] doubleTapOnHomeTestArguments() {
- // testName, testKeys, doubleTapOnHomeBehavior, expectedKeyGestureType, expectedKey,
- // expectedModifierState
- return new Object[][]{
- {"Double tap HOME -> Open App switcher",
- new int[]{KeyEvent.KEYCODE_HOME}, DOUBLE_TAP_HOME_RECENT_SYSTEM_UI,
- KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH, KeyEvent.KEYCODE_HOME,
- 0},
- {"Double tap META + ENTER -> Open App switcher",
- new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
- DOUBLE_TAP_HOME_RECENT_SYSTEM_UI,
- KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH,
- KeyEvent.KEYCODE_ENTER, META_ON},
- {"Double tap META + H -> Open App switcher",
- new int[]{META_KEY, KeyEvent.KEYCODE_H}, DOUBLE_TAP_HOME_RECENT_SYSTEM_UI,
- KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH, KeyEvent.KEYCODE_H,
- META_ON}};
- }
-
- @Keep
- private static Object[][] settingsKeyTestArguments() {
- // testName, testKeys, settingsKeyBehavior, expectedKeyGestureType, expectedKey,
- // expectedModifierState
- return new Object[][]{
- {"SETTINGS key -> Toggle Notification panel", new int[]{KeyEvent.KEYCODE_SETTINGS},
- SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL,
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
- KeyEvent.KEYCODE_SETTINGS, 0}};
+ KeyEvent.KEYCODE_HOME, 0}};
}
@Before
@@ -381,8 +332,18 @@
}
@Test
- @Parameters(method = "shortcutTestArguments")
- public void testShortcut(String testName, int[] testKeys,
+ @Parameters(method = "shortcutTestArgumentsNotMigratedToKeyGestureController")
+ public void testShortcuts_notMigratedToKeyGestureController(String testName,
+ int[] testKeys, @KeyGestureEvent.KeyGestureType int expectedKeyGestureType,
+ int expectedKey, int expectedModifierState) {
+ testShortcutInternal(testName, testKeys, expectedKeyGestureType, expectedKey,
+ expectedModifierState);
+ }
+
+ @Test
+ @Parameters(method = "shortcutTestArgumentsMigratedToKeyGestureController")
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+ public void testShortcuts_migratedToKeyGestureController(String testName, int[] testKeys,
@KeyGestureEvent.KeyGestureType int expectedKeyGestureType, int expectedKey,
int expectedModifierState) {
testShortcutInternal(testName, testKeys, expectedKeyGestureType, expectedKey,
@@ -402,31 +363,29 @@
}
@Test
- @Parameters(method = "doubleTapOnHomeTestArguments")
- public void testDoubleTapOnHomeBehavior(String testName, int[] testKeys,
- int doubleTapOnHomeBehavior,
- @KeyGestureEvent.KeyGestureType int expectedKeyGestureType, int expectedKey,
- int expectedModifierState) {
- mPhoneWindowManager.overriderDoubleTapOnHomeBehavior(doubleTapOnHomeBehavior);
- sendKeyCombination(testKeys, 0 /* duration */);
- sendKeyCombination(testKeys, 0 /* duration */);
+ public void testDoubleTapOnHomeBehavior_AppSwitchBehavior() {
+ mPhoneWindowManager.overriderDoubleTapOnHomeBehavior(DOUBLE_TAP_HOME_RECENT_SYSTEM_UI);
+ sendKeyCombination(new int[]{KeyEvent.KEYCODE_HOME}, 0 /* duration */);
+ sendKeyCombination(new int[]{KeyEvent.KEYCODE_HOME}, 0 /* duration */);
mPhoneWindowManager.assertKeyGestureCompleted(
- new int[]{expectedKey}, expectedModifierState, expectedKeyGestureType,
- "Failed while executing " + testName);
+ new int[]{KeyEvent.KEYCODE_HOME}, /* modifierState = */0,
+ KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH,
+ "Failed while executing Double tap HOME -> Open App switcher");
}
@Test
- @Parameters(method = "settingsKeyTestArguments")
- public void testSettingsKey(String testName, int[] testKeys, int settingsKeyBehavior,
- @KeyGestureEvent.KeyGestureType int expectedKeyGestureType, int expectedKey,
- int expectedModifierState) {
- mPhoneWindowManager.overrideSettingsKeyBehavior(settingsKeyBehavior);
- testShortcutInternal(testName, testKeys, expectedKeyGestureType, expectedKey,
- expectedModifierState);
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+ public void testSettingsKey_ToggleNotificationBehavior() {
+ mPhoneWindowManager.overrideSettingsKeyBehavior(SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL);
+ testShortcutInternal("SETTINGS key -> Toggle Notification panel",
+ new int[]{KeyEvent.KEYCODE_SETTINGS},
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
+ KeyEvent.KEYCODE_SETTINGS, 0);
}
@Test
@EnableFlags(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT)
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
public void testBugreportShortcutPress() {
testShortcutInternal("Meta + Ctrl + Del -> Trigger bug report",
new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DEL},
@@ -599,4 +558,145 @@
sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH));
mPhoneWindowManager.assertLaunchSearch();
}
+
+ @Test
+ public void testKeyGestureScreenshotChord() {
+ Assert.assertTrue(
+ sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD));
+ mPhoneWindowManager.moveTimeForward(500);
+ Assert.assertTrue(
+ sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD));
+ mPhoneWindowManager.assertTakeScreenshotCalled();
+ }
+
+ @Test
+ public void testKeyGestureScreenshotChordCancelled() {
+ Assert.assertTrue(
+ sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD));
+ Assert.assertTrue(
+ sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD));
+ mPhoneWindowManager.assertTakeScreenshotNotCalled();
+ }
+
+ @Test
+ public void testKeyGestureAccessibilityShortcutChord() {
+ Assert.assertTrue(
+ sendKeyGestureEventStart(
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD));
+ mPhoneWindowManager.moveTimeForward(5000);
+ Assert.assertTrue(
+ sendKeyGestureEventCancel(
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD));
+ mPhoneWindowManager.assertAccessibilityKeychordCalled();
+ }
+
+ @Test
+ public void testKeyGestureAccessibilityShortcutChordCancelled() {
+ Assert.assertTrue(
+ sendKeyGestureEventStart(
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD));
+ Assert.assertTrue(
+ sendKeyGestureEventCancel(
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD));
+ mPhoneWindowManager.assertAccessibilityKeychordNotCalled();
+ }
+
+ @Test
+ public void testKeyGestureRingerToggleChord() {
+ mPhoneWindowManager.overridePowerVolumeUp(POWER_VOLUME_UP_BEHAVIOR_MUTE);
+ Assert.assertTrue(
+ sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD));
+ mPhoneWindowManager.moveTimeForward(500);
+ Assert.assertTrue(
+ sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD));
+ mPhoneWindowManager.assertVolumeMute();
+ }
+
+ @Test
+ public void testKeyGestureRingerToggleChordCancelled() {
+ mPhoneWindowManager.overridePowerVolumeUp(POWER_VOLUME_UP_BEHAVIOR_MUTE);
+ Assert.assertTrue(
+ sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD));
+ Assert.assertTrue(
+ sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD));
+ mPhoneWindowManager.assertVolumeNotMuted();
+ }
+
+ @Test
+ public void testKeyGestureGlobalAction() {
+ mPhoneWindowManager.overridePowerVolumeUp(POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS);
+ Assert.assertTrue(
+ sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS));
+ mPhoneWindowManager.moveTimeForward(500);
+ Assert.assertTrue(
+ sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS));
+ mPhoneWindowManager.assertShowGlobalActionsCalled();
+ }
+
+ @Test
+ public void testKeyGestureGlobalActionCancelled() {
+ mPhoneWindowManager.overridePowerVolumeUp(POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS);
+ Assert.assertTrue(
+ sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS));
+ Assert.assertTrue(
+ sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS));
+ mPhoneWindowManager.assertShowGlobalActionsNotCalled();
+ }
+
+ @Test
+ public void testKeyGestureAccessibilityTvShortcutChord() {
+ Assert.assertTrue(
+ sendKeyGestureEventStart(
+ KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD));
+ mPhoneWindowManager.moveTimeForward(5000);
+ Assert.assertTrue(
+ sendKeyGestureEventCancel(
+ KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD));
+ mPhoneWindowManager.assertAccessibilityKeychordCalled();
+ }
+
+ @Test
+ public void testKeyGestureAccessibilityTvShortcutChordCancelled() {
+ Assert.assertTrue(
+ sendKeyGestureEventStart(
+ KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD));
+ Assert.assertTrue(
+ sendKeyGestureEventCancel(
+ KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD));
+ mPhoneWindowManager.assertAccessibilityKeychordNotCalled();
+ }
+
+ @Test
+ public void testKeyGestureTvTriggerBugReport() {
+ Assert.assertTrue(
+ sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT));
+ mPhoneWindowManager.moveTimeForward(1000);
+ Assert.assertTrue(
+ sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT));
+ mPhoneWindowManager.assertBugReportTakenForTv();
+ }
+
+ @Test
+ public void testKeyGestureTvTriggerBugReportCancelled() {
+ Assert.assertTrue(
+ sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT));
+ Assert.assertTrue(
+ sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT));
+ mPhoneWindowManager.assertBugReportNotTakenForTv();
+ }
+
+ @Test
+ public void testKeyGestureAccessibilityShortcut() {
+ Assert.assertTrue(
+ sendKeyGestureEventComplete(
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT));
+ mPhoneWindowManager.assertAccessibilityKeychordCalled();
+ }
+
+ @Test
+ public void testKeyGestureCloseAllDialogs() {
+ Assert.assertTrue(
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS));
+ mPhoneWindowManager.assertCloseAllDialogs();
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
index 43171f8..c186a03 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
@@ -119,6 +119,7 @@
* ALT + TAB to show recent apps.
*/
@Test
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
public void testAltTab() {
mPhoneWindowManager.overrideStatusBarManagerInternal();
sendKeyCombination(new int[]{KEYCODE_ALT_LEFT, KEYCODE_TAB}, 0);
@@ -129,6 +130,7 @@
* CTRL + SPACE to switch keyboard layout.
*/
@Test
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
public void testCtrlSpace() {
sendKeyCombination(new int[]{KEYCODE_CTRL_LEFT, KEYCODE_SPACE}, /* duration= */ 0,
ANY_DISPLAY_ID);
@@ -139,6 +141,7 @@
* CTRL + SHIFT + SPACE to switch keyboard layout backwards.
*/
@Test
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
public void testCtrlShiftSpace() {
sendKeyCombination(new int[]{KEYCODE_CTRL_LEFT, KEYCODE_SHIFT_LEFT, KEYCODE_SPACE},
/* duration= */ 0, ANY_DISPLAY_ID);
@@ -149,6 +152,7 @@
* CTRL + ALT + Z to enable accessibility service.
*/
@Test
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
public void testCtrlAltZ() {
sendKeyCombination(new int[]{KEYCODE_CTRL_LEFT, KEYCODE_ALT_LEFT, KEYCODE_Z}, 0);
mPhoneWindowManager.assertAccessibilityKeychordCalled();
@@ -158,6 +162,7 @@
* META + CTRL+ S to take screenshot.
*/
@Test
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
public void testMetaCtrlS() {
sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_CTRL_LEFT, KEYCODE_S}, 0);
mPhoneWindowManager.assertTakeScreenshotCalled();
@@ -167,6 +172,7 @@
* META + N to expand notification panel.
*/
@Test
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
public void testMetaN() throws RemoteException {
mPhoneWindowManager.overrideTogglePanel();
sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_N}, 0);
@@ -177,6 +183,7 @@
* META + SLASH to toggle shortcuts menu.
*/
@Test
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
public void testMetaSlash() {
mPhoneWindowManager.overrideStatusBarManagerInternal();
sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_SLASH}, 0);
@@ -187,6 +194,7 @@
* META + ALT to toggle Cap Lock.
*/
@Test
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
public void testMetaAlt() {
sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_ALT_LEFT}, 0);
mPhoneWindowManager.assertToggleCapsLock();
@@ -196,6 +204,7 @@
* META + H to go to homescreen
*/
@Test
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
public void testMetaH() {
mPhoneWindowManager.overrideLaunchHome();
sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_H}, 0);
@@ -206,6 +215,7 @@
* META + ENTER to go to homescreen
*/
@Test
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
public void testMetaEnter() {
mPhoneWindowManager.overrideLaunchHome();
sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_ENTER}, 0);
@@ -216,6 +226,7 @@
* Sends a KEYCODE_BRIGHTNESS_DOWN event and validates the brightness is decreased as expected;
*/
@Test
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
public void testKeyCodeBrightnessDown() {
float[] currentBrightness = new float[]{0.1f, 0.05f, 0.0f};
float[] newBrightness = new float[]{0.065738f, 0.0275134f, 0.0f};
@@ -231,9 +242,9 @@
* Sends a KEYCODE_SCREENSHOT and validates screenshot is taken if flag is enabled
*/
@Test
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE)
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
public void testTakeScreenshot_flagEnabled() {
- mSetFlagsRule.enableFlags(com.android.hardware.input.Flags
- .FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE);
sendKeyCombination(new int[]{KEYCODE_SCREENSHOT}, 0);
mPhoneWindowManager.assertTakeScreenshotCalled();
}
@@ -242,9 +253,9 @@
* Sends a KEYCODE_SCREENSHOT and validates screenshot is not taken if flag is disabled
*/
@Test
+ @DisableFlags({com.android.hardware.input.Flags.FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE,
+ com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER})
public void testTakeScreenshot_flagDisabled() {
- mSetFlagsRule.disableFlags(com.android.hardware.input.Flags
- .FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE);
sendKeyCombination(new int[]{KEYCODE_SCREENSHOT}, 0);
mPhoneWindowManager.assertTakeScreenshotNotCalled();
}
@@ -254,6 +265,7 @@
*/
@Test
@EnableFlags(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT)
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
public void testTakeBugReport_flagEnabled() throws RemoteException {
sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_CTRL_LEFT, KEYCODE_DEL}, 0);
mPhoneWindowManager.assertTakeBugreport(true);
@@ -263,7 +275,8 @@
* META+CTRL+BACKSPACE for taking a bugreport does nothing when the flag is disabledd.
*/
@Test
- @DisableFlags(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT)
+ @DisableFlags({com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT,
+ com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER})
public void testTakeBugReport_flagDisabled() throws RemoteException {
sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_CTRL_LEFT, KEYCODE_DEL}, 0);
mPhoneWindowManager.assertTakeBugreport(false);
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
index 50b7db4..9e47a00 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -241,6 +241,13 @@
KeyGestureEvent.ACTION_GESTURE_COMPLETE).build());
}
+ boolean sendKeyGestureEventCancel(int gestureType) {
+ return mPhoneWindowManager.sendKeyGestureEvent(
+ new KeyGestureEvent.Builder().setKeyGestureType(gestureType).setAction(
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE).setFlags(
+ KeyGestureEvent.FLAG_CANCELLED).build());
+ }
+
boolean sendKeyGestureEventComplete(int gestureType, int modifierState) {
return mPhoneWindowManager.sendKeyGestureEvent(
new KeyGestureEvent.Builder().setModifierState(modifierState).setKeyGestureType(
@@ -276,7 +283,7 @@
if ((actions & ACTION_PASS_TO_USER) != 0) {
if (0 == mPhoneWindowManager.interceptKeyBeforeDispatching(keyEvent)) {
if (!mDispatchedKeyHandler.onKeyDispatched(keyEvent)) {
- mPhoneWindowManager.dispatchUnhandledKey(keyEvent);
+ mPhoneWindowManager.interceptUnhandledKey(keyEvent);
}
}
}
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 98401b3..1aa9087 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -418,8 +418,8 @@
mKeyEventPolicyFlags);
}
- void dispatchUnhandledKey(KeyEvent event) {
- mPhoneWindowManager.dispatchUnhandledKey(mInputToken, event, FLAG_INTERACTIVE);
+ void interceptUnhandledKey(KeyEvent event) {
+ mPhoneWindowManager.interceptUnhandledKey(event, mInputToken);
}
boolean sendKeyGestureEvent(KeyGestureEvent event) {
@@ -657,16 +657,36 @@
verify(mPowerManager).userActivity(anyLong(), anyBoolean());
}
+ void assertShowGlobalActionsNotCalled() {
+ mTestLooper.dispatchAll();
+ verify(mGlobalActions, never()).showDialog(anyBoolean(), anyBoolean());
+ verify(mPowerManager, never()).userActivity(anyLong(), anyBoolean());
+ }
+
void assertVolumeMute() {
mTestLooper.dispatchAll();
verify(mAudioManagerInternal).silenceRingerModeInternal(eq("volume_hush"));
}
+ void assertVolumeNotMuted() {
+ mTestLooper.dispatchAll();
+ verify(mAudioManagerInternal, never()).silenceRingerModeInternal(any());
+ }
+
void assertAccessibilityKeychordCalled() {
mTestLooper.dispatchAll();
verify(mAccessibilityShortcutController).performAccessibilityShortcut();
}
+ void assertAccessibilityKeychordNotCalled() {
+ mTestLooper.dispatchAll();
+ verify(mAccessibilityShortcutController, never()).performAccessibilityShortcut();
+ }
+
+ void assertCloseAllDialogs() {
+ verify(mContext).closeSystemDialogs();
+ }
+
void assertDreamRequest() {
mTestLooper.dispatchAll();
verify(mDreamManagerInternal).requestDream();
@@ -809,6 +829,16 @@
}
+ void assertBugReportTakenForTv() {
+ mTestLooper.dispatchAll();
+ verify(mPhoneWindowManager).requestBugreportForTv();
+ }
+
+ void assertBugReportNotTakenForTv() {
+ mTestLooper.dispatchAll();
+ verify(mPhoneWindowManager, never()).requestBugreportForTv();
+ }
+
void assertTogglePanel() throws RemoteException {
mTestLooper.dispatchAll();
verify(mPhoneWindowManager.mStatusBarService).togglePanel();
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index 8227ed9..63983437 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -230,6 +230,10 @@
mDisplayContent.setIgnoreOrientationRequest(enabled);
}
+ void setTopOrganizedTaskAsTopTask() {
+ doReturn(mTaskStack.top()).when(mActivityStack.top()).getOrganizedTask();
+ }
+
void setTopTaskInMultiWindowMode(boolean inMultiWindowMode) {
doReturn(inMultiWindowMode).when(mTaskStack.top()).inMultiWindowMode();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
index 21fac9b..3f34b81 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
@@ -16,10 +16,14 @@
package com.android.server.wm;
+import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE;
+
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static org.mockito.Mockito.when;
+import android.app.CameraCompatTaskInfo.FreeformCameraCompatMode;
+import android.app.TaskInfo;
import android.platform.test.annotations.Presubmit;
import androidx.annotation.NonNull;
@@ -114,6 +118,72 @@
});
}
+ @Test
+ public void testTopActivityEligibleForUserAspectRatioButton_eligible() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponentInNewTask();
+ a.setIgnoreOrientationRequest(true);
+ });
+ robot.conf().enableUserAppAspectRatioSettings(true);
+
+ robot.checkTaskInfoEligibleForUserAspectRatioButton(true);
+ });
+ }
+
+ @Test
+ public void testTopActivityEligibleForUserAspectRatioButton_disabled_notEligible() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponentInNewTask();
+ a.setIgnoreOrientationRequest(true);
+ });
+ robot.conf().enableUserAppAspectRatioSettings(false);
+
+ robot.checkTaskInfoEligibleForUserAspectRatioButton(false);
+ });
+ }
+
+ @Test
+ public void testTopActivityEligibleForUserAspectRatioButton_inSizeCompatMode_notEligible() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponentInNewTask();
+ a.setIgnoreOrientationRequest(true);
+ a.setTopOrganizedTaskAsTopTask();
+ a.setTopActivityInSizeCompatMode(true);
+ a.setTopActivityVisible(true);
+ });
+ robot.conf().enableUserAppAspectRatioSettings(true);
+
+ robot.checkTaskInfoEligibleForUserAspectRatioButton(false);
+ });
+ }
+
+ @Test
+ public void testTopActivityEligibleForUserAspectRatioButton_transparentTop_notEligible() {
+ runTestScenario((robot) -> {
+ robot.transparentActivity((ta) -> {
+ ta.launchTransparentActivityInTask();
+ ta.activity().setIgnoreOrientationRequest(true);
+ });
+ robot.conf().enableUserAppAspectRatioSettings(true);
+
+ robot.checkTaskInfoEligibleForUserAspectRatioButton(false);
+ });
+ }
+
+ @Test
+ public void getTaskInfoPropagatesCameraCompatMode() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity(AppCompatActivityRobot::createActivityWithComponentInNewTask);
+
+ robot.setFreeformCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE);
+ robot.checkTaskInfoFreeformCameraCompatMode(
+ CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE);
+ });
+ }
+
/**
* Runs a test scenario providing a Robot.
*/
@@ -125,11 +195,14 @@
private static class AppCompatUtilsRobotTest extends AppCompatRobotBase {
private final WindowState mWindowState;
+ @NonNull
+ private final AppCompatTransparentActivityRobot mTransparentActivityRobot;
AppCompatUtilsRobotTest(@NonNull WindowManagerService wm,
@NonNull ActivityTaskManagerService atm,
@NonNull ActivityTaskSupervisor supervisor) {
super(wm, atm, supervisor);
+ mTransparentActivityRobot = new AppCompatTransparentActivityRobot(activity());
mWindowState = Mockito.mock(WindowState.class);
}
@@ -139,6 +212,12 @@
spyOn(activity.mAppCompatController.getAppCompatAspectRatioPolicy());
}
+ void transparentActivity(@NonNull Consumer<AppCompatTransparentActivityRobot> consumer) {
+ // We always create at least an opaque activity in a Task.
+ activity().createNewTaskWithBaseActivity();
+ consumer.accept(mTransparentActivityRobot);
+ }
+
void setIsLetterboxedForFixedOrientationAndAspectRatio(
boolean forFixedOrientationAndAspectRatio) {
when(activity().top().mAppCompatController.getAppCompatAspectRatioPolicy()
@@ -155,11 +234,30 @@
when(mWindowState.isLetterboxedForDisplayCutout()).thenReturn(displayCutout);
}
+ void setFreeformCameraCompatMode(@FreeformCameraCompatMode int mode) {
+ activity().top().mAppCompatController.getAppCompatCameraOverrides()
+ .setFreeformCameraCompatMode(mode);
+ }
+
void checkTopActivityLetterboxReason(@NonNull String expected) {
Assert.assertEquals(expected,
AppCompatUtils.getLetterboxReasonString(activity().top(), mWindowState));
}
+ @NonNull
+ TaskInfo getTopTaskInfo() {
+ return activity().top().getTask().getTaskInfo();
+ }
+
+ void checkTaskInfoEligibleForUserAspectRatioButton(boolean eligible) {
+ Assert.assertEquals(eligible, getTopTaskInfo().appCompatTaskInfo
+ .eligibleForUserAspectRatioButton());
+ }
+
+ void checkTaskInfoFreeformCameraCompatMode(@FreeformCameraCompatMode int mode) {
+ Assert.assertEquals(mode, getTopTaskInfo().appCompatTaskInfo
+ .cameraCompatTaskInfo.freeformCameraCompatMode);
+ }
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index e0344d7..df17cd1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -432,6 +432,29 @@
}
@Test
+ public void testAddTaskCompatibleWindowingMode_withFreeformAndFullscreen_expectRemove() {
+ Task task1 = createTaskBuilder(".Task1")
+ .setFlags(FLAG_ACTIVITY_NEW_TASK)
+ .build();
+ doReturn(WINDOWING_MODE_FREEFORM).when(task1).getWindowingMode();
+ mRecentTasks.add(task1);
+ mCallbacksRecorder.clear();
+
+ Task task2 = createTaskBuilder(".Task1")
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK)
+ .build();
+ assertEquals(WINDOWING_MODE_FULLSCREEN, task2.getWindowingMode());
+ mRecentTasks.add(task2);
+
+ assertThat(mCallbacksRecorder.mAdded).hasSize(1);
+ assertThat(mCallbacksRecorder.mAdded).contains(task2);
+ assertThat(mCallbacksRecorder.mTrimmed).isEmpty();
+ assertThat(mCallbacksRecorder.mRemoved).hasSize(1);
+ assertThat(mCallbacksRecorder.mRemoved).contains(task1);
+ }
+
+ @Test
public void testAddTaskIncompatibleWindowingMode_expectNoRemove() {
Task task1 = createTaskBuilder(".Task1")
.setWindowingMode(WINDOWING_MODE_FULLSCREEN)
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 4b03483..e4512c3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -18,7 +18,6 @@
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
@@ -705,50 +704,6 @@
}
@Test
- public void testTopActivityEligibleForUserAspectRatioButton() {
- DisplayContent display = mAtm.mRootWindowContainer.getDefaultDisplay();
- final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
- .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build();
- final Task task = rootTask.getBottomMostTask();
- final ActivityRecord root = task.getTopNonFinishingActivity();
- spyOn(mWm.mAppCompatConfiguration);
- spyOn(root);
- spyOn(root.mAppCompatController.getAppCompatAspectRatioOverrides());
-
- doReturn(true).when(root).fillsParent();
- doReturn(true).when(
- root.mAppCompatController.getAppCompatAspectRatioOverrides())
- .shouldEnableUserAspectRatioSettings();
- doReturn(false).when(root).inSizeCompatMode();
- doReturn(task).when(root).getOrganizedTask();
-
- // The button should be eligible to be displayed
- assertTrue(task.getTaskInfo()
- .appCompatTaskInfo.eligibleForUserAspectRatioButton());
-
- // When shouldApplyUserMinAspectRatioOverride is disable the button is not enabled
- doReturn(false).when(
- root.mAppCompatController.getAppCompatAspectRatioOverrides())
- .shouldEnableUserAspectRatioSettings();
- assertFalse(task.getTaskInfo()
- .appCompatTaskInfo.eligibleForUserAspectRatioButton());
- doReturn(true).when(root.mAppCompatController
- .getAppCompatAspectRatioOverrides()).shouldEnableUserAspectRatioSettings();
-
- // When in size compat mode the button is not enabled
- doReturn(true).when(root).inSizeCompatMode();
- assertFalse(task.getTaskInfo()
- .appCompatTaskInfo.eligibleForUserAspectRatioButton());
- doReturn(false).when(root).inSizeCompatMode();
-
- // When the top activity is transparent, the button is not enabled
- doReturn(false).when(root).fillsParent();
- assertFalse(task.getTaskInfo()
- .appCompatTaskInfo.eligibleForUserAspectRatioButton());
- doReturn(true).when(root).fillsParent();
- }
-
- @Test
public void testIsTopActivityTranslucent() {
DisplayContent display = mAtm.mRootWindowContainer.getDefaultDisplay();
final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
@@ -2112,17 +2067,6 @@
}
@Test
- public void getTaskInfoPropagatesCameraCompatMode() {
- final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
- final ActivityRecord activity = task.getTopMostActivity();
- activity.mAppCompatController.getAppCompatCameraOverrides().setFreeformCameraCompatMode(
- CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE);
-
- assertEquals(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE,
- task.getTaskInfo().appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode);
- }
-
- @Test
public void testUpdateTaskDescriptionOnReparent() {
final Task rootTask1 = createTask(mDisplayContent);
final Task rootTask2 = createTask(mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index eebb487..9e9874b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -103,8 +103,8 @@
}
@Override
- public KeyEvent dispatchUnhandledKey(IBinder focusedToken, KeyEvent event, int policyFlags) {
- return null;
+ public boolean interceptUnhandledKey(KeyEvent event, IBinder focusedToken) {
+ return false;
}
@Override
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index dc5f6e9..fb031bd 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -842,7 +842,7 @@
return;
}
- model.setRequested(config.allowMultipleTriggers);
+ model.setRequested(config.isAllowMultipleTriggers());
// TODO: Remove this block if the lower layer supports multiple triggers.
if (model.isRequested()) {
updateRecognitionLocked(model, true);
@@ -964,7 +964,7 @@
RecognitionConfig config = modelData.getRecognitionConfig();
if (config != null) {
// Whether we should continue by starting this again.
- modelData.setRequested(config.allowMultipleTriggers);
+ modelData.setRequested(config.isAllowMultipleTriggers());
}
// TODO: Remove this block if the lower layer supports multiple triggers.
if (modelData.isRequested()) {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 862aff9..2bb86bc 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -1439,7 +1439,7 @@
runOrAddOperation(new Operation(
// always execute:
() -> {
- if (!mRecognitionConfig.allowMultipleTriggers) {
+ if (!mRecognitionConfig.isAllowMultipleTriggers()) {
// Unregister this remoteService once op is done
synchronized (mCallbacksLock) {
mCallbacks.remove(mPuuid.getUuid());
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index ba36007..4ae06a4 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -18,6 +18,8 @@
import android.content.Context
import android.content.ContextWrapper
+import android.content.pm.PackageManager
+import android.content.res.Resources
import android.hardware.input.IInputManager
import android.hardware.input.AidlKeyGestureEvent
import android.hardware.input.IKeyGestureEventListener
@@ -25,15 +27,20 @@
import android.hardware.input.InputManager
import android.hardware.input.InputManagerGlobal
import android.hardware.input.KeyGestureEvent
-import android.hardware.input.KeyGestureEvent.KeyGestureType
import android.os.IBinder
import android.os.Process
+import android.os.SystemClock
+import android.os.SystemProperties
import android.os.test.TestLooper
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.platform.test.annotations.Presubmit
+import android.platform.test.flag.junit.SetFlagsRule
import android.view.InputDevice
-import android.view.KeyCharacterMap
import android.view.KeyEvent
+import android.view.WindowManagerPolicyConstants.FLAG_INTERACTIVE
import androidx.test.core.app.ApplicationProvider
+import com.android.internal.R
import com.android.internal.annotations.Keep
import com.android.internal.util.FrameworkStatsLog
import com.android.modules.utils.testing.ExtendedMockitoRule
@@ -78,32 +85,60 @@
KeyEvent.KEYCODE_META_LEFT to (KeyEvent.META_META_LEFT_ON or KeyEvent.META_META_ON),
KeyEvent.KEYCODE_META_RIGHT to (KeyEvent.META_META_RIGHT_ON or KeyEvent.META_META_ON),
)
+ const val SEARCH_KEY_BEHAVIOR_DEFAULT_SEARCH = 0
+ const val SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY = 1
+ const val SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY = 0
+ const val SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL = 1
+ const val SETTINGS_KEY_BEHAVIOR_NOTHING = 2
}
@JvmField
@Rule
val extendedMockitoRule = ExtendedMockitoRule.Builder(this)
- .mockStatic(FrameworkStatsLog::class.java).build()!!
+ .mockStatic(FrameworkStatsLog::class.java)
+ .mockStatic(SystemProperties::class.java).build()!!
+
+ @JvmField
+ @Rule
+ val rule = SetFlagsRule()
@Mock
private lateinit var iInputManager: IInputManager
+ @Mock
+ private lateinit var resources: Resources
+
+ @Mock
+ private lateinit var packageManager: PackageManager
+
private var currentPid = 0
- private lateinit var keyGestureController: KeyGestureController
private lateinit var context: Context
private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
private lateinit var testLooper: TestLooper
private var events = mutableListOf<KeyGestureEvent>()
- private var handleEvents = mutableListOf<KeyGestureEvent>()
@Before
fun setup() {
context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+ Mockito.`when`(context.resources).thenReturn(resources)
inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager)
setupInputDevices()
+ setupBehaviors()
testLooper = TestLooper()
currentPid = Process.myPid()
- keyGestureController = KeyGestureController(context, testLooper.looper)
+ }
+
+ private fun setupBehaviors() {
+ Mockito.`when`(
+ resources.getBoolean(
+ com.android.internal.R.bool.config_enableScreenshotChord
+ )
+ ).thenReturn(true)
+ Mockito.`when`(packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH))
+ .thenReturn(true)
+ Mockito.`when`(packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK))
+ .thenReturn(true)
+ Mockito.`when`(context.packageManager).thenReturn(packageManager)
}
private fun setupInputDevices() {
@@ -116,19 +151,22 @@
Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardDevice)
}
- private fun notifyHomeGestureCompleted() {
- keyGestureController.notifyKeyGestureCompleted(DEVICE_ID, intArrayOf(KeyEvent.KEYCODE_H),
+ private fun notifyHomeGestureCompleted(keyGestureController: KeyGestureController) {
+ keyGestureController.notifyKeyGestureCompleted(
+ DEVICE_ID, intArrayOf(KeyEvent.KEYCODE_H),
KeyEvent.META_META_ON or KeyEvent.META_META_LEFT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+ KeyGestureEvent.KEY_GESTURE_TYPE_HOME
+ )
}
@Test
fun testKeyGestureEvent_registerUnregisterListener() {
+ val keyGestureController = KeyGestureController(context, testLooper.looper)
val listener = KeyGestureEventListener()
// Register key gesture event listener
keyGestureController.registerKeyGestureEventListener(listener, 0)
- notifyHomeGestureCompleted()
+ notifyHomeGestureCompleted(keyGestureController)
testLooper.dispatchAll()
assertEquals(
"Listener should get callbacks on key gesture event completed",
@@ -144,7 +182,7 @@
// Unregister listener
events.clear()
keyGestureController.unregisterKeyGestureEventListener(listener, 0)
- notifyHomeGestureCompleted()
+ notifyHomeGestureCompleted(keyGestureController)
testLooper.dispatchAll()
assertEquals(
"Listener should not get callback after being unregistered",
@@ -155,20 +193,22 @@
@Test
fun testKeyGestureEvent_multipleGestureHandlers() {
+ val keyGestureController = KeyGestureController(context, testLooper.looper)
+
// Set up two callbacks.
var callbackCount1 = 0
var callbackCount2 = 0
var selfCallback = 0
val externalHandler1 = KeyGestureHandler { _, _ ->
- callbackCount1++;
+ callbackCount1++
true
}
val externalHandler2 = KeyGestureHandler { _, _ ->
- callbackCount2++;
+ callbackCount2++
true
}
val selfHandler = KeyGestureHandler { _, _ ->
- selfCallback++;
+ selfCallback++
false
}
@@ -406,6 +446,14 @@
intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
),
TestData(
+ "META + / -> Open Shortcut Helper",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_SLASH),
+ KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER,
+ intArrayOf(KeyEvent.KEYCODE_SLASH),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
"BRIGHTNESS_UP -> Brightness Up",
intArrayOf(KeyEvent.KEYCODE_BRIGHTNESS_UP),
KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP,
@@ -528,12 +576,314 @@
KeyGestureEvent.ACTION_GESTURE_COMPLETE
)
),
+ TestData(
+ "CTRL + SPACE -> Switch Language Forward",
+ intArrayOf(KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.KEYCODE_SPACE),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
+ intArrayOf(KeyEvent.KEYCODE_SPACE),
+ KeyEvent.META_CTRL_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "CTRL + SHIFT + SPACE -> Switch Language Backward",
+ intArrayOf(
+ KeyEvent.KEYCODE_CTRL_LEFT,
+ KeyEvent.KEYCODE_SHIFT_LEFT,
+ KeyEvent.KEYCODE_SPACE
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
+ intArrayOf(KeyEvent.KEYCODE_SPACE),
+ KeyEvent.META_CTRL_ON or KeyEvent.META_SHIFT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "CTRL + ALT + Z -> Accessibility Shortcut",
+ intArrayOf(
+ KeyEvent.KEYCODE_CTRL_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_Z
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT,
+ intArrayOf(KeyEvent.KEYCODE_Z),
+ KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "SYSRQ -> Take screenshot",
+ intArrayOf(KeyEvent.KEYCODE_SYSRQ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT,
+ intArrayOf(KeyEvent.KEYCODE_SYSRQ),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "ESC -> Close All Dialogs",
+ intArrayOf(KeyEvent.KEYCODE_ESCAPE),
+ KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS,
+ intArrayOf(KeyEvent.KEYCODE_ESCAPE),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
)
}
@Test
@Parameters(method = "keyGestureEventHandlerTestArguments")
fun testKeyGestures(test: TestData) {
+ val keyGestureController = KeyGestureController(context, testLooper.looper)
+ testKeyGestureInternal(keyGestureController, test)
+ }
+
+ @Test
+ fun testKeycodesFullyConsumed_irrespectiveOfHandlers() {
+ val keyGestureController = KeyGestureController(context, testLooper.looper)
+ val testKeys = intArrayOf(
+ KeyEvent.KEYCODE_RECENT_APPS,
+ KeyEvent.KEYCODE_APP_SWITCH,
+ KeyEvent.KEYCODE_BRIGHTNESS_UP,
+ KeyEvent.KEYCODE_BRIGHTNESS_DOWN,
+ KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN,
+ KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP,
+ KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE,
+ KeyEvent.KEYCODE_ALL_APPS,
+ KeyEvent.KEYCODE_NOTIFICATION,
+ KeyEvent.KEYCODE_SETTINGS,
+ KeyEvent.KEYCODE_LANGUAGE_SWITCH,
+ KeyEvent.KEYCODE_SCREENSHOT,
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_META_RIGHT,
+ KeyEvent.KEYCODE_ASSIST,
+ KeyEvent.KEYCODE_VOICE_ASSIST,
+ KeyEvent.KEYCODE_STYLUS_BUTTON_PRIMARY,
+ KeyEvent.KEYCODE_STYLUS_BUTTON_SECONDARY,
+ KeyEvent.KEYCODE_STYLUS_BUTTON_TERTIARY,
+ KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL,
+ )
+
+ val handler = KeyGestureHandler { _, _ -> false }
+ keyGestureController.registerKeyGestureHandler(handler, 0)
+
+ for (key in testKeys) {
+ sendKeys(keyGestureController, intArrayOf(key), assertNotSentToApps = true)
+ }
+ }
+
+ @Test
+ fun testSearchKeyGestures_defaultSearch() {
+ Mockito.`when`(resources.getInteger(R.integer.config_searchKeyBehavior))
+ .thenReturn(SEARCH_KEY_BEHAVIOR_DEFAULT_SEARCH)
+ val keyGestureController = KeyGestureController(context, testLooper.looper)
+ testKeyGestureNotProduced(
+ keyGestureController,
+ "SEARCH -> Default Search",
+ intArrayOf(KeyEvent.KEYCODE_SEARCH),
+ )
+ }
+
+ @Test
+ fun testSearchKeyGestures_searchActivity() {
+ Mockito.`when`(resources.getInteger(R.integer.config_searchKeyBehavior))
+ .thenReturn(SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY)
+ val keyGestureController = KeyGestureController(context, testLooper.looper)
+ testKeyGestureInternal(
+ keyGestureController,
+ TestData(
+ "SEARCH -> Launch Search Activity",
+ intArrayOf(KeyEvent.KEYCODE_SEARCH),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH,
+ intArrayOf(KeyEvent.KEYCODE_SEARCH),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ )
+ )
+ }
+
+ @Test
+ fun testSettingKeyGestures_doNothing() {
+ Mockito.`when`(resources.getInteger(R.integer.config_settingsKeyBehavior))
+ .thenReturn(SETTINGS_KEY_BEHAVIOR_NOTHING)
+ val keyGestureController = KeyGestureController(context, testLooper.looper)
+ testKeyGestureNotProduced(
+ keyGestureController,
+ "SETTINGS -> Do Nothing",
+ intArrayOf(KeyEvent.KEYCODE_SETTINGS),
+ )
+ }
+
+ @Test
+ fun testSettingKeyGestures_settingsActivity() {
+ Mockito.`when`(resources.getInteger(R.integer.config_settingsKeyBehavior))
+ .thenReturn(SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY)
+ val keyGestureController = KeyGestureController(context, testLooper.looper)
+ testKeyGestureInternal(
+ keyGestureController,
+ TestData(
+ "SETTINGS -> Launch Settings Activity",
+ intArrayOf(KeyEvent.KEYCODE_SETTINGS),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS,
+ intArrayOf(KeyEvent.KEYCODE_SETTINGS),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ )
+ )
+ }
+
+ @Test
+ fun testSettingKeyGestures_notificationPanel() {
+ Mockito.`when`(resources.getInteger(R.integer.config_settingsKeyBehavior))
+ .thenReturn(SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL)
+ val keyGestureController = KeyGestureController(context, testLooper.looper)
+ testKeyGestureInternal(
+ keyGestureController,
+ TestData(
+ "SETTINGS -> Toggle Notification Panel",
+ intArrayOf(KeyEvent.KEYCODE_SETTINGS),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
+ intArrayOf(KeyEvent.KEYCODE_SETTINGS),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ )
+ )
+ }
+
+ @Test
+ @EnableFlags(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT)
+ fun testTriggerBugReport() {
+ Mockito.`when`(SystemProperties.get("ro.debuggable")).thenReturn("1")
+ val keyGestureController = KeyGestureController(context, testLooper.looper)
+ testKeyGestureInternal(
+ keyGestureController,
+ TestData(
+ "META + CTRL + DEL -> Trigger Bug Report",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_CTRL_LEFT,
+ KeyEvent.KEYCODE_DEL
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT,
+ intArrayOf(KeyEvent.KEYCODE_DEL),
+ KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ )
+ )
+ }
+
+ @Test
+ @DisableFlags(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT)
+ fun testTriggerBugReport_flagDisabled() {
+ Mockito.`when`(SystemProperties.get("ro.debuggable")).thenReturn("1")
+ val keyGestureController = KeyGestureController(context, testLooper.looper)
+ testKeyGestureInternal(
+ keyGestureController,
+ TestData(
+ "META + CTRL + DEL -> Not Trigger Bug Report (Fallback to BACK)",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_CTRL_LEFT,
+ KeyEvent.KEYCODE_DEL
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_BACK,
+ intArrayOf(KeyEvent.KEYCODE_DEL),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ )
+ )
+ }
+
+ @Test
+ fun testCapsLockPressNotified() {
+ val keyGestureController = KeyGestureController(context, testLooper.looper)
+ val listener = KeyGestureEventListener()
+
+ keyGestureController.registerKeyGestureEventListener(listener, 0)
+ sendKeys(keyGestureController, intArrayOf(KeyEvent.KEYCODE_CAPS_LOCK))
+ testLooper.dispatchAll()
+ assertEquals(
+ "Listener should get callbacks on key gesture event completed",
+ 1,
+ events.size
+ )
+ assertEquals(
+ "Listener should get callback for Toggle Caps Lock key gesture complete event",
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
+ events[0].keyGestureType
+ )
+ }
+
+ @Keep
+ private fun keyGestureEventHandlerTestArguments_forKeyCombinations(): Array<TestData> {
+ return arrayOf(
+ TestData(
+ "VOLUME_DOWN + POWER -> Screenshot Chord",
+ intArrayOf(KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_POWER),
+ KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD,
+ intArrayOf(KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_POWER),
+ 0,
+ intArrayOf(
+ KeyGestureEvent.ACTION_GESTURE_START,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE
+ )
+ ),
+ TestData(
+ "POWER + STEM_PRIMARY -> Screenshot Chord",
+ intArrayOf(KeyEvent.KEYCODE_POWER, KeyEvent.KEYCODE_STEM_PRIMARY),
+ KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD,
+ intArrayOf(KeyEvent.KEYCODE_POWER, KeyEvent.KEYCODE_STEM_PRIMARY),
+ 0,
+ intArrayOf(
+ KeyGestureEvent.ACTION_GESTURE_START,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE
+ )
+ ),
+ TestData(
+ "VOLUME_DOWN + VOLUME_UP -> Accessibility Chord",
+ intArrayOf(KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP),
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD,
+ intArrayOf(KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP),
+ 0,
+ intArrayOf(
+ KeyGestureEvent.ACTION_GESTURE_START,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE
+ )
+ ),
+ TestData(
+ "BACK + DPAD_DOWN -> TV Accessibility Chord",
+ intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD,
+ intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN),
+ 0,
+ intArrayOf(
+ KeyGestureEvent.ACTION_GESTURE_START,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE
+ )
+ ),
+ TestData(
+ "BACK + DPAD_CENTER -> TV Trigger Bug Report",
+ intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_CENTER),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT,
+ intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_CENTER),
+ 0,
+ intArrayOf(
+ KeyGestureEvent.ACTION_GESTURE_START,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE
+ )
+ ),
+ )
+ }
+
+ @Test
+ @Parameters(method = "keyGestureEventHandlerTestArguments_forKeyCombinations")
+ @EnableFlags(
+ com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER,
+ com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER_MULTI_PRESS_GESTURES
+ )
+ fun testKeyCombinationGestures(test: TestData) {
+ val keyGestureController = KeyGestureController(context, testLooper.looper)
+ testKeyGestureInternal(keyGestureController, test)
+ }
+
+ private fun testKeyGestureInternal(keyGestureController: KeyGestureController, test: TestData) {
+ var handleEvents = mutableListOf<KeyGestureEvent>()
val handler = KeyGestureHandler { event, _ ->
handleEvents.add(KeyGestureEvent(event))
true
@@ -541,7 +891,7 @@
keyGestureController.registerKeyGestureHandler(handler, 0)
handleEvents.clear()
- sendKeys(test.keys, /* assertAllConsumed = */ false)
+ sendKeys(keyGestureController, test.keys)
assertEquals(
"Test: $test doesn't produce correct number of key gesture events",
@@ -575,55 +925,37 @@
keyGestureController.unregisterKeyGestureHandler(handler, 0)
}
- @Test
- fun testKeycodesFullyConsumed_irrespectiveOfHandlers() {
- val testKeys = intArrayOf(
- KeyEvent.KEYCODE_RECENT_APPS,
- KeyEvent.KEYCODE_APP_SWITCH,
- KeyEvent.KEYCODE_BRIGHTNESS_UP,
- KeyEvent.KEYCODE_BRIGHTNESS_DOWN,
- KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN,
- KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP,
- KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE,
- KeyEvent.KEYCODE_ALL_APPS,
- KeyEvent.KEYCODE_NOTIFICATION,
- KeyEvent.KEYCODE_SETTINGS,
- KeyEvent.KEYCODE_LANGUAGE_SWITCH,
- KeyEvent.KEYCODE_SCREENSHOT,
- KeyEvent.KEYCODE_META_LEFT,
- KeyEvent.KEYCODE_META_RIGHT,
- KeyEvent.KEYCODE_ASSIST,
- KeyEvent.KEYCODE_VOICE_ASSIST,
- KeyEvent.KEYCODE_STYLUS_BUTTON_PRIMARY,
- KeyEvent.KEYCODE_STYLUS_BUTTON_SECONDARY,
- KeyEvent.KEYCODE_STYLUS_BUTTON_TERTIARY,
- KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL,
- )
-
- val handler = KeyGestureHandler { _, _ -> false }
- keyGestureController.registerKeyGestureHandler(handler, 0)
-
- for (key in testKeys) {
- sendKeys(intArrayOf(key), /* assertAllConsumed = */ true)
+ private fun testKeyGestureNotProduced(
+ keyGestureController: KeyGestureController,
+ testName: String,
+ testKeys: IntArray
+ ) {
+ var handleEvents = mutableListOf<KeyGestureEvent>()
+ val handler = KeyGestureHandler { event, _ ->
+ handleEvents.add(KeyGestureEvent(event))
+ true
}
+ keyGestureController.registerKeyGestureHandler(handler, 0)
+ handleEvents.clear()
+
+ sendKeys(keyGestureController, testKeys)
+ assertEquals("Test: $testName should not produce Key gesture", 0, handleEvents.size)
}
- private fun sendKeys(testKeys: IntArray, assertAllConsumed: Boolean) {
+ private fun sendKeys(
+ keyGestureController: KeyGestureController,
+ testKeys: IntArray,
+ assertNotSentToApps: Boolean = false
+ ) {
var metaState = 0
+ val now = SystemClock.uptimeMillis()
for (key in testKeys) {
val downEvent = KeyEvent(
- /* downTime = */0, /* eventTime = */ 0, KeyEvent.ACTION_DOWN, key,
- 0 /*repeat*/, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/,
- 0 /*flags*/, InputDevice.SOURCE_KEYBOARD
+ now, now, KeyEvent.ACTION_DOWN, key, 0 /*repeat*/, metaState,
+ DEVICE_ID, 0 /*scancode*/, 0 /*flags*/,
+ InputDevice.SOURCE_KEYBOARD
)
- val consumed =
- keyGestureController.interceptKeyBeforeDispatching(null, downEvent, 0) == -1L
- if (assertAllConsumed) {
- assertTrue(
- "interceptKeyBeforeDispatching should consume all events $downEvent",
- consumed
- )
- }
+ interceptKey(keyGestureController, downEvent, assertNotSentToApps)
metaState = metaState or MODIFIER.getOrDefault(key, 0)
downEvent.recycle()
@@ -632,24 +964,39 @@
for (key in testKeys.reversed()) {
val upEvent = KeyEvent(
- /* downTime = */0, /* eventTime = */ 0, KeyEvent.ACTION_UP, key,
- 0 /*repeat*/, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/,
- 0 /*flags*/, InputDevice.SOURCE_KEYBOARD
+ now, now, KeyEvent.ACTION_UP, key, 0 /*repeat*/, metaState,
+ DEVICE_ID, 0 /*scancode*/, 0 /*flags*/,
+ InputDevice.SOURCE_KEYBOARD
)
- val consumed =
- keyGestureController.interceptKeyBeforeDispatching(null, upEvent, 0) == -1L
- if (assertAllConsumed) {
- assertTrue(
- "interceptKeyBeforeDispatching should consume all events $upEvent",
- consumed
- )
- }
+ interceptKey(keyGestureController, upEvent, assertNotSentToApps)
+ metaState = metaState and MODIFIER.getOrDefault(key, 0).inv()
upEvent.recycle()
testLooper.dispatchAll()
}
}
+ private fun interceptKey(
+ keyGestureController: KeyGestureController,
+ event: KeyEvent,
+ assertNotSentToApps: Boolean
+ ) {
+ keyGestureController.interceptKeyBeforeQueueing(event, FLAG_INTERACTIVE)
+ testLooper.dispatchAll()
+
+ val consumed =
+ keyGestureController.interceptKeyBeforeDispatching(null, event, 0) == -1L
+ if (assertNotSentToApps) {
+ assertTrue(
+ "interceptKeyBeforeDispatching should consume all events $event",
+ consumed
+ )
+ }
+ if (!consumed) {
+ keyGestureController.interceptUnhandledKey(event, null)
+ }
+ }
+
inner class KeyGestureEventListener : IKeyGestureEventListener.Stub() {
override fun onKeyGestureEvent(event: AidlKeyGestureEvent) {
events.add(KeyGestureEvent(event))