Merge "Move binder calls to the background threads" into main
diff --git a/android-sdk-flags/OWNERS b/android-sdk-flags/OWNERS
new file mode 100644
index 0000000..01f45dd
--- /dev/null
+++ b/android-sdk-flags/OWNERS
@@ -0,0 +1 @@
+include /SDK_OWNERS
diff --git a/android-sdk-flags/flags.aconfig b/android-sdk-flags/flags.aconfig
index cfe298e..19c7bf6 100644
--- a/android-sdk-flags/flags.aconfig
+++ b/android-sdk-flags/flags.aconfig
@@ -6,6 +6,7 @@
namespace: "android_sdk"
description: "Use the new SDK major.minor versioning scheme (e.g. Android 40.1) which replaces the old single-integer scheme (e.g. Android 15)."
bug: "350458259"
+ is_exported: true
# Use is_fixed_read_only because DeviceConfig may not be available when Build.VERSION_CODES is first accessed
is_fixed_read_only: true
diff --git a/apct-tests/perftests/core/src/android/os/LongArrayMultiStateCounterPerfTest.java b/apct-tests/perftests/core/src/android/os/LongArrayMultiStateCounterPerfTest.java
index 8d2d044..8160ca2 100644
--- a/apct-tests/perftests/core/src/android/os/LongArrayMultiStateCounterPerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/LongArrayMultiStateCounterPerfTest.java
@@ -159,9 +159,7 @@
LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4);
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
long time = 1000;
- LongArrayMultiStateCounter.LongArrayContainer timeInFreq =
- new LongArrayMultiStateCounter.LongArrayContainer(4);
- timeInFreq.setValues(new long[]{100, 200, 300, 400});
+ long[] timeInFreq = {100, 200, 300, 400};
while (state.keepRunning()) {
counter.setState(1, time);
counter.setState(0, time + 1000);
diff --git a/apex/jobscheduler/framework/aconfig/job.aconfig b/apex/jobscheduler/framework/aconfig/job.aconfig
index debd850..79aef1e 100644
--- a/apex/jobscheduler/framework/aconfig/job.aconfig
+++ b/apex/jobscheduler/framework/aconfig/job.aconfig
@@ -37,3 +37,11 @@
description: "Ignore the important_while_foreground flag and change the related APIs to be not effective"
bug: "374175032"
}
+
+flag {
+ name: "get_pending_job_reasons_api"
+ is_exported: true
+ namespace: "backstage_power"
+ description: "Introduce a new getPendingJobReasons() API which returns reasons why a job may not have executed. Also deprecate the existing getPendingJobReason() API."
+ bug: "372031023"
+}
diff --git a/core/api/current.txt b/core/api/current.txt
index 2cb7a30..d9ab273 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -52997,7 +52997,7 @@
method public void addOnUnhandledKeyEventListener(android.view.View.OnUnhandledKeyEventListener);
method public void addTouchables(java.util.ArrayList<android.view.View>);
method public android.view.ViewPropertyAnimator animate();
- method public void announceForAccessibility(CharSequence);
+ method @Deprecated @FlaggedApi("android.view.accessibility.deprecate_accessibility_announcement_apis") public void announceForAccessibility(CharSequence);
method public void autofill(android.view.autofill.AutofillValue);
method public void autofill(@NonNull android.util.SparseArray<android.view.autofill.AutofillValue>);
method protected boolean awakenScrollBars();
@@ -55247,7 +55247,7 @@
field public static final int SPEECH_STATE_SPEAKING_END = 2; // 0x2
field public static final int SPEECH_STATE_SPEAKING_START = 1; // 0x1
field public static final int TYPES_ALL_MASK = -1; // 0xffffffff
- field public static final int TYPE_ANNOUNCEMENT = 16384; // 0x4000
+ field @Deprecated @FlaggedApi("android.view.accessibility.deprecate_accessibility_announcement_apis") public static final int TYPE_ANNOUNCEMENT = 16384; // 0x4000
field public static final int TYPE_ASSIST_READING_CONTEXT = 16777216; // 0x1000000
field public static final int TYPE_GESTURE_DETECTION_END = 524288; // 0x80000
field public static final int TYPE_GESTURE_DETECTION_START = 262144; // 0x40000
@@ -62048,6 +62048,11 @@
method public void markSyncReady();
}
+ @FlaggedApi("com.android.window.flags.predictive_back_system_override_callback") public final class SystemOnBackInvokedCallbacks {
+ method @FlaggedApi("com.android.window.flags.predictive_back_system_override_callback") @NonNull public static android.window.OnBackInvokedCallback finishAndRemoveTaskCallback(@NonNull android.app.Activity);
+ method @FlaggedApi("com.android.window.flags.predictive_back_system_override_callback") @NonNull public static android.window.OnBackInvokedCallback moveTaskToBackCallback(@NonNull android.app.Activity);
+ }
+
@FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public final class TrustedPresentationThresholds implements android.os.Parcelable {
ctor @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public TrustedPresentationThresholds(@FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @IntRange(from=1) int);
method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public int describeContents();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 047a0df..3fde749 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3558,10 +3558,12 @@
method @Deprecated public int getDefaultActivityPolicy();
method @Deprecated public int getDefaultNavigationPolicy();
method public int getDevicePolicy(int);
+ method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public java.time.Duration getDimDuration();
method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @Nullable public android.content.ComponentName getHomeComponent();
method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @Nullable public android.content.ComponentName getInputMethodComponent();
method public int getLockState();
method @Nullable public String getName();
+ method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public java.time.Duration getScreenOffTimeout();
method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts();
method @NonNull public java.util.List<android.companion.virtual.sensor.VirtualSensorConfig> getVirtualSensorConfigs();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -3579,6 +3581,7 @@
field @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public static final int POLICY_TYPE_BLOCKED_ACTIVITY = 6; // 0x6
field @FlaggedApi("android.companion.virtual.flags.virtual_camera") public static final int POLICY_TYPE_CAMERA = 5; // 0x5
field @FlaggedApi("android.companion.virtual.flags.cross_device_clipboard") public static final int POLICY_TYPE_CLIPBOARD = 4; // 0x4
+ field @FlaggedApi("android.companion.virtualdevice.flags.default_device_camera_access_policy") public static final int POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS = 7; // 0x7
field public static final int POLICY_TYPE_RECENTS = 2; // 0x2
field public static final int POLICY_TYPE_SENSORS = 0; // 0x0
}
@@ -3594,10 +3597,12 @@
method @Deprecated @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedActivities(@NonNull java.util.Set<android.content.ComponentName>);
method @Deprecated @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDevicePolicy(int, int);
+ method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDimDuration(@NonNull java.time.Duration);
method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setHomeComponent(@Nullable android.content.ComponentName);
method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setInputMethodComponent(@Nullable android.content.ComponentName);
method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setName(@NonNull String);
+ method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setScreenOffTimeout(@NonNull java.time.Duration);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setUsersWithMatchingAccounts(@NonNull java.util.Set<android.os.UserHandle>);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setVirtualSensorCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.sensor.VirtualSensorCallback);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setVirtualSensorDirectChannelCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.sensor.VirtualSensorDirectChannelCallback);
@@ -7234,6 +7239,7 @@
field @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.MODIFY_AUDIO_ROUTING}) public static final int USAGE_CALL_ASSISTANT = 17; // 0x11
field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int USAGE_EMERGENCY = 1000; // 0x3e8
field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int USAGE_SAFETY = 1001; // 0x3e9
+ field @FlaggedApi("android.media.audio.speaker_cleanup_usage") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int USAGE_SPEAKER_CLEANUP = 1004; // 0x3ec
field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int USAGE_VEHICLE_STATUS = 1002; // 0x3ea
}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 2b0e86c..0629b8a 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -910,159 +910,157 @@
/** @hide No operation specified. */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int OP_NONE = AppProtoEnums.APP_OP_NONE;
+ public static final int OP_NONE = AppOpEnums.APP_OP_NONE;
/** @hide Access to coarse location information. */
@UnsupportedAppUsage
@TestApi
- public static final int OP_COARSE_LOCATION = AppProtoEnums.APP_OP_COARSE_LOCATION;
+ public static final int OP_COARSE_LOCATION = AppOpEnums.APP_OP_COARSE_LOCATION;
/** @hide Access to fine location information. */
@UnsupportedAppUsage
- public static final int OP_FINE_LOCATION = AppProtoEnums.APP_OP_FINE_LOCATION;
+ public static final int OP_FINE_LOCATION = AppOpEnums.APP_OP_FINE_LOCATION;
/** @hide Causing GPS to run. */
@UnsupportedAppUsage
- public static final int OP_GPS = AppProtoEnums.APP_OP_GPS;
+ public static final int OP_GPS = AppOpEnums.APP_OP_GPS;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_VIBRATE = AppProtoEnums.APP_OP_VIBRATE;
+ public static final int OP_VIBRATE = AppOpEnums.APP_OP_VIBRATE;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_READ_CONTACTS = AppProtoEnums.APP_OP_READ_CONTACTS;
+ public static final int OP_READ_CONTACTS = AppOpEnums.APP_OP_READ_CONTACTS;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_WRITE_CONTACTS = AppProtoEnums.APP_OP_WRITE_CONTACTS;
+ public static final int OP_WRITE_CONTACTS = AppOpEnums.APP_OP_WRITE_CONTACTS;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_READ_CALL_LOG = AppProtoEnums.APP_OP_READ_CALL_LOG;
+ public static final int OP_READ_CALL_LOG = AppOpEnums.APP_OP_READ_CALL_LOG;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_WRITE_CALL_LOG = AppProtoEnums.APP_OP_WRITE_CALL_LOG;
+ public static final int OP_WRITE_CALL_LOG = AppOpEnums.APP_OP_WRITE_CALL_LOG;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_READ_CALENDAR = AppProtoEnums.APP_OP_READ_CALENDAR;
+ public static final int OP_READ_CALENDAR = AppOpEnums.APP_OP_READ_CALENDAR;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_WRITE_CALENDAR = AppProtoEnums.APP_OP_WRITE_CALENDAR;
+ public static final int OP_WRITE_CALENDAR = AppOpEnums.APP_OP_WRITE_CALENDAR;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_WIFI_SCAN = AppProtoEnums.APP_OP_WIFI_SCAN;
+ public static final int OP_WIFI_SCAN = AppOpEnums.APP_OP_WIFI_SCAN;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_POST_NOTIFICATION = AppProtoEnums.APP_OP_POST_NOTIFICATION;
+ public static final int OP_POST_NOTIFICATION = AppOpEnums.APP_OP_POST_NOTIFICATION;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_NEIGHBORING_CELLS = AppProtoEnums.APP_OP_NEIGHBORING_CELLS;
+ public static final int OP_NEIGHBORING_CELLS = AppOpEnums.APP_OP_NEIGHBORING_CELLS;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_CALL_PHONE = AppProtoEnums.APP_OP_CALL_PHONE;
+ public static final int OP_CALL_PHONE = AppOpEnums.APP_OP_CALL_PHONE;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_READ_SMS = AppProtoEnums.APP_OP_READ_SMS;
+ public static final int OP_READ_SMS = AppOpEnums.APP_OP_READ_SMS;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_WRITE_SMS = AppProtoEnums.APP_OP_WRITE_SMS;
+ public static final int OP_WRITE_SMS = AppOpEnums.APP_OP_WRITE_SMS;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_RECEIVE_SMS = AppProtoEnums.APP_OP_RECEIVE_SMS;
+ public static final int OP_RECEIVE_SMS = AppOpEnums.APP_OP_RECEIVE_SMS;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_RECEIVE_EMERGECY_SMS =
- AppProtoEnums.APP_OP_RECEIVE_EMERGENCY_SMS;
+ public static final int OP_RECEIVE_EMERGECY_SMS = AppOpEnums.APP_OP_RECEIVE_EMERGENCY_SMS;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_RECEIVE_MMS = AppProtoEnums.APP_OP_RECEIVE_MMS;
+ public static final int OP_RECEIVE_MMS = AppOpEnums.APP_OP_RECEIVE_MMS;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_RECEIVE_WAP_PUSH = AppProtoEnums.APP_OP_RECEIVE_WAP_PUSH;
+ public static final int OP_RECEIVE_WAP_PUSH = AppOpEnums.APP_OP_RECEIVE_WAP_PUSH;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_SEND_SMS = AppProtoEnums.APP_OP_SEND_SMS;
+ public static final int OP_SEND_SMS = AppOpEnums.APP_OP_SEND_SMS;
/** @hide */
- public static final int OP_MANAGE_ONGOING_CALLS = AppProtoEnums.APP_OP_MANAGE_ONGOING_CALLS;
+ public static final int OP_MANAGE_ONGOING_CALLS = AppOpEnums.APP_OP_MANAGE_ONGOING_CALLS;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_READ_ICC_SMS = AppProtoEnums.APP_OP_READ_ICC_SMS;
+ public static final int OP_READ_ICC_SMS = AppOpEnums.APP_OP_READ_ICC_SMS;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_WRITE_ICC_SMS = AppProtoEnums.APP_OP_WRITE_ICC_SMS;
+ public static final int OP_WRITE_ICC_SMS = AppOpEnums.APP_OP_WRITE_ICC_SMS;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_WRITE_SETTINGS = AppProtoEnums.APP_OP_WRITE_SETTINGS;
+ public static final int OP_WRITE_SETTINGS = AppOpEnums.APP_OP_WRITE_SETTINGS;
/** @hide Required to draw on top of other apps. */
@UnsupportedAppUsage
@TestApi
- public static final int OP_SYSTEM_ALERT_WINDOW = AppProtoEnums.APP_OP_SYSTEM_ALERT_WINDOW;
+ public static final int OP_SYSTEM_ALERT_WINDOW = AppOpEnums.APP_OP_SYSTEM_ALERT_WINDOW;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_ACCESS_NOTIFICATIONS =
- AppProtoEnums.APP_OP_ACCESS_NOTIFICATIONS;
+ public static final int OP_ACCESS_NOTIFICATIONS = AppOpEnums.APP_OP_ACCESS_NOTIFICATIONS;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_CAMERA = AppProtoEnums.APP_OP_CAMERA;
+ public static final int OP_CAMERA = AppOpEnums.APP_OP_CAMERA;
/** @hide */
@UnsupportedAppUsage
@TestApi
- public static final int OP_RECORD_AUDIO = AppProtoEnums.APP_OP_RECORD_AUDIO;
+ public static final int OP_RECORD_AUDIO = AppOpEnums.APP_OP_RECORD_AUDIO;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_PLAY_AUDIO = AppProtoEnums.APP_OP_PLAY_AUDIO;
+ public static final int OP_PLAY_AUDIO = AppOpEnums.APP_OP_PLAY_AUDIO;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_READ_CLIPBOARD = AppProtoEnums.APP_OP_READ_CLIPBOARD;
+ public static final int OP_READ_CLIPBOARD = AppOpEnums.APP_OP_READ_CLIPBOARD;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_WRITE_CLIPBOARD = AppProtoEnums.APP_OP_WRITE_CLIPBOARD;
+ public static final int OP_WRITE_CLIPBOARD = AppOpEnums.APP_OP_WRITE_CLIPBOARD;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_TAKE_MEDIA_BUTTONS = AppProtoEnums.APP_OP_TAKE_MEDIA_BUTTONS;
+ public static final int OP_TAKE_MEDIA_BUTTONS = AppOpEnums.APP_OP_TAKE_MEDIA_BUTTONS;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_TAKE_AUDIO_FOCUS = AppProtoEnums.APP_OP_TAKE_AUDIO_FOCUS;
+ public static final int OP_TAKE_AUDIO_FOCUS = AppOpEnums.APP_OP_TAKE_AUDIO_FOCUS;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_AUDIO_MASTER_VOLUME = AppProtoEnums.APP_OP_AUDIO_MASTER_VOLUME;
+ public static final int OP_AUDIO_MASTER_VOLUME = AppOpEnums.APP_OP_AUDIO_MASTER_VOLUME;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_AUDIO_VOICE_VOLUME = AppProtoEnums.APP_OP_AUDIO_VOICE_VOLUME;
+ public static final int OP_AUDIO_VOICE_VOLUME = AppOpEnums.APP_OP_AUDIO_VOICE_VOLUME;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_AUDIO_RING_VOLUME = AppProtoEnums.APP_OP_AUDIO_RING_VOLUME;
+ public static final int OP_AUDIO_RING_VOLUME = AppOpEnums.APP_OP_AUDIO_RING_VOLUME;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_AUDIO_MEDIA_VOLUME = AppProtoEnums.APP_OP_AUDIO_MEDIA_VOLUME;
+ public static final int OP_AUDIO_MEDIA_VOLUME = AppOpEnums.APP_OP_AUDIO_MEDIA_VOLUME;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_AUDIO_ALARM_VOLUME = AppProtoEnums.APP_OP_AUDIO_ALARM_VOLUME;
+ public static final int OP_AUDIO_ALARM_VOLUME = AppOpEnums.APP_OP_AUDIO_ALARM_VOLUME;
/** @hide */
@UnsupportedAppUsage
public static final int OP_AUDIO_NOTIFICATION_VOLUME =
- AppProtoEnums.APP_OP_AUDIO_NOTIFICATION_VOLUME;
+ AppOpEnums.APP_OP_AUDIO_NOTIFICATION_VOLUME;
/** @hide */
@UnsupportedAppUsage
public static final int OP_AUDIO_BLUETOOTH_VOLUME =
- AppProtoEnums.APP_OP_AUDIO_BLUETOOTH_VOLUME;
+ AppOpEnums.APP_OP_AUDIO_BLUETOOTH_VOLUME;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_WAKE_LOCK = AppProtoEnums.APP_OP_WAKE_LOCK;
+ public static final int OP_WAKE_LOCK = AppOpEnums.APP_OP_WAKE_LOCK;
/** @hide Continually monitoring location data. */
@UnsupportedAppUsage
public static final int OP_MONITOR_LOCATION =
- AppProtoEnums.APP_OP_MONITOR_LOCATION;
+ AppOpEnums.APP_OP_MONITOR_LOCATION;
/** @hide Continually monitoring location data with a relatively high power request. */
@UnsupportedAppUsage
public static final int OP_MONITOR_HIGH_POWER_LOCATION =
- AppProtoEnums.APP_OP_MONITOR_HIGH_POWER_LOCATION;
+ AppOpEnums.APP_OP_MONITOR_HIGH_POWER_LOCATION;
/** @hide Retrieve current usage stats via {@link UsageStatsManager}. */
@UnsupportedAppUsage
- public static final int OP_GET_USAGE_STATS = AppProtoEnums.APP_OP_GET_USAGE_STATS;
+ public static final int OP_GET_USAGE_STATS = AppOpEnums.APP_OP_GET_USAGE_STATS;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_MUTE_MICROPHONE = AppProtoEnums.APP_OP_MUTE_MICROPHONE;
+ public static final int OP_MUTE_MICROPHONE = AppOpEnums.APP_OP_MUTE_MICROPHONE;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_TOAST_WINDOW = AppProtoEnums.APP_OP_TOAST_WINDOW;
+ public static final int OP_TOAST_WINDOW = AppOpEnums.APP_OP_TOAST_WINDOW;
/** @hide Capture the device's display contents and/or audio */
@UnsupportedAppUsage
- public static final int OP_PROJECT_MEDIA = AppProtoEnums.APP_OP_PROJECT_MEDIA;
+ public static final int OP_PROJECT_MEDIA = AppOpEnums.APP_OP_PROJECT_MEDIA;
/**
* Start (without additional user intervention) a VPN connection, as used by {@link
* android.net.VpnService} along with as Platform VPN connections, as used by {@link
@@ -1075,146 +1073,141 @@
* @hide
*/
@UnsupportedAppUsage
- public static final int OP_ACTIVATE_VPN = AppProtoEnums.APP_OP_ACTIVATE_VPN;
+ public static final int OP_ACTIVATE_VPN = AppOpEnums.APP_OP_ACTIVATE_VPN;
/** @hide Access the WallpaperManagerAPI to write wallpapers. */
@UnsupportedAppUsage
- public static final int OP_WRITE_WALLPAPER = AppProtoEnums.APP_OP_WRITE_WALLPAPER;
+ public static final int OP_WRITE_WALLPAPER = AppOpEnums.APP_OP_WRITE_WALLPAPER;
/** @hide Received the assist structure from an app. */
@UnsupportedAppUsage
- public static final int OP_ASSIST_STRUCTURE = AppProtoEnums.APP_OP_ASSIST_STRUCTURE;
+ public static final int OP_ASSIST_STRUCTURE = AppOpEnums.APP_OP_ASSIST_STRUCTURE;
/** @hide Received a screenshot from assist. */
@UnsupportedAppUsage
- public static final int OP_ASSIST_SCREENSHOT = AppProtoEnums.APP_OP_ASSIST_SCREENSHOT;
+ public static final int OP_ASSIST_SCREENSHOT = AppOpEnums.APP_OP_ASSIST_SCREENSHOT;
/** @hide Read the phone state. */
@UnsupportedAppUsage
- public static final int OP_READ_PHONE_STATE = AppProtoEnums.APP_OP_READ_PHONE_STATE;
+ public static final int OP_READ_PHONE_STATE = AppOpEnums.APP_OP_READ_PHONE_STATE;
/** @hide Add voicemail messages to the voicemail content provider. */
@UnsupportedAppUsage
- public static final int OP_ADD_VOICEMAIL = AppProtoEnums.APP_OP_ADD_VOICEMAIL;
+ public static final int OP_ADD_VOICEMAIL = AppOpEnums.APP_OP_ADD_VOICEMAIL;
/** @hide Access APIs for SIP calling over VOIP or WiFi. */
@UnsupportedAppUsage
- public static final int OP_USE_SIP = AppProtoEnums.APP_OP_USE_SIP;
+ public static final int OP_USE_SIP = AppOpEnums.APP_OP_USE_SIP;
/** @hide Intercept outgoing calls. */
@UnsupportedAppUsage
- public static final int OP_PROCESS_OUTGOING_CALLS =
- AppProtoEnums.APP_OP_PROCESS_OUTGOING_CALLS;
+ public static final int OP_PROCESS_OUTGOING_CALLS = AppOpEnums.APP_OP_PROCESS_OUTGOING_CALLS;
/** @hide User the fingerprint API. */
@UnsupportedAppUsage
- public static final int OP_USE_FINGERPRINT = AppProtoEnums.APP_OP_USE_FINGERPRINT;
+ public static final int OP_USE_FINGERPRINT = AppOpEnums.APP_OP_USE_FINGERPRINT;
/** @hide Access to body sensors such as heart rate, etc. */
@UnsupportedAppUsage
- public static final int OP_BODY_SENSORS = AppProtoEnums.APP_OP_BODY_SENSORS;
+ public static final int OP_BODY_SENSORS = AppOpEnums.APP_OP_BODY_SENSORS;
/** @hide Read previously received cell broadcast messages. */
@UnsupportedAppUsage
- public static final int OP_READ_CELL_BROADCASTS = AppProtoEnums.APP_OP_READ_CELL_BROADCASTS;
+ public static final int OP_READ_CELL_BROADCASTS = AppOpEnums.APP_OP_READ_CELL_BROADCASTS;
/** @hide Inject mock location into the system. */
@UnsupportedAppUsage
- public static final int OP_MOCK_LOCATION = AppProtoEnums.APP_OP_MOCK_LOCATION;
+ public static final int OP_MOCK_LOCATION = AppOpEnums.APP_OP_MOCK_LOCATION;
/** @hide Read external storage. */
@UnsupportedAppUsage
- public static final int OP_READ_EXTERNAL_STORAGE = AppProtoEnums.APP_OP_READ_EXTERNAL_STORAGE;
+ public static final int OP_READ_EXTERNAL_STORAGE = AppOpEnums.APP_OP_READ_EXTERNAL_STORAGE;
/** @hide Write external storage. */
@UnsupportedAppUsage
- public static final int OP_WRITE_EXTERNAL_STORAGE =
- AppProtoEnums.APP_OP_WRITE_EXTERNAL_STORAGE;
+ public static final int OP_WRITE_EXTERNAL_STORAGE = AppOpEnums.APP_OP_WRITE_EXTERNAL_STORAGE;
/** @hide Turned on the screen. */
@UnsupportedAppUsage
- public static final int OP_TURN_SCREEN_ON = AppProtoEnums.APP_OP_TURN_SCREEN_ON;
+ public static final int OP_TURN_SCREEN_ON = AppOpEnums.APP_OP_TURN_SCREEN_ON;
/** @hide Get device accounts. */
@UnsupportedAppUsage
- public static final int OP_GET_ACCOUNTS = AppProtoEnums.APP_OP_GET_ACCOUNTS;
+ public static final int OP_GET_ACCOUNTS = AppOpEnums.APP_OP_GET_ACCOUNTS;
/** @hide Control whether an application is allowed to run in the background. */
@UnsupportedAppUsage
- public static final int OP_RUN_IN_BACKGROUND =
- AppProtoEnums.APP_OP_RUN_IN_BACKGROUND;
+ public static final int OP_RUN_IN_BACKGROUND = AppOpEnums.APP_OP_RUN_IN_BACKGROUND;
/** @hide */
@UnsupportedAppUsage
public static final int OP_AUDIO_ACCESSIBILITY_VOLUME =
- AppProtoEnums.APP_OP_AUDIO_ACCESSIBILITY_VOLUME;
+ AppOpEnums.APP_OP_AUDIO_ACCESSIBILITY_VOLUME;
/** @hide Read the phone number. */
@UnsupportedAppUsage
- public static final int OP_READ_PHONE_NUMBERS = AppProtoEnums.APP_OP_READ_PHONE_NUMBERS;
+ public static final int OP_READ_PHONE_NUMBERS = AppOpEnums.APP_OP_READ_PHONE_NUMBERS;
/** @hide Request package installs through package installer */
@UnsupportedAppUsage
public static final int OP_REQUEST_INSTALL_PACKAGES =
- AppProtoEnums.APP_OP_REQUEST_INSTALL_PACKAGES;
+ AppOpEnums.APP_OP_REQUEST_INSTALL_PACKAGES;
/** @hide Enter picture-in-picture. */
@UnsupportedAppUsage
- public static final int OP_PICTURE_IN_PICTURE = AppProtoEnums.APP_OP_PICTURE_IN_PICTURE;
+ public static final int OP_PICTURE_IN_PICTURE = AppOpEnums.APP_OP_PICTURE_IN_PICTURE;
/** @hide Instant app start foreground service. */
@UnsupportedAppUsage
public static final int OP_INSTANT_APP_START_FOREGROUND =
- AppProtoEnums.APP_OP_INSTANT_APP_START_FOREGROUND;
+ AppOpEnums.APP_OP_INSTANT_APP_START_FOREGROUND;
/** @hide Answer incoming phone calls */
@UnsupportedAppUsage
- public static final int OP_ANSWER_PHONE_CALLS = AppProtoEnums.APP_OP_ANSWER_PHONE_CALLS;
+ public static final int OP_ANSWER_PHONE_CALLS = AppOpEnums.APP_OP_ANSWER_PHONE_CALLS;
/** @hide Run jobs when in background */
@UnsupportedAppUsage
- public static final int OP_RUN_ANY_IN_BACKGROUND = AppProtoEnums.APP_OP_RUN_ANY_IN_BACKGROUND;
+ public static final int OP_RUN_ANY_IN_BACKGROUND = AppOpEnums.APP_OP_RUN_ANY_IN_BACKGROUND;
/** @hide Change Wi-Fi connectivity state */
@UnsupportedAppUsage
- public static final int OP_CHANGE_WIFI_STATE = AppProtoEnums.APP_OP_CHANGE_WIFI_STATE;
+ public static final int OP_CHANGE_WIFI_STATE = AppOpEnums.APP_OP_CHANGE_WIFI_STATE;
/** @hide Request package deletion through package installer */
@UnsupportedAppUsage
- public static final int OP_REQUEST_DELETE_PACKAGES =
- AppProtoEnums.APP_OP_REQUEST_DELETE_PACKAGES;
+ public static final int OP_REQUEST_DELETE_PACKAGES = AppOpEnums.APP_OP_REQUEST_DELETE_PACKAGES;
/** @hide Bind an accessibility service. */
@UnsupportedAppUsage
public static final int OP_BIND_ACCESSIBILITY_SERVICE =
- AppProtoEnums.APP_OP_BIND_ACCESSIBILITY_SERVICE;
+ AppOpEnums.APP_OP_BIND_ACCESSIBILITY_SERVICE;
/** @hide Continue handover of a call from another app */
@UnsupportedAppUsage
- public static final int OP_ACCEPT_HANDOVER = AppProtoEnums.APP_OP_ACCEPT_HANDOVER;
+ public static final int OP_ACCEPT_HANDOVER = AppOpEnums.APP_OP_ACCEPT_HANDOVER;
/** @hide Create and Manage IPsec Tunnels */
@UnsupportedAppUsage
- public static final int OP_MANAGE_IPSEC_TUNNELS = AppProtoEnums.APP_OP_MANAGE_IPSEC_TUNNELS;
+ public static final int OP_MANAGE_IPSEC_TUNNELS = AppOpEnums.APP_OP_MANAGE_IPSEC_TUNNELS;
/** @hide Any app start foreground service. */
@UnsupportedAppUsage
@TestApi
- public static final int OP_START_FOREGROUND = AppProtoEnums.APP_OP_START_FOREGROUND;
+ public static final int OP_START_FOREGROUND = AppOpEnums.APP_OP_START_FOREGROUND;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_BLUETOOTH_SCAN = AppProtoEnums.APP_OP_BLUETOOTH_SCAN;
+ public static final int OP_BLUETOOTH_SCAN = AppOpEnums.APP_OP_BLUETOOTH_SCAN;
/** @hide */
- public static final int OP_BLUETOOTH_CONNECT = AppProtoEnums.APP_OP_BLUETOOTH_CONNECT;
+ public static final int OP_BLUETOOTH_CONNECT = AppOpEnums.APP_OP_BLUETOOTH_CONNECT;
/** @hide */
- public static final int OP_BLUETOOTH_ADVERTISE = AppProtoEnums.APP_OP_BLUETOOTH_ADVERTISE;
+ public static final int OP_BLUETOOTH_ADVERTISE = AppOpEnums.APP_OP_BLUETOOTH_ADVERTISE;
/** @hide Use the BiometricPrompt/BiometricManager APIs. */
- public static final int OP_USE_BIOMETRIC = AppProtoEnums.APP_OP_USE_BIOMETRIC;
+ public static final int OP_USE_BIOMETRIC = AppOpEnums.APP_OP_USE_BIOMETRIC;
/** @hide Physical activity recognition. */
- public static final int OP_ACTIVITY_RECOGNITION = AppProtoEnums.APP_OP_ACTIVITY_RECOGNITION;
+ public static final int OP_ACTIVITY_RECOGNITION = AppOpEnums.APP_OP_ACTIVITY_RECOGNITION;
/** @hide Financial app sms read. */
public static final int OP_SMS_FINANCIAL_TRANSACTIONS =
- AppProtoEnums.APP_OP_SMS_FINANCIAL_TRANSACTIONS;
+ AppOpEnums.APP_OP_SMS_FINANCIAL_TRANSACTIONS;
/** @hide Read media of audio type. */
- public static final int OP_READ_MEDIA_AUDIO = AppProtoEnums.APP_OP_READ_MEDIA_AUDIO;
+ public static final int OP_READ_MEDIA_AUDIO = AppOpEnums.APP_OP_READ_MEDIA_AUDIO;
/** @hide Write media of audio type. */
- public static final int OP_WRITE_MEDIA_AUDIO = AppProtoEnums.APP_OP_WRITE_MEDIA_AUDIO;
+ public static final int OP_WRITE_MEDIA_AUDIO = AppOpEnums.APP_OP_WRITE_MEDIA_AUDIO;
/** @hide Read media of video type. */
- public static final int OP_READ_MEDIA_VIDEO = AppProtoEnums.APP_OP_READ_MEDIA_VIDEO;
+ public static final int OP_READ_MEDIA_VIDEO = AppOpEnums.APP_OP_READ_MEDIA_VIDEO;
/** @hide Write media of video type. */
- public static final int OP_WRITE_MEDIA_VIDEO = AppProtoEnums.APP_OP_WRITE_MEDIA_VIDEO;
+ public static final int OP_WRITE_MEDIA_VIDEO = AppOpEnums.APP_OP_WRITE_MEDIA_VIDEO;
/** @hide Read media of image type. */
- public static final int OP_READ_MEDIA_IMAGES = AppProtoEnums.APP_OP_READ_MEDIA_IMAGES;
+ public static final int OP_READ_MEDIA_IMAGES = AppOpEnums.APP_OP_READ_MEDIA_IMAGES;
/** @hide Write media of image type. */
- public static final int OP_WRITE_MEDIA_IMAGES = AppProtoEnums.APP_OP_WRITE_MEDIA_IMAGES;
+ public static final int OP_WRITE_MEDIA_IMAGES = AppOpEnums.APP_OP_WRITE_MEDIA_IMAGES;
/** @hide Has a legacy (non-isolated) view of storage. */
- public static final int OP_LEGACY_STORAGE = AppProtoEnums.APP_OP_LEGACY_STORAGE;
+ public static final int OP_LEGACY_STORAGE = AppOpEnums.APP_OP_LEGACY_STORAGE;
/** @hide Accessing accessibility features */
- public static final int OP_ACCESS_ACCESSIBILITY = AppProtoEnums.APP_OP_ACCESS_ACCESSIBILITY;
+ public static final int OP_ACCESS_ACCESSIBILITY = AppOpEnums.APP_OP_ACCESS_ACCESSIBILITY;
/** @hide Read the device identifiers (IMEI / MEID, IMSI, SIM / Build serial) */
public static final int OP_READ_DEVICE_IDENTIFIERS =
- AppProtoEnums.APP_OP_READ_DEVICE_IDENTIFIERS;
+ AppOpEnums.APP_OP_READ_DEVICE_IDENTIFIERS;
/** @hide Read location metadata from media */
- public static final int OP_ACCESS_MEDIA_LOCATION = AppProtoEnums.APP_OP_ACCESS_MEDIA_LOCATION;
+ public static final int OP_ACCESS_MEDIA_LOCATION = AppOpEnums.APP_OP_ACCESS_MEDIA_LOCATION;
/** @hide Query all apps on device, regardless of declarations in the calling app manifest */
- public static final int OP_QUERY_ALL_PACKAGES = AppProtoEnums.APP_OP_QUERY_ALL_PACKAGES;
+ public static final int OP_QUERY_ALL_PACKAGES = AppOpEnums.APP_OP_QUERY_ALL_PACKAGES;
/** @hide Access all external storage */
- public static final int OP_MANAGE_EXTERNAL_STORAGE =
- AppProtoEnums.APP_OP_MANAGE_EXTERNAL_STORAGE;
+ public static final int OP_MANAGE_EXTERNAL_STORAGE = AppOpEnums.APP_OP_MANAGE_EXTERNAL_STORAGE;
/** @hide Communicate cross-profile within the same profile group. */
public static final int OP_INTERACT_ACROSS_PROFILES =
- AppProtoEnums.APP_OP_INTERACT_ACROSS_PROFILES;
+ AppOpEnums.APP_OP_INTERACT_ACROSS_PROFILES;
/**
* Start (without additional user intervention) a Platform VPN connection, as used by {@link
* android.net.VpnManager}
@@ -1225,16 +1218,16 @@
*
* @hide
*/
- public static final int OP_ACTIVATE_PLATFORM_VPN = AppProtoEnums.APP_OP_ACTIVATE_PLATFORM_VPN;
+ public static final int OP_ACTIVATE_PLATFORM_VPN = AppOpEnums.APP_OP_ACTIVATE_PLATFORM_VPN;
/** @hide Controls whether or not read logs are available for incremental installations. */
- public static final int OP_LOADER_USAGE_STATS = AppProtoEnums.APP_OP_LOADER_USAGE_STATS;
+ public static final int OP_LOADER_USAGE_STATS = AppOpEnums.APP_OP_LOADER_USAGE_STATS;
// App op deprecated/removed.
- private static final int OP_DEPRECATED_1 = AppProtoEnums.APP_OP_DEPRECATED_1;
+ private static final int OP_DEPRECATED_1 = AppOpEnums.APP_OP_DEPRECATED_1;
/** @hide Auto-revoke app permissions if app is unused for an extended period */
public static final int OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED =
- AppProtoEnums.APP_OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED;
+ AppOpEnums.APP_OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED;
/**
* Whether {@link #OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED} is allowed to be changed by
@@ -1243,55 +1236,55 @@
* @hide
*/
public static final int OP_AUTO_REVOKE_MANAGED_BY_INSTALLER =
- AppProtoEnums.APP_OP_AUTO_REVOKE_MANAGED_BY_INSTALLER;
+ AppOpEnums.APP_OP_AUTO_REVOKE_MANAGED_BY_INSTALLER;
/** @hide */
- public static final int OP_NO_ISOLATED_STORAGE = AppProtoEnums.APP_OP_NO_ISOLATED_STORAGE;
+ public static final int OP_NO_ISOLATED_STORAGE = AppOpEnums.APP_OP_NO_ISOLATED_STORAGE;
/**
* Phone call is using microphone
*
* @hide
*/
- public static final int OP_PHONE_CALL_MICROPHONE = AppProtoEnums.APP_OP_PHONE_CALL_MICROPHONE;
+ public static final int OP_PHONE_CALL_MICROPHONE = AppOpEnums.APP_OP_PHONE_CALL_MICROPHONE;
/**
* Phone call is using camera
*
* @hide
*/
- public static final int OP_PHONE_CALL_CAMERA = AppProtoEnums.APP_OP_PHONE_CALL_CAMERA;
+ public static final int OP_PHONE_CALL_CAMERA = AppOpEnums.APP_OP_PHONE_CALL_CAMERA;
/**
* Audio is being recorded for hotword detection.
*
* @hide
*/
- public static final int OP_RECORD_AUDIO_HOTWORD = AppProtoEnums.APP_OP_RECORD_AUDIO_HOTWORD;
+ public static final int OP_RECORD_AUDIO_HOTWORD = AppOpEnums.APP_OP_RECORD_AUDIO_HOTWORD;
/**
* Manage credentials in the system KeyChain.
*
* @hide
*/
- public static final int OP_MANAGE_CREDENTIALS = AppProtoEnums.APP_OP_MANAGE_CREDENTIALS;
+ public static final int OP_MANAGE_CREDENTIALS = AppOpEnums.APP_OP_MANAGE_CREDENTIALS;
/** @hide */
public static final int OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER =
- AppProtoEnums.APP_OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER;
+ AppOpEnums.APP_OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER;
/**
* App output audio is being recorded
*
* @hide
*/
- public static final int OP_RECORD_AUDIO_OUTPUT = AppProtoEnums.APP_OP_RECORD_AUDIO_OUTPUT;
+ public static final int OP_RECORD_AUDIO_OUTPUT = AppOpEnums.APP_OP_RECORD_AUDIO_OUTPUT;
/**
* App can schedule exact alarm to perform timing based background work
*
* @hide
*/
- public static final int OP_SCHEDULE_EXACT_ALARM = AppProtoEnums.APP_OP_SCHEDULE_EXACT_ALARM;
+ public static final int OP_SCHEDULE_EXACT_ALARM = AppOpEnums.APP_OP_SCHEDULE_EXACT_ALARM;
/**
* Fine location being accessed by a location source, which is
@@ -1301,7 +1294,7 @@
*
* @hide
*/
- public static final int OP_FINE_LOCATION_SOURCE = AppProtoEnums.APP_OP_FINE_LOCATION_SOURCE;
+ public static final int OP_FINE_LOCATION_SOURCE = AppOpEnums.APP_OP_FINE_LOCATION_SOURCE;
/**
* Coarse location being accessed by a location source, which is
@@ -1311,7 +1304,7 @@
*
* @hide
*/
- public static final int OP_COARSE_LOCATION_SOURCE = AppProtoEnums.APP_OP_COARSE_LOCATION_SOURCE;
+ public static final int OP_COARSE_LOCATION_SOURCE = AppOpEnums.APP_OP_COARSE_LOCATION_SOURCE;
/**
* Allow apps to create the requests to manage the media files without user confirmation.
@@ -1323,13 +1316,13 @@
*
* @hide
*/
- public static final int OP_MANAGE_MEDIA = AppProtoEnums.APP_OP_MANAGE_MEDIA;
+ public static final int OP_MANAGE_MEDIA = AppOpEnums.APP_OP_MANAGE_MEDIA;
/** @hide */
- public static final int OP_UWB_RANGING = AppProtoEnums.APP_OP_UWB_RANGING;
+ public static final int OP_UWB_RANGING = AppOpEnums.APP_OP_UWB_RANGING;
/** @hide */
- public static final int OP_NEARBY_WIFI_DEVICES = AppProtoEnums.APP_OP_NEARBY_WIFI_DEVICES;
+ public static final int OP_NEARBY_WIFI_DEVICES = AppOpEnums.APP_OP_NEARBY_WIFI_DEVICES;
/**
* Activity recognition being accessed by an activity recognition source, which
@@ -1339,7 +1332,7 @@
* @hide
*/
public static final int OP_ACTIVITY_RECOGNITION_SOURCE =
- AppProtoEnums.APP_OP_ACTIVITY_RECOGNITION_SOURCE;
+ AppOpEnums.APP_OP_ACTIVITY_RECOGNITION_SOURCE;
/**
* Incoming phone audio is being recorded
@@ -1347,21 +1340,21 @@
* @hide
*/
public static final int OP_RECORD_INCOMING_PHONE_AUDIO =
- AppProtoEnums.APP_OP_RECORD_INCOMING_PHONE_AUDIO;
+ AppOpEnums.APP_OP_RECORD_INCOMING_PHONE_AUDIO;
/**
* VPN app establishes a connection through the VpnService API.
*
* @hide
*/
- public static final int OP_ESTABLISH_VPN_SERVICE = AppProtoEnums.APP_OP_ESTABLISH_VPN_SERVICE;
+ public static final int OP_ESTABLISH_VPN_SERVICE = AppOpEnums.APP_OP_ESTABLISH_VPN_SERVICE;
/**
* VPN app establishes a connection through the VpnManager API.
*
* @hide
*/
- public static final int OP_ESTABLISH_VPN_MANAGER = AppProtoEnums.APP_OP_ESTABLISH_VPN_MANAGER;
+ public static final int OP_ESTABLISH_VPN_MANAGER = AppOpEnums.APP_OP_ESTABLISH_VPN_MANAGER;
/**
* Access restricted settings.
@@ -1369,7 +1362,7 @@
* @hide
*/
public static final int OP_ACCESS_RESTRICTED_SETTINGS =
- AppProtoEnums.APP_OP_ACCESS_RESTRICTED_SETTINGS;
+ AppOpEnums.APP_OP_ACCESS_RESTRICTED_SETTINGS;
/**
* Receive microphone audio from an ambient sound detection event
@@ -1377,7 +1370,7 @@
* @hide
*/
public static final int OP_RECEIVE_AMBIENT_TRIGGER_AUDIO =
- AppProtoEnums.APP_OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
+ AppOpEnums.APP_OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
/**
* Receive audio from near-field mic (ie. TV remote)
@@ -1387,15 +1380,14 @@
* @hide
*/
public static final int OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO =
- AppProtoEnums.APP_OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO;
+ AppOpEnums.APP_OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO;
/**
* App can schedule user-initiated jobs.
*
* @hide
*/
- public static final int OP_RUN_USER_INITIATED_JOBS =
- AppProtoEnums.APP_OP_RUN_USER_INITIATED_JOBS;
+ public static final int OP_RUN_USER_INITIATED_JOBS = AppOpEnums.APP_OP_RUN_USER_INITIATED_JOBS;
/**
* Notify apps that they have been granted URI permission photos
@@ -1403,7 +1395,7 @@
* @hide
*/
public static final int OP_READ_MEDIA_VISUAL_USER_SELECTED =
- AppProtoEnums.APP_OP_READ_MEDIA_VISUAL_USER_SELECTED;
+ AppOpEnums.APP_OP_READ_MEDIA_VISUAL_USER_SELECTED;
/**
* Prevent an app from being suspended.
@@ -1413,7 +1405,7 @@
* @hide
*/
public static final int OP_SYSTEM_EXEMPT_FROM_SUSPENSION =
- AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_SUSPENSION;
+ AppOpEnums.APP_OP_SYSTEM_EXEMPT_FROM_SUSPENSION;
/**
* Prevent an app from dismissible notifications. Starting from Android U, notifications with
@@ -1425,14 +1417,14 @@
* @hide
*/
public static final int OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS =
- AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS;
+ AppOpEnums.APP_OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS;
/**
* An app op for reading/writing health connect data.
*
* @hide
*/
- public static final int OP_READ_WRITE_HEALTH_DATA = AppProtoEnums.APP_OP_READ_WRITE_HEALTH_DATA;
+ public static final int OP_READ_WRITE_HEALTH_DATA = AppOpEnums.APP_OP_READ_WRITE_HEALTH_DATA;
/**
* Use foreground service with the type
@@ -1441,7 +1433,7 @@
* @hide
*/
public static final int OP_FOREGROUND_SERVICE_SPECIAL_USE =
- AppProtoEnums.APP_OP_FOREGROUND_SERVICE_SPECIAL_USE;
+ AppOpEnums.APP_OP_FOREGROUND_SERVICE_SPECIAL_USE;
/**
* Exempt an app from all power-related restrictions, including app standby and doze.
@@ -1453,7 +1445,7 @@
* @hide
*/
public static final int OP_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS =
- AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS;
+ AppOpEnums.APP_OP_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS;
/**
* Prevent an app from being placed into hibernation.
@@ -1463,7 +1455,7 @@
* @hide
*/
public static final int OP_SYSTEM_EXEMPT_FROM_HIBERNATION =
- AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_HIBERNATION;
+ AppOpEnums.APP_OP_SYSTEM_EXEMPT_FROM_HIBERNATION;
/**
* Allows an application to start an activity while running in the background.
@@ -1473,7 +1465,7 @@
* @hide
*/
public static final int OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION =
- AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION;
+ AppOpEnums.APP_OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION;
/**
* Allows an application to capture bugreport directly without consent dialog when using the
@@ -1482,33 +1474,31 @@
* @hide
*/
public static final int OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD =
- AppProtoEnums.APP_OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD;
+ AppOpEnums.APP_OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD;
// App op deprecated/removed.
- private static final int OP_DEPRECATED_2 = AppProtoEnums.APP_OP_BODY_SENSORS_WRIST_TEMPERATURE;
+ private static final int OP_DEPRECATED_2 = AppOpEnums.APP_OP_BODY_SENSORS_WRIST_TEMPERATURE;
/**
* Send an intent to launch instead of posting the notification to the status bar.
*
* @hide
*/
- public static final int OP_USE_FULL_SCREEN_INTENT = AppProtoEnums.APP_OP_USE_FULL_SCREEN_INTENT;
+ public static final int OP_USE_FULL_SCREEN_INTENT = AppOpEnums.APP_OP_USE_FULL_SCREEN_INTENT;
/**
* Hides camera indicator for sandboxed detection apps that directly access the service.
*
* @hide
*/
- public static final int OP_CAMERA_SANDBOXED =
- AppProtoEnums.APP_OP_CAMERA_SANDBOXED;
+ public static final int OP_CAMERA_SANDBOXED = AppOpEnums.APP_OP_CAMERA_SANDBOXED;
/**
* Hides microphone indicator for sandboxed detection apps that directly access the service.
*
* @hide
*/
- public static final int OP_RECORD_AUDIO_SANDBOXED =
- AppProtoEnums.APP_OP_RECORD_AUDIO_SANDBOXED;
+ public static final int OP_RECORD_AUDIO_SANDBOXED = AppOpEnums.APP_OP_RECORD_AUDIO_SANDBOXED;
/**
* Allows the assistant app to be voice-triggered by detected hotwords from a trusted detection
@@ -1517,14 +1507,14 @@
* @hide
*/
public static final int OP_RECEIVE_SANDBOX_TRIGGER_AUDIO =
- AppProtoEnums.APP_OP_RECEIVE_SANDBOX_TRIGGER_AUDIO;
+ AppOpEnums.APP_OP_RECEIVE_SANDBOX_TRIGGER_AUDIO;
/**
* This op has been deprecated.
*
*/
private static final int OP_DEPRECATED_3 =
- AppProtoEnums.APP_OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA;
+ AppOpEnums.APP_OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA;
/**
* Creation of an overlay using accessibility services
@@ -1532,20 +1522,20 @@
* @hide
*/
public static final int OP_CREATE_ACCESSIBILITY_OVERLAY =
- AppProtoEnums.APP_OP_CREATE_ACCESSIBILITY_OVERLAY;
+ AppOpEnums.APP_OP_CREATE_ACCESSIBILITY_OVERLAY;
/**
* Indicate that the user has enabled or disabled mobile data
* @hide
*/
public static final int OP_ENABLE_MOBILE_DATA_BY_USER =
- AppProtoEnums.APP_OP_ENABLE_MOBILE_DATA_BY_USER;
+ AppOpEnums.APP_OP_ENABLE_MOBILE_DATA_BY_USER;
/**
* See {@link #OPSTR_MEDIA_ROUTING_CONTROL}.
* @hide
*/
- public static final int OP_MEDIA_ROUTING_CONTROL = AppProtoEnums.APP_OP_MEDIA_ROUTING_CONTROL;
+ public static final int OP_MEDIA_ROUTING_CONTROL = AppOpEnums.APP_OP_MEDIA_ROUTING_CONTROL;
/**
* Op code for use by tests to avoid interfering history logs that the wider system might
@@ -1553,7 +1543,7 @@
*
* @hide
*/
- public static final int OP_RESERVED_FOR_TESTING = AppProtoEnums.APP_OP_RESERVED_FOR_TESTING;
+ public static final int OP_RESERVED_FOR_TESTING = AppOpEnums.APP_OP_RESERVED_FOR_TESTING;
/**
* Rapid clearing of notifications by a notification listener
@@ -1562,28 +1552,28 @@
*/
// See b/289080543 for more details
public static final int OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER =
- AppProtoEnums.APP_OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER;
+ AppOpEnums.APP_OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER;
/**
* See {@link #OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER}.
* @hide
*/
public static final int OP_READ_SYSTEM_GRAMMATICAL_GENDER =
- AppProtoEnums.APP_OP_READ_SYSTEM_GRAMMATICAL_GENDER;
+ AppOpEnums.APP_OP_READ_SYSTEM_GRAMMATICAL_GENDER;
/**
* This app has been removed..
*
* @hide
*/
- private static final int OP_DEPRECATED_4 = AppProtoEnums.APP_OP_RUN_BACKUP_JOBS;
+ private static final int OP_DEPRECATED_4 = AppOpEnums.APP_OP_RUN_BACKUP_JOBS;
/**
* Whether the app has enabled to receive the icon overlay for fetching archived apps.
*
* @hide
*/
- public static final int OP_ARCHIVE_ICON_OVERLAY = AppProtoEnums.APP_OP_ARCHIVE_ICON_OVERLAY;
+ public static final int OP_ARCHIVE_ICON_OVERLAY = AppOpEnums.APP_OP_ARCHIVE_ICON_OVERLAY;
/**
* Whether the app has enabled compatibility support for unarchival.
@@ -1591,7 +1581,7 @@
* @hide
*/
public static final int OP_UNARCHIVAL_CONFIRMATION =
- AppProtoEnums.APP_OP_UNARCHIVAL_CONFIRMATION;
+ AppOpEnums.APP_OP_UNARCHIVAL_CONFIRMATION;
/**
* Allows an app to access location without the traditional location permissions and while the
@@ -1603,7 +1593,7 @@
*
* @hide
*/
- public static final int OP_EMERGENCY_LOCATION = AppProtoEnums.APP_OP_EMERGENCY_LOCATION;
+ public static final int OP_EMERGENCY_LOCATION = AppOpEnums.APP_OP_EMERGENCY_LOCATION;
/**
* Allows apps with a NotificationListenerService to receive notifications with sensitive
@@ -1613,13 +1603,13 @@
* @hide
*/
public static final int OP_RECEIVE_SENSITIVE_NOTIFICATIONS =
- AppProtoEnums.APP_OP_RECEIVE_SENSITIVE_NOTIFICATIONS;
+ AppOpEnums.APP_OP_RECEIVE_SENSITIVE_NOTIFICATIONS;
/** @hide Access to read heart rate sensor. */
- public static final int OP_READ_HEART_RATE = AppProtoEnums.APP_OP_READ_HEART_RATE;
+ public static final int OP_READ_HEART_RATE = AppOpEnums.APP_OP_READ_HEART_RATE;
/** @hide Access to read skin temperature. */
- public static final int OP_READ_SKIN_TEMPERATURE = AppProtoEnums.APP_OP_READ_SKIN_TEMPERATURE;
+ public static final int OP_READ_SKIN_TEMPERATURE = AppOpEnums.APP_OP_READ_SKIN_TEMPERATURE;
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index a39cf84..038dcdb 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -17,6 +17,7 @@
package android.app;
import static android.text.TextUtils.formatSimple;
+import static com.android.internal.util.Preconditions.checkArgumentPositive;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -40,6 +41,7 @@
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
+import dalvik.annotation.optimization.NeverCompile;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
@@ -201,6 +203,23 @@
}
/**
+ * The list of known and legal modules. The list is not sorted.
+ */
+ private static final String[] sValidModule = {
+ MODULE_SYSTEM, MODULE_BLUETOOTH, MODULE_TELEPHONY, MODULE_TEST,
+ };
+
+ /**
+ * Verify that the module string is in the legal list. Throw if it is not.
+ */
+ private static void throwIfInvalidModule(@NonNull String name) {
+ for (int i = 0; i < sValidModule.length; i++) {
+ if (sValidModule[i].equals(name)) return;
+ }
+ throw new IllegalArgumentException("invalid module: " + name);
+ }
+
+ /**
* All legal keys start with one of the following strings.
*/
private static final String[] sValidKeyPrefix = {
@@ -254,8 +273,11 @@
// written to global store.
private static final int NONCE_BYPASS = 3;
+ // The largest reserved nonce value. Update this whenever a reserved nonce is added.
+ private static final int MAX_RESERVED_NONCE = NONCE_BYPASS;
+
private static boolean isReservedNonce(long n) {
- return n >= NONCE_UNSET && n <= NONCE_BYPASS;
+ return n >= NONCE_UNSET && n <= MAX_RESERVED_NONCE;
}
/**
@@ -293,7 +315,7 @@
private long mMisses = 0;
@GuardedBy("mLock")
- private long[] mSkips = new long[]{ 0, 0, 0, 0 };
+ private long[] mSkips = new long[MAX_RESERVED_NONCE + 1];
@GuardedBy("mLock")
private long mMissOverflow = 0;
@@ -854,6 +876,73 @@
}
/**
+ * A public argument builder to configure cache behavior. The root instance requires a
+ * module; this is immutable. New instances are created with member methods. It is important
+ * to note that the member methods create new instances: they do not modify 'this'. The api
+ * is allowed to be null in the record constructor to facility reuse of Args instances.
+ * @hide
+ */
+ public static record Args(@NonNull String mModule, @Nullable String mApi, int mMaxEntries) {
+
+ // Validation: the module must be one of the known module strings and the maxEntries must
+ // be positive.
+ public Args {
+ throwIfInvalidModule(mModule);
+ checkArgumentPositive(mMaxEntries, "max cache size must be positive");
+ }
+
+ // The base constructor must include the module. Modules do not change in a source file,
+ // so even if the Args is reused, the module will not/should not change. The api is null,
+ // which is not legal, but there is no reasonable default. Clients must call the api
+ // method to set the field properly.
+ public Args(@NonNull String module) {
+ this(module, /* api */ null, /* maxEntries */ 32);
+ }
+
+ public Args api(@NonNull String api) {
+ return new Args(mModule, api, mMaxEntries);
+ }
+
+ public Args maxEntries(int val) {
+ return new Args(mModule, mApi, val);
+ }
+ }
+
+ /**
+ * Make a new property invalidated cache. The key is computed from the module and api
+ * parameters.
+ *
+ * @param args The cache configuration.
+ * @param cacheName Name of this cache in debug and dumpsys
+ * @param computer The code to compute values that are not in the cache.
+ * @hide
+ */
+ public PropertyInvalidatedCache(@NonNull Args args, @NonNull String cacheName,
+ @Nullable QueryHandler<Query, Result> computer) {
+ mPropertyName = createPropertyName(args.mModule, args.mApi);
+ mCacheName = cacheName;
+ mNonce = getNonceHandler(mPropertyName);
+ mMaxEntries = args.mMaxEntries;
+ mCache = createMap();
+ mComputer = (computer != null) ? computer : new DefaultComputer<>(this);
+ registerCache();
+ }
+
+ /**
+ * Burst a property name into module and api. Throw if the key is invalid. This method is
+ * used in to transition legacy cache constructors to the args constructor.
+ */
+ private static Args parseProperty(@NonNull String name) {
+ throwIfInvalidCacheKey(name);
+ // Strip off the leading well-known prefix.
+ String base = name.substring(CACHE_KEY_PREFIX.length() + 1);
+ int dot = base.indexOf(".");
+ String module = base.substring(0, dot);
+ String api = base.substring(dot + 1);
+ return new Args(module).api(api);
+ }
+
+ /**
* Make a new property invalidated cache. This constructor names the cache after the
* property name. New clients should prefer the constructor that takes an explicit
* cache name.
@@ -867,7 +956,7 @@
* @hide
*/
public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName) {
- this(maxEntries, propertyName, propertyName);
+ this(parseProperty(propertyName).maxEntries(maxEntries), propertyName, null);
}
/**
@@ -883,13 +972,7 @@
*/
public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName,
@NonNull String cacheName) {
- mPropertyName = propertyName;
- mCacheName = cacheName;
- mNonce = getNonceHandler(mPropertyName);
- mMaxEntries = maxEntries;
- mComputer = new DefaultComputer<>(this);
- mCache = createMap();
- registerCache();
+ this(parseProperty(propertyName).maxEntries(maxEntries), cacheName, null);
}
/**
@@ -907,13 +990,7 @@
@TestApi
public PropertyInvalidatedCache(int maxEntries, @NonNull String module, @NonNull String api,
@NonNull String cacheName, @NonNull QueryHandler<Query, Result> computer) {
- mPropertyName = createPropertyName(module, api);
- mCacheName = cacheName;
- mNonce = getNonceHandler(mPropertyName);
- mMaxEntries = maxEntries;
- mComputer = computer;
- mCache = createMap();
- registerCache();
+ this(new Args(module).maxEntries(maxEntries).api(api), cacheName, computer);
}
// Create a map. This should be called only from the constructor.
@@ -1181,7 +1258,8 @@
public @Nullable Result query(@NonNull Query query) {
// Let access to mDisabled race: it's atomic anyway.
long currentNonce = (!isDisabled()) ? getCurrentNonce() : NONCE_DISABLED;
- if (bypass(query)) {
+ if (!isReservedNonce(currentNonce)
+ && bypass(query)) {
currentNonce = NONCE_BYPASS;
}
for (;;) {
@@ -1649,54 +1727,62 @@
return false;
}
- /**
- * helper method to check if dump should be skipped due to zero values
- * @param args takes command arguments to check if -brief is present
- * @return True if dump should be skipped
- */
- private boolean skipDump(String[] args) {
- for (String a : args) {
- if (a.equals(BRIEF)) {
- return (mSkips[NONCE_CORKED] + mSkips[NONCE_UNSET] + mSkips[NONCE_DISABLED]
- + mSkips[NONCE_BYPASS] + mHits + mMisses) == 0;
- }
+ @GuardedBy("mLock")
+ private long getSkipsLocked() {
+ int sum = 0;
+ for (int i = 0; i < mSkips.length; i++) {
+ sum += mSkips[i];
}
- return false;
+ return sum;
}
+ // Return true if this cache has had any activity. If the hits, misses, and skips are all
+ // zero then the client never tried to use the cache.
+ private boolean isActive() {
+ synchronized (mLock) {
+ return mHits + mMisses + getSkipsLocked() > 0;
+ }
+ }
+
+ @NeverCompile
private void dumpContents(PrintWriter pw, boolean detailed, String[] args) {
// If the user has requested specific caches and this is not one of them, return
// immediately.
if (detailed && !showDetailed(args)) {
return;
}
+ // Does the user want brief output?
+ boolean brief = false;
+ for (String a : args) brief |= a.equals(BRIEF);
NonceHandler.Stats stats = mNonce.getStats();
synchronized (mLock) {
- if (!skipDump(args)) {
- pw.println(formatSimple(" Cache Name: %s", cacheName()));
- pw.println(formatSimple(" Property: %s", mPropertyName));
- final long skips =
- mSkips[NONCE_CORKED] + mSkips[NONCE_UNSET] + mSkips[NONCE_DISABLED]
- + mSkips[NONCE_BYPASS];
- pw.println(formatSimple(
- " Hits: %d, Misses: %d, Skips: %d, Clears: %d",
- mHits, mMisses, skips, mClears));
- pw.println(formatSimple(
- " Skip-corked: %d, Skip-unset: %d, Skip-bypass: %d, Skip-other: %d",
- mSkips[NONCE_CORKED], mSkips[NONCE_UNSET],
- mSkips[NONCE_BYPASS], mSkips[NONCE_DISABLED]));
- pw.println(formatSimple(
- " Nonce: 0x%016x, Invalidates: %d, CorkedInvalidates: %d",
- mLastSeenNonce, stats.invalidated, stats.corkedInvalidates));
- pw.println(formatSimple(
- " Current Size: %d, Max Size: %d, HW Mark: %d, Overflows: %d",
- mCache.size(), mMaxEntries, mHighWaterMark, mMissOverflow));
- pw.println(formatSimple(" Enabled: %s", mDisabled ? "false" : "true"));
- pw.println("");
+ if (brief && !isActive()) {
+ return;
}
+ pw.println(formatSimple(" Cache Name: %s", cacheName()));
+ pw.println(formatSimple(" Property: %s", mPropertyName));
+ pw.println(formatSimple(
+ " Hits: %d, Misses: %d, Skips: %d, Clears: %d",
+ mHits, mMisses, getSkipsLocked(), mClears));
+
+ // Print all the skip reasons.
+ pw.format(" Skip-%s: %d", sNonceName[0], mSkips[0]);
+ for (int i = 1; i < mSkips.length; i++) {
+ pw.format(", Skip-%s: %d", sNonceName[i], mSkips[i]);
+ }
+ pw.println();
+
+ pw.println(formatSimple(
+ " Nonce: 0x%016x, Invalidates: %d, CorkedInvalidates: %d",
+ mLastSeenNonce, stats.invalidated, stats.corkedInvalidates));
+ pw.println(formatSimple(
+ " Current Size: %d, Max Size: %d, HW Mark: %d, Overflows: %d",
+ mCache.size(), mMaxEntries, mHighWaterMark, mMissOverflow));
+ pw.println(formatSimple(" Enabled: %s", mDisabled ? "false" : "true"));
+
// No specific cache was requested. This is the default, and no details
// should be dumped.
if (!detailed) {
@@ -1723,6 +1809,7 @@
* specific caches (selection is by cache name or property name); if these switches
* are used then the output includes both cache statistics and cache entries.
*/
+ @NeverCompile
private static void dumpCacheInfo(@NonNull PrintWriter pw, @NonNull String[] args) {
if (!sEnabled) {
pw.println(" Caching is disabled in this process.");
@@ -1755,6 +1842,7 @@
* are used then the output includes both cache statistics and cache entries.
* @hide
*/
+ @NeverCompile
public static void dumpCacheInfo(@NonNull ParcelFileDescriptor pfd, @NonNull String[] args) {
// Create a PrintWriter that uses a byte array. The code can safely write to
// this array without fear of blocking. The completed byte array will be sent
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 63e03914..b7285c3 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -602,6 +602,15 @@
@LoggingOnly
private static final long MEDIA_CONTROL_BLANK_TITLE = 274775190L;
+ /**
+ * Media controls based on {@link android.app.Notification.MediaStyle} notifications will have
+ * actions from the associated {@link androidx.media3.MediaController}, if available.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT)
+ // TODO(b/360196209): Set target SDK to Baklava once available
+ private static final long MEDIA_CONTROL_MEDIA3_ACTIONS = 360196209L;
+
@UnsupportedAppUsage
private Context mContext;
private IStatusBarService mService;
@@ -1270,6 +1279,21 @@
}
/**
+ * Checks whether the media controls for a given package should use a Media3 controller
+ *
+ * @param packageName App posting media controls
+ * @param user Current user handle
+ * @return true if Media3 should be used
+ *
+ * @hide
+ */
+ @RequiresPermission(allOf = {android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
+ android.Manifest.permission.LOG_COMPAT_CHANGE})
+ public static boolean useMedia3ControllerForApp(String packageName, UserHandle user) {
+ return CompatChanges.isChangeEnabled(MEDIA_CONTROL_MEDIA3_ACTIONS, packageName, user);
+ }
+
+ /**
* Checks whether the supplied activity can {@link Activity#startActivityForResult(Intent, int)}
* a system activity that captures content on the screen to take a screenshot.
*
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 0a4d8f2..c4a6dec 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -296,6 +296,12 @@
public boolean isVisibleRequested;
/**
+ * Whether the top activity is to be displayed. See {@link android.R.attr#windowNoDisplay}.
+ * @hide
+ */
+ public boolean isTopActivityNoDisplay;
+
+ /**
* Whether this task is sleeping due to sleeping display.
* @hide
*/
@@ -308,12 +314,6 @@
public boolean isTopActivityTransparent;
/**
- * Whether the top activity has specified style floating.
- * @hide
- */
- public boolean isTopActivityStyleFloating;
-
- /**
* The last non-fullscreen bounds the task was launched in or resized to.
* @hide
*/
@@ -482,13 +482,13 @@
&& isFocused == that.isFocused
&& isVisible == that.isVisible
&& isVisibleRequested == that.isVisibleRequested
+ && isTopActivityNoDisplay == that.isTopActivityNoDisplay
&& isSleeping == that.isSleeping
&& Objects.equals(mTopActivityLocusId, that.mTopActivityLocusId)
&& parentTaskId == that.parentTaskId
&& Objects.equals(topActivity, that.topActivity)
&& isTopActivityTransparent == that.isTopActivityTransparent
- && isTopActivityStyleFloating == that.isTopActivityStyleFloating
- && lastNonFullscreenBounds == this.lastNonFullscreenBounds
+ && Objects.equals(lastNonFullscreenBounds, that.lastNonFullscreenBounds)
&& Objects.equals(capturedLink, that.capturedLink)
&& capturedLinkTimestamp == that.capturedLinkTimestamp
&& requestedVisibleTypes == that.requestedVisibleTypes
@@ -561,11 +561,11 @@
isFocused = source.readBoolean();
isVisible = source.readBoolean();
isVisibleRequested = source.readBoolean();
+ isTopActivityNoDisplay = source.readBoolean();
isSleeping = source.readBoolean();
mTopActivityLocusId = source.readTypedObject(LocusId.CREATOR);
displayAreaFeatureId = source.readInt();
isTopActivityTransparent = source.readBoolean();
- isTopActivityStyleFloating = source.readBoolean();
lastNonFullscreenBounds = source.readTypedObject(Rect.CREATOR);
capturedLink = source.readTypedObject(Uri.CREATOR);
capturedLinkTimestamp = source.readLong();
@@ -616,11 +616,11 @@
dest.writeBoolean(isFocused);
dest.writeBoolean(isVisible);
dest.writeBoolean(isVisibleRequested);
+ dest.writeBoolean(isTopActivityNoDisplay);
dest.writeBoolean(isSleeping);
dest.writeTypedObject(mTopActivityLocusId, flags);
dest.writeInt(displayAreaFeatureId);
dest.writeBoolean(isTopActivityTransparent);
- dest.writeBoolean(isTopActivityStyleFloating);
dest.writeTypedObject(lastNonFullscreenBounds, flags);
dest.writeTypedObject(capturedLink, flags);
dest.writeLong(capturedLinkTimestamp);
@@ -661,11 +661,11 @@
+ " isFocused=" + isFocused
+ " isVisible=" + isVisible
+ " isVisibleRequested=" + isVisibleRequested
+ + " isTopActivityNoDisplay=" + isTopActivityNoDisplay
+ " isSleeping=" + isSleeping
+ " locusId=" + mTopActivityLocusId
+ " displayAreaFeatureId=" + displayAreaFeatureId
+ " isTopActivityTransparent=" + isTopActivityTransparent
- + " isTopActivityStyleFloating=" + isTopActivityStyleFloating
+ " lastNonFullscreenBounds=" + lastNonFullscreenBounds
+ " capturedLink=" + capturedLink
+ " capturedLinkTimestamp=" + capturedLinkTimestamp
diff --git a/core/java/android/app/jank/JankTracker.java b/core/java/android/app/jank/JankTracker.java
new file mode 100644
index 0000000..df422e0
--- /dev/null
+++ b/core/java/android/app/jank/JankTracker.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.jank;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.view.AttachedSurfaceControl;
+import android.view.Choreographer;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.ViewTreeObserver;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+
+/**
+ * This class is responsible for registering callbacks that will receive JankData batches.
+ * It handles managing the background thread that JankData will be processed on. As well as acting
+ * as an intermediary between widgets and the state tracker, routing state changes to the tracker.
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+public class JankTracker {
+
+ // Tracks states reported by widgets.
+ private StateTracker mStateTracker;
+ // Processes JankData batches and associates frames to widget states.
+ private JankDataProcessor mJankDataProcessor;
+
+ // Background thread responsible for processing JankData batches.
+ private HandlerThread mHandlerThread = new HandlerThread("AppJankTracker");
+ private Handler mHandler = null;
+
+ // Needed so we know when the view is attached to a window.
+ private ViewTreeObserver mViewTreeObserver;
+
+ // Handle to a registered OnJankData listener.
+ private SurfaceControl.OnJankDataListenerRegistration mJankDataListenerRegistration;
+
+ // The interface to the windowing system that enables us to register for JankData.
+ private AttachedSurfaceControl mSurfaceControl;
+ // Name of the activity that is currently tracking Jank metrics.
+ private String mActivityName;
+ // The apps uid.
+ private int mAppUid;
+ // View that gives us access to ViewTreeObserver.
+ private View mDecorView;
+
+ /**
+ * Set by the activity to enable or disable jank tracking. Activities may disable tracking if
+ * they are paused or not enable tracking if they are not visible or if the app category is not
+ * set.
+ */
+ private boolean mTrackingEnabled = false;
+ /**
+ * Set to true once listeners are registered and JankData will start to be received. Both
+ * mTrackingEnabled and mListenersRegistered need to be true for JankData to be processed.
+ */
+ private boolean mListenersRegistered = false;
+
+
+ public JankTracker(Choreographer choreographer, View decorView) {
+ mStateTracker = new StateTracker(choreographer);
+ mJankDataProcessor = new JankDataProcessor(mStateTracker);
+ mDecorView = decorView;
+ mHandlerThread.start();
+ registerWindowListeners();
+ }
+
+ public void setActivityName(@NonNull String activityName) {
+ mActivityName = activityName;
+ }
+
+ public void setAppUid(int uid) {
+ mAppUid = uid;
+ }
+
+ /**
+ * Will add the widget category, id and state as a UI state to associate frames to it.
+ * @param widgetCategory preselected general widget category
+ * @param widgetId developer defined widget id if available.
+ * @param widgetState the current active widget state.
+ */
+ public void addUiState(String widgetCategory, String widgetId, String widgetState) {
+ if (!shouldTrack()) return;
+
+ mStateTracker.putState(widgetCategory, widgetId, widgetState);
+ }
+
+ /**
+ * Will remove the widget category, id and state as a ui state and no longer attribute frames
+ * to it.
+ * @param widgetCategory preselected general widget category
+ * @param widgetId developer defined widget id if available.
+ * @param widgetState no longer active widget state.
+ */
+ public void removeUiState(String widgetCategory, String widgetId, String widgetState) {
+ if (!shouldTrack()) return;
+
+ mStateTracker.removeState(widgetCategory, widgetId, widgetState);
+ }
+
+ /**
+ * Call to update a jank state to a different state.
+ * @param widgetCategory preselected general widget category.
+ * @param widgetId developer defined widget id if available.
+ * @param currentState current state of the widget.
+ * @param nextState the state the widget will be in.
+ */
+ public void updateUiState(String widgetCategory, String widgetId, String currentState,
+ String nextState) {
+ if (!shouldTrack()) return;
+
+ mStateTracker.updateState(widgetCategory, widgetId, currentState, nextState);
+ }
+
+ /**
+ * Will enable jank tracking, and add the activity as a state to associate frames to.
+ */
+ public void enableAppJankTracking() {
+ // Add the activity as a state, this will ensure we track frames to the activity without the
+ // need of a decorated widget to be used.
+ // TODO b/376116199 replace "NONE" with UNSPECIFIED once the API changes are merged.
+ mStateTracker.putState("NONE", mActivityName, "NONE");
+ mTrackingEnabled = true;
+ }
+
+ /**
+ * Will disable jank tracking, and remove the activity as a state to associate frames to.
+ */
+ public void disableAppJankTracking() {
+ mTrackingEnabled = false;
+ // TODO b/376116199 replace "NONE" with UNSPECIFIED once the API changes are merged.
+ mStateTracker.removeState("NONE", mActivityName, "NONE");
+ }
+
+ /**
+ * Retrieve all pending widget states, this is intended for testing purposes only.
+ * @param stateDataList the ArrayList that will be populated with the pending states.
+ */
+ @VisibleForTesting
+ public void getAllUiStates(@NonNull ArrayList<StateTracker.StateData> stateDataList) {
+ mStateTracker.retrieveAllStates(stateDataList);
+ }
+
+ /**
+ * Only intended to be used by tests, the runnable that registers the listeners may not run
+ * in time for tests to pass. This forces them to run immediately.
+ */
+ @VisibleForTesting
+ public void forceListenerRegistration() {
+ mSurfaceControl = mDecorView.getRootSurfaceControl();
+ registerForJankData();
+ // TODO b/376116199 Check if registration is good.
+ mListenersRegistered = true;
+ }
+
+ private void registerForJankData() {
+ if (mSurfaceControl == null) return;
+ /*
+ TODO b/376115668 Register for JankData batches from new JankTracking API
+ */
+ }
+
+ private boolean shouldTrack() {
+ return mTrackingEnabled && mListenersRegistered;
+ }
+
+ /**
+ * Need to know when the decor view gets attached to the window in order to get
+ * AttachedSurfaceControl. In order to register a callback for OnJankDataListener
+ * AttachedSurfaceControl needs to be created which only happens after onWindowAttached is
+ * called. This is why there is a delay in posting the runnable.
+ */
+ private void registerWindowListeners() {
+ if (mDecorView == null) return;
+ mViewTreeObserver = mDecorView.getViewTreeObserver();
+ mViewTreeObserver.addOnWindowAttachListener(new ViewTreeObserver.OnWindowAttachListener() {
+ @Override
+ public void onWindowAttached() {
+ getHandler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ forceListenerRegistration();
+ }
+ }, 1000);
+ }
+
+ @Override
+ public void onWindowDetached() {
+ // TODO b/376116199 do we un-register the callback or just not process the data.
+ }
+ });
+ }
+
+ private Handler getHandler() {
+ if (mHandler == null) {
+ mHandler = new Handler(mHandlerThread.getLooper());
+ }
+ return mHandler;
+ }
+}
diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig
index 940a4d2..ce51576 100644
--- a/core/java/android/appwidget/flags.aconfig
+++ b/core/java/android/appwidget/flags.aconfig
@@ -55,7 +55,7 @@
name: "remote_views_proto"
namespace: "app_widgets"
description: "Enable support for persisting RemoteViews previews to Protobuf"
- bug: "306546610"
+ bug: "364420494"
}
flag {
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 65f9cbe..2be27da 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -160,7 +160,7 @@
*/
@IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_SENSORS, POLICY_TYPE_AUDIO,
POLICY_TYPE_RECENTS, POLICY_TYPE_ACTIVITY, POLICY_TYPE_CLIPBOARD, POLICY_TYPE_CAMERA,
- POLICY_TYPE_BLOCKED_ACTIVITY})
+ POLICY_TYPE_BLOCKED_ACTIVITY, POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS})
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
public @interface PolicyType {}
@@ -301,6 +301,21 @@
@FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API)
public static final int POLICY_TYPE_BLOCKED_ACTIVITY = 6;
+ /**
+ * Tells the virtual device framework how to handle camera access of the default device by apps
+ * running on the virtual device.
+ *
+ * <ul>
+ * <li>{@link #DEVICE_POLICY_DEFAULT}: Default device camera access will be allowed.
+ * <li>{@link #DEVICE_POLICY_CUSTOM}: Default device camera access will be blocked.
+ * </ul>
+ *
+ * @see Context#DEVICE_ID_DEFAULT
+ */
+ @FlaggedApi(android.companion.virtualdevice.flags.Flags
+ .FLAG_DEFAULT_DEVICE_CAMERA_ACCESS_POLICY)
+ public static final int POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS = 7;
+
private final int mLockState;
@NonNull private final ArraySet<UserHandle> mUsersWithMatchingAccounts;
@NavigationPolicy
@@ -318,6 +333,8 @@
@Nullable private final IVirtualSensorCallback mVirtualSensorCallback;
private final int mAudioPlaybackSessionId;
private final int mAudioRecordingSessionId;
+ private final long mDimDuration;
+ private final long mScreenOffTimeout;
private VirtualDeviceParams(
@LockState int lockState,
@@ -333,7 +350,9 @@
@NonNull List<VirtualSensorConfig> virtualSensorConfigs,
@Nullable IVirtualSensorCallback virtualSensorCallback,
int audioPlaybackSessionId,
- int audioRecordingSessionId) {
+ int audioRecordingSessionId,
+ long dimDuration,
+ long screenOffTimeout) {
mLockState = lockState;
mUsersWithMatchingAccounts =
new ArraySet<>(Objects.requireNonNull(usersWithMatchingAccounts));
@@ -351,6 +370,8 @@
mVirtualSensorCallback = virtualSensorCallback;
mAudioPlaybackSessionId = audioPlaybackSessionId;
mAudioRecordingSessionId = audioRecordingSessionId;
+ mDimDuration = dimDuration;
+ mScreenOffTimeout = screenOffTimeout;
}
@SuppressWarnings("unchecked")
@@ -371,6 +392,8 @@
mAudioRecordingSessionId = parcel.readInt();
mHomeComponent = parcel.readTypedObject(ComponentName.CREATOR);
mInputMethodComponent = parcel.readTypedObject(ComponentName.CREATOR);
+ mDimDuration = parcel.readLong();
+ mScreenOffTimeout = parcel.readLong();
}
/**
@@ -382,6 +405,26 @@
}
/**
+ * Returns the dim duration for the displays of this device.
+ *
+ * @see Builder#setDimDuration(Duration)
+ */
+ @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ public @NonNull Duration getDimDuration() {
+ return Duration.ofMillis(mDimDuration);
+ }
+
+ /**
+ * Returns the screen off timeout of the displays of this device.
+ *
+ * @see Builder#setDimDuration(Duration)
+ */
+ @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ public @NonNull Duration getScreenOffTimeout() {
+ return Duration.ofMillis(mScreenOffTimeout);
+ }
+
+ /**
* Returns the custom component used as home on all displays owned by this virtual device that
* support home activities.
*
@@ -604,6 +647,8 @@
dest.writeInt(mAudioRecordingSessionId);
dest.writeTypedObject(mHomeComponent, flags);
dest.writeTypedObject(mInputMethodComponent, flags);
+ dest.writeLong(mDimDuration);
+ dest.writeLong(mScreenOffTimeout);
}
@Override
@@ -638,7 +683,9 @@
&& Objects.equals(mHomeComponent, that.mHomeComponent)
&& Objects.equals(mInputMethodComponent, that.mInputMethodComponent)
&& mAudioPlaybackSessionId == that.mAudioPlaybackSessionId
- && mAudioRecordingSessionId == that.mAudioRecordingSessionId;
+ && mAudioRecordingSessionId == that.mAudioRecordingSessionId
+ && mDimDuration == that.mDimDuration
+ && mScreenOffTimeout == that.mScreenOffTimeout;
}
@Override
@@ -647,7 +694,7 @@
mLockState, mUsersWithMatchingAccounts, mCrossTaskNavigationExemptions,
mDefaultNavigationPolicy, mActivityPolicyExemptions, mDefaultActivityPolicy, mName,
mDevicePolicies, mHomeComponent, mInputMethodComponent, mAudioPlaybackSessionId,
- mAudioRecordingSessionId);
+ mAudioRecordingSessionId, mDimDuration, mScreenOffTimeout);
for (int i = 0; i < mDevicePolicies.size(); i++) {
hashCode = 31 * hashCode + mDevicePolicies.keyAt(i);
hashCode = 31 * hashCode + mDevicePolicies.valueAt(i);
@@ -671,6 +718,8 @@
+ " mInputMethodComponent=" + mInputMethodComponent
+ " mAudioPlaybackSessionId=" + mAudioPlaybackSessionId
+ " mAudioRecordingSessionId=" + mAudioRecordingSessionId
+ + " mDimDuration=" + mDimDuration
+ + " mScreenOffTimeout=" + mScreenOffTimeout
+ ")";
}
@@ -692,11 +741,13 @@
pw.println(prefix + "mInputMethodComponent=" + mInputMethodComponent);
pw.println(prefix + "mAudioPlaybackSessionId=" + mAudioPlaybackSessionId);
pw.println(prefix + "mAudioRecordingSessionId=" + mAudioRecordingSessionId);
+ pw.println(prefix + "mDimDuration=" + mDimDuration);
+ pw.println(prefix + "mScreenOffTimeout=" + mScreenOffTimeout);
}
@NonNull
public static final Parcelable.Creator<VirtualDeviceParams> CREATOR =
- new Parcelable.Creator<VirtualDeviceParams>() {
+ new Parcelable.Creator<>() {
public VirtualDeviceParams createFromParcel(Parcel in) {
return new VirtualDeviceParams(in);
}
@@ -711,6 +762,8 @@
*/
public static final class Builder {
+ private static final Duration INFINITE_TIMEOUT = Duration.ofDays(365 * 1000);
+
private @LockState int mLockState = LOCK_STATE_DEFAULT;
@NonNull private Set<UserHandle> mUsersWithMatchingAccounts = Collections.emptySet();
@NonNull private Set<ComponentName> mCrossTaskNavigationExemptions = Collections.emptySet();
@@ -733,6 +786,8 @@
@Nullable private VirtualSensorDirectChannelCallback mVirtualSensorDirectChannelCallback;
@Nullable private ComponentName mHomeComponent;
@Nullable private ComponentName mInputMethodComponent;
+ private Duration mDimDuration = Duration.ZERO;
+ private Duration mScreenOffTimeout = Duration.ZERO;
private static class VirtualSensorCallbackDelegate extends IVirtualSensorCallback.Stub {
@NonNull
@@ -810,6 +865,57 @@
}
/**
+ * Sets the dim duration for all trusted non-mirror displays of the device.
+ *
+ * <p>The system will reduce the display brightness for the specified duration if there
+ * has been no interaction just before the displays turn off.</p>
+ *
+ * <p>If set, the screen off timeout must also be set to a value larger than the dim
+ * duration. If left unset or set to zero, then the display brightness will not be reduced.
+ * </p>
+ *
+ * @throws IllegalArgumentException if the dim duration is negative or if the dim duration
+ * is longer than the screen off timeout.
+ * @see android.hardware.display.DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED
+ * @see android.hardware.display.DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR
+ * @see #setScreenOffTimeout
+ */
+ @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ @NonNull
+ public Builder setDimDuration(@NonNull Duration dimDuration) {
+ if (Objects.requireNonNull(dimDuration).compareTo(Duration.ZERO) < 0) {
+ throw new IllegalArgumentException("The dim duration cannot be negative");
+ }
+ mDimDuration = dimDuration;
+ return this;
+ }
+
+ /**
+ * Sets the timeout, after which all trusted non-mirror displays of the device will turn
+ * off, if there has been no interaction with the device.
+ *
+ * <p>If dim duration is set, the screen off timeout must be set to a value larger than the
+ * dim duration. If left unset or set to zero, then the displays will never be turned off
+ * due to inactivity.</p>
+ *
+ * @throws IllegalArgumentException if the screen off timeout is negative or if the dim
+ * duration is longer than the screen off timeout.
+ * @see android.hardware.display.DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED
+ * @see android.hardware.display.DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR
+ * @see #setDimDuration
+ * @see VirtualDeviceManager.VirtualDevice#goToSleep()
+ */
+ @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ @NonNull
+ public Builder setScreenOffTimeout(@NonNull Duration screenOffTimeout) {
+ if (Objects.requireNonNull(screenOffTimeout).compareTo(Duration.ZERO) < 0) {
+ throw new IllegalArgumentException("The screen off timeout cannot be negative");
+ }
+ mScreenOffTimeout = screenOffTimeout;
+ return this;
+ }
+
+ /**
* Specifies a component to be used as home on all displays owned by this virtual device
* that support home activities.
* *
@@ -1205,6 +1311,14 @@
}
}
+ if (mDimDuration.compareTo(mScreenOffTimeout) > 0) {
+ throw new IllegalArgumentException(
+ "The dim duration cannot be greater than the screen off timeout.");
+ }
+ if (mScreenOffTimeout.compareTo(Duration.ZERO) == 0) {
+ mScreenOffTimeout = INFINITE_TIMEOUT;
+ }
+
if (!Flags.crossDeviceClipboard()) {
mDevicePolicies.delete(POLICY_TYPE_CLIPBOARD);
}
@@ -1213,6 +1327,10 @@
mDevicePolicies.delete(POLICY_TYPE_CAMERA);
}
+ if (!android.companion.virtualdevice.flags.Flags.defaultDeviceCameraAccessPolicy()) {
+ mDevicePolicies.delete(POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS);
+ }
+
if (!android.companion.virtualdevice.flags.Flags.activityControlApi()) {
mDevicePolicies.delete(POLICY_TYPE_BLOCKED_ACTIVITY);
}
@@ -1250,7 +1368,9 @@
mVirtualSensorConfigs,
virtualSensorCallbackDelegate,
mAudioPlaybackSessionId,
- mAudioRecordingSessionId);
+ mAudioRecordingSessionId,
+ mDimDuration.toMillis(),
+ mScreenOffTimeout.toMillis());
}
}
}
diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig
index fc9c94d..3e6919b 100644
--- a/core/java/android/companion/virtual/flags.aconfig
+++ b/core/java/android/companion/virtual/flags.aconfig
@@ -67,13 +67,6 @@
}
flag {
- name: "stream_camera"
- namespace: "virtual_devices"
- description: "Enable streaming camera to Virtual Devices"
- bug: "291740640"
-}
-
-flag {
name: "persistent_device_id_api"
is_exported: true
namespace: "virtual_devices"
diff --git a/core/java/android/content/res/TEST_MAPPING b/core/java/android/content/res/TEST_MAPPING
index c2febae..e8893e4 100644
--- a/core/java/android/content/res/TEST_MAPPING
+++ b/core/java/android/content/res/TEST_MAPPING
@@ -5,6 +5,9 @@
},
{
"path": "frameworks/base/core/tests/coretests/src/com/android/internal/content/res"
+ },
+ {
+ "path": "platform_testing/libraries/screenshot"
}
],
"presubmit": [
diff --git a/core/java/android/database/BulkCursorNative.java b/core/java/android/database/BulkCursorNative.java
index 41585b3..7d6e7ad 100644
--- a/core/java/android/database/BulkCursorNative.java
+++ b/core/java/android/database/BulkCursorNative.java
@@ -215,7 +215,7 @@
// If close() is being called from the finalizer thread, do not wait for a reply from
// the remote side.
final boolean fromFinalizer =
- android.database.sqlite.Flags.onewayFinalizerClose()
+ android.database.sqlite.Flags.onewayFinalizerCloseFixed()
&& "FinalizerDaemon".equals(Thread.currentThread().getName());
mRemote.transact(CLOSE_TRANSACTION, data, reply,
fromFinalizer ? IBinder.FLAG_ONEWAY: 0);
diff --git a/core/java/android/database/sqlite/flags.aconfig b/core/java/android/database/sqlite/flags.aconfig
index 826b908..2d18d26 100644
--- a/core/java/android/database/sqlite/flags.aconfig
+++ b/core/java/android/database/sqlite/flags.aconfig
@@ -9,6 +9,14 @@
}
flag {
+ name: "oneway_finalizer_close_fixed"
+ namespace: "system_performance"
+ is_fixed_read_only: true
+ description: "Make BuildCursorNative.close oneway if in the the finalizer"
+ bug: "368221351"
+}
+
+flag {
name: "sqlite_apis_35"
is_exported: true
namespace: "system_performance"
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index bce9518..39dddb7 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -279,4 +279,6 @@
void removeAllCustomInputGestures();
AidlInputGestureData[] getCustomInputGestures();
+
+ AidlInputGestureData[] getAppLaunchBookmarks();
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 876ba10..2051dbe 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1494,9 +1494,8 @@
try {
return mIm.addCustomInputGesture(inputGestureData.getAidlData());
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ throw e.rethrowFromSystemServer();
}
- return CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER;
}
/** Removes an existing custom gesture
@@ -1517,9 +1516,8 @@
try {
return mIm.removeCustomInputGesture(inputGestureData.getAidlData());
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ throw e.rethrowFromSystemServer();
}
- return CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER;
}
/** Removes all custom input gestures
@@ -1534,7 +1532,7 @@
try {
mIm.removeAllCustomInputGestures();
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ throw e.rethrowFromSystemServer();
}
}
@@ -1552,12 +1550,32 @@
result.add(new InputGestureData(data));
}
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ throw e.rethrowFromSystemServer();
}
return result;
}
/**
+ * Return the set of application launch bookmarks handled by the input framework.
+ *
+ * @return list of {@link InputGestureData} containing the application launch shortcuts parsed
+ * at boot time from {@code bookmarks.xml}.
+ *
+ * @hide
+ */
+ public List<InputGestureData> getAppLaunchBookmarks() {
+ try {
+ List<InputGestureData> result = new ArrayList<>();
+ for (AidlInputGestureData data : mIm.getAppLaunchBookmarks()) {
+ result.add(new InputGestureData(data));
+ }
+ return result;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* A callback used to be notified about battery state changes for an input device. The
* {@link #onBatteryStateChanged(int, long, BatteryState)} method will be called once after the
* listener is successfully registered to provide the initial battery state of the device.
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index 4a9efe0..c456698 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -20,16 +20,18 @@
import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS;
import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG;
import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG;
+import static com.android.hardware.input.Flags.enableCustomizableInputGestures;
import static com.android.hardware.input.Flags.keyboardA11yBounceKeysFlag;
+import static com.android.hardware.input.Flags.keyboardA11yMouseKeys;
import static com.android.hardware.input.Flags.keyboardA11ySlowKeysFlag;
import static com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag;
-import static com.android.hardware.input.Flags.keyboardA11yMouseKeys;
import static com.android.hardware.input.Flags.mouseReverseVerticalScrolling;
import static com.android.hardware.input.Flags.mouseSwapPrimaryButton;
import static com.android.hardware.input.Flags.touchpadTapDragging;
+import static com.android.hardware.input.Flags.touchpadThreeFingerTapShortcut;
import static com.android.hardware.input.Flags.touchpadVisualizer;
-import static com.android.input.flags.Flags.enableInputFilterRustImpl;
import static com.android.input.flags.Flags.FLAG_KEYBOARD_REPEAT_KEYS;
+import static com.android.input.flags.Flags.enableInputFilterRustImpl;
import static com.android.input.flags.Flags.keyboardRepeatKeys;
import android.Manifest;
@@ -379,6 +381,15 @@
}
/**
+ * Returns true if the feature flag for the touchpad three-finger tap shortcut is enabled.
+ *
+ * @hide
+ */
+ public static boolean isTouchpadThreeFingerTapShortcutFeatureFlagEnabled() {
+ return enableCustomizableInputGestures() && touchpadThreeFingerTapShortcut();
+ }
+
+ /**
* Returns true if the feature flag for mouse reverse vertical scrolling is enabled.
* @hide
*/
@@ -498,6 +509,22 @@
}
/**
+ * Returns true if three-finger taps on the touchpad should trigger a customizable shortcut
+ * rather than a middle click.
+ *
+ * The returned value only applies to gesture-compatible touchpads.
+ *
+ * @param context The application context.
+ * @return Whether three-finger taps should trigger the shortcut.
+ *
+ * @hide
+ */
+ public static boolean useTouchpadThreeFingerTapShortcut(@NonNull Context context) {
+ // TODO(b/365063048): determine whether to enable the shortcut based on the settings.
+ return isTouchpadThreeFingerTapShortcutFeatureFlagEnabled();
+ }
+
+ /**
* Whether a pointer icon will be shown over the location of a stylus pointer.
*
* @hide
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 71c91e9..f9cb94a 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -158,3 +158,10 @@
description: "Allows privileged focused windows to capture power key events."
bug: "357144512"
}
+
+flag {
+ name: "touchpad_three_finger_tap_shortcut"
+ namespace: "input"
+ description: "Turns three-finger touchpad taps into a customizable shortcut."
+ bug: "365063048"
+}
diff --git a/core/java/android/hardware/usb/OWNERS b/core/java/android/hardware/usb/OWNERS
index a753f96..37604bc 100644
--- a/core/java/android/hardware/usb/OWNERS
+++ b/core/java/android/hardware/usb/OWNERS
@@ -1,7 +1,7 @@
# Bug component: 175220
-aprasath@google.com
-kumarashishg@google.com
-sarup@google.com
anothermark@google.com
+febinthattil@google.com
+aprasath@google.com
badhri@google.com
+kumarashishg@google.com
\ No newline at end of file
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index 6c3c285..5ae425f 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -1315,7 +1315,7 @@
}
synchronized (BatteryUsageStats.class) {
- if (!sInstances.isEmpty()) {
+ if (sInstances != null && !sInstances.isEmpty()) {
Exception callSite = sInstances.entrySet().iterator().next().getValue();
int count = sInstances.size();
sInstances.clear();
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 13d7e3c..b3aebad 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -40,8 +40,6 @@
import android.util.Slog;
import android.view.View;
-import com.android.internal.ravenwood.RavenwoodEnvironment;
-
import dalvik.system.VMRuntime;
import java.lang.annotation.Retention;
@@ -57,10 +55,6 @@
*/
@RavenwoodKeepWholeClass
public class Build {
- static {
- // Set up the default system properties.
- RavenwoodEnvironment.ensureRavenwoodInitialized();
- }
private static final String TAG = "Build";
/** Value used for when a build property is unknown. */
diff --git a/core/java/android/os/IpcDataCache.java b/core/java/android/os/IpcDataCache.java
index 7875c23..8db1567 100644
--- a/core/java/android/os/IpcDataCache.java
+++ b/core/java/android/os/IpcDataCache.java
@@ -23,6 +23,7 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.PropertyInvalidatedCache;
+import android.app.PropertyInvalidatedCache.Args;
import android.text.TextUtils;
import android.util.ArraySet;
@@ -341,7 +342,7 @@
public IpcDataCache(int maxEntries, @NonNull @IpcDataCacheModule String module,
@NonNull String api, @NonNull String cacheName,
@NonNull QueryHandler<Query, Result> computer) {
- super(maxEntries, module, api, cacheName, computer);
+ super(new Args(module).maxEntries(maxEntries).api(api), cacheName, computer);
}
/**
@@ -563,7 +564,8 @@
* @hide
*/
public IpcDataCache(@NonNull Config config, @NonNull QueryHandler<Query, Result> computer) {
- super(config.maxEntries(), config.module(), config.api(), config.name(), computer);
+ super(new Args(config.module()).maxEntries(config.maxEntries()).api(config.api()),
+ config.name(), computer);
}
/**
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index e80efd2..60eeb2b 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -41,7 +41,6 @@
import android.net.Uri;
import android.os.MessageQueue.OnFileDescriptorEventListener;
import android.ravenwood.annotation.RavenwoodKeepWholeClass;
-import android.ravenwood.annotation.RavenwoodReplace;
import android.ravenwood.annotation.RavenwoodThrow;
import android.system.ErrnoException;
import android.system.Os;
@@ -51,8 +50,6 @@
import android.util.Log;
import android.util.Slog;
-import com.android.internal.ravenwood.RavenwoodEnvironment;
-
import dalvik.system.VMRuntime;
import libcore.io.IoUtils;
@@ -1254,15 +1251,10 @@
}
}
- @RavenwoodReplace
private static boolean isAtLeastQ() {
return (VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.Q);
}
- private static boolean isAtLeastQ$ravenwood() {
- return RavenwoodEnvironment.workaround().isTargetSdkAtLeastQ();
- }
-
private static int ifAtLeastQ(int value) {
return isAtLeastQ() ? value : 0;
}
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 71d29af..e728243 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -29,6 +29,11 @@
import android.annotation.UptimeMillisLong;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build.VERSION_CODES;
+import android.ravenwood.annotation.RavenwoodKeep;
+import android.ravenwood.annotation.RavenwoodKeepPartialClass;
+import android.ravenwood.annotation.RavenwoodRedirect;
+import android.ravenwood.annotation.RavenwoodRedirectionClass;
+import android.ravenwood.annotation.RavenwoodReplace;
import android.sysprop.MemoryProperties;
import android.system.ErrnoException;
import android.system.Os;
@@ -37,8 +42,6 @@
import android.util.Pair;
import android.webkit.WebViewZygote;
-import com.android.internal.os.SomeArgs;
-import com.android.internal.util.Preconditions;
import com.android.sdksandbox.flags.Flags;
import dalvik.system.VMDebug;
@@ -55,6 +58,8 @@
/**
* Tools for managing OS processes.
*/
+@RavenwoodKeepPartialClass
+@RavenwoodRedirectionClass("Process_ravenwood")
public class Process {
private static final String LOG_TAG = "Process";
@@ -671,7 +676,6 @@
*/
public static final ZygoteProcess ZYGOTE_PROCESS = new ZygoteProcess();
-
/**
* The process name set via {@link #setArgV0(String)}.
*/
@@ -845,47 +849,20 @@
/**
* Returns true if the current process is a 64-bit runtime.
*/
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static final boolean is64Bit() {
return VMRuntime.getRuntime().is64Bit();
}
- private static volatile ThreadLocal<SomeArgs> sIdentity$ravenwood;
-
- /** @hide */
- @android.ravenwood.annotation.RavenwoodKeep
- public static void init$ravenwood(final int uid, final int pid) {
- sIdentity$ravenwood = ThreadLocal.withInitial(() -> {
- final SomeArgs args = SomeArgs.obtain();
- args.argi1 = uid;
- args.argi2 = pid;
- args.argi3 = Long.hashCode(Thread.currentThread().getId());
- args.argi4 = THREAD_PRIORITY_DEFAULT;
- args.arg1 = Boolean.TRUE; // backgroundOk
- return args;
- });
- }
-
- /** @hide */
- @android.ravenwood.annotation.RavenwoodKeep
- public static void reset$ravenwood() {
- sIdentity$ravenwood = null;
- }
-
/**
* Returns the identifier of this process, which can be used with
* {@link #killProcess} and {@link #sendSignal}.
*/
- @android.ravenwood.annotation.RavenwoodReplace
+ @RavenwoodKeep
public static final int myPid() {
return Os.getpid();
}
- /** @hide */
- public static final int myPid$ravenwood() {
- return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi2;
- }
-
/**
* Returns the identifier of this process' parent.
* @hide
@@ -899,39 +876,29 @@
* Returns the identifier of the calling thread, which be used with
* {@link #setThreadPriority(int, int)}.
*/
- @android.ravenwood.annotation.RavenwoodReplace
+ @RavenwoodKeep
public static final int myTid() {
return Os.gettid();
}
- /** @hide */
- public static final int myTid$ravenwood() {
- return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi3;
- }
-
/**
* Returns the identifier of this process's uid. This is the kernel uid
* that the process is running under, which is the identity of its
* app-specific sandbox. It is different from {@link #myUserHandle} in that
* a uid identifies a specific app sandbox in a specific user.
*/
- @android.ravenwood.annotation.RavenwoodReplace
+ @RavenwoodKeep
public static final int myUid() {
return Os.getuid();
}
- /** @hide */
- public static final int myUid$ravenwood() {
- return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi1;
- }
-
/**
* Returns this process's user handle. This is the
* user the process is running under. It is distinct from
* {@link #myUid()} in that a particular user will have multiple
* distinct apps running under it each with their own uid.
*/
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static UserHandle myUserHandle() {
return UserHandle.of(UserHandle.getUserId(myUid()));
}
@@ -940,7 +907,7 @@
* Returns whether the given uid belongs to a system core component or not.
* @hide
*/
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static boolean isCoreUid(int uid) {
return UserHandle.isCore(uid);
}
@@ -951,7 +918,7 @@
* @return Whether the uid corresponds to an application sandbox running in
* a specific user.
*/
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static boolean isApplicationUid(int uid) {
return UserHandle.isApp(uid);
}
@@ -959,7 +926,7 @@
/**
* Returns whether the current process is in an isolated sandbox.
*/
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static final boolean isIsolated() {
return isIsolated(myUid());
}
@@ -971,7 +938,7 @@
@Deprecated
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
publicAlternatives = "Use {@link #isIsolatedUid(int)} instead.")
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static final boolean isIsolated(int uid) {
return isIsolatedUid(uid);
}
@@ -979,7 +946,7 @@
/**
* Returns whether the process with the given {@code uid} is an isolated sandbox.
*/
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static final boolean isIsolatedUid(int uid) {
uid = UserHandle.getAppId(uid);
return (uid >= FIRST_ISOLATED_UID && uid <= LAST_ISOLATED_UID)
@@ -991,7 +958,7 @@
* @see android.app.sdksandbox.SdkSandboxManager
*/
@SuppressLint("UnflaggedApi") // promoting from @SystemApi.
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static final boolean isSdkSandboxUid(int uid) {
uid = UserHandle.getAppId(uid);
return (uid >= FIRST_SDK_SANDBOX_UID && uid <= LAST_SDK_SANDBOX_UID);
@@ -1007,7 +974,7 @@
* @throws IllegalArgumentException if input is not an sdk sandbox uid
*/
@SuppressLint("UnflaggedApi") // promoting from @SystemApi.
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static final int getAppUidForSdkSandboxUid(int uid) {
if (!isSdkSandboxUid(uid)) {
throw new IllegalArgumentException("Input UID is not an SDK sandbox UID");
@@ -1023,7 +990,7 @@
*/
@SystemApi(client = MODULE_LIBRARIES)
@TestApi
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
// TODO(b/318651609): Deprecate once Process#getSdkSandboxUidForAppUid is rolled out to 100%
public static final int toSdkSandboxUid(int uid) {
return uid + (FIRST_SDK_SANDBOX_UID - FIRST_APPLICATION_UID);
@@ -1039,7 +1006,7 @@
* @throws IllegalArgumentException if input is not an app uid
*/
@FlaggedApi(Flags.FLAG_SDK_SANDBOX_UID_TO_APP_UID_API)
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static final int getSdkSandboxUidForAppUid(int uid) {
if (!isApplicationUid(uid)) {
throw new IllegalArgumentException("Input UID is not an app UID");
@@ -1050,7 +1017,7 @@
/**
* Returns whether the current process is a sdk sandbox process.
*/
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static final boolean isSdkSandbox() {
return isSdkSandboxUid(myUid());
}
@@ -1127,28 +1094,11 @@
* not have permission to modify the given thread, or to use the given
* priority.
*/
- @android.ravenwood.annotation.RavenwoodReplace
+ @RavenwoodRedirect
public static final native void setThreadPriority(int tid,
@IntRange(from = -20, to = THREAD_PRIORITY_LOWEST) int priority)
throws IllegalArgumentException, SecurityException;
- /** @hide */
- public static final void setThreadPriority$ravenwood(int tid, int priority) {
- final SomeArgs args =
- Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get();
- if (args.argi3 == tid) {
- boolean backgroundOk = (args.arg1 == Boolean.TRUE);
- if (priority >= THREAD_PRIORITY_BACKGROUND && !backgroundOk) {
- throw new IllegalArgumentException(
- "Priority " + priority + " blocked by setCanSelfBackground()");
- }
- args.argi4 = priority;
- } else {
- throw new UnsupportedOperationException(
- "Cross-thread priority management not yet available in Ravenwood");
- }
- }
-
/**
* Call with 'false' to cause future calls to {@link #setThreadPriority(int)} to
* throw an exception if passed a background-level thread priority. This is only
@@ -1156,16 +1106,9 @@
*
* @hide
*/
- @android.ravenwood.annotation.RavenwoodReplace
+ @RavenwoodRedirect
public static final native void setCanSelfBackground(boolean backgroundOk);
- /** @hide */
- public static final void setCanSelfBackground$ravenwood(boolean backgroundOk) {
- final SomeArgs args =
- Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get();
- args.arg1 = Boolean.valueOf(backgroundOk);
- }
-
/**
* Sets the scheduling group for a thread.
* @hide
@@ -1294,13 +1237,12 @@
*
* @see #setThreadPriority(int, int)
*/
- @android.ravenwood.annotation.RavenwoodReplace
+ @RavenwoodReplace
public static final native void setThreadPriority(
@IntRange(from = -20, to = THREAD_PRIORITY_LOWEST) int priority)
throws IllegalArgumentException, SecurityException;
- /** @hide */
- public static final void setThreadPriority$ravenwood(int priority) {
+ private static void setThreadPriority$ravenwood(int priority) {
setThreadPriority(myTid(), priority);
}
@@ -1317,23 +1259,11 @@
* @throws IllegalArgumentException Throws IllegalArgumentException if
* <var>tid</var> does not exist.
*/
- @android.ravenwood.annotation.RavenwoodReplace
+ @RavenwoodRedirect
@IntRange(from = -20, to = THREAD_PRIORITY_LOWEST)
public static final native int getThreadPriority(int tid)
throws IllegalArgumentException;
- /** @hide */
- public static final int getThreadPriority$ravenwood(int tid) {
- final SomeArgs args =
- Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get();
- if (args.argi3 == tid) {
- return args.argi4;
- } else {
- throw new UnsupportedOperationException(
- "Cross-thread priority management not yet available in Ravenwood");
- }
- }
-
/**
* Return the current scheduling policy of a thread, based on Linux.
*
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 90993e1..edeb75b 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -365,7 +365,7 @@
public static final int NETWORK_POLICY_REJECT = 2;
/**
- * Detect explicit calls to {@link Runtime#gc()}.
+ * Detects explicit calls to {@link Runtime#gc()}.
*/
@ChangeId
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@@ -501,7 +501,7 @@
private Executor mExecutor;
/**
- * Create a Builder that detects nothing and has no violations. (but note that {@link
+ * Creates a Builder that detects nothing and has no violations. (but note that {@link
* #build} will default to enabling {@link #penaltyLog} if no other penalties are
* specified)
*/
@@ -509,7 +509,7 @@
mMask = 0;
}
- /** Initialize a Builder from an existing ThreadPolicy. */
+ /** Initializes a Builder from an existing ThreadPolicy. */
public Builder(ThreadPolicy policy) {
mMask = policy.mask;
mListener = policy.mListener;
@@ -517,7 +517,7 @@
}
/**
- * Detect everything that's potentially suspect.
+ * Detects everything that's potentially suspect.
*
* <p>As of the Gingerbread release this includes network and disk operations but will
* likely expand in future releases.
@@ -544,52 +544,52 @@
return this;
}
- /** Disable the detection of everything. */
+ /** Disables the detection of everything. */
public @NonNull Builder permitAll() {
return disable(DETECT_THREAD_ALL);
}
- /** Enable detection of network operations. */
+ /** Enables detection of network operations. */
public @NonNull Builder detectNetwork() {
return enable(DETECT_THREAD_NETWORK);
}
- /** Disable detection of network operations. */
+ /** Disables detection of network operations. */
public @NonNull Builder permitNetwork() {
return disable(DETECT_THREAD_NETWORK);
}
- /** Enable detection of disk reads. */
+ /** Enables detection of disk reads. */
public @NonNull Builder detectDiskReads() {
return enable(DETECT_THREAD_DISK_READ);
}
- /** Disable detection of disk reads. */
+ /** Disables detection of disk reads. */
public @NonNull Builder permitDiskReads() {
return disable(DETECT_THREAD_DISK_READ);
}
- /** Enable detection of slow calls. */
+ /** Enables detection of slow calls. */
public @NonNull Builder detectCustomSlowCalls() {
return enable(DETECT_THREAD_CUSTOM);
}
- /** Disable detection of slow calls. */
+ /** Disables detection of slow calls. */
public @NonNull Builder permitCustomSlowCalls() {
return disable(DETECT_THREAD_CUSTOM);
}
- /** Disable detection of mismatches between defined resource types and getter calls. */
+ /** Disables detection of mismatches between defined resource types and getter calls. */
public @NonNull Builder permitResourceMismatches() {
return disable(DETECT_THREAD_RESOURCE_MISMATCH);
}
- /** Detect unbuffered input/output operations. */
+ /** Detects unbuffered input/output operations. */
public @NonNull Builder detectUnbufferedIo() {
return enable(DETECT_THREAD_UNBUFFERED_IO);
}
- /** Disable detection of unbuffered input/output operations. */
+ /** Disables detection of unbuffered input/output operations. */
public @NonNull Builder permitUnbufferedIo() {
return disable(DETECT_THREAD_UNBUFFERED_IO);
}
@@ -610,32 +610,32 @@
return enable(DETECT_THREAD_RESOURCE_MISMATCH);
}
- /** Enable detection of disk writes. */
+ /** Enables detection of disk writes. */
public @NonNull Builder detectDiskWrites() {
return enable(DETECT_THREAD_DISK_WRITE);
}
- /** Disable detection of disk writes. */
+ /** Disables detection of disk writes. */
public @NonNull Builder permitDiskWrites() {
return disable(DETECT_THREAD_DISK_WRITE);
}
/**
- * Detect calls to {@link Runtime#gc()}.
+ * Detects calls to {@link Runtime#gc()}.
*/
public @NonNull Builder detectExplicitGc() {
return enable(DETECT_THREAD_EXPLICIT_GC);
}
/**
- * Disable detection of calls to {@link Runtime#gc()}.
+ * Disables detection of calls to {@link Runtime#gc()}.
*/
public @NonNull Builder permitExplicitGc() {
return disable(DETECT_THREAD_EXPLICIT_GC);
}
/**
- * Show an annoying dialog to the developer on detected violations, rate-limited to be
+ * Shows an annoying dialog to the developer on detected violations, rate-limited to be
* only a little annoying.
*/
public @NonNull Builder penaltyDialog() {
@@ -643,7 +643,7 @@
}
/**
- * Crash the whole process on violation. This penalty runs at the end of all enabled
+ * Crashes the whole process on violation. This penalty runs at the end of all enabled
* penalties so you'll still get see logging or other violations before the process
* dies.
*
@@ -655,7 +655,7 @@
}
/**
- * Crash the whole process on any network usage. Unlike {@link #penaltyDeath}, this
+ * Crashes the whole process on any network usage. Unlike {@link #penaltyDeath}, this
* penalty runs <em>before</em> anything else. You must still have called {@link
* #detectNetwork} to enable this.
*
@@ -665,18 +665,18 @@
return enable(PENALTY_DEATH_ON_NETWORK);
}
- /** Flash the screen during a violation. */
+ /** Flashes the screen during a violation. */
public @NonNull Builder penaltyFlashScreen() {
return enable(PENALTY_FLASH);
}
- /** Log detected violations to the system log. */
+ /** Logs detected violations to the system log. */
public @NonNull Builder penaltyLog() {
return enable(PENALTY_LOG);
}
/**
- * Enable detected violations log a stacktrace and timing data to the {@link
+ * Enables detected violations log a stacktrace and timing data to the {@link
* android.os.DropBoxManager DropBox} on policy violation. Intended mostly for platform
* integrators doing beta user field data collection.
*/
@@ -685,7 +685,7 @@
}
/**
- * Call #{@link OnThreadViolationListener#onThreadViolation(Violation)} on specified
+ * Calls #{@link OnThreadViolationListener#onThreadViolation(Violation)} on specified
* executor every violation.
*/
public @NonNull Builder penaltyListener(
@@ -715,7 +715,7 @@
}
/**
- * Construct the ThreadPolicy instance.
+ * Constructs the ThreadPolicy instance.
*
* <p>Note: if no penalties are enabled before calling <code>build</code>, {@link
* #penaltyLog} is implicitly set.
@@ -805,7 +805,7 @@
mMask = 0;
}
- /** Build upon an existing VmPolicy. */
+ /** Builds upon an existing VmPolicy. */
public Builder(VmPolicy base) {
mMask = base.mask;
mClassInstanceLimitNeedCow = true;
@@ -815,7 +815,7 @@
}
/**
- * Set an upper bound on how many instances of a class can be in memory at once. Helps
+ * Sets an upper bound on how many instances of a class can be in memory at once. Helps
* to prevent object leaks.
*/
public @NonNull Builder setClassInstanceLimit(Class klass, int instanceLimit) {
@@ -838,7 +838,7 @@
return this;
}
- /** Detect leaks of {@link android.app.Activity} subclasses. */
+ /** Detects leaks of {@link android.app.Activity} subclasses. */
public @NonNull Builder detectActivityLeaks() {
return enable(DETECT_VM_ACTIVITY_LEAKS);
}
@@ -852,7 +852,7 @@
}
/**
- * Detect reflective usage of APIs that are not part of the public Android SDK.
+ * Detects reflective usage of APIs that are not part of the public Android SDK.
*
* <p>Note that any non-SDK APIs that this processes accesses before this detection is
* enabled may not be detected. To ensure that all such API accesses are detected,
@@ -863,7 +863,7 @@
}
/**
- * Permit reflective usage of APIs that are not part of the public Android SDK. Note
+ * Permits reflective usage of APIs that are not part of the public Android SDK. Note
* that this <b>only</b> affects {@code StrictMode}, the underlying runtime may
* continue to restrict or warn on access to methods that are not part of the
* public SDK.
@@ -873,7 +873,7 @@
}
/**
- * Detect everything that's potentially suspect.
+ * Detects everything that's potentially suspect.
*
* <p>In the Honeycomb release this includes leaks of SQLite cursors, Activities, and
* other closable objects but will likely expand in future releases.
@@ -924,8 +924,8 @@
}
/**
- * Detect when an {@link android.database.sqlite.SQLiteCursor} or other SQLite object is
- * finalized without having been closed.
+ * Detects when an {@link android.database.sqlite.SQLiteCursor} or other SQLite
+ * object is finalized without having been closed.
*
* <p>You always want to explicitly close your SQLite cursors to avoid unnecessary
* database contention and temporary memory leaks.
@@ -935,8 +935,8 @@
}
/**
- * Detect when an {@link java.io.Closeable} or other object with an explicit termination
- * method is finalized without having been closed.
+ * Detects when an {@link java.io.Closeable} or other object with an explicit
+ * termination method is finalized without having been closed.
*
* <p>You always want to explicitly close such objects to avoid unnecessary resources
* leaks.
@@ -946,16 +946,16 @@
}
/**
- * Detect when a {@link BroadcastReceiver} or {@link ServiceConnection} is leaked during
- * {@link Context} teardown.
+ * Detects when a {@link BroadcastReceiver} or {@link ServiceConnection} is leaked
+ * during {@link Context} teardown.
*/
public @NonNull Builder detectLeakedRegistrationObjects() {
return enable(DETECT_VM_REGISTRATION_LEAKS);
}
/**
- * Detect when the calling application exposes a {@code file://} {@link android.net.Uri}
- * to another app.
+ * Detects when the calling application exposes a {@code file://}
+ * {@link android.net.Uri} to another app.
*
* <p>This exposure is discouraged since the receiving app may not have access to the
* shared path. For example, the receiving app may not have requested the {@link
@@ -973,9 +973,9 @@
}
/**
- * Detect any network traffic from the calling app which is not wrapped in SSL/TLS. This
- * can help you detect places that your app is inadvertently sending cleartext data
- * across the network.
+ * Detects any network traffic from the calling app which is not wrapped in SSL/TLS.
+ * This can help you detect places that your app is inadvertently sending cleartext
+ * data across the network.
*
* <p>Using {@link #penaltyDeath()} or {@link #penaltyDeathOnCleartextNetwork()} will
* block further traffic on that socket to prevent accidental data leakage, in addition
@@ -992,7 +992,7 @@
}
/**
- * Detect when the calling application sends a {@code content://} {@link
+ * Detects when the calling application sends a {@code content://} {@link
* android.net.Uri} to another app without setting {@link
* Intent#FLAG_GRANT_READ_URI_PERMISSION} or {@link
* Intent#FLAG_GRANT_WRITE_URI_PERMISSION}.
@@ -1008,7 +1008,7 @@
}
/**
- * Detect any sockets in the calling app which have not been tagged using {@link
+ * Detects any sockets in the calling app which have not been tagged using {@link
* TrafficStats}. Tagging sockets can help you investigate network usage inside your
* app, such as a narrowing down heavy usage to a specific library or component.
*
@@ -1028,7 +1028,7 @@
}
/**
- * Detect any implicit reliance on Direct Boot automatic filtering
+ * Detects any implicit reliance on Direct Boot automatic filtering
* of {@link PackageManager} values. Violations are only triggered
* when implicit calls are made while the user is locked.
* <p>
@@ -1051,7 +1051,7 @@
}
/**
- * Detect access to filesystem paths stored in credential protected
+ * Detects access to filesystem paths stored in credential protected
* storage areas while the user is locked.
* <p>
* When a user is locked, credential protected storage is
@@ -1072,7 +1072,7 @@
}
/**
- * Detect attempts to invoke a method on a {@link Context} that is not suited for such
+ * Detects attempts to invoke a method on a {@link Context} that is not suited for such
* operation.
* <p>An example of this is trying to obtain an instance of UI service (e.g.
* {@link android.view.WindowManager}) from a non-visual {@link Context}. This is not
@@ -1086,7 +1086,7 @@
}
/**
- * Disable detection of incorrect context use.
+ * Disables detection of incorrect context use.
*
* @see #detectIncorrectContextUse()
*
@@ -1098,7 +1098,7 @@
}
/**
- * Detect when your app sends an unsafe {@link Intent}.
+ * Detects when your app sends an unsafe {@link Intent}.
* <p>
* Violations may indicate security vulnerabilities in the design of
* your app, where a malicious app could trick you into granting
@@ -1139,7 +1139,7 @@
}
/**
- * Permit your app to launch any {@link Intent} which originated
+ * Permits your app to launch any {@link Intent} which originated
* from outside your app.
* <p>
* Disabling this check is <em>strongly discouraged</em>, as
@@ -1214,13 +1214,13 @@
return enable(PENALTY_DEATH_ON_FILE_URI_EXPOSURE);
}
- /** Log detected violations to the system log. */
+ /** Logs detected violations to the system log. */
public @NonNull Builder penaltyLog() {
return enable(PENALTY_LOG);
}
/**
- * Enable detected violations log a stacktrace and timing data to the {@link
+ * Enables detected violations log a stacktrace and timing data to the {@link
* android.os.DropBoxManager DropBox} on policy violation. Intended mostly for platform
* integrators doing beta user field data collection.
*/
@@ -1229,7 +1229,7 @@
}
/**
- * Call #{@link OnVmViolationListener#onVmViolation(Violation)} on every violation.
+ * Calls #{@link OnVmViolationListener#onVmViolation(Violation)} on every violation.
*/
public @NonNull Builder penaltyListener(
@NonNull Executor executor, @NonNull OnVmViolationListener listener) {
@@ -1258,7 +1258,7 @@
}
/**
- * Construct the VmPolicy instance.
+ * Constructs the VmPolicy instance.
*
* <p>Note: if no penalties are enabled before calling <code>build</code>, {@link
* #penaltyLog} is implicitly set.
@@ -1474,7 +1474,7 @@
}
/**
- * Determine if the given app is "bundled" as part of the system image. These bundled apps are
+ * Determines if the given app is "bundled" as part of the system image. These bundled apps are
* developed in lock-step with the OS, and they aren't updated outside of an OTA, so we want to
* chase any {@link StrictMode} regressions by enabling detection when running on {@link
* Build#IS_USERDEBUG} or {@link Build#IS_ENG} builds.
@@ -1512,7 +1512,7 @@
}
/**
- * Initialize default {@link ThreadPolicy} for the current thread.
+ * Initializes default {@link ThreadPolicy} for the current thread.
*
* @hide
*/
@@ -1547,7 +1547,7 @@
}
/**
- * Initialize default {@link VmPolicy} for the current VM.
+ * Initializes default {@link VmPolicy} for the current VM.
*
* @hide
*/
@@ -2244,7 +2244,7 @@
}
/**
- * Enable the recommended StrictMode defaults, with violations just being logged.
+ * Enables the recommended StrictMode defaults, with violations just being logged.
*
* <p>This catches disk and network access on the main thread, as well as leaked SQLite cursors
* and unclosed resources. This is simply a wrapper around {@link #setVmPolicy} and {@link
@@ -2545,7 +2545,7 @@
private static final SparseLongArray sRealLastVmViolationTime = new SparseLongArray();
/**
- * Clamp the given map by removing elements with timestamp older than the given retainSince.
+ * Clamps the given map by removing elements with timestamp older than the given retainSince.
*/
private static void clampViolationTimeMap(final @NonNull SparseLongArray violationTime,
final long retainSince) {
@@ -2812,7 +2812,7 @@
};
/**
- * Enter a named critical span (e.g. an animation)
+ * Enters a named critical span (e.g. an animation)
*
* <p>The name is an arbitary label (or tag) that will be applied to any strictmode violation
* that happens while this span is active. You must call finish() on the span when done.
@@ -3056,7 +3056,7 @@
/** If this is a instance count violation, the number of instances in memory, else -1. */
public long numInstances = -1;
- /** Create an instance of ViolationInfo initialized from an exception. */
+ /** Creates an instance of ViolationInfo initialized from an exception. */
ViolationInfo(Violation tr, int penaltyMask) {
this.mViolation = tr;
this.mPenaltyMask = penaltyMask;
@@ -3131,8 +3131,8 @@
}
/**
- * Add a {@link Throwable} from the current process that caused the underlying violation. We
- * only preserve the stack trace elements.
+ * Adds a {@link Throwable} from the current process that caused the underlying violation.
+ * We only preserve the stack trace elements.
*
* @hide
*/
@@ -3160,14 +3160,14 @@
return result;
}
- /** Create an instance of ViolationInfo initialized from a Parcel. */
+ /** Creates an instance of ViolationInfo initialized from a Parcel. */
@UnsupportedAppUsage
public ViolationInfo(Parcel in) {
this(in, false);
}
/**
- * Create an instance of ViolationInfo initialized from a Parcel.
+ * Creates an instance of ViolationInfo initialized from a Parcel.
*
* @param unsetGatheringBit if true, the caller is the root caller and the gathering penalty
* should be removed.
@@ -3203,7 +3203,7 @@
tags = in.readStringArray();
}
- /** Save a ViolationInfo instance to a parcel. */
+ /** Saves a ViolationInfo instance to a parcel. */
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeSerializable(mViolation);
@@ -3248,7 +3248,7 @@
}
}
- /** Dump a ViolationInfo instance to a Printer. */
+ /** Dumps a ViolationInfo instance to a Printer. */
public void dump(Printer pw, String prefix) {
pw.println(prefix + "stackTrace: " + getStackTrace());
pw.println(prefix + "penalty: " + mPenaltyMask);
diff --git a/core/java/android/service/games/GameSession.java b/core/java/android/service/games/GameSession.java
index e8d53d3..531e0b1 100644
--- a/core/java/android/service/games/GameSession.java
+++ b/core/java/android/service/games/GameSession.java
@@ -516,6 +516,8 @@
options,
future);
+ trampolineIntent.collectExtraIntentKeys();
+
try {
int result = ActivityTaskManager.getService().startActivityFromGameSession(
mContext.getIApplicationThread(), mContext.getPackageName(), "GameSession",
diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java
index 1fe06d4..66d64d7 100644
--- a/core/java/android/view/HapticFeedbackConstants.java
+++ b/core/java/android/view/HapticFeedbackConstants.java
@@ -176,7 +176,7 @@
/**
* The user is executing a swipe/drag-style gesture, such as pull-to-refresh, where the
- * gesture action is “eligible” at a certain threshold of movement, and can be cancelled by
+ * gesture action is "eligible" at a certain threshold of movement, and can be cancelled by
* moving back past the threshold. This constant indicates that the user's motion has just
* passed the threshold for the action to be activated on release.
*
@@ -186,7 +186,7 @@
/**
* The user is executing a swipe/drag-style gesture, such as pull-to-refresh, where the
- * gesture action is “eligible” at a certain threshold of movement, and can be cancelled by
+ * gesture action is "eligible" at a certain threshold of movement, and can be cancelled by
* moving back past the threshold. This constant indicates that the user's motion has just
* re-crossed back "under" the threshold for the action to be activated, meaning the gesture is
* currently in a cancelled state.
diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java
index 7877352..acbd95bf 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -212,8 +212,7 @@
&& mInitiallyVisible == that.mInitiallyVisible
&& mSurfacePosition.equals(that.mSurfacePosition)
&& mInsetsHint.equals(that.mInsetsHint)
- && mSkipAnimationOnce == that.mSkipAnimationOnce
- && Objects.equals(mImeStatsToken, that.mImeStatsToken);
+ && mSkipAnimationOnce == that.mSkipAnimationOnce;
}
@Override
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index e49eec6..5ee229f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -30,6 +30,7 @@
import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
+import static android.view.accessibility.Flags.FLAG_DEPRECATE_ACCESSIBILITY_ANNOUNCEMENT_APIS;
import static android.view.accessibility.Flags.FLAG_SUPPLEMENTAL_DESCRIPTION;
import static android.view.accessibility.Flags.removeChildHoverCheckForTouchExploration;
import static android.view.accessibility.Flags.supplementalDescription;
@@ -42,6 +43,7 @@
import static android.view.flags.Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION_API;
import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+import static android.view.flags.Flags.calculateBoundsInParentFromBoundsInScreen;
import static android.view.flags.Flags.enableUseMeasureCacheDuringForceLayout;
import static android.view.flags.Flags.sensitiveContentAppProtection;
import static android.view.flags.Flags.toolkitFrameRateAnimationBugfix25q1;
@@ -970,6 +972,13 @@
private static boolean sAlwaysRemeasureExactly = false;
/**
+ * When true calculates the bounds in parent from bounds in screen relative to its parents.
+ * This addresses the deprecated API (setBoundsInParent) in Compose, which causes empty
+ * getBoundsInParent call for Compose apps.
+ */
+ private static boolean sCalculateBoundsInParentFromBoundsInScreenFlagValue = false;
+
+ /**
* When true makes it possible to use onMeasure caches also when the force layout flag is
* enabled. This helps avoiding multiple measures in the same frame with the same dimensions.
*/
@@ -2561,6 +2570,8 @@
sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly();
sToolkitMetricsForFrameRateDecisionFlagValue = toolkitMetricsForFrameRateDecision();
+ sCalculateBoundsInParentFromBoundsInScreenFlagValue =
+ calculateBoundsInParentFromBoundsInScreen();
sUseMeasureCacheDuringForceLayoutFlagValue = enableUseMeasureCacheDuringForceLayout();
}
@@ -8941,44 +8952,45 @@
}
/**
- * Convenience method for sending a {@link AccessibilityEvent#TYPE_ANNOUNCEMENT}
- * {@link AccessibilityEvent} to suggest that an accessibility service announce the
- * specified text to its users.
- * <p>
- * Note: The event generated with this API carries no semantic meaning, and is appropriate only
- * in exceptional situations. Apps can generally achieve correct behavior for accessibility by
- * accurately supplying the semantics of their UI.
- * They should not need to specify what exactly is announced to users.
+ * Convenience method for sending a {@link AccessibilityEvent#TYPE_ANNOUNCEMENT} {@link
+ * AccessibilityEvent} to suggest that an accessibility service announce the specified text to
+ * its users.
*
- * <p>
- * In general, only announce transitions and don't generate a confirmation message for simple
- * actions like a button press. Label your controls concisely and precisely instead, and for
- * significant UI changes like window changes, use
- * {@link android.app.Activity#setTitle(CharSequence)} and
- * {@link #setAccessibilityPaneTitle(CharSequence)}.
+ * <p>Note: The event generated with this API carries no semantic meaning, and accessibility
+ * services may choose to ignore it. Apps that accurately supply accessibility with the
+ * semantics of their UI should not need to specify what exactly is announced.
*
- * <p>
- * Use {@link #setAccessibilityLiveRegion(int)} to inform the user of changes to critical
+ * <p>In general, do not attempt to generate announcements as confirmation message for simple
+ * actions like a button press. Label your controls concisely and precisely instead.
+ *
+ * <p>To convey significant UI changes like window changes, use {@link
+ * android.app.Activity#setTitle(CharSequence)} and {@link
+ * #setAccessibilityPaneTitle(CharSequence)}.
+ *
+ * <p>Use {@link #setAccessibilityLiveRegion(int)} to inform the user of changes to critical
* views within the user interface. These should still be used sparingly as they may generate
* announcements every time a View is updated.
*
- * <p>
- * For notifying users about errors, such as in a login screen with text that displays an
- * "incorrect password" notification, that view should send an AccessibilityEvent of type
- * {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_ERROR} and set
- * {@link AccessibilityNodeInfo#setError(CharSequence)} instead. Custom widgets should expose
- * error-setting methods that support accessibility automatically. For example, instead of
- * explicitly sending this event when using a TextView, use
- * {@link android.widget.TextView#setError(CharSequence)}.
- *
- * <p>
- * Use {@link #setStateDescription(CharSequence)} to convey state changes to views within the
+ * <p>Use {@link #setStateDescription(CharSequence)} to convey state changes to views within the
* user interface. While a live region may send different types of events generated by the view,
* state description will send {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} events of
* type {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_STATE_DESCRIPTION}.
*
+ * <p>For notifying users about errors, such as in a login screen with text that displays an
+ * "incorrect password" notification, set {@link AccessibilityNodeInfo#setError(CharSequence)}
+ * and dispatch an {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event with a change
+ * type of {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_ERROR}, instead. Some widgets may
+ * expose methods that convey error states to accessibility automatically, such as {@link
+ * android.widget.TextView#setError(CharSequence)}, which manages these accessibility semantics
+ * and event dispatch for callers.
+ *
+ * @deprecated Use one of the methods described in the documentation above to semantically
+ * describe UI instead of using an announcement, as accessibility services may choose to
+ * ignore events dispatched with this method.
* @param text The announcement text.
*/
+ @FlaggedApi(FLAG_DEPRECATE_ACCESSIBILITY_ANNOUNCEMENT_APIS)
+ @Deprecated
public void announceForAccessibility(CharSequence text) {
if (AccessibilityManager.getInstance(mContext).isEnabled() && mParent != null) {
AccessibilityEvent event = AccessibilityEvent.obtain(
@@ -9806,7 +9818,7 @@
structure.setChildCount(1);
final ViewStructure root = structure.newChild(0);
if (info != null) {
- populateVirtualStructure(root, provider, info, forAutofill);
+ populateVirtualStructure(root, provider, info, null, forAutofill);
info.recycle();
} else {
Log.w(AUTOFILL_LOG_TAG, "AccessibilityNodeInfo is null.");
@@ -11105,11 +11117,19 @@
private void populateVirtualStructure(ViewStructure structure,
AccessibilityNodeProvider provider, AccessibilityNodeInfo info,
- boolean forAutofill) {
+ @Nullable AccessibilityNodeInfo parentInfo, boolean forAutofill) {
structure.setId(AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId()),
null, null, info.getViewIdResourceName());
Rect rect = structure.getTempRect();
- info.getBoundsInParent(rect);
+ // The bounds in parent for Jetpack Compose views aren't set as setBoundsInParent is
+ // deprecated, and only setBoundsInScreen is called.
+ // The bounds in parent can be calculated by diff'ing the child view's bounds in screen with
+ // the parent's.
+ if (sCalculateBoundsInParentFromBoundsInScreenFlagValue) {
+ getBoundsInParent(info, parentInfo, rect);
+ } else {
+ info.getBoundsInParent(rect);
+ }
structure.setDimens(rect.left, rect.top, 0, 0, rect.width(), rect.height());
structure.setVisibility(VISIBLE);
structure.setEnabled(info.isEnabled());
@@ -11193,13 +11213,32 @@
AccessibilityNodeInfo.getVirtualDescendantId(info.getChildId(i)));
if (cinfo != null) {
ViewStructure child = structure.newChild(i);
- populateVirtualStructure(child, provider, cinfo, forAutofill);
+ populateVirtualStructure(child, provider, cinfo, info, forAutofill);
cinfo.recycle();
}
}
}
}
+ private void getBoundsInParent(@NonNull AccessibilityNodeInfo info,
+ @Nullable AccessibilityNodeInfo parentInfo, @NonNull Rect rect) {
+ info.getBoundsInParent(rect);
+ // Fallback to calculate bounds in parent by diffing the bounds in
+ // screen if it's all 0.
+ if ((rect.left | rect.top | rect.right | rect.bottom) == 0) {
+ if (parentInfo != null) {
+ Rect parentBoundsInScreen = parentInfo.getBoundsInScreen();
+ Rect boundsInScreen = info.getBoundsInScreen();
+ rect.set(boundsInScreen.left - parentBoundsInScreen.left,
+ boundsInScreen.top - parentBoundsInScreen.top,
+ boundsInScreen.right - parentBoundsInScreen.left,
+ boundsInScreen.bottom - parentBoundsInScreen.top);
+ } else {
+ info.getBoundsInScreen(rect);
+ }
+ }
+ }
+
/**
* Dispatch creation of {@link ViewStructure} down the hierarchy. The default
* implementation calls {@link #onProvideStructure} and
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 5b39f62..b4b0687 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1426,6 +1426,31 @@
"android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE";
/**
+ * Activity-level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * that specifies whether this activity can declare or request
+ * {@link android.R.attr#screenOrientation fixed orientation},
+ * {@link android.R.attr#minAspectRatio max aspect ratio},
+ * {@link android.R.attr#maxAspectRatio min aspect ratio}
+ * {@link android.R.attr#resizeableActivity unresizable} on large screen devices with the
+ * ignore orientation request display setting enabled since Android 16 (API level 36) or higher.
+ *
+ * <p>The default value is {@code false}.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * <activity>
+ * <property
+ * android:name="android.window.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY"
+ * android:value="true"/>
+ * </activity>
+ * </pre>
+ * @hide
+ */
+ // TODO(b/357141415): Make this public API.
+ String PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY =
+ "android.window.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY";
+
+ /**
* @hide
*/
public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array";
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index c690787..0dfaf41 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -563,10 +563,13 @@
/**
* Represents the event of an application making an announcement.
- * <p>
- * In general, follow the practices described in
- * {@link View#announceForAccessibility(CharSequence)}.
+ *
+ * @deprecated Use one of the semantic alternative methods described in the documentation of
+ * {@link View#announceForAccessibility(CharSequence)} instead of using this event, as
+ * accessibility services may choose to ignore dispatch of this event type.
*/
+ @FlaggedApi(Flags.FLAG_DEPRECATE_ACCESSIBILITY_ANNOUNCEMENT_APIS)
+ @Deprecated
public static final int TYPE_ANNOUNCEMENT = 1 << 14;
/**
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index c07da41..7177ef3 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -85,6 +85,13 @@
flag {
namespace: "accessibility"
+ name: "deprecate_accessibility_announcement_apis"
+ description: "Controls the deprecation of platform APIs related to disruptive accessibility announcements"
+ bug: "376727542"
+}
+
+flag {
+ namespace: "accessibility"
name: "fix_merged_content_change_event_v2"
description: "Fixes event type and source of content change event merged in ViewRootImpl"
bug: "277305460"
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index 2ca62a0..dd32d57 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -221,6 +221,7 @@
PHASE_WM_INVOKING_IME_REQUESTED_LISTENER,
PHASE_CLIENT_ALREADY_HIDDEN,
PHASE_CLIENT_VIEW_HANDLER_AVAILABLE,
+ PHASE_SERVER_UPDATE_CLIENT_VISIBILITY,
})
@Retention(RetentionPolicy.SOURCE)
@interface Phase {}
@@ -430,6 +431,11 @@
* continue without.
*/
int PHASE_CLIENT_VIEW_HANDLER_AVAILABLE = ImeProtoEnums.PHASE_CLIENT_VIEW_HANDLER_AVAILABLE;
+ /**
+ * ImeInsetsSourceProvider sets the reported visibility of the caller/client window (either the
+ * app or the RemoteInsetsControlTarget).
+ */
+ int PHASE_SERVER_UPDATE_CLIENT_VISIBILITY = ImeProtoEnums.PHASE_SERVER_UPDATE_CLIENT_VISIBILITY;
/**
* Called when an IME request is started.
diff --git a/core/java/android/window/OnBackInvokedCallbackInfo.java b/core/java/android/window/OnBackInvokedCallbackInfo.java
index bb5fe96..44c7bd9 100644
--- a/core/java/android/window/OnBackInvokedCallbackInfo.java
+++ b/core/java/android/window/OnBackInvokedCallbackInfo.java
@@ -16,6 +16,8 @@
package android.window;
+import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED;
+
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
@@ -29,19 +31,23 @@
private final IOnBackInvokedCallback mCallback;
private @OnBackInvokedDispatcher.Priority int mPriority;
private final boolean mIsAnimationCallback;
+ private final @SystemOverrideOnBackInvokedCallback.OverrideBehavior int mOverrideBehavior;
public OnBackInvokedCallbackInfo(@NonNull IOnBackInvokedCallback callback,
int priority,
- boolean isAnimationCallback) {
+ boolean isAnimationCallback,
+ int overrideBehavior) {
mCallback = callback;
mPriority = priority;
mIsAnimationCallback = isAnimationCallback;
+ mOverrideBehavior = overrideBehavior;
}
private OnBackInvokedCallbackInfo(@NonNull Parcel in) {
mCallback = IOnBackInvokedCallback.Stub.asInterface(in.readStrongBinder());
mPriority = in.readInt();
mIsAnimationCallback = in.readBoolean();
+ mOverrideBehavior = in.readInt();
}
@Override
@@ -54,6 +60,7 @@
dest.writeStrongInterface(mCallback);
dest.writeInt(mPriority);
dest.writeBoolean(mIsAnimationCallback);
+ dest.writeInt(mOverrideBehavior);
}
public static final Creator<OnBackInvokedCallbackInfo> CREATOR =
@@ -70,7 +77,8 @@
};
public boolean isSystemCallback() {
- return mPriority == OnBackInvokedDispatcher.PRIORITY_SYSTEM;
+ return mPriority == OnBackInvokedDispatcher.PRIORITY_SYSTEM
+ || mOverrideBehavior != OVERRIDE_UNDEFINED;
}
@NonNull
@@ -87,12 +95,18 @@
return mIsAnimationCallback;
}
+ @SystemOverrideOnBackInvokedCallback.OverrideBehavior
+ public int getOverrideBehavior() {
+ return mOverrideBehavior;
+ }
+
@Override
public String toString() {
return "OnBackInvokedCallbackInfo{"
+ "mCallback=" + mCallback
+ ", mPriority=" + mPriority
+ ", mIsAnimationCallback=" + mIsAnimationCallback
+ + ", mOverrideBehavior=" + mOverrideBehavior
+ '}';
}
}
diff --git a/core/java/android/window/SystemOnBackInvokedCallbacks.java b/core/java/android/window/SystemOnBackInvokedCallbacks.java
new file mode 100644
index 0000000..f67520b
--- /dev/null
+++ b/core/java/android/window/SystemOnBackInvokedCallbacks.java
@@ -0,0 +1,168 @@
+/*
+ * 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.window;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.app.Activity;
+import android.util.ArrayMap;
+
+import com.android.window.flags.Flags;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Utility class providing {@link OnBackInvokedCallback}s to override the default behavior when
+ * system back is invoked. e.g. {@link Activity#finish}
+ *
+ * <p>By registering these callbacks with the {@link OnBackInvokedDispatcher}, the system can
+ * trigger specific behaviors and play corresponding ahead-of-time animations when the back
+ * gesture is invoked.
+ *
+ * <p>For example, to trigger the {@link Activity#moveTaskToBack} behavior:
+ * <pre>
+ * OnBackInvokedDispatcher dispatcher = activity.getOnBackInvokedDispatcher();
+ * dispatcher.registerOnBackInvokedCallback(
+ * OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+ * SystemOnBackInvokedCallbacks.moveTaskToBackCallback(activity));
+ * </pre>
+ */
+@SuppressWarnings("SingularCallback")
+@FlaggedApi(Flags.FLAG_PREDICTIVE_BACK_SYSTEM_OVERRIDE_CALLBACK)
+public final class SystemOnBackInvokedCallbacks {
+ private static final OverrideCallbackFactory<Activity> sMoveTaskToBackFactory = new
+ MoveTaskToBackCallbackFactory();
+ private static final OverrideCallbackFactory<Activity> sFinishAndRemoveTaskFactory = new
+ FinishAndRemoveTaskCallbackFactory();
+
+ private SystemOnBackInvokedCallbacks() {
+ throw new UnsupportedOperationException("This is a utility class and cannot be "
+ + "instantiated");
+ }
+
+ /**
+ * <p>Get a callback to triggers {@link Activity#moveTaskToBack(boolean)} on the associated
+ * {@link Activity}, moving the task containing the activity to the background. The system
+ * will play the corresponding transition animation, regardless of whether the activity
+ * is the root activity of the task.</p>
+ *
+ * @param activity The associated {@link Activity}
+ * @see Activity#moveTaskToBack(boolean)
+ */
+ @FlaggedApi(Flags.FLAG_PREDICTIVE_BACK_SYSTEM_OVERRIDE_CALLBACK)
+ @NonNull
+ public static OnBackInvokedCallback moveTaskToBackCallback(@NonNull Activity activity) {
+ return sMoveTaskToBackFactory.getOverrideCallback(activity);
+ }
+
+ /**
+ * <p>Get a callback to triggers {@link Activity#finishAndRemoveTask()} on the associated
+ * {@link Activity}. If the activity is the root activity of its task, the entire task
+ * will be removed from the recents task. The activity will be finished in all cases.
+ * The system will play the corresponding transition animation.</p>
+ *
+ * @param activity The associated {@link Activity}
+ * @see Activity#finishAndRemoveTask()
+ */
+ @FlaggedApi(Flags.FLAG_PREDICTIVE_BACK_SYSTEM_OVERRIDE_CALLBACK)
+ @NonNull
+ public static OnBackInvokedCallback finishAndRemoveTaskCallback(@NonNull Activity activity) {
+ return sFinishAndRemoveTaskFactory.getOverrideCallback(activity);
+ }
+
+ /**
+ * Abstract factory for creating system override {@link SystemOverrideOnBackInvokedCallback}
+ * instances.
+ *
+ * <p>Concrete implementations of this factory are responsible for creating callbacks that
+ * override the default system back navigation behavior. These callbacks should be used
+ * exclusively for system overrides and should never be invoked directly.</p>
+ */
+ private abstract static class OverrideCallbackFactory<TYPE> {
+ private final ArrayMap<WeakReference<TYPE>,
+ WeakReference<SystemOverrideOnBackInvokedCallback>> mObjectMap = new ArrayMap<>();
+
+ protected abstract SystemOverrideOnBackInvokedCallback createCallback(
+ @NonNull TYPE context);
+
+ @NonNull SystemOverrideOnBackInvokedCallback getOverrideCallback(@NonNull TYPE object) {
+ if (object == null) {
+ throw new NullPointerException("Input object cannot be null");
+ }
+ synchronized (mObjectMap) {
+ WeakReference<SystemOverrideOnBackInvokedCallback> callback = null;
+ for (int i = mObjectMap.size() - 1; i >= 0; --i) {
+ final WeakReference<TYPE> next = mObjectMap.keyAt(i);
+ if (next.get() == object) {
+ callback = mObjectMap.get(next);
+ break;
+ }
+ }
+ if (callback != null) {
+ return callback.get();
+ }
+ final SystemOverrideOnBackInvokedCallback contextCallback = createCallback(object);
+ if (contextCallback != null) {
+ mObjectMap.put(new WeakReference<>(object),
+ new WeakReference<>(contextCallback));
+ }
+ return contextCallback;
+ }
+ }
+ }
+
+ private static class MoveTaskToBackCallbackFactory extends OverrideCallbackFactory<Activity> {
+ @Override
+ protected SystemOverrideOnBackInvokedCallback createCallback(Activity activity) {
+ final WeakReference<Activity> activityRef = new WeakReference<>(activity);
+ return new SystemOverrideOnBackInvokedCallback() {
+ @Override
+ public void onBackInvoked() {
+ if (activityRef.get() != null) {
+ activityRef.get().moveTaskToBack(true /* nonRoot */);
+ }
+ }
+
+ @Override
+ public int overrideBehavior() {
+ return OVERRIDE_MOVE_TASK_TO_BACK;
+ }
+ };
+ }
+ }
+
+ private static class FinishAndRemoveTaskCallbackFactory extends
+ OverrideCallbackFactory<Activity> {
+ @Override
+ protected SystemOverrideOnBackInvokedCallback createCallback(Activity activity) {
+ final WeakReference<Activity> activityRef = new WeakReference<>(activity);
+ return new SystemOverrideOnBackInvokedCallback() {
+ @Override
+ public void onBackInvoked() {
+ if (activityRef.get() != null) {
+ activityRef.get().finishAndRemoveTask();
+ }
+ }
+
+ @Override
+ public int overrideBehavior() {
+ return OVERRIDE_FINISH_AND_REMOVE_TASK;
+ }
+ };
+ }
+ }
+}
diff --git a/core/java/android/window/SystemOverrideOnBackInvokedCallback.java b/core/java/android/window/SystemOverrideOnBackInvokedCallback.java
new file mode 100644
index 0000000..3360a19
--- /dev/null
+++ b/core/java/android/window/SystemOverrideOnBackInvokedCallback.java
@@ -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 android.window;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Non-default ahead-of-time system OnBackInvokedCallback.
+ * @hide
+ */
+public interface SystemOverrideOnBackInvokedCallback extends OnBackInvokedCallback {
+ /**
+ * No override request
+ */
+ int OVERRIDE_UNDEFINED = 0;
+
+ /**
+ * Navigating back will bring the task to back
+ */
+ int OVERRIDE_MOVE_TASK_TO_BACK = 1;
+
+ /**
+ * Navigating back will finish activity, and remove the task if this activity is root activity.
+ */
+ int OVERRIDE_FINISH_AND_REMOVE_TASK = 2;
+
+ /** @hide */
+ @IntDef({
+ OVERRIDE_UNDEFINED,
+ OVERRIDE_MOVE_TASK_TO_BACK,
+ OVERRIDE_FINISH_AND_REMOVE_TASK,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface OverrideBehavior {
+ }
+
+ /**
+ * @return Override type of this callback.
+ */
+ @OverrideBehavior
+ default int overrideBehavior() {
+ return OVERRIDE_UNDEFINED;
+ }
+}
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index c9d458f..0ea4bb4 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -16,6 +16,9 @@
package android.window;
+import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED;
+
+import static com.android.window.flags.Flags.predictiveBackSystemOverrideCallback;
import static com.android.window.flags.Flags.predictiveBackPrioritySystemNavigationObserver;
import static com.android.window.flags.Flags.predictiveBackTimestampApi;
@@ -201,6 +204,15 @@
mImeDispatcher.registerOnBackInvokedCallback(priority, callback);
return;
}
+ if (predictiveBackPrioritySystemNavigationObserver()
+ && predictiveBackSystemOverrideCallback()) {
+ if (priority == PRIORITY_SYSTEM_NAVIGATION_OBSERVER
+ && callback instanceof SystemOverrideOnBackInvokedCallback) {
+ Log.e(TAG, "System override callbacks cannot be registered to "
+ + "NAVIGATION_OBSERVER");
+ return;
+ }
+ }
if (predictiveBackPrioritySystemNavigationObserver()) {
if (priority == PRIORITY_SYSTEM_NAVIGATION_OBSERVER) {
registerSystemNavigationObserverCallback(callback);
@@ -365,7 +377,8 @@
public void tryInvokeSystemNavigationObserverCallback() {
OnBackInvokedCallback topCallback = getTopCallback();
Integer callbackPriority = mAllCallbacks.getOrDefault(topCallback, null);
- if (callbackPriority != null && callbackPriority == PRIORITY_SYSTEM) {
+ final boolean isSystemOverride = topCallback instanceof SystemOverrideOnBackInvokedCallback;
+ if ((callbackPriority != null && callbackPriority == PRIORITY_SYSTEM) || isSystemOverride) {
invokeSystemNavigationObserverCallback();
}
}
@@ -384,14 +397,22 @@
OnBackInvokedCallbackInfo callbackInfo = null;
if (callback != null) {
int priority = mAllCallbacks.get(callback);
+ int overrideAnimation = OVERRIDE_UNDEFINED;
+ if (callback instanceof SystemOverrideOnBackInvokedCallback) {
+ overrideAnimation = ((SystemOverrideOnBackInvokedCallback) callback)
+ .overrideBehavior();
+ }
+ final boolean isSystemCallback = priority == PRIORITY_SYSTEM
+ || overrideAnimation != OVERRIDE_UNDEFINED;
final IOnBackInvokedCallback iCallback = new OnBackInvokedCallbackWrapper(callback,
mTouchTracker, mProgressAnimator, mHandler, this::callOnKeyPreIme,
this::invokeSystemNavigationObserverCallback,
- /*isSystemCallback*/ priority == PRIORITY_SYSTEM);
+ isSystemCallback /*isSystemCallback*/);
callbackInfo = new OnBackInvokedCallbackInfo(
iCallback,
priority,
- callback instanceof OnBackAnimationCallback);
+ callback instanceof OnBackAnimationCallback,
+ overrideAnimation);
}
mWindowSession.setOnBackInvokedCallbackInfo(mWindow, callbackInfo);
} catch (RemoteException e) {
diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig
index fd5de91..b2f125d 100644
--- a/core/java/android/window/flags/responsible_apis.aconfig
+++ b/core/java/android/window/flags/responsible_apis.aconfig
@@ -16,13 +16,6 @@
}
flag {
- name: "bal_show_toasts"
- namespace: "responsible_apis"
- description: "Enable toasts to indicate (potential) BAL blocking."
- bug: "308059069"
-}
-
-flag {
name: "bal_show_toasts_blocked"
namespace: "responsible_apis"
description: "Enable toasts to indicate actual BAL blocking."
@@ -64,14 +57,6 @@
bug: "339720406"
}
-# replaced by bal_strict_mode_ro
-flag {
- name: "bal_strict_mode"
- namespace: "responsible_apis"
- description: "Strict mode flag"
- bug: "324089586"
-}
-
flag {
name: "bal_strict_mode_ro"
namespace: "responsible_apis"
diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig
index 460df31..392c307 100644
--- a/core/java/android/window/flags/window_surfaces.aconfig
+++ b/core/java/android/window/flags/window_surfaces.aconfig
@@ -13,14 +13,6 @@
flag {
namespace: "window_surfaces"
- name: "explicit_refresh_rate_hints"
- description: "Performance related hints during transitions"
- is_fixed_read_only: true
- bug: "300019131"
-}
-
-flag {
- namespace: "window_surfaces"
name: "delete_capture_display"
description: "Delete uses of ScreenCapture#captureDisplay"
is_fixed_read_only: true
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 3a03508..11f6849 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -49,6 +49,17 @@
}
flag {
+ name: "respect_animation_clip"
+ namespace: "windowing_frontend"
+ description: "Fix missing clip transformation of animation"
+ bug: "376601866"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "edge_to_edge_by_default"
namespace: "windowing_frontend"
description: "Make app go edge-to-edge by default when targeting SDK 35 or greater"
@@ -350,6 +361,17 @@
}
flag {
+ name: "defer_predictive_animation_if_no_snapshot"
+ namespace: "windowing_frontend"
+ description: "If no snapshot for previous window, start animation until the client has draw."
+ bug: "374621014"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "disallow_app_progress_embedded_window"
namespace: "windowing_frontend"
description: "Pilfer pointers when app transfer input gesture to embedded window."
@@ -358,4 +380,12 @@
metadata {
purpose: PURPOSE_BUGFIX
}
+}
+
+flag {
+ name: "predictive_back_system_override_callback"
+ namespace: "windowing_frontend"
+ description: "Provide pre-make predictive back API extension"
+ is_fixed_read_only: true
+ bug: "362938401"
}
\ No newline at end of file
diff --git a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
index de3edeb..15736ed 100644
--- a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
@@ -18,14 +18,13 @@
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import dalvik.annotation.optimization.CriticalNative;
-
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -255,8 +254,8 @@
* the delta in the supplied array container.
*/
public void addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs,
- LongArrayMultiStateCounter.LongArrayContainer deltaContainer) {
- mInjector.addDelta(uid, counter, timestampMs, deltaContainer);
+ long[] delta) {
+ mInjector.addDelta(uid, counter, timestampMs, delta);
}
@VisibleForTesting
@@ -274,15 +273,13 @@
* The delta is also returned via the optional deltaOut parameter.
*/
public boolean addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs,
- LongArrayMultiStateCounter.LongArrayContainer deltaOut) {
- return addDeltaFromBpf(uid, counter.mNativeObject, timestampMs,
- deltaOut != null ? deltaOut.mNativeObject : 0);
+ long[] deltaOut) {
+ return addDeltaFromBpf(uid, counter.mNativeObject, timestampMs, deltaOut);
}
- @CriticalNative
private static native boolean addDeltaFromBpf(int uid,
long longArrayMultiStateCounterNativePointer, long timestampMs,
- long longArrayContainerNativePointer);
+ @Nullable long[] deltaOut);
/**
* Used for testing.
@@ -291,14 +288,14 @@
*/
public boolean addDeltaForTest(int uid, LongArrayMultiStateCounter counter,
long timestampMs, long[][] timeInFreqDataNanos,
- LongArrayMultiStateCounter.LongArrayContainer deltaOut) {
+ long[] deltaOut) {
return addDeltaForTest(uid, counter.mNativeObject, timestampMs, timeInFreqDataNanos,
- deltaOut != null ? deltaOut.mNativeObject : 0);
+ deltaOut);
}
private static native boolean addDeltaForTest(int uid,
long longArrayMultiStateCounterNativePointer, long timestampMs,
- long[][] timeInFreqDataNanos, long longArrayContainerNativePointer);
+ long[][] timeInFreqDataNanos, long[] deltaOut);
}
@VisibleForTesting
diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
index 489721f..b3480ab 100644
--- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
+++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
@@ -30,9 +30,6 @@
import libcore.util.NativeAllocationRegistry;
-import java.util.Arrays;
-import java.util.concurrent.atomic.AtomicReference;
-
/**
* Performs per-state counting of multi-element values over time. The class' behavior is illustrated
* by this example:
@@ -44,15 +41,14 @@
* counter.setState(1, 1000);
*
* // At 3000 ms, the tracked values are updated to {30, 300}
- * arrayContainer.setValues(new long[]{{30, 300}};
- * counter.updateValues(arrayContainer, 3000);
+ * counter.updateValues(arrayContainer, new long[]{{30, 300}, 3000);
*
* // The values are distributed between states 0 and 1 according to the time
* // spent in those respective states. In this specific case, 1000 and 2000 ms.
- * counter.getValues(arrayContainer, 0);
- * // arrayContainer now has values {10, 100}
- * counter.getValues(arrayContainer, 1);
- * // arrayContainer now has values {20, 200}
+ * counter.getCounts(array, 0);
+ * // array now has values {10, 100}
+ * counter.getCounts(array, 1);
+ * // array now has values {20, 200}
* </pre>
*
* The tracked values are expected to increase monotonically.
@@ -62,110 +58,7 @@
@RavenwoodKeepWholeClass
@RavenwoodRedirectionClass("LongArrayMultiStateCounter_host")
public final class LongArrayMultiStateCounter implements Parcelable {
-
- /**
- * Container for a native equivalent of a long[].
- */
- @RavenwoodKeepWholeClass
- @RavenwoodRedirectionClass("LongArrayContainer_host")
- public static class LongArrayContainer {
- private static NativeAllocationRegistry sRegistry;
-
- // Visible to other objects in this package so that it can be passed to @CriticalNative
- // methods.
- final long mNativeObject;
- private final int mLength;
-
- public LongArrayContainer(int length) {
- mLength = length;
- mNativeObject = native_init(length);
- registerNativeAllocation();
- }
-
- @RavenwoodReplace
- private void registerNativeAllocation() {
- if (sRegistry == null) {
- synchronized (LongArrayMultiStateCounter.class) {
- if (sRegistry == null) {
- sRegistry = NativeAllocationRegistry.createMalloced(
- LongArrayContainer.class.getClassLoader(), native_getReleaseFunc());
- }
- }
- }
- sRegistry.registerNativeAllocation(this, mNativeObject);
- }
-
- private void registerNativeAllocation$ravenwood() {
- // No-op under ravenwood
- }
-
- /**
- * Copies the supplied values into the underlying native array.
- */
- public void setValues(long[] array) {
- if (array.length != mLength) {
- throw new IllegalArgumentException(
- "Invalid array length: " + array.length + ", expected: " + mLength);
- }
- native_setValues(mNativeObject, array);
- }
-
- /**
- * Copies the underlying native array values to the supplied array.
- */
- public void getValues(long[] array) {
- if (array.length != mLength) {
- throw new IllegalArgumentException(
- "Invalid array length: " + array.length + ", expected: " + mLength);
- }
- native_getValues(mNativeObject, array);
- }
-
- /**
- * Combines contained values into a smaller array by aggregating them
- * according to an index map.
- */
- public boolean combineValues(long[] array, int[] indexMap) {
- if (indexMap.length != mLength) {
- throw new IllegalArgumentException(
- "Wrong index map size " + indexMap.length + ", expected " + mLength);
- }
- return native_combineValues(mNativeObject, array, indexMap);
- }
-
- @Override
- public String toString() {
- final long[] array = new long[mLength];
- getValues(array);
- return Arrays.toString(array);
- }
-
- @CriticalNative
- @RavenwoodRedirect
- private static native long native_init(int length);
-
- @CriticalNative
- @RavenwoodRedirect
- private static native long native_getReleaseFunc();
-
- @FastNative
- @RavenwoodRedirect
- private static native void native_setValues(long nativeObject, long[] array);
-
- @FastNative
- @RavenwoodRedirect
- private static native void native_getValues(long nativeObject, long[] array);
-
- @FastNative
- @RavenwoodRedirect
- private static native boolean native_combineValues(long nativeObject, long[] array,
- int[] indexMap);
- }
-
private static volatile NativeAllocationRegistry sRegistry;
- private static final AtomicReference<LongArrayContainer> sTmpArrayContainer =
- new AtomicReference<>();
-
private final int mStateCount;
private final int mLength;
@@ -257,13 +150,14 @@
throw new IllegalArgumentException(
"Invalid array length: " + values.length + ", expected: " + mLength);
}
- LongArrayContainer container = sTmpArrayContainer.getAndSet(null);
- if (container == null || container.mLength != values.length) {
- container = new LongArrayContainer(values.length);
- }
- container.setValues(values);
- native_setValues(mNativeObject, state, container.mNativeObject);
- sTmpArrayContainer.set(container);
+ native_setValues(mNativeObject, state, values);
+ }
+
+ /**
+ * Adds the supplied values to the current accumulated values in the counter.
+ */
+ public void incrementValues(long[] values, long timestampMs) {
+ native_incrementValues(mNativeObject, values, timestampMs);
}
/**
@@ -272,51 +166,22 @@
* since the previous call to updateValues.
*/
public void updateValues(long[] values, long timestampMs) {
- LongArrayContainer container = sTmpArrayContainer.getAndSet(null);
- if (container == null || container.mLength != values.length) {
- container = new LongArrayContainer(values.length);
+ if (values.length != mLength) {
+ throw new IllegalArgumentException(
+ "Invalid array length: " + values.length + ", expected: " + mLength);
}
- container.setValues(values);
- updateValues(container, timestampMs);
- sTmpArrayContainer.set(container);
+ native_updateValues(mNativeObject, values, timestampMs);
}
/**
* Adds the supplied values to the current accumulated values in the counter.
*/
- public void incrementValues(long[] values, long timestampMs) {
- LongArrayContainer container = sTmpArrayContainer.getAndSet(null);
- if (container == null || container.mLength != values.length) {
- container = new LongArrayContainer(values.length);
- }
- container.setValues(values);
- native_incrementValues(mNativeObject, container.mNativeObject, timestampMs);
- sTmpArrayContainer.set(container);
- }
-
- /**
- * Sets the new values. The delta between the previously set values and these values
- * is distributed among the state according to the time the object spent in those states
- * since the previous call to updateValues.
- */
- public void updateValues(LongArrayContainer longArrayContainer, long timestampMs) {
- if (longArrayContainer.mLength != mLength) {
+ public void addCounts(long[] counts) {
+ if (counts.length != mLength) {
throw new IllegalArgumentException(
- "Invalid array length: " + longArrayContainer.mLength + ", expected: "
- + mLength);
+ "Invalid array length: " + counts.length + ", expected: " + mLength);
}
- native_updateValues(mNativeObject, longArrayContainer.mNativeObject, timestampMs);
- }
-
- /**
- * Adds the supplied values to the current accumulated values in the counter.
- */
- public void addCounts(LongArrayContainer counts) {
- if (counts.mLength != mLength) {
- throw new IllegalArgumentException(
- "Invalid array length: " + counts.mLength + ", expected: " + mLength);
- }
- native_addCounts(mNativeObject, counts.mNativeObject);
+ native_addCounts(mNativeObject, counts);
}
/**
@@ -330,29 +195,15 @@
* Populates the array with the accumulated counts for the specified state.
*/
public void getCounts(long[] counts, int state) {
- LongArrayContainer container = sTmpArrayContainer.getAndSet(null);
- if (container == null || container.mLength != counts.length) {
- container = new LongArrayContainer(counts.length);
- }
- getCounts(container, state);
- container.getValues(counts);
- sTmpArrayContainer.set(container);
- }
-
- /**
- * Populates longArrayContainer with the accumulated counts for the specified state.
- */
- public void getCounts(LongArrayContainer longArrayContainer, int state) {
if (state < 0 || state >= mStateCount) {
throw new IllegalArgumentException(
"State: " + state + ", outside the range: [0-" + mStateCount + "]");
}
- if (longArrayContainer.mLength != mLength) {
+ if (counts.length != mLength) {
throw new IllegalArgumentException(
- "Invalid array length: " + longArrayContainer.mLength
- + ", expected: " + mLength);
+ "Invalid array length: " + counts.length + ", expected: " + mLength);
}
- native_getCounts(mNativeObject, longArrayContainer.mNativeObject, state);
+ native_getCounts(mNativeObject, counts, state);
}
@Override
@@ -370,18 +221,17 @@
return 0;
}
- public static final Creator<LongArrayMultiStateCounter> CREATOR =
- new Creator<LongArrayMultiStateCounter>() {
- @Override
- public LongArrayMultiStateCounter createFromParcel(Parcel in) {
- return new LongArrayMultiStateCounter(in);
- }
+ public static final Creator<LongArrayMultiStateCounter> CREATOR = new Creator<>() {
+ @Override
+ public LongArrayMultiStateCounter createFromParcel(Parcel in) {
+ return new LongArrayMultiStateCounter(in);
+ }
- @Override
- public LongArrayMultiStateCounter[] newArray(int size) {
- return new LongArrayMultiStateCounter[size];
- }
- };
+ @Override
+ public LongArrayMultiStateCounter[] newArray(int size) {
+ return new LongArrayMultiStateCounter[size];
+ }
+ };
@CriticalNative
@@ -406,34 +256,31 @@
private static native void native_copyStatesFrom(long nativeObjectTarget,
long nativeObjectSource);
- @CriticalNative
+ @FastNative
@RavenwoodRedirect
- private static native void native_setValues(long nativeObject, int state,
- long longArrayContainerNativeObject);
+ private static native void native_setValues(long nativeObject, int state, long[] values);
- @CriticalNative
+ @FastNative
@RavenwoodRedirect
- private static native void native_updateValues(long nativeObject,
- long longArrayContainerNativeObject, long timestampMs);
+ private static native void native_updateValues(long nativeObject, long[] values,
+ long timestampMs);
- @CriticalNative
+ @FastNative
@RavenwoodRedirect
- private static native void native_incrementValues(long nativeObject,
- long longArrayContainerNativeObject, long timestampMs);
+ private static native void native_incrementValues(long nativeObject, long[] values,
+ long timestampMs);
- @CriticalNative
+ @FastNative
@RavenwoodRedirect
- private static native void native_addCounts(long nativeObject,
- long longArrayContainerNativeObject);
+ private static native void native_addCounts(long nativeObject, long[] counts);
@CriticalNative
@RavenwoodRedirect
private static native void native_reset(long nativeObject);
- @CriticalNative
+ @FastNative
@RavenwoodRedirect
- private static native void native_getCounts(long nativeObject,
- long longArrayContainerNativeObject, int state);
+ private static native void native_getCounts(long nativeObject, long[] counts, int state);
@FastNative
@RavenwoodRedirect
diff --git a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
index 30b160a..a69d2e4 100644
--- a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
+++ b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
@@ -28,19 +28,9 @@
public final class RavenwoodEnvironment {
public static final String TAG = "RavenwoodEnvironment";
- private static final RavenwoodEnvironment sInstance;
- private static final Workaround sWorkaround;
+ private static RavenwoodEnvironment sInstance = new RavenwoodEnvironment();
- private RavenwoodEnvironment() {
- }
-
- static {
- sInstance = new RavenwoodEnvironment();
- sWorkaround = new Workaround();
- ensureRavenwoodInitialized();
- }
-
- public static RuntimeException notSupportedOnDevice() {
+ private static RuntimeException notSupportedOnDevice() {
return new UnsupportedOperationException("This method can only be used on Ravenwood");
}
@@ -52,15 +42,6 @@
}
/**
- * Initialize the ravenwood environment if it hasn't happened already, if running on Ravenwood.
- *
- * No-op if called on the device side.
- */
- @RavenwoodRedirect
- public static void ensureRavenwoodInitialized() {
- }
-
- /**
* USE IT SPARINGLY! Returns true if it's running on Ravenwood, hostside test environment.
*
* <p>Using this allows code to behave differently on a real device and on Ravenwood, but
@@ -91,38 +72,10 @@
}
/**
- * See {@link Workaround}. It's only usable on Ravenwood.
- */
- @RavenwoodReplace
- public static Workaround workaround() {
- throw notSupportedOnDevice();
- }
-
- private static Workaround workaround$ravenwood() {
- return sWorkaround;
- }
-
- /**
* @return the "ravenwood-runtime" directory.
*/
@RavenwoodRedirect
public String getRavenwoodRuntimePath() {
throw notSupportedOnDevice();
}
-
- /**
- * A set of APIs used to work around missing features on Ravenwood. Ideally, this class should
- * be empty, and all its APIs should be able to be implemented properly.
- */
- public static class Workaround {
- Workaround() {
- }
-
- /**
- * @return whether the app's target SDK level is at least Q.
- */
- public boolean isTargetSdkAtLeastQ() {
- return true;
- }
- }
}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 2bb6e71..2541258 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -76,7 +76,6 @@
"android_content_res_ApkAssets.cpp",
"android_os_SystemClock.cpp",
"android_os_SystemProperties.cpp",
- "android_os_Trace.cpp",
"android_text_AndroidCharacter.cpp",
"android_util_AssetManager.cpp",
"android_util_EventLog.cpp",
@@ -104,10 +103,6 @@
"system/media/private/camera/include",
],
- shared_libs: [
- "libtracing_perfetto",
- ],
-
static_libs: [
"libziparchive_for_incfs",
"libguiflags",
@@ -190,6 +185,7 @@
"android_os_ServiceManagerNative.cpp",
"android_os_SharedMemory.cpp",
"android_os_storage_StorageManager.cpp",
+ "android_os_Trace.cpp",
"android_os_UEventObserver.cpp",
"android_os_incremental_IncrementalManager.cpp",
"android_net_LocalSocketImpl.cpp",
diff --git a/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp b/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp
index bea5ffe..a5b5057 100644
--- a/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp
+++ b/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp
@@ -49,27 +49,26 @@
* to the supplied multi-state counter in accordance with the counter's state.
*/
static jboolean addCpuTimeInFreqDelta(
- jint uid, jlong counterNativePtr, jlong timestampMs,
+ JNIEnv *env, jint uid, jlong counterNativePtr, jlong timestampMs,
std::optional<std::vector<std::vector<uint64_t>>> timeInFreqDataNanos,
- jlong deltaOutContainerNativePtr) {
+ jlongArray deltaOut) {
if (!timeInFreqDataNanos) {
return false;
}
- battery::LongArrayMultiStateCounter *counter =
- reinterpret_cast<battery::LongArrayMultiStateCounter *>(counterNativePtr);
+ auto counter = reinterpret_cast<battery::LongArrayMultiStateCounter *>(counterNativePtr);
size_t s = 0;
for (const auto &cluster : *timeInFreqDataNanos) s += cluster.size();
- std::vector<uint64_t> flattened;
- flattened.reserve(s);
- auto offset = flattened.begin();
+ battery::Uint64ArrayRW flattened(s);
+ uint64_t *out = flattened.dataRW();
+ auto offset = out;
for (const auto &cluster : *timeInFreqDataNanos) {
- flattened.insert(offset, cluster.begin(), cluster.end());
+ memcpy(offset, cluster.data(), cluster.size() * sizeof(uint64_t));
offset += cluster.size();
}
for (size_t i = 0; i < s; ++i) {
- flattened[i] /= NSEC_PER_MSEC;
+ out[i] /= NSEC_PER_MSEC;
}
if (s != counter->getCount(0).size()) { // Counter has at least one state
ALOGE("Mismatch between eBPF data size (%d) and the counter size (%d)", (int)s,
@@ -77,29 +76,32 @@
return false;
}
- const std::vector<uint64_t> &delta = counter->updateValue(flattened, timestampMs);
- if (deltaOutContainerNativePtr) {
- std::vector<uint64_t> *vector =
- reinterpret_cast<std::vector<uint64_t> *>(deltaOutContainerNativePtr);
- *vector = delta;
+ const battery::Uint64Array &delta = counter->updateValue(flattened, timestampMs);
+ if (deltaOut) {
+ ScopedLongArrayRW scopedArray(env, deltaOut);
+ uint64_t *array = reinterpret_cast<uint64_t *>(scopedArray.get());
+ if (delta.data() != nullptr) {
+ memcpy(array, delta.data(), s * sizeof(uint64_t));
+ } else {
+ memset(array, 0, s * sizeof(uint64_t));
+ }
}
return true;
}
-static jboolean addDeltaFromBpf(jint uid, jlong counterNativePtr, jlong timestampMs,
- jlong deltaOutContainerNativePtr) {
- return addCpuTimeInFreqDelta(uid, counterNativePtr, timestampMs,
- android::bpf::getUidCpuFreqTimes(uid), deltaOutContainerNativePtr);
+static jboolean addDeltaFromBpf(JNIEnv *env, jlong self, jint uid, jlong counterNativePtr,
+ jlong timestampMs, jlongArray deltaOut) {
+ return addCpuTimeInFreqDelta(env, uid, counterNativePtr, timestampMs,
+ android::bpf::getUidCpuFreqTimes(uid), deltaOut);
}
static jboolean addDeltaForTest(JNIEnv *env, jclass, jint uid, jlong counterNativePtr,
jlong timestampMs, jobjectArray timeInFreqDataNanos,
- jlong deltaOutContainerNativePtr) {
+ jlongArray deltaOut) {
if (!timeInFreqDataNanos) {
- return addCpuTimeInFreqDelta(uid, counterNativePtr, timestampMs,
- std::optional<std::vector<std::vector<uint64_t>>>(),
- deltaOutContainerNativePtr);
+ return addCpuTimeInFreqDelta(env, uid, counterNativePtr, timestampMs,
+ std::optional<std::vector<std::vector<uint64_t>>>(), deltaOut);
}
std::vector<std::vector<uint64_t>> timeInFreqData;
@@ -113,18 +115,16 @@
}
timeInFreqData.push_back(cluster);
}
- return addCpuTimeInFreqDelta(uid, counterNativePtr, timestampMs, std::optional(timeInFreqData),
- deltaOutContainerNativePtr);
+ return addCpuTimeInFreqDelta(env, uid, counterNativePtr, timestampMs,
+ std::optional(timeInFreqData), deltaOut);
}
static const JNINativeMethod g_single_methods[] = {
{"readBpfData", "(I)[J", (void *)getUidCpuFreqTimeMs},
-
- // @CriticalNative
- {"addDeltaFromBpf", "(IJJJ)Z", (void *)addDeltaFromBpf},
+ {"addDeltaFromBpf", "(IJJ[J)Z", (void *)addDeltaFromBpf},
// Used for testing
- {"addDeltaForTest", "(IJJ[[JJ)Z", (void *)addDeltaForTest},
+ {"addDeltaForTest", "(IJJ[[J[J)Z", (void *)addDeltaForTest},
};
int register_com_android_internal_os_KernelSingleUidTimeReader(JNIEnv *env) {
diff --git a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
index b3c41df..7ffe0ed 100644
--- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
@@ -26,16 +26,40 @@
#include "core_jni_helpers.h"
namespace android {
+namespace battery {
+
+/**
+ * Implementation of Uint64Array that wraps a Java long[]. Since it uses the "critical"
+ * version of JNI array access (halting GC), any usage of this class must be extra quick.
+ */
+class JavaUint64Array : public Uint64Array {
+ JNIEnv *mEnv;
+ jlongArray mJavaArray;
+ uint64_t *mData;
+
+public:
+ JavaUint64Array(JNIEnv *env, jlongArray values) : Uint64Array(env->GetArrayLength(values)) {
+ mEnv = env;
+ mJavaArray = values;
+ mData = reinterpret_cast<uint64_t *>(mEnv->GetPrimitiveArrayCritical(mJavaArray, nullptr));
+ }
+
+ ~JavaUint64Array() override {
+ mEnv->ReleasePrimitiveArrayCritical(mJavaArray, mData, 0);
+ }
+
+ const uint64_t *data() const override {
+ return mData;
+ }
+};
static jlong native_init(jint stateCount, jint arrayLength) {
- battery::LongArrayMultiStateCounter *counter =
- new battery::LongArrayMultiStateCounter(stateCount, std::vector<uint64_t>(arrayLength));
+ auto *counter = new LongArrayMultiStateCounter(stateCount, Uint64Array(arrayLength));
return reinterpret_cast<jlong>(counter);
}
static void native_dispose(void *nativePtr) {
- battery::LongArrayMultiStateCounter *counter =
- reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+ auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
delete counter;
}
@@ -44,80 +68,63 @@
}
static void native_setEnabled(jlong nativePtr, jboolean enabled, jlong timestamp) {
- battery::LongArrayMultiStateCounter *counter =
- reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+ auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
counter->setEnabled(enabled, timestamp);
}
static void native_setState(jlong nativePtr, jint state, jlong timestamp) {
- battery::LongArrayMultiStateCounter *counter =
- reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+ auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
counter->setState(state, timestamp);
}
static void native_copyStatesFrom(jlong nativePtrTarget, jlong nativePtrSource) {
- battery::LongArrayMultiStateCounter *counterTarget =
- reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtrTarget);
- battery::LongArrayMultiStateCounter *counterSource =
- reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtrSource);
+ auto *counterTarget = reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtrTarget);
+ auto *counterSource = reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtrSource);
counterTarget->copyStatesFrom(*counterSource);
}
-static void native_setValues(jlong nativePtr, jint state, jlong longArrayContainerNativePtr) {
- battery::LongArrayMultiStateCounter *counter =
- reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
- std::vector<uint64_t> *vector =
- reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr);
-
- counter->setValue(state, *vector);
+static void native_setValues(JNIEnv *env, jclass, jlong nativePtr, jint state, jlongArray values) {
+ auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
+ counter->setValue(state, JavaUint64Array(env, values));
}
-static void native_updateValues(jlong nativePtr, jlong longArrayContainerNativePtr,
+static void native_updateValues(JNIEnv *env, jclass, jlong nativePtr, jlongArray values,
jlong timestamp) {
- battery::LongArrayMultiStateCounter *counter =
- reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
- std::vector<uint64_t> *vector =
- reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr);
-
- counter->updateValue(*vector, timestamp);
+ auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
+ counter->updateValue(JavaUint64Array(env, values), timestamp);
}
-static void native_incrementValues(jlong nativePtr, jlong longArrayContainerNativePtr,
+static void native_incrementValues(JNIEnv *env, jclass, jlong nativePtr, jlongArray values,
jlong timestamp) {
- battery::LongArrayMultiStateCounter *counter =
- reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
- std::vector<uint64_t> *vector =
- reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr);
-
- counter->incrementValue(*vector, timestamp);
+ auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
+ counter->incrementValue(JavaUint64Array(env, values), timestamp);
}
-static void native_addCounts(jlong nativePtr, jlong longArrayContainerNativePtr) {
- battery::LongArrayMultiStateCounter *counter =
- reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
- std::vector<uint64_t> *vector =
- reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr);
- counter->addValue(*vector);
+static void native_addCounts(JNIEnv *env, jclass, jlong nativePtr, jlongArray values) {
+ auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
+ counter->addValue(JavaUint64Array(env, values));
}
static void native_reset(jlong nativePtr) {
- battery::LongArrayMultiStateCounter *counter =
- reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+ auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
counter->reset();
}
-static void native_getCounts(jlong nativePtr, jlong longArrayContainerNativePtr, jint state) {
- battery::LongArrayMultiStateCounter *counter =
- reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
- std::vector<uint64_t> *vector =
- reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr);
-
- *vector = counter->getCount(state);
+static void native_getCounts(JNIEnv *env, jclass, jlong nativePtr, jlongArray values, jint state) {
+ auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
+ ScopedLongArrayRW scopedArray(env, values);
+ auto *data = counter->getCount(state).data();
+ auto size = env->GetArrayLength(values);
+ auto *outData = scopedArray.get();
+ if (data == nullptr) {
+ memset(outData, 0, size * sizeof(uint64_t));
+ } else {
+ memcpy(outData, data, size * sizeof(uint64_t));
+ }
}
static jobject native_toString(JNIEnv *env, jclass, jlong nativePtr) {
- battery::LongArrayMultiStateCounter *counter =
- reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+ auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
return env->NewStringUTF(counter->toString().c_str());
}
@@ -137,20 +144,26 @@
static void native_writeToParcel(JNIEnv *env, jclass, jlong nativePtr, jobject jParcel,
jint flags) {
- battery::LongArrayMultiStateCounter *counter =
- reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+ auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
ndk::ScopedAParcel parcel(AParcel_fromJavaParcel(env, jParcel));
uint16_t stateCount = counter->getStateCount();
THROW_AND_RETURN_ON_WRITE_ERROR(AParcel_writeInt32(parcel.get(), stateCount));
// LongArrayMultiStateCounter has at least state 0
- const std::vector<uint64_t> &anyState = counter->getCount(0);
+ const Uint64Array &anyState = counter->getCount(0);
THROW_AND_RETURN_ON_WRITE_ERROR(AParcel_writeInt32(parcel.get(), anyState.size()));
for (battery::state_t state = 0; state < stateCount; state++) {
- THROW_AND_RETURN_ON_WRITE_ERROR(
- ndk::AParcel_writeVector(parcel.get(), counter->getCount(state)));
+ const Uint64Array &value = counter->getCount(state);
+ if (value.data() == nullptr) {
+ THROW_AND_RETURN_ON_WRITE_ERROR(AParcel_writeBool(parcel.get(), false));
+ } else {
+ THROW_AND_RETURN_ON_WRITE_ERROR(AParcel_writeBool(parcel.get(), true));
+ for (size_t i = 0; i < anyState.size(); i++) {
+ THROW_AND_RETURN_ON_WRITE_ERROR(AParcel_writeUint64(parcel.get(), value.data()[i]));
+ }
+ }
}
}
@@ -183,40 +196,37 @@
int32_t arrayLength;
THROW_AND_RETURN_ON_READ_ERROR(AParcel_readInt32(parcel.get(), &arrayLength));
- auto counter = std::make_unique<battery::LongArrayMultiStateCounter>(stateCount,
- std::vector<uint64_t>(
- arrayLength));
-
- std::vector<uint64_t> value;
- value.reserve(arrayLength);
-
+ auto counter =
+ std::make_unique<LongArrayMultiStateCounter>(stateCount, Uint64Array(arrayLength));
+ Uint64ArrayRW array(arrayLength);
for (battery::state_t state = 0; state < stateCount; state++) {
- THROW_AND_RETURN_ON_READ_ERROR(ndk::AParcel_readVector(parcel.get(), &value));
- counter->setValue(state, value);
+ bool hasValues;
+ THROW_AND_RETURN_ON_READ_ERROR(AParcel_readBool(parcel.get(), &hasValues));
+ if (hasValues) {
+ for (int i = 0; i < arrayLength; i++) {
+ THROW_AND_RETURN_ON_READ_ERROR(
+ AParcel_readUint64(parcel.get(), &(array.dataRW()[i])));
+ }
+ counter->setValue(state, array);
+ }
}
return reinterpret_cast<jlong>(counter.release());
}
static jint native_getStateCount(jlong nativePtr) {
- battery::LongArrayMultiStateCounter *counter =
- reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+ auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
return counter->getStateCount();
}
static jint native_getArrayLength(jlong nativePtr) {
- battery::LongArrayMultiStateCounter *counter =
- reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+ auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
// LongArrayMultiStateCounter has at least state 0
- const std::vector<uint64_t> &anyState = counter->getCount(0);
+ const Uint64Array &anyState = counter->getCount(0);
return anyState.size();
}
-static jlong native_init_LongArrayContainer(jint length) {
- return reinterpret_cast<jlong>(new std::vector<uint64_t>(length));
-}
-
static const JNINativeMethod g_LongArrayMultiStateCounter_methods[] = {
// @CriticalNative
{"native_init", "(II)J", (void *)native_init},
@@ -228,18 +238,18 @@
{"native_setState", "(JIJ)V", (void *)native_setState},
// @CriticalNative
{"native_copyStatesFrom", "(JJ)V", (void *)native_copyStatesFrom},
- // @CriticalNative
- {"native_setValues", "(JIJ)V", (void *)native_setValues},
- // @CriticalNative
- {"native_updateValues", "(JJJ)V", (void *)native_updateValues},
- // @CriticalNative
- {"native_incrementValues", "(JJJ)V", (void *)native_incrementValues},
- // @CriticalNative
- {"native_addCounts", "(JJ)V", (void *)native_addCounts},
+ // @FastNative
+ {"native_setValues", "(JI[J)V", (void *)native_setValues},
+ // @FastNative
+ {"native_updateValues", "(J[JJ)V", (void *)native_updateValues},
+ // @FastNative
+ {"native_incrementValues", "(J[JJ)V", (void *)native_incrementValues},
+ // @FastNative
+ {"native_addCounts", "(J[J)V", (void *)native_addCounts},
// @CriticalNative
{"native_reset", "(J)V", (void *)native_reset},
- // @CriticalNative
- {"native_getCounts", "(JJI)V", (void *)native_getCounts},
+ // @FastNative
+ {"native_getCounts", "(J[JI)V", (void *)native_getCounts},
// @FastNative
{"native_toString", "(J)Ljava/lang/String;", (void *)native_toString},
// @FastNative
@@ -252,91 +262,12 @@
{"native_getArrayLength", "(J)I", (void *)native_getArrayLength},
};
-/////////////////////// LongArrayMultiStateCounter.LongArrayContainer ////////////////////////
-
-static void native_dispose_LongArrayContainer(jlong nativePtr) {
- std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
- delete vector;
-}
-
-static jlong native_getReleaseFunc_LongArrayContainer() {
- return reinterpret_cast<jlong>(native_dispose_LongArrayContainer);
-}
-
-static void native_setValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr,
- jlongArray jarray) {
- std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
- ScopedLongArrayRO scopedArray(env, jarray);
- const uint64_t *array = reinterpret_cast<const uint64_t *>(scopedArray.get());
- uint8_t size = scopedArray.size();
-
- // Boundary checks are performed in the Java layer
- std::copy(array, array + size, vector->data());
-}
-
-static void native_getValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr,
- jlongArray jarray) {
- std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
- ScopedLongArrayRW scopedArray(env, jarray);
-
- // Boundary checks are performed in the Java layer
- std::copy(vector->data(), vector->data() + vector->size(), scopedArray.get());
-}
-
-static jboolean native_combineValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr,
- jlongArray jarray, jintArray jindexMap) {
- std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
- ScopedLongArrayRW scopedArray(env, jarray);
- ScopedIntArrayRO scopedIndexMap(env, jindexMap);
-
- const uint64_t *data = vector->data();
- uint64_t *array = reinterpret_cast<uint64_t *>(scopedArray.get());
- const uint8_t size = scopedArray.size();
-
- for (int i = 0; i < size; i++) {
- array[i] = 0;
- }
-
- bool nonZero = false;
- for (size_t i = 0; i < vector->size(); i++) {
- jint index = scopedIndexMap[i];
- if (index < 0 || index >= size) {
- jniThrowExceptionFmt(env, "java/lang/IndexOutOfBoundsException",
- "Index %d is out of bounds: [0, %d]", index, size - 1);
- return false;
- }
-
- if (data[i] != 0L) {
- array[index] += data[i];
- nonZero = true;
- }
- }
-
- return nonZero;
-}
-
-static const JNINativeMethod g_LongArrayContainer_methods[] = {
- // @CriticalNative
- {"native_init", "(I)J", (void *)native_init_LongArrayContainer},
- // @CriticalNative
- {"native_getReleaseFunc", "()J", (void *)native_getReleaseFunc_LongArrayContainer},
- // @FastNative
- {"native_setValues", "(J[J)V", (void *)native_setValues_LongArrayContainer},
- // @FastNative
- {"native_getValues", "(J[J)V", (void *)native_getValues_LongArrayContainer},
- // @FastNative
- {"native_combineValues", "(J[J[I)Z", (void *)native_combineValues_LongArrayContainer},
-};
+} // namespace battery
int register_com_android_internal_os_LongArrayMultiStateCounter(JNIEnv *env) {
// 0 represents success, thus "|" and not "&"
return RegisterMethodsOrDie(env, "com/android/internal/os/LongArrayMultiStateCounter",
- g_LongArrayMultiStateCounter_methods,
- NELEM(g_LongArrayMultiStateCounter_methods)) |
- RegisterMethodsOrDie(env,
- "com/android/internal/os/LongArrayMultiStateCounter"
- "$LongArrayContainer",
- g_LongArrayContainer_methods, NELEM(g_LongArrayContainer_methods));
+ battery::g_LongArrayMultiStateCounter_methods,
+ NELEM(battery::g_LongArrayMultiStateCounter_methods));
}
-
} // namespace android
diff --git a/core/jni/com_android_internal_os_LongMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
index 56d3fbb..b3bfd0bc 100644
--- a/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
@@ -28,7 +28,7 @@
namespace battery {
-typedef battery::MultiStateCounter<int64_t> LongMultiStateCounter;
+typedef battery::MultiStateCounter<int64_t, int64_t> LongMultiStateCounter;
template <>
bool LongMultiStateCounter::delta(const int64_t &previousValue, const int64_t &newValue,
@@ -47,12 +47,6 @@
*value1 += value2;
}
}
-
-template <>
-std::string LongMultiStateCounter::valueToString(const int64_t &v) const {
- return std::to_string(v);
-}
-
} // namespace battery
static inline battery::LongMultiStateCounter *asLongMultiStateCounter(const jlong nativePtr) {
diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp
index 3747299..7fca117 100644
--- a/core/jni/platform/host/HostRuntime.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -87,7 +87,6 @@
extern int register_android_os_Parcel(JNIEnv* env);
extern int register_android_os_SystemClock(JNIEnv* env);
extern int register_android_os_SystemProperties(JNIEnv* env);
-extern int register_android_os_Trace(JNIEnv* env);
extern int register_android_text_AndroidCharacter(JNIEnv* env);
extern int register_android_util_EventLog(JNIEnv* env);
extern int register_android_util_Log(JNIEnv* env);
@@ -133,7 +132,6 @@
#endif
{"android.os.SystemClock", REG_JNI(register_android_os_SystemClock)},
{"android.os.SystemProperties", REG_JNI(register_android_os_SystemProperties)},
- {"android.os.Trace", REG_JNI(register_android_os_Trace)},
{"android.text.AndroidCharacter", REG_JNI(register_android_text_AndroidCharacter)},
{"android.util.EventLog", REG_JNI(register_android_util_EventLog)},
{"android.util.Log", REG_JNI(register_android_util_Log)},
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 654d83c..407790c 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -465,6 +465,7 @@
repeated .android.graphics.RectProto unrestricted_keep_clear_areas = 46;
repeated .android.view.InsetsSourceProto mergedLocalInsetsSources = 47;
optional int32 requested_visible_types = 48;
+ optional .android.graphics.RectProto dim_bounds = 49;
}
message IdentifierProto {
diff --git a/core/proto/android/service/appwidget.proto b/core/proto/android/service/appwidget.proto
index 97350ef..fb90719 100644
--- a/core/proto/android/service/appwidget.proto
+++ b/core/proto/android/service/appwidget.proto
@@ -20,6 +20,8 @@
option java_multiple_files = true;
option java_outer_classname = "AppWidgetServiceProto";
+import "frameworks/base/core/proto/android/widget/remoteviews.proto";
+
// represents the object holding the dump info of the app widget service
message AppWidgetServiceDumpProto {
repeated WidgetProto widgets = 1; // the array of bound widgets
@@ -38,3 +40,14 @@
optional int32 maxHeight = 9;
optional bool restoreCompleted = 10;
}
+
+// represents a set of widget previews for a particular provider
+message GeneratedPreviewsProto {
+ repeated Preview previews = 1;
+
+ // represents a particular RemoteViews preview, which may be set for multiple categories
+ message Preview {
+ repeated int32 widget_categories = 1;
+ optional android.widget.RemoteViewsProto views = 2;
+ }
+}
\ No newline at end of file
diff --git a/core/res/res/drawable-watch/ic_lock_bugreport.xml b/core/res/res/drawable-watch/ic_lock_bugreport.xml
index b664fe4f..35834be 100644
--- a/core/res/res/drawable-watch/ic_lock_bugreport.xml
+++ b/core/res/res/drawable-watch/ic_lock_bugreport.xml
@@ -13,19 +13,6 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24.0dp"
- android:height="24.0dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:tint="@android:color/white">
- <path
- android:fillColor="@android:color/white"
- android:pathData="M20,10V8h-2.81c-0.45,-0.78 -1.07,-1.46 -1.82,-1.96L17,4.41L15.59,3l-2.17,2.17c-0.03,-0.01 -0.05,-0.01 -0.08,-0.01c-0.16,-0.04 -0.32,-0.06 -0.49,-0.09c-0.06,-0.01 -0.11,-0.02 -0.17,-0.03C12.46,5.02 12.23,5 12,5h0c-0.49,0 -0.97,0.07 -1.42,0.18l0.02,-0.01L8.41,3L7,4.41l1.62,1.63l0.01,0C7.88,6.54 7.26,7.22 6.81,8H4v2h2.09C6.03,10.33 6,10.66 6,11v1H4v2h2v1c0,0.34 0.04,0.67 0.09,1H4v2h2.81c1.04,1.79 2.97,3 5.19,3h0c2.22,0 4.15,-1.21 5.19,-3H20v-2h-2.09l0,0c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1l0,0H20zM16,15c0,2.21 -1.79,4 -4,4c-2.21,0 -4,-1.79 -4,-4v-4c0,-2.21 1.79,-4 4,-4h0c2.21,0 4,1.79 4,4V15z"/>
- <path
- android:fillColor="@android:color/white"
- android:pathData="M10,14h4v2h-4z"/>
- <path
- android:fillColor="@android:color/white"
- android:pathData="M10,10h4v2h-4z"/>
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="960" android:viewportHeight="960" android:tint="?attr/colorControlNormal">
+<path android:fillColor="@android:color/white" android:pathData="M480.01,848.13Q412.37,848.13 354.6,814.34Q296.83,780.54 263.87,721.91L193.78,721.91Q175.97,721.91 163.92,709.86Q151.87,697.81 151.87,680Q151.87,662.19 163.92,650.14Q175.97,638.09 193.78,638.09L235.63,638.09Q232.81,619.12 232.34,600.16Q231.87,581.2 231.87,561.91L193.78,561.91Q175.97,561.91 163.92,549.86Q151.87,537.81 151.87,520Q151.87,502.19 163.92,490.14Q175.97,478.09 193.78,478.09L231.87,478.09Q231.87,458.8 232.34,439.84Q232.81,420.88 235.63,401.91L193.78,401.91Q175.97,401.91 163.92,389.86Q151.87,377.81 151.87,360Q151.87,342.19 163.92,330.14Q175.97,318.09 193.78,318.09L265.07,318.09Q278.11,294.13 295.97,273.77Q313.83,253.41 337.07,237.93L301.26,201.37Q289.54,189.65 289.66,172.32Q289.78,154.98 302.26,142.5Q313.98,130.78 331.7,130.78Q349.41,130.78 361.13,142.5L417.7,199.07Q447.13,188.63 477.92,188.39Q508.72,188.15 538.15,198.35L597.2,140.3Q608.79,128.59 626.19,128.59Q643.59,128.59 656.07,141.07Q667.78,152.78 667.78,170.5Q667.78,188.22 656.07,199.93L619.98,236.02Q644.41,251.98 663.51,273.03Q682.61,294.09 696.38,320L767.17,320Q784.41,320 796.27,331.86Q808.13,343.72 808.13,360.96Q808.13,378.2 796.27,390.05Q784.41,401.91 767.17,401.91L724.37,401.91Q727.19,420.88 727.66,439.84Q728.13,458.8 728.13,478.09L766.22,478.09Q784.03,478.09 796.08,490.14Q808.13,502.19 808.13,520Q808.13,537.81 796.08,549.86Q784.03,561.91 766.22,561.91L728.13,561.91Q728.13,581.2 727.51,600.12Q726.89,619.04 724.13,638.09L766.22,638.09Q784.03,638.09 796.08,650.14Q808.13,662.19 808.13,680Q808.13,697.81 796.08,709.86Q784.03,721.91 766.22,721.91L696.13,721.91Q663.17,780.54 605.42,814.34Q547.66,848.13 480.01,848.13ZM441.91,641.91L518.09,641.91Q535.9,641.91 547.95,629.86Q560,617.81 560,600Q560,582.19 547.95,570.14Q535.9,558.09 518.09,558.09L441.91,558.09Q424.1,558.09 412.05,570.14Q400,582.19 400,600Q400,617.81 412.05,629.86Q424.1,641.91 441.91,641.91ZM441.91,481.91L518.09,481.91Q535.9,481.91 547.95,469.86Q560,457.81 560,440Q560,422.19 547.95,410.14Q535.9,398.09 518.09,398.09L441.91,398.09Q424.1,398.09 412.05,410.14Q400,422.19 400,440Q400,457.81 412.05,469.86Q424.1,481.91 441.91,481.91Z"/>
</vector>
diff --git a/core/res/res/drawable-watch/ic_lock_power_off.xml b/core/res/res/drawable-watch/ic_lock_power_off.xml
index b437a4b..c42d7d2 100644
--- a/core/res/res/drawable-watch/ic_lock_power_off.xml
+++ b/core/res/res/drawable-watch/ic_lock_power_off.xml
@@ -14,13 +14,6 @@
~ limitations under the License.
-->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24"
- android:viewportHeight="24"
- android:tint="@android:color/white">
- <path
- android:fillColor="@android:color/white"
- android:pathData="M11,2h2v10h-2zM18.37,5.64l-1.41,1.41c2.73,2.73 2.72,7.16 -0.01,9.89 -2.73,2.73 -7.17,2.73 -9.89,0.01 -2.73,-2.73 -2.74,-7.18 -0.01,-9.91l-1.41,-1.4c-3.51,3.51 -3.51,9.21 0.01,12.73 3.51,3.51 9.21,3.51 12.72,-0.01 3.51,-3.51 3.51,-9.2 0,-12.72z"/>
-</vector>
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="960" android:viewportHeight="960" android:tint="?attr/colorControlNormal">
+<path android:fillColor="@android:color/white" android:pathData="M480,888.13Q395.09,888.13 320.65,856.03Q246.22,823.93 191.14,768.86Q136.07,713.78 103.97,639.35Q71.87,564.91 71.87,480Q71.87,406.52 96.01,340.78Q120.15,275.04 162.91,222.33Q175.11,206.65 192.52,207.41Q209.93,208.17 222.61,219.37Q235.28,230.57 239.26,248.84Q243.24,267.11 227.8,287.22Q197.48,327.26 180.17,376.09Q162.87,424.91 162.87,480Q162.87,613.04 254.91,705.09Q346.96,797.13 480,797.13Q613.04,797.13 705.09,705.09Q797.13,613.04 797.13,480Q797.13,424.91 779.83,376.09Q762.52,327.26 732.2,287.22Q716.76,267.11 720.74,248.84Q724.72,230.57 737.39,219.37Q750.07,208.17 767.48,207.41Q784.89,206.65 797.09,222.33Q839.85,275.04 863.99,340.78Q888.13,406.52 888.13,480Q888.13,564.91 856.03,639.35Q823.93,713.78 768.86,768.86Q713.78,823.93 639.35,856.03Q564.91,888.13 480,888.13ZM480,525.5Q460.85,525.5 447.67,512.33Q434.5,499.15 434.5,480L434.5,117.37Q434.5,98.22 447.67,85.04Q460.85,71.87 480,71.87Q499.15,71.87 512.33,85.04Q525.5,98.22 525.5,117.37L525.5,480Q525.5,499.15 512.33,512.33Q499.15,525.5 480,525.5Z"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable-watch/ic_restart.xml b/core/res/res/drawable-watch/ic_restart.xml
index 52933aa..ddcfd25 100644
--- a/core/res/res/drawable-watch/ic_restart.xml
+++ b/core/res/res/drawable-watch/ic_restart.xml
@@ -14,13 +14,6 @@
~ limitations under the License.
-->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24"
- android:viewportHeight="24"
- android:tint="@android:color/white">
- <path
- android:fillColor="@android:color/white"
- android:pathData="M6,13c0,-1.65 0.67,-3.15 1.76,-4.24L6.34,7.34C4.9,8.79 4,10.79 4,13c0,4.08 3.05,7.44 7,7.93v-2.02c-2.83,-0.48 -5,-2.94 -5,-5.91zM20,13c0,-4.42 -3.58,-8 -8,-8 -0.06,0 -0.12,0.01 -0.18,0.01l1.09,-1.09L11.5,2.5 8,6l3.5,3.5 1.41,-1.41 -1.08,-1.08c0.06,0 0.12,-0.01 0.17,-0.01 3.31,0 6,2.69 6,6 0,2.97 -2.17,5.43 -5,5.91v2.02c3.95,-0.49 7,-3.85 7,-7.93z"/>
-</vector>
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="960" android:viewportHeight="960" android:tint="?attr/colorControlNormal">
+<path android:fillColor="@android:color/white" android:pathData="M385.83,834.46Q282.35,803.54 217.11,717.61Q151.87,631.67 151.87,520.48Q151.87,464.91 169.91,414.25Q187.96,363.59 220.8,320.83Q232.76,305.72 252.11,304.98Q271.46,304.24 286.85,319.63Q298.57,331.35 299.3,348.66Q300.04,365.98 288.8,381.41Q266.72,411.22 254.79,446.54Q242.87,481.87 242.87,520.48Q242.87,599.33 288.46,661.27Q334.04,723.22 406.41,746.7Q421.09,751.65 430.54,764.09Q440,776.52 440,790.96Q440,814.3 423.73,827.6Q407.46,840.89 385.83,834.46ZM574.17,834.46Q552.54,840.89 536.27,827.22Q520,813.54 520,790.2Q520,776.76 529.46,764.21Q538.91,751.65 553.59,746.7Q625.72,722.22 671.42,660.65Q717.13,599.09 717.13,520.48Q717.13,423.11 649.64,354.3Q582.15,285.5 485.02,283.59L481.07,283.59L497.3,299.83Q509.02,311.54 509.02,329.14Q509.02,346.74 497.3,358.46Q485.59,370.17 467.99,370.17Q450.39,370.17 438.67,358.46L348.46,268.24Q341.74,261.52 338.76,253.45Q335.78,245.37 335.78,236.41Q335.78,227.46 338.76,219.38Q341.74,211.3 348.46,204.59L438.67,114.13Q450.39,102.41 467.99,102.41Q485.59,102.41 497.3,114.13Q509.02,125.85 509.02,143.45Q509.02,161.04 497.3,172.76L477.72,192.35L481.91,192.35Q618.54,192.35 713.34,287.98Q808.13,383.61 808.13,520.48Q808.13,630.91 742.89,717.11Q677.65,803.3 574.17,834.46Z"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable-watch/ic_settings.xml b/core/res/res/drawable-watch/ic_settings.xml
new file mode 100644
index 0000000..cef10e9
--- /dev/null
+++ b/core/res/res/drawable-watch/ic_settings.xml
@@ -0,0 +1,19 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="960" android:viewportHeight="960" android:tint="?attr/colorControlNormal">
+<path android:fillColor="@android:color/white" android:pathData="M428.46,888.13Q400.26,888.13 379.92,869.53Q359.59,850.93 355.59,823.74L346.59,757.5Q335.5,753.22 325.55,747.17Q315.61,741.13 306.04,734.33L244.28,760.33Q218.33,771.57 192.13,762.45Q165.93,753.33 151.46,729.13L99.91,638.76Q85.43,614.8 91.55,587.73Q97.67,560.65 119.63,543.17L172.39,503.17Q171.63,497.13 171.63,491.59Q171.63,486.04 171.63,480Q171.63,473.96 171.63,468.41Q171.63,462.87 172.39,456.83L119.63,417.07Q97.43,399.59 91.43,372.51Q85.43,345.43 99.91,321.24L151.46,231.11Q165.93,207.15 192.01,197.91Q218.09,188.67 244.04,199.91L306.52,225.91Q316.09,219.11 326.17,213.18Q336.26,207.26 346.59,202.98L355.59,136.5Q359.59,109.07 379.92,90.47Q400.26,71.87 428.46,71.87L531.54,71.87Q559.74,71.87 580.08,90.47Q600.41,109.07 604.41,136.5L613.41,202.98Q624.5,207.26 634.45,213.18Q644.39,219.11 653.96,225.91L715.72,199.91Q741.67,188.67 767.87,197.91Q794.07,207.15 808.54,231.11L860.09,321.24Q874.57,345.43 868.57,372.51Q862.57,399.59 840.37,417.07L787.37,456.83Q788.13,462.87 788.13,468.41Q788.13,473.96 788.13,480Q788.13,486.04 788.01,491.59Q787.89,497.13 786.37,503.17L839.37,542.93Q861.57,560.41 867.57,587.49Q873.57,614.57 859.09,638.76L806.54,729.13Q792.07,753.09 765.99,762.33Q739.91,771.57 713.96,760.33L653.48,734.33Q643.91,741.13 633.83,747.17Q623.74,753.22 613.41,757.5L604.41,823.74Q600.41,850.93 580.08,869.53Q559.74,888.13 531.54,888.13L428.46,888.13ZM481.28,620Q539.28,620 580.28,579Q621.28,538 621.28,480Q621.28,422 580.28,381Q539.28,340 481.28,340Q422.52,340 381.9,381Q341.28,422 341.28,480Q341.28,538 381.9,579Q422.52,620 481.28,620Z"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ac9bb93..7402a2f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3087,6 +3087,12 @@
<!-- Whether UI for multi user should be shown -->
<bool name="config_enableMultiUserUI">false</bool>
+ <!-- Indicates the boot strategy in Headless System User Mode (HSUM)
+ This config has no effect if the device is not in HSUM.
+ 0 (Default) : boot to the previous foreground user if there is one, otherwise the first switchable user.
+ 1 : boot to the first switchable full user for initial boot (unprovisioned device), else to the headless system user, i.e. user 0. -->
+ <integer name="config_hsumBootStrategy">0</integer>
+
<!-- Whether to boot system with the headless system user, i.e. user 0. If set to true,
system will be booted with the headless system user, or user 0. It has no effect if device
is not in Headless System User Mode (HSUM). -->
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 779422a..31e9913 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -472,10 +472,13 @@
<integer name="config_mt_sms_polling_throttle_millis">300000</integer>
<java-symbol type="integer" name="config_mt_sms_polling_throttle_millis" />
-
<!-- The receiver class of the intent that hidden menu sends to start satellite non-emergency mode -->
<string name="config_satellite_carrier_roaming_non_emergency_session_class" translatable="false"></string>
<java-symbol type="string" name="config_satellite_carrier_roaming_non_emergency_session_class" />
+ <!-- Whether to show the system notification to users whenever there is a change
+ in the satellite availability state at the current location. -->
+ <bool name="config_satellite_should_notify_availability">false</bool>
+ <java-symbol type="bool" name="config_satellite_should_notify_availability" />
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d3ef07ca..e23e665 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6522,6 +6522,54 @@
<string name="satellite_manual_selection_state_popup_cancel">Go back</string>
<!-- Initial/System provided label shown for an app which gets unarchived. [CHAR LIMIT=64]. -->
<string name="unarchival_session_app_label">Pending...</string>
+ <!-- Notification title when satellite service is available. -->
+ <string name="satellite_sos_available_notification_title">Satellite SOS is now available</string>
+ <!-- Notification summary when satellite service is available. [CHAR LIMIT=NONE] -->
+ <string name="satellite_sos_available_notification_summary">You can message emergency services if there\'s no mobile or Wi-Fi network. Google Messages must be your default messaging app.</string>
+ <!-- Notification title when satellite service is not supported by device. -->
+ <string name="satellite_sos_not_supported_notification_title">Satellite SOS isn\'t supported</string>
+ <!-- Notification summary when satellite service is not supported by device. [CHAR LIMIT=NONE] -->
+ <string name="satellite_sos_not_supported_notification_summary">Satellite SOS isn\'t supported on this device</string>
+ <!-- Notification title when satellite service is not provisioned. [CHAR LIMIT=NONE] -->
+ <string name="satellite_sos_not_provisioned_notification_title">Satellite SOS isn\'t set up</string>
+ <!-- Notification summary when satellite service is not provisioned. [CHAR LIMIT=NONE] -->
+ <string name="satellite_sos_not_provisioned_notification_summary">Make sure you\'re connected to the internet and try setup again</string>
+ <!-- Notification title when satellite service is not allowed at current location. -->
+ <string name="satellite_sos_not_in_allowed_region_notification_title">Satellite SOS isn\'t available</string>
+ <!-- Notification summary when satellite service is not allowed at current location. [CHAR LIMIT=NONE] -->
+ <string name="satellite_sos_not_in_allowed_region_notification_summary">Satellite SOS isn\'t available in this country or region</string>
+ <!-- Notification title when default messaging app does not support satellite. [CHAR LIMIT=NONE] -->
+ <string name="satellite_sos_unsupported_default_sms_app_notification_title">Satellite SOS not set up</string>
+ <!-- Notification summary when default messaging app does not support satellite. [CHAR LIMIT=NONE] -->
+ <string name="satellite_sos_unsupported_default_sms_app_notification_summary">To message by satellite, set Google Messages as your default messaging app</string>
+ <!-- Notification title when location settings is disabled. -->
+ <string name="satellite_sos_location_disabled_notification_title">Satellite SOS isn\'t available</string>
+ <!-- Notification summary when location settings is disabled. [CHAR LIMIT=NONE] -->
+ <string name="satellite_sos_location_disabled_notification_summary">To check if satellite SOS is available in this country or region, turn on location settings</string>
+ <!-- Notification title when satellite service is available. -->
+ <string name="satellite_messaging_available_notification_title">Satellite messaging available</string>
+ <!-- Notification summary when satellite service is available. [CHAR LIMIT=NONE] -->
+ <string name="satellite_messaging_available_notification_summary">You can message by satellite if there\'s no mobile or Wi-Fi network. Google Messages must be your default messaging app.</string>
+ <!-- Notification title when satellite service is not supported by device. -->
+ <string name="satellite_messaging_not_supported_notification_title">Satellite messaging not supported</string>
+ <!-- Notification summary when satellite service is not supported by device. [CHAR LIMIT=NONE] -->
+ <string name="satellite_messaging_not_supported_notification_summary">Satellite messaging isn\'t supported on this device</string>
+ <!-- Notification title when satellite service is not provisioned. [CHAR LIMIT=NONE] -->
+ <string name="satellite_messaging_not_provisioned_notification_title">Satellite messaging not set up</string>
+ <!-- Notification summary when satellite service is not provisioned. [CHAR LIMIT=NONE] -->
+ <string name="satellite_messaging_not_provisioned_notification_summary">Make sure you\'re connected to the internet and try setup again</string>
+ <!-- Notification title when satellite service is not allowed at current location. -->
+ <string name="satellite_messaging_not_in_allowed_region_notification_title">Satellite messaging not available</string>
+ <!-- Notification summary when satellite service is not allowed at current location. [CHAR LIMIT=NONE] -->
+ <string name="satellite_messaging_not_in_allowed_region_notification_summary">Satellite messaging isn\'t available in this country or region</string>
+ <!-- Notification title when default messaging app does not support satellite. [CHAR LIMIT=NONE] -->
+ <string name="satellite_messaging_unsupported_default_sms_app_notification_title">Satellite messaging not set up</string>
+ <!-- Notification summary when default messaging app does not support satellite. [CHAR LIMIT=NONE] -->
+ <string name="satellite_messaging_unsupported_default_sms_app_notification_summary">To message by satellite, set Google Messages as your default messaging app</string>
+ <!-- Notification title when location settings is disabled. -->
+ <string name="satellite_messaging_location_disabled_notification_title">Satellite messaging not available</string>
+ <!-- Notification summary when location settings is disabled. [CHAR LIMIT=NONE] -->
+ <string name="satellite_messaging_location_disabled_notification_summary">To check if satellite messaging is available in this country or region, turn on location settings</string>
<!-- Fingerprint dangling notification title -->
<string name="fingerprint_dangling_notification_title">Set up Fingerprint Unlock again</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 515ebd5..fec8bbb 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -312,6 +312,7 @@
<java-symbol type="bool" name="config_disableLockscreenByDefault" />
<java-symbol type="bool" name="config_enableBurnInProtection" />
<java-symbol type="bool" name="config_hotswapCapable" />
+ <java-symbol type="integer" name="config_hsumBootStrategy" />
<java-symbol type="bool" name="config_mms_content_disposition_support" />
<java-symbol type="bool" name="config_networkSamplingWakesDevice" />
<java-symbol type="bool" name="config_showMenuShortcutsWhenKeyboardPresent" />
@@ -5551,6 +5552,30 @@
<java-symbol type="string" name="satellite_manual_selection_state_popup_cancel" />
<java-symbol type="drawable" name="ic_satellite_alt_24px" />
<java-symbol type="drawable" name="ic_android_satellite_24px" />
+ <java-symbol type="string" name="satellite_sos_available_notification_title" />
+ <java-symbol type="string" name="satellite_sos_available_notification_summary" />
+ <java-symbol type="string" name="satellite_sos_not_in_allowed_region_notification_title" />
+ <java-symbol type="string" name="satellite_sos_not_in_allowed_region_notification_summary" />
+ <java-symbol type="string" name="satellite_sos_not_supported_notification_title" />
+ <java-symbol type="string" name="satellite_sos_not_supported_notification_summary" />
+ <java-symbol type="string" name="satellite_sos_not_provisioned_notification_title" />
+ <java-symbol type="string" name="satellite_sos_not_provisioned_notification_summary" />
+ <java-symbol type="string" name="satellite_sos_unsupported_default_sms_app_notification_title" />
+ <java-symbol type="string" name="satellite_sos_unsupported_default_sms_app_notification_summary" />
+ <java-symbol type="string" name="satellite_sos_location_disabled_notification_title" />
+ <java-symbol type="string" name="satellite_sos_location_disabled_notification_summary" />
+ <java-symbol type="string" name="satellite_messaging_available_notification_title" />
+ <java-symbol type="string" name="satellite_messaging_available_notification_summary" />
+ <java-symbol type="string" name="satellite_messaging_not_in_allowed_region_notification_title" />
+ <java-symbol type="string" name="satellite_messaging_not_in_allowed_region_notification_summary" />
+ <java-symbol type="string" name="satellite_messaging_not_supported_notification_title" />
+ <java-symbol type="string" name="satellite_messaging_not_supported_notification_summary" />
+ <java-symbol type="string" name="satellite_messaging_not_provisioned_notification_title" />
+ <java-symbol type="string" name="satellite_messaging_not_provisioned_notification_summary" />
+ <java-symbol type="string" name="satellite_messaging_unsupported_default_sms_app_notification_title" />
+ <java-symbol type="string" name="satellite_messaging_unsupported_default_sms_app_notification_summary" />
+ <java-symbol type="string" name="satellite_messaging_location_disabled_notification_title" />
+ <java-symbol type="string" name="satellite_messaging_location_disabled_notification_summary" />
<!-- DisplayManager configs. -->
<java-symbol type="bool" name="config_evenDimmerEnabled" />
diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
index c2d8f91..da1fffa 100644
--- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
+++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
@@ -17,6 +17,9 @@
package android.app;
import static android.app.PropertyInvalidatedCache.NONCE_UNSET;
+import static android.app.PropertyInvalidatedCache.MODULE_BLUETOOTH;
+import static android.app.PropertyInvalidatedCache.MODULE_SYSTEM;
+import static android.app.PropertyInvalidatedCache.MODULE_TEST;
import static android.app.PropertyInvalidatedCache.NonceStore.INVALID_NONCE_INDEX;
import static com.android.internal.os.Flags.FLAG_APPLICATION_SHARED_MEMORY_ENABLED;
@@ -27,6 +30,8 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.app.PropertyInvalidatedCache.Args;
+import android.annotation.SuppressLint;
import com.android.internal.os.ApplicationSharedMemory;
import android.platform.test.annotations.IgnoreUnderRavenwood;
@@ -57,7 +62,7 @@
DeviceFlagsValueProvider.createCheckFlagsRule();
// Configuration for creating caches
- private static final String MODULE = PropertyInvalidatedCache.MODULE_TEST;
+ private static final String MODULE = MODULE_TEST;
private static final String API = "testApi";
// This class is a proxy for binder calls. It contains a counter that increments
@@ -245,6 +250,12 @@
mQuery = query;
}
+ // Create a cache from the args. The name of the cache is the api.
+ TestCache(Args args, TestQuery query) {
+ super(args, args.mApi(), query);
+ mQuery = query;
+ }
+
public int getRecomputeCount() {
return mQuery.getRecomputeCount();
}
@@ -374,14 +385,11 @@
@Test
public void testPropertyNames() {
String n1;
- n1 = PropertyInvalidatedCache.createPropertyName(
- PropertyInvalidatedCache.MODULE_SYSTEM, "getPackageInfo");
+ n1 = PropertyInvalidatedCache.createPropertyName(MODULE_SYSTEM, "getPackageInfo");
assertEquals(n1, "cache_key.system_server.get_package_info");
- n1 = PropertyInvalidatedCache.createPropertyName(
- PropertyInvalidatedCache.MODULE_SYSTEM, "get_package_info");
+ n1 = PropertyInvalidatedCache.createPropertyName(MODULE_SYSTEM, "get_package_info");
assertEquals(n1, "cache_key.system_server.get_package_info");
- n1 = PropertyInvalidatedCache.createPropertyName(
- PropertyInvalidatedCache.MODULE_BLUETOOTH, "getState");
+ n1 = PropertyInvalidatedCache.createPropertyName(MODULE_BLUETOOTH, "getState");
assertEquals(n1, "cache_key.bluetooth.get_state");
}
@@ -391,7 +399,7 @@
reason = "SystemProperties doesn't have permission check")
public void testPermissionFailure() {
// Create a cache that will write a system nonce.
- TestCache sysCache = new TestCache(PropertyInvalidatedCache.MODULE_SYSTEM, "mode1");
+ TestCache sysCache = new TestCache(MODULE_SYSTEM, "mode1");
try {
// Invalidate the cache, which writes the system property. There must be a permission
// failure.
@@ -407,7 +415,7 @@
@Test
public void testTestMode() {
// Create a cache that will write a system nonce.
- TestCache sysCache = new TestCache(PropertyInvalidatedCache.MODULE_SYSTEM, "mode1");
+ TestCache sysCache = new TestCache(MODULE_SYSTEM, "mode1");
sysCache.testPropertyName();
// Invalidate the cache. This must succeed because the property has been marked for
@@ -416,7 +424,7 @@
// Create a cache that uses MODULE_TEST. Invalidation succeeds whether or not the
// property is tagged as being tested.
- TestCache testCache = new TestCache(PropertyInvalidatedCache.MODULE_TEST, "mode2");
+ TestCache testCache = new TestCache(MODULE_TEST, "mode2");
testCache.invalidateCache();
testCache.testPropertyName();
testCache.invalidateCache();
@@ -432,7 +440,7 @@
// The expected exception.
}
// Configuring a property for testing must fail if test mode is false.
- TestCache cache2 = new TestCache(PropertyInvalidatedCache.MODULE_SYSTEM, "mode3");
+ TestCache cache2 = new TestCache(MODULE_SYSTEM, "mode3");
try {
cache2.testPropertyName();
fail("expected an IllegalStateException");
@@ -444,6 +452,34 @@
PropertyInvalidatedCache.setTestMode(true);
}
+ // Test the Args-style constructor.
+ @Test
+ public void testArgsConstructor() {
+ // Create a cache with a maximum of four entries.
+ TestCache cache = new TestCache(new Args(MODULE_TEST).api("init1").maxEntries(4),
+ new TestQuery());
+
+ cache.invalidateCache();
+ for (int i = 1; i <= 4; i++) {
+ assertEquals("foo" + i, cache.query(i));
+ assertEquals(i, cache.getRecomputeCount());
+ }
+ // Everything is in the cache. The recompute count must not increase.
+ for (int i = 1; i <= 4; i++) {
+ assertEquals("foo" + i, cache.query(i));
+ assertEquals(4, cache.getRecomputeCount());
+ }
+ // Overflow the max entries. The recompute count increases by one.
+ assertEquals("foo5", cache.query(5));
+ assertEquals(5, cache.getRecomputeCount());
+ // The oldest entry (1) has been evicted. Iterating through the first four entries will
+ // sequentially evict them all because the loop is proceeding oldest to newest.
+ for (int i = 1; i <= 4; i++) {
+ assertEquals("foo" + i, cache.query(i));
+ assertEquals(5+i, cache.getRecomputeCount());
+ }
+ }
+
// Verify the behavior of shared memory nonce storage. This does not directly test the cache
// storing nonces in shared memory.
@RequiresFlagsEnabled(FLAG_APPLICATION_SHARED_MEMORY_ENABLED)
@@ -495,4 +531,43 @@
shmem.close();
}
+
+ // Verify that an invalid module causes an exception.
+ private void testInvalidModule(String module) {
+ try {
+ @SuppressLint("UnusedVariable")
+ Args arg = new Args(module);
+ fail("expected an invalid module exception: module=" + module);
+ } catch (IllegalArgumentException e) {
+ // Expected exception.
+ }
+ }
+
+ // Test various instantiation errors. The good path is tested in other methods.
+ @Test
+ public void testArgumentErrors() {
+ // Verify that an illegal module throws an exception.
+ testInvalidModule(MODULE_SYSTEM.substring(0, MODULE_SYSTEM.length() - 1));
+ testInvalidModule(MODULE_SYSTEM + "x");
+ testInvalidModule("mymodule");
+
+ // Verify that a negative max entries throws.
+ Args arg = new Args(MODULE_SYSTEM);
+ try {
+ arg.maxEntries(0);
+ fail("expected an invalid maxEntries exception");
+ } catch (IllegalArgumentException e) {
+ // Expected exception.
+ }
+
+ // Verify that creating a cache with an invalid property string throws.
+ try {
+ final String badKey = "cache_key.volume_list";
+ @SuppressLint("UnusedVariable")
+ var cache = new PropertyInvalidatedCache<Integer, Void>(4, badKey);
+ fail("expected bad property exception: prop=" + badKey);
+ } catch (IllegalArgumentException e) {
+ // Expected exception.
+ }
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
index 120a4de..3239598 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
@@ -301,18 +301,14 @@
{1_000_000, 2_000_000, 3_000_000},
{4_000_000, 5_000_000}});
- LongArrayMultiStateCounter.LongArrayContainer array =
- new LongArrayMultiStateCounter.LongArrayContainer(5);
+
long[] out = new long[5];
- success = mInjector.addDelta(TEST_UID, counter, 2000, array);
+ success = mInjector.addDelta(TEST_UID, counter, 2000, out);
assertThat(success).isTrue();
-
- array.getValues(out);
assertThat(out).isEqualTo(new long[]{1, 2, 3, 4, 5});
- counter.getCounts(array, 0);
- array.getValues(out);
+ counter.getCounts(out, 0);
assertThat(out).isEqualTo(new long[]{1, 2, 3, 4, 5});
counter.setState(1, 3000);
@@ -322,18 +318,14 @@
{11_000_000, 22_000_000, 33_000_000},
{44_000_000, 55_000_000}});
- success = mInjector.addDelta(TEST_UID, counter, 4000, array);
+ success = mInjector.addDelta(TEST_UID, counter, 4000, out);
assertThat(success).isTrue();
-
- array.getValues(out);
assertThat(out).isEqualTo(new long[]{10, 20, 30, 40, 50});
- counter.getCounts(array, 0);
- array.getValues(out);
+ counter.getCounts(out, 0);
assertThat(out).isEqualTo(new long[]{1 + 5, 2 + 10, 3 + 15, 4 + 20, 5 + 25});
- counter.getCounts(array, 1);
- array.getValues(out);
+ counter.getCounts(out, 1);
assertThat(out).isEqualTo(new long[]{5, 10, 15, 20, 25});
}
@@ -385,7 +377,7 @@
@Override
public boolean addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs,
- LongArrayMultiStateCounter.LongArrayContainer deltaOut) {
+ long[] deltaOut) {
return addDeltaForTest(uid, counter, timestampMs, mCpuTimeInStatePerClusterNs,
deltaOut);
}
diff --git a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
index b86dc58..7e5d0a4 100644
--- a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
@@ -24,14 +24,11 @@
import android.os.Parcel;
import android.platform.test.ravenwood.RavenwoodRule;
-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;
-@RunWith(AndroidJUnit4.class)
@SmallTest
public class LongArrayMultiStateCounterTest {
@Rule
@@ -41,11 +38,11 @@
public void setStateAndUpdateValue() {
LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4);
- updateValue(counter, new long[]{0, 0, 0, 0}, 1000);
+ counter.updateValues(new long[]{0, 0, 0, 0}, 1000);
counter.setState(0, 1000);
counter.setState(1, 2000);
counter.setState(0, 4000);
- updateValue(counter, new long[]{100, 200, 300, 400}, 9000);
+ counter.updateValues(new long[]{100, 200, 300, 400}, 9000);
assertCounts(counter, 0, new long[]{75, 150, 225, 300});
assertCounts(counter, 1, new long[]{25, 50, 75, 100});
@@ -55,15 +52,28 @@
}
@Test
+ public void increment() {
+ LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4);
+
+ counter.updateValues(new long[]{0, 0, 0, 0}, 1000);
+ counter.setState(0, 1000);
+ counter.incrementValues(new long[]{1, 2, 3, 4}, 2000);
+ counter.incrementValues(new long[]{100, 200, 300, 400}, 3000);
+
+ assertCounts(counter, 0, new long[]{101, 202, 303, 404});
+ assertCounts(counter, 1, new long[]{0, 0, 0, 0});
+ }
+
+ @Test
public void copyStatesFrom() {
LongArrayMultiStateCounter source = new LongArrayMultiStateCounter(2, 1);
- updateValue(source, new long[]{0}, 1000);
+ source.updateValues(new long[]{0}, 1000);
source.setState(0, 1000);
source.setState(1, 2000);
LongArrayMultiStateCounter target = new LongArrayMultiStateCounter(2, 1);
target.copyStatesFrom(source);
- updateValue(target, new long[]{1000}, 5000);
+ target.updateValues(new long[]{1000}, 5000);
assertCounts(target, 0, new long[]{250});
assertCounts(target, 1, new long[]{750});
@@ -83,25 +93,25 @@
public void setEnabled() {
LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4);
counter.setState(0, 1000);
- updateValue(counter, new long[]{0, 0, 0, 0}, 1000);
- updateValue(counter, new long[]{100, 200, 300, 400}, 2000);
+ counter.updateValues(new long[]{0, 0, 0, 0}, 1000);
+ counter.updateValues(new long[]{100, 200, 300, 400}, 2000);
assertCounts(counter, 0, new long[]{100, 200, 300, 400});
counter.setEnabled(false, 3000);
// Partially included, because the counter is disabled after the previous update
- updateValue(counter, new long[]{200, 300, 400, 500}, 4000);
+ counter.updateValues(new long[]{200, 300, 400, 500}, 4000);
// Count only 50%, because the counter was disabled for 50% of the time
assertCounts(counter, 0, new long[]{150, 250, 350, 450});
// Not counted because the counter is disabled
- updateValue(counter, new long[]{250, 350, 450, 550}, 5000);
+ counter.updateValues(new long[]{250, 350, 450, 550}, 5000);
counter.setEnabled(true, 6000);
- updateValue(counter, new long[]{300, 400, 500, 600}, 7000);
+ counter.updateValues(new long[]{300, 400, 500, 600}, 7000);
// Again, take 50% of the delta
assertCounts(counter, 0, new long[]{175, 275, 375, 475});
@@ -111,8 +121,8 @@
public void reset() {
LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4);
counter.setState(0, 1000);
- updateValue(counter, new long[]{0, 0, 0, 0}, 1000);
- updateValue(counter, new long[]{100, 200, 300, 400}, 2000);
+ counter.updateValues(new long[]{0, 0, 0, 0}, 1000);
+ counter.updateValues(new long[]{100, 200, 300, 400}, 2000);
assertCounts(counter, 0, new long[]{100, 200, 300, 400});
@@ -120,8 +130,8 @@
assertCounts(counter, 0, new long[]{0, 0, 0, 0});
- updateValue(counter, new long[]{200, 300, 400, 500}, 3000);
- updateValue(counter, new long[]{300, 400, 500, 600}, 4000);
+ counter.updateValues(new long[]{200, 300, 400, 500}, 3000);
+ counter.updateValues(new long[]{300, 400, 500, 600}, 4000);
assertCounts(counter, 0, new long[]{100, 100, 100, 100});
}
@@ -129,11 +139,11 @@
@Test
public void parceling() {
LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4);
- updateValue(counter, new long[]{0, 0, 0, 0}, 1000);
+ counter.updateValues(new long[]{0, 0, 0, 0}, 1000);
counter.setState(0, 1000);
- updateValue(counter, new long[]{100, 200, 300, 400}, 2000);
+ counter.updateValues(new long[]{100, 200, 300, 400}, 2000);
counter.setState(1, 2000);
- updateValue(counter, new long[]{101, 202, 304, 408}, 3000);
+ counter.updateValues(new long[]{101, 202, 304, 408}, 3000);
assertCounts(counter, 0, new long[]{100, 200, 300, 400});
assertCounts(counter, 1, new long[]{1, 2, 4, 8});
@@ -158,27 +168,17 @@
// State, last update timestamp and current counts are undefined at this point.
newCounter.setState(0, 100);
- updateValue(newCounter, new long[]{300, 400, 500, 600}, 100);
+ newCounter.updateValues(new long[]{300, 400, 500, 600}, 100);
// A new base state and counters are established; we can continue accumulating deltas
- updateValue(newCounter, new long[]{316, 432, 564, 728}, 200);
+ newCounter.updateValues(new long[]{316, 432, 564, 728}, 200);
assertCounts(newCounter, 0, new long[]{116, 232, 364, 528});
}
- private void updateValue(LongArrayMultiStateCounter counter, long[] values, int timestamp) {
- LongArrayMultiStateCounter.LongArrayContainer container =
- new LongArrayMultiStateCounter.LongArrayContainer(values.length);
- container.setValues(values);
- counter.updateValues(container, timestamp);
- }
-
private void assertCounts(LongArrayMultiStateCounter counter, int state, long[] expected) {
- LongArrayMultiStateCounter.LongArrayContainer container =
- new LongArrayMultiStateCounter.LongArrayContainer(expected.length);
long[] counts = new long[expected.length];
- counter.getCounts(container, state);
- container.getValues(counts);
+ counter.getCounts(counts, state);
assertThat(counts).isEqualTo(expected);
}
@@ -230,33 +230,4 @@
parcel.writeInt(endPos - startPos);
parcel.setDataPosition(endPos);
}
-
- @Test
- public void combineValues() {
- long[] values = new long[] {0, 1, 2, 3, 42};
- LongArrayMultiStateCounter.LongArrayContainer container =
- new LongArrayMultiStateCounter.LongArrayContainer(values.length);
- container.setValues(values);
-
- long[] out = new long[3];
- int[] indexes = {2, 1, 1, 0, 0};
- boolean nonZero = container.combineValues(out, indexes);
- assertThat(nonZero).isTrue();
- assertThat(out).isEqualTo(new long[]{45, 3, 0});
-
- // All zeros
- container.setValues(new long[]{0, 0, 0, 0, 0});
- nonZero = container.combineValues(out, indexes);
- assertThat(nonZero).isFalse();
- assertThat(out).isEqualTo(new long[]{0, 0, 0});
-
- // Index out of range
- IndexOutOfBoundsException e1 = assertThrows(
- IndexOutOfBoundsException.class,
- () -> container.combineValues(out, new int[]{0, 1, -1, 0, 0}));
- assertThat(e1.getMessage()).isEqualTo("Index -1 is out of bounds: [0, 2]");
- IndexOutOfBoundsException e2 = assertThrows(IndexOutOfBoundsException.class,
- () -> container.combineValues(out, new int[]{0, 1, 4, 0, 0}));
- assertThat(e2.getMessage()).isEqualTo("Index 4 is out of bounds: [0, 2]");
- }
}
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_change_aspect_ratio.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_change_aspect_ratio.xml
new file mode 100644
index 0000000..4442e9d
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_change_aspect_ratio.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@color/compat_controls_text"
+ android:pathData="M19,12h-2v3h-3v2h5v-5zM7,9h3L10,7L5,7v5h2L7,9zM21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,19.01L3,19.01L3,4.99h18v14.02z"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
index 5609663..f90e165 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -157,6 +157,14 @@
android:drawableStart="@drawable/desktop_mode_ic_handle_menu_manage_windows"
android:drawableTint="?androidprv:attr/materialColorOnSurface"
style="@style/DesktopModeHandleMenuActionButton" />
+
+ <Button
+ android:id="@+id/change_aspect_ratio_button"
+ android:contentDescription="@string/change_aspect_ratio_text"
+ android:text="@string/change_aspect_ratio_text"
+ android:drawableStart="@drawable/desktop_mode_ic_handle_menu_change_aspect_ratio"
+ android:drawableTint="?androidprv:attr/materialColorOnSurface"
+ style="@style/DesktopModeHandleMenuActionButton" />
</LinearLayout>
<LinearLayout
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 34f950c..249e9a2 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -523,8 +523,9 @@
<dimen name="desktop_mode_handle_menu_width">216dp</dimen>
<!-- The maximum height of the handle menu in desktop mode. Three pills at 52dp each,
- additional actions pill 156dp, plus 2dp spacing between them plus 4dp top padding. -->
- <dimen name="desktop_mode_handle_menu_height">322dp</dimen>
+ additional actions pill 208dp, plus 2dp spacing between them plus 4dp top padding.
+ 52*3 + 52*4 + (4-1)*2 + 4 = 374 -->
+ <dimen name="desktop_mode_handle_menu_height">374dp</dimen>
<!-- The elevation set on the handle menu pills. -->
<dimen name="desktop_mode_handle_menu_pill_elevation">1dp</dimen>
@@ -547,6 +548,9 @@
<!-- The height of the handle menu's "Open in browser" pill in desktop mode. -->
<dimen name="desktop_mode_handle_menu_open_in_browser_pill_height">52dp</dimen>
+ <!-- The height of the handle menu's "Change aspect ratio" pill in desktop mode. -->
+ <dimen name="desktop_mode_handle_menu_change_aspect_ratio_height">52dp</dimen>
+
<!-- The margin between pills of the handle menu in desktop mode. -->
<dimen name="desktop_mode_handle_menu_pill_spacing_margin">2dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 52585d4..8f1ef6c 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -305,6 +305,8 @@
<string name="new_window_text">New Window</string>
<!-- Accessibility text for the handle menu new window button [CHAR LIMIT=NONE] -->
<string name="manage_windows_text">Manage Windows</string>
+ <!-- Accessibility text for the handle menu change aspect ratio button [CHAR LIMIT=NONE] -->
+ <string name="change_aspect_ratio_text">Change aspect ratio</string>
<!-- Accessibility text for the handle menu close button [CHAR LIMIT=NONE] -->
<string name="close_text">Close</string>
<!-- Accessibility text for the handle menu close menu button [CHAR LIMIT=NONE] -->
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
index 88878c6..e033f67 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
@@ -360,7 +360,7 @@
windowConfiguration = new WindowConfiguration();
}
- Rect localBounds = new Rect();
+ Rect bounds = windowConfiguration.getBounds();
RemoteAnimationTarget target = new RemoteAnimationTarget(
taskId,
newModeToLegacyMode(mode),
@@ -373,12 +373,12 @@
new Rect(0, 0, 0, 0),
order,
null,
- localBounds,
- new Rect(),
+ bounds,
+ bounds,
windowConfiguration,
isNotInRecents,
null,
- new Rect(),
+ bounds,
taskInfo,
false,
INVALID_WINDOW_TYPE
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/OWNERS b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/OWNERS
new file mode 100644
index 0000000..20d5c33
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/OWNERS
@@ -0,0 +1,4 @@
+# WM shell sub-module PiP owner
+hwwang@google.com
+gabiyev@google.com
+wuperry@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index f59fed9..dfe76b8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -487,6 +487,20 @@
return mHomeTaskOverlayContainer;
}
+ /**
+ * Returns the home task surface, not for wide use.
+ */
+ @Nullable
+ public SurfaceControl getHomeTaskSurface() {
+ for (int i = 0; i < mTasks.size(); i++) {
+ final TaskAppearedInfo info = mTasks.valueAt(i);
+ if (info.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME) {
+ return info.getLeash();
+ }
+ }
+ return null;
+ }
+
@Override
public void addStartingWindow(StartingWindowInfo info) {
if (mStartingWindow != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index f296c71..b9a3050 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -29,6 +29,7 @@
import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME;
import static com.android.window.flags.Flags.migratePredictiveBackTransition;
import static com.android.window.flags.Flags.predictiveBackSystemAnims;
+import static com.android.window.flags.Flags.unifyBackNavigationTransition;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
@@ -1279,6 +1280,13 @@
if (transition == mClosePrepareTransition && aborted) {
mClosePrepareTransition = null;
applyFinishOpenTransition();
+ } else if (!aborted && unifyBackNavigationTransition()) {
+ // Since the closing target participates in the predictive back transition, the
+ // merged transition must be applied with the first transition to ensure a seamless
+ // animation.
+ if (mFinishOpenTransaction != null && finishTransaction != null) {
+ mFinishOpenTransaction.merge(finishTransaction);
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 5f0eed9..14f8cc7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1632,6 +1632,7 @@
if (!isShowingAsBubbleBar()) {
callback = b -> {
if (mStackView != null) {
+ b.setSuppressFlyout(true);
mStackView.addBubble(b);
mStackView.setSelectedBubble(b);
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
index d1b2347..62d5098 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
@@ -23,9 +23,15 @@
import com.android.internal.R
// TODO(b/347289970): Consider replacing with API
+/**
+ * If the top activity should be exempt from desktop windowing and forced back to fullscreen.
+ * Currently includes all system ui activities and modal dialogs. However is the top activity is not
+ * being displayed, regardless of its configuration, we will not exempt it as to remain in the
+ * desktop windowing environment.
+ */
fun isTopActivityExemptFromDesktopWindowing(context: Context, task: TaskInfo) =
- isSystemUiTask(context, task) || (task.isTopActivityTransparent && task.numActivities == 1
- && !task.isTopActivityStyleFloating)
+ (isSystemUiTask(context, task) || (task.isTopActivityTransparent && task.numActivities == 1))
+ && !task.isTopActivityNoDisplay
private fun isSystemUiTask(context: Context, task: TaskInfo): Boolean {
val sysUiPackageName: String =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 6146ecd..886330f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -688,6 +688,12 @@
private void launchUserAspectRatioSettings(
@NonNull TaskInfo taskInfo, @NonNull ShellTaskOrganizer.TaskListener taskListener) {
+ launchUserAspectRatioSettings(mContext, taskInfo);
+ }
+
+ /** Launch the user aspect ratio settings for the package of the given task. */
+ public static void launchUserAspectRatioSettings(
+ @NonNull Context context, @NonNull TaskInfo taskInfo) {
final Intent intent = new Intent(Settings.ACTION_MANAGE_USER_ASPECT_RATIO_SETTINGS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
@@ -697,7 +703,7 @@
intent.setData(packageUri);
}
final UserHandle userHandle = UserHandle.of(taskInfo.userId);
- mContext.startActivityAsUser(intent, userHandle);
+ context.startActivityAsUser(intent, userHandle);
}
@VisibleForTesting
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 2a5a519..77e041e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -401,9 +401,6 @@
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
RootTaskDisplayAreaOrganizer rootTdaOrganizer) {
- if (!com.android.window.flags.Flags.explicitRefreshRateHints()) {
- return Optional.empty();
- }
final PerfHintController perfHintController =
new PerfHintController(context, shellInit, shellCommandHandler, rootTdaOrganizer);
return Optional.of(perfHintController.getHinter());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index fec4c16..706a678 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -67,6 +67,7 @@
import com.android.wm.shell.desktopmode.CloseDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.DefaultDragToDesktopTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
+import com.android.wm.shell.desktopmode.DesktopDisplayEventHandler;
import com.android.wm.shell.desktopmode.DesktopImmersiveController;
import com.android.wm.shell.desktopmode.DesktopMixedTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopModeDragAndDropTransitionHandler;
@@ -1016,6 +1017,30 @@
@WMSingleton
@Provides
+ static Optional<DesktopDisplayEventHandler> provideDesktopDisplayEventHandler(
+ Context context,
+ ShellInit shellInit,
+ Transitions transitions,
+ DisplayController displayController,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ IWindowManager windowManager
+ ) {
+ if (!DesktopModeStatus.canEnterDesktopMode(context)
+ || !Flags.enableDisplayWindowingModeSwitching()) {
+ return Optional.empty();
+ }
+ return Optional.of(
+ new DesktopDisplayEventHandler(
+ context,
+ shellInit,
+ transitions,
+ displayController,
+ rootTaskDisplayAreaOrganizer,
+ windowManager));
+ }
+
+ @WMSingleton
+ @Provides
static AppHandleEducationDatastoreRepository provideAppHandleEducationDatastoreRepository(
Context context) {
return new AppHandleEducationDatastoreRepository(context);
@@ -1180,7 +1205,8 @@
@Provides
static Object provideIndependentShellComponentsToCreate(
DragAndDropController dragAndDropController,
- Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional) {
+ Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional,
+ Optional<DesktopDisplayEventHandler> desktopDisplayEventHandler) {
return new Object();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
new file mode 100644
index 0000000..ba383fa
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package com.android.wm.shell.desktopmode
+
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.content.Context
+import android.provider.Settings
+import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
+import android.view.Display.DEFAULT_DISPLAY
+import android.view.IWindowManager
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.window.WindowContainerTransaction
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.Transitions
+
+/** Handles display events in desktop mode */
+class DesktopDisplayEventHandler(
+ private val context: Context,
+ shellInit: ShellInit,
+ private val transitions: Transitions,
+ private val displayController: DisplayController,
+ private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ private val windowManager: IWindowManager,
+) : OnDisplaysChangedListener {
+
+ init {
+ shellInit.addInitCallback({ onInit() }, this)
+ }
+
+ private fun onInit() {
+ displayController.addDisplayWindowListener(this)
+ }
+
+ override fun onDisplayAdded(displayId: Int) {
+ if (displayId == DEFAULT_DISPLAY) {
+ return
+ }
+ refreshDisplayWindowingMode()
+ }
+
+ override fun onDisplayRemoved(displayId: Int) {
+ if (displayId == DEFAULT_DISPLAY) {
+ return
+ }
+ refreshDisplayWindowingMode()
+ }
+
+ private fun refreshDisplayWindowingMode() {
+ // TODO: b/375319538 - Replace the check with a DisplayManager API once it's available.
+ val isExtendedDisplayEnabled = 0 != Settings.Global.getInt(
+ context.contentResolver, DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0
+ )
+ if (!isExtendedDisplayEnabled) {
+ // No action needed in mirror or projected mode.
+ return
+ }
+
+ val hasNonDefaultDisplay = rootTaskDisplayAreaOrganizer.getDisplayIds()
+ .any { displayId -> displayId != DEFAULT_DISPLAY }
+ val targetDisplayWindowingMode =
+ if (hasNonDefaultDisplay) {
+ WINDOWING_MODE_FREEFORM
+ } else {
+ // Use the default display windowing mode when no non-default display.
+ windowManager.getWindowingMode(DEFAULT_DISPLAY)
+ }
+ val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)
+ requireNotNull(tdaInfo) { "DisplayAreaInfo of DEFAULT_DISPLAY must be non-null." }
+ if (tdaInfo.configuration.windowConfiguration.windowingMode == targetDisplayWindowingMode) {
+ // Already in the target mode.
+ return
+ }
+
+ val wct = WindowContainerTransaction()
+ wct.setWindowingMode(tdaInfo.token, targetDisplayWindowingMode)
+ transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 75f8839..162879c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -554,19 +554,6 @@
}
}
- /** Move a desktop app to split screen. */
- fun moveToSplit(task: RunningTaskInfo) {
- logV( "moveToSplit taskId=%s", task.taskId)
- desktopTilingDecorViewModel.removeTaskIfTiled(task.displayId, task.taskId)
- val wct = WindowContainerTransaction()
- wct.setBounds(task.token, Rect())
- // Rather than set windowing mode to multi-window at task level, set it to
- // undefined and inherit from split stage.
- wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED)
-
- transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
- }
-
private fun exitSplitIfApplicable(wct: WindowContainerTransaction, taskInfo: RunningTaskInfo) {
if (splitScreenController.isTaskInSplitScreen(taskInfo.taskId)) {
splitScreenController.prepareExitSplitScreen(
@@ -2050,6 +2037,18 @@
}
/**
+ * Cancel the drag-to-desktop transition.
+ *
+ * @param taskInfo the task being dragged.
+ */
+ fun onDragPositioningCancelThroughStatusBar(
+ taskInfo: RunningTaskInfo,
+ ) {
+ interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
+ cancelDragToDesktop(taskInfo)
+ }
+
+ /**
* Perform checks required when drag ends under status bar area.
*
* @param taskInfo the task being dragged.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
index 96719fa..9411150 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
@@ -21,6 +21,7 @@
import android.animation.ValueAnimator
import android.graphics.Rect
import android.os.IBinder
+import android.view.Choreographer
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
import android.window.TransitionInfo
@@ -126,6 +127,7 @@
tx.setPosition(leash, rect.left.toFloat(), rect.top.toFloat())
.setWindowCrop(leash, rect.width(), rect.height())
.show(leash)
+ .setFrameTimeline(Choreographer.getInstance().getVsyncId())
onTaskResizeAnimationListener.onBoundsChange(taskId, tx, rect)
}
start()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
index e513758..eb33ff4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
@@ -20,6 +20,7 @@
import static android.view.Surface.ROTATION_90;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
@@ -34,6 +35,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.common.pip.PipUtils;
@@ -45,8 +47,7 @@
/**
* Animator that handles bounds animations for entering PIP.
*/
-public class PipEnterAnimator extends ValueAnimator
- implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener {
+public class PipEnterAnimator extends ValueAnimator {
@NonNull private final SurfaceControl mLeash;
private final SurfaceControl.Transaction mStartTransaction;
private final SurfaceControl.Transaction mFinishTransaction;
@@ -56,49 +57,82 @@
private final RectEvaluator mRectEvaluator;
private final Rect mEndBounds = new Rect();
- @Nullable private final Rect mSourceRectHint;
private final @Surface.Rotation int mRotation;
@Nullable private Runnable mAnimationStartCallback;
@Nullable private Runnable mAnimationEndCallback;
- private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
+ private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
mSurfaceControlTransactionFactory;
Matrix mTransformTensor = new Matrix();
final float[] mMatrixTmp = new float[9];
@Nullable private PipContentOverlay mContentOverlay;
+ private PipAppIconOverlaySupplier mPipAppIconOverlaySupplier;
// Internal state representing initial transform - cached to avoid recalculation.
private final PointF mInitScale = new PointF();
private final PointF mInitPos = new PointF();
private final Rect mInitCrop = new Rect();
- private final PointF mInitActivityScale = new PointF();
- private final PointF mInitActivityPos = new PointF();
+
+ private final Animator.AnimatorListener mAnimatorListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ if (mAnimationStartCallback != null) {
+ mAnimationStartCallback.run();
+ }
+ if (mStartTransaction != null) {
+ onEnterAnimationUpdate(0f /* fraction */, mStartTransaction);
+ mStartTransaction.apply();
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (mFinishTransaction != null) {
+ onEnterAnimationUpdate(1f /* fraction */, mFinishTransaction);
+ }
+ if (mAnimationEndCallback != null) {
+ mAnimationEndCallback.run();
+ }
+ }
+ };
+
+ private final AnimatorUpdateListener mAnimatorUpdateListener = new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(@NonNull ValueAnimator animation) {
+ final SurfaceControl.Transaction tx =
+ mSurfaceControlTransactionFactory.getTransaction();
+ final float fraction = getAnimatedFraction();
+ onEnterAnimationUpdate(fraction, tx);
+ tx.apply();
+ }
+ };
public PipEnterAnimator(Context context,
@NonNull SurfaceControl leash,
SurfaceControl.Transaction startTransaction,
SurfaceControl.Transaction finishTransaction,
@NonNull Rect endBounds,
- @Nullable Rect sourceRectHint,
@Surface.Rotation int rotation) {
mLeash = leash;
mStartTransaction = startTransaction;
mFinishTransaction = finishTransaction;
mRectEvaluator = new RectEvaluator(mAnimatedRect);
mEndBounds.set(endBounds);
- mSourceRectHint = sourceRectHint != null ? new Rect(sourceRectHint) : null;
mRotation = rotation;
mSurfaceControlTransactionFactory =
new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
+ mPipAppIconOverlaySupplier = this::getAppIconOverlay;
final int enterAnimationDuration = context.getResources()
.getInteger(R.integer.config_pipEnterAnimationDuration);
setDuration(enterAnimationDuration);
setFloatValues(0f, 1f);
setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- addListener(this);
- addUpdateListener(this);
+ addListener(mAnimatorListener);
+ addUpdateListener(mAnimatorUpdateListener);
}
public void setAnimationStartCallback(@NonNull Runnable runnable) {
@@ -109,35 +143,6 @@
mAnimationEndCallback = runnable;
}
- @Override
- public void onAnimationStart(@NonNull Animator animation) {
- if (mAnimationStartCallback != null) {
- mAnimationStartCallback.run();
- }
- if (mStartTransaction != null) {
- onEnterAnimationUpdate(0f /* fraction */, mStartTransaction);
- mStartTransaction.apply();
- }
- }
-
- @Override
- public void onAnimationEnd(@NonNull Animator animation) {
- if (mFinishTransaction != null) {
- onEnterAnimationUpdate(1f /* fraction */, mFinishTransaction);
- }
- if (mAnimationEndCallback != null) {
- mAnimationEndCallback.run();
- }
- }
-
- @Override
- public void onAnimationUpdate(@NonNull ValueAnimator animation) {
- final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
- final float fraction = getAnimatedFraction();
- onEnterAnimationUpdate(fraction, tx);
- tx.apply();
- }
-
/**
* Updates the transaction to reflect the state of PiP leash at a certain fraction during enter.
*
@@ -177,14 +182,6 @@
}
}
- // no-ops
-
- @Override
- public void onAnimationCancel(@NonNull Animator animation) {}
-
- @Override
- public void onAnimationRepeat(@NonNull Animator animation) {}
-
/**
* Caches the initial transform relevant values for the bounds enter animation.
*
@@ -201,18 +198,13 @@
*/
public void setAppIconContentOverlay(Context context, Rect appBounds, Rect destinationBounds,
ActivityInfo activityInfo, int appIconSizePx) {
- reattachAppIconOverlay(
- new PipAppIconOverlay(context, appBounds, destinationBounds,
- new IconProvider(context).getIcon(activityInfo), appIconSizePx));
- }
-
- private void reattachAppIconOverlay(PipAppIconOverlay overlay) {
final SurfaceControl.Transaction tx =
mSurfaceControlTransactionFactory.getTransaction();
if (mContentOverlay != null) {
mContentOverlay.detach(tx);
}
- mContentOverlay = overlay;
+ mContentOverlay = mPipAppIconOverlaySupplier.get(context, appBounds, destinationBounds,
+ activityInfo, appIconSizePx);
mContentOverlay.attach(tx, mLeash);
}
@@ -229,6 +221,13 @@
mContentOverlay = null;
}
+ private PipAppIconOverlay getAppIconOverlay(
+ Context context, Rect appBounds, Rect destinationBounds,
+ ActivityInfo activityInfo, int iconSize) {
+ return new PipAppIconOverlay(context, appBounds, destinationBounds,
+ new IconProvider(context).getIcon(activityInfo), iconSize);
+ }
+
/**
* @return the app icon overlay leash; null if no overlay is attached.
*/
@@ -239,4 +238,21 @@
}
return mContentOverlay.getLeash();
}
+
+ @VisibleForTesting
+ void setSurfaceControlTransactionFactory(
+ @NonNull PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
+ mSurfaceControlTransactionFactory = factory;
+ }
+
+ @VisibleForTesting
+ interface PipAppIconOverlaySupplier {
+ PipAppIconOverlay get(Context context, Rect appBounds, Rect destinationBounds,
+ ActivityInfo activityInfo, int iconSize);
+ }
+
+ @VisibleForTesting
+ void setPipAppIconOverlaySupplier(@NonNull PipAppIconOverlaySupplier supplier) {
+ mPipAppIconOverlaySupplier = supplier;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 64d8887..6bf92f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -352,17 +352,11 @@
handleBoundsTypeFixedRotation(pipChange, pipActivityChange, endRotation);
}
- Rect sourceRectHint = null;
- if (pipChange.getTaskInfo() != null
- && pipChange.getTaskInfo().pictureInPictureParams != null) {
- sourceRectHint = pipChange.getTaskInfo().pictureInPictureParams.getSourceRectHint();
- }
-
prepareConfigAtEndActivity(startTransaction, finishTransaction, pipChange,
pipActivityChange);
startTransaction.merge(finishTransaction);
PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash,
- startTransaction, finishTransaction, destinationBounds, sourceRectHint, delta);
+ startTransaction, finishTransaction, destinationBounds, delta);
animator.setEnterStartState(pipChange);
animator.onEnterAnimationUpdate(1.0f /* fraction */, startTransaction);
startTransaction.apply();
@@ -433,7 +427,7 @@
}
PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash,
- startTransaction, finishTransaction, endBounds, adjustedSourceRectHint, delta);
+ startTransaction, finishTransaction, endBounds, delta);
if (sourceRectHint == null) {
// update the src-rect-hint in params in place, to set up initial animator transform.
params.getSourceRectHint().set(adjustedSourceRectHint);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 6d4d4b4..40065b9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -544,10 +544,10 @@
.findFirst()
.get();
final RemoteAnimationTarget openingTarget = TransitionUtil.newSyntheticTarget(
- homeTask, mShellTaskOrganizer.getHomeTaskOverlayContainer(), TRANSIT_OPEN,
+ homeTask, mShellTaskOrganizer.getHomeTaskSurface(), TRANSIT_OPEN,
0, true /* isTranslucent */);
final RemoteAnimationTarget closingTarget = TransitionUtil.newSyntheticTarget(
- homeTask, mShellTaskOrganizer.getHomeTaskOverlayContainer(), TRANSIT_CLOSE,
+ homeTask, mShellTaskOrganizer.getHomeTaskSurface(), TRANSIT_CLOSE,
0, true /* isTranslucent */);
final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>();
apps.add(openingTarget);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index b33f3e9..4407e5b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -245,9 +245,9 @@
return;
}
mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
- mVisible = taskInfo.isVisible && taskInfo.isVisibleRequested;
+ mVisible = isStageVisible();
mCallbacks.onChildTaskStatusChanged(this, taskInfo.taskId, true /* present */,
- mVisible);
+ taskInfo.isVisible && taskInfo.isVisibleRequested);
} else {
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ "\n mRootTaskInfo: " + mRootTaskInfo);
@@ -293,6 +293,19 @@
t.reparent(sc, findTaskSurface(taskId));
}
+ /**
+ * Checks against all children task info and return true if any are marked as visible.
+ */
+ private boolean isStageVisible() {
+ for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
+ if (mChildrenTaskInfo.valueAt(i).isVisible
+ && mChildrenTaskInfo.valueAt(i).isVisibleRequested) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private SurfaceControl findTaskSurface(int taskId) {
if (mRootTaskInfo.taskId == taskId) {
return mRootLeash;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java
index a1a9ca9..4ea4613 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java
@@ -136,6 +136,7 @@
@NonNull final Animation mAnim;
@Nullable final Point mPosition;
@Nullable final Rect mClipRect;
+ @Nullable private final Rect mAnimClipRect;
final float mCornerRadius;
final boolean mIsActivity;
@@ -147,6 +148,7 @@
mPosition = (position != null && (position.x != 0 || position.y != 0))
? position : null;
mClipRect = (clipRect != null && !clipRect.isEmpty()) ? clipRect : null;
+ mAnimClipRect = mClipRect != null ? new Rect() : null;
mCornerRadius = cornerRadius;
mIsActivity = isActivity;
}
@@ -169,18 +171,26 @@
t.setAlpha(leash, transformation.getAlpha());
if (mClipRect != null) {
- Rect clipRect = mClipRect;
+ boolean needCrop = false;
+ mAnimClipRect.set(mClipRect);
+ if (transformation.hasClipRect()
+ && com.android.window.flags.Flags.respectAnimationClip()) {
+ mAnimClipRect.intersectUnchecked(transformation.getClipRect());
+ needCrop = true;
+ }
final Insets extensionInsets = Insets.min(transformation.getInsets(), Insets.NONE);
if (!extensionInsets.equals(Insets.NONE)) {
// Clip out any overflowing edge extension.
- clipRect = new Rect(mClipRect);
- clipRect.inset(extensionInsets);
- t.setCrop(leash, clipRect);
+ mAnimClipRect.inset(extensionInsets);
+ needCrop = true;
}
if (mCornerRadius > 0 && mAnim.hasRoundedCorners()) {
// Rounded corner can only be applied if a crop is set.
- t.setCrop(leash, clipRect);
t.setCornerRadius(leash, mCornerRadius);
+ needCrop = true;
+ }
+ if (needCrop) {
+ t.setCrop(leash, mAnimClipRect);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 67775f7..17e3dd2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -41,7 +41,6 @@
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
-import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import android.annotation.NonNull;
import android.app.ActivityManager;
@@ -49,6 +48,7 @@
import android.app.ActivityTaskManager;
import android.app.IActivityManager;
import android.app.IActivityTaskManager;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Point;
@@ -103,6 +103,7 @@
import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator;
@@ -121,8 +122,6 @@
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
-import com.android.wm.shell.splitscreen.SplitScreen;
-import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -445,17 +444,6 @@
@Override
public void setSplitScreenController(SplitScreenController splitScreenController) {
mSplitScreenController = splitScreenController;
- mSplitScreenController.registerSplitScreenListener(new SplitScreen.SplitScreenListener() {
- @Override
- public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {
- if (visible && stage != STAGE_TYPE_UNDEFINED) {
- DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId);
- if (decor != null && DesktopModeStatus.canEnterDesktopMode(mContext)) {
- mDesktopTasksController.moveToSplit(decor.mTaskInfo);
- }
- }
- }
- });
}
@Override
@@ -1289,6 +1277,7 @@
}
break;
}
+ case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP: {
if (mTransitionDragActive) {
final DesktopModeVisualIndicator.DragStartState dragStartState =
@@ -1303,32 +1292,11 @@
// Though this isn't a hover event, we need to update handle's hover state
// as it likely will change.
relevantDecor.updateHoverAndPressStatus(ev);
- DesktopModeVisualIndicator.IndicatorType resultType =
- mDesktopTasksController.onDragPositioningEndThroughStatusBar(
- new PointF(ev.getRawX(), ev.getRawY()),
- relevantDecor.mTaskInfo,
- relevantDecor.mTaskSurface);
- // If we are entering split select, handle will no longer be visible and
- // should not be receiving any input.
- if (resultType == TO_SPLIT_LEFT_INDICATOR
- || resultType == TO_SPLIT_RIGHT_INDICATOR) {
- relevantDecor.disposeStatusBarInputLayer();
- // We should also dispose the other split task's input layer if
- // applicable.
- final int splitPosition = mSplitScreenController
- .getSplitPosition(relevantDecor.mTaskInfo.taskId);
- if (splitPosition != SPLIT_POSITION_UNDEFINED) {
- final int oppositePosition =
- splitPosition == SPLIT_POSITION_TOP_OR_LEFT
- ? SPLIT_POSITION_BOTTOM_OR_RIGHT
- : SPLIT_POSITION_TOP_OR_LEFT;
- final RunningTaskInfo oppositeTaskInfo =
- mSplitScreenController.getTaskInfo(oppositePosition);
- if (oppositeTaskInfo != null) {
- mWindowDecorByTaskId.get(oppositeTaskInfo.taskId)
- .disposeStatusBarInputLayer();
- }
- }
+ if (ev.getActionMasked() == ACTION_CANCEL) {
+ mDesktopTasksController.onDragPositioningCancelThroughStatusBar(
+ relevantDecor.mTaskInfo);
+ } else {
+ endDragToDesktop(ev, relevantDecor);
}
mMoveToDesktopAnimator = null;
return;
@@ -1377,10 +1345,35 @@
}
break;
}
+ }
+ }
- case MotionEvent.ACTION_CANCEL: {
- mTransitionDragActive = false;
- mMoveToDesktopAnimator = null;
+ private void endDragToDesktop(MotionEvent ev, DesktopModeWindowDecoration relevantDecor) {
+ DesktopModeVisualIndicator.IndicatorType resultType =
+ mDesktopTasksController.onDragPositioningEndThroughStatusBar(
+ new PointF(ev.getRawX(), ev.getRawY()),
+ relevantDecor.mTaskInfo,
+ relevantDecor.mTaskSurface);
+ // If we are entering split select, handle will no longer be visible and
+ // should not be receiving any input.
+ if (resultType == TO_SPLIT_LEFT_INDICATOR
+ || resultType == TO_SPLIT_RIGHT_INDICATOR) {
+ relevantDecor.disposeStatusBarInputLayer();
+ // We should also dispose the other split task's input layer if
+ // applicable.
+ final int splitPosition = mSplitScreenController
+ .getSplitPosition(relevantDecor.mTaskInfo.taskId);
+ if (splitPosition != SPLIT_POSITION_UNDEFINED) {
+ final int oppositePosition =
+ splitPosition == SPLIT_POSITION_TOP_OR_LEFT
+ ? SPLIT_POSITION_BOTTOM_OR_RIGHT
+ : SPLIT_POSITION_TOP_OR_LEFT;
+ final RunningTaskInfo oppositeTaskInfo =
+ mSplitScreenController.getTaskInfo(oppositePosition);
+ if (oppositeTaskInfo != null) {
+ mWindowDecorByTaskId.get(oppositeTaskInfo.taskId)
+ .disposeStatusBarInputLayer();
+ }
}
}
}
@@ -1480,6 +1473,9 @@
&& isTopActivityExemptFromDesktopWindowing(mContext, taskInfo)) {
return false;
}
+ if (isPartOfDefaultHomePackage(taskInfo)) {
+ return false;
+ }
return DesktopModeStatus.canEnterDesktopMode(mContext)
&& !DesktopWallpaperActivity.isWallpaperTask(taskInfo)
&& taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED
@@ -1487,6 +1483,14 @@
&& !taskInfo.configuration.windowConfiguration.isAlwaysOnTop();
}
+ private boolean isPartOfDefaultHomePackage(RunningTaskInfo taskInfo) {
+ final ComponentName currentDefaultHome =
+ mContext.getPackageManager().getHomeActivities(new ArrayList<>());
+ return currentDefaultHome != null && taskInfo.baseActivity != null
+ && currentDefaultHome.getPackageName()
+ .equals(taskInfo.baseActivity.getPackageName());
+ }
+
private void createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
@@ -1572,6 +1576,10 @@
onManageWindows(windowDecoration);
return Unit.INSTANCE;
});
+ windowDecoration.setOnChangeAspectRatioClickListener(() -> {
+ CompatUIController.launchUserAspectRatioSettings(mContext, taskInfo);
+ return Unit.INSTANCE;
+ });
windowDecoration.setCaptionListeners(
touchEventListener, touchEventListener, touchEventListener, touchEventListener);
windowDecoration.setExclusionRegionListener(mExclusionRegionListener);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index af7cd05..d97632a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -154,6 +154,7 @@
private Function0<Unit> mOnToSplitscreenClickListener;
private Function0<Unit> mOnNewWindowClickListener;
private Function0<Unit> mOnManageWindowsClickListener;
+ private Function0<Unit> mOnChangeAspectRatioClickListener;
private DragPositioningCallback mDragPositioningCallback;
private DragResizeInputListener mDragResizeListener;
private Runnable mCurrentViewHostRunnable = null;
@@ -364,6 +365,11 @@
mOnManageWindowsClickListener = listener;
}
+ /** Registers a listener to be called when the aspect ratio action is triggered. */
+ void setOnChangeAspectRatioClickListener(Function0<Unit> listener) {
+ mOnChangeAspectRatioClickListener = listener;
+ }
+
void setCaptionListeners(
View.OnClickListener onCaptionButtonClickListener,
View.OnTouchListener onCaptionTouchListener,
@@ -1358,6 +1364,8 @@
&& Flags.enableDesktopWindowingMultiInstanceFeatures();
final boolean shouldShowManageWindowsButton = supportsMultiInstance
&& mMinimumInstancesFound;
+ final boolean shouldShowChangeAspectRatioButton = HandleMenu.Companion
+ .shouldShowChangeAspectRatioButton(mTaskInfo);
final boolean inDesktopImmersive = mDesktopRepository
.isTaskInFullImmersiveState(mTaskInfo.taskId);
mHandleMenu = mHandleMenuFactory.create(
@@ -1370,6 +1378,7 @@
canEnterDesktopMode(mContext),
supportsMultiInstance,
shouldShowManageWindowsButton,
+ shouldShowChangeAspectRatioButton,
getBrowserLink(),
mResult.mCaptionWidth,
mResult.mCaptionHeight,
@@ -1390,6 +1399,7 @@
/* onToSplitScreenClickListener= */ mOnToSplitscreenClickListener,
/* onNewWindowClickListener= */ mOnNewWindowClickListener,
/* onManageWindowsClickListener= */ mOnManageWindowsClickListener,
+ /* onAspectRatioSettingsClickListener= */ mOnChangeAspectRatioClickListener,
/* openInBrowserClickListener= */ (intent) -> {
mOpenInBrowserClickListener.accept(intent);
onCapturedLinkExpired();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index 93bd929..2edc380 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -19,6 +19,7 @@
import android.annotation.DimenRes
import android.annotation.SuppressLint
import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
@@ -71,6 +72,7 @@
private val shouldShowWindowingPill: Boolean,
private val shouldShowNewWindowButton: Boolean,
private val shouldShowManageWindowsButton: Boolean,
+ private val shouldShowChangeAspectRatioButton: Boolean,
private val openInBrowserIntent: Intent?,
private val captionWidth: Int,
private val captionHeight: Int,
@@ -111,6 +113,10 @@
private val shouldShowBrowserPill: Boolean
get() = openInBrowserIntent != null
+ private val shouldShowMoreActionsPill: Boolean
+ get() = SHOULD_SHOW_SCREENSHOT_BUTTON || shouldShowNewWindowButton ||
+ shouldShowManageWindowsButton || shouldShowChangeAspectRatioButton
+
init {
updateHandleMenuPillPositions(captionX, captionY)
}
@@ -121,6 +127,7 @@
onToSplitScreenClickListener: () -> Unit,
onNewWindowClickListener: () -> Unit,
onManageWindowsClickListener: () -> Unit,
+ onChangeAspectRatioClickListener: () -> Unit,
openInBrowserClickListener: (Intent) -> Unit,
onOpenByDefaultClickListener: () -> Unit,
onCloseMenuClickListener: () -> Unit,
@@ -138,6 +145,7 @@
onToSplitScreenClickListener = onToSplitScreenClickListener,
onNewWindowClickListener = onNewWindowClickListener,
onManageWindowsClickListener = onManageWindowsClickListener,
+ onChangeAspectRatioClickListener = onChangeAspectRatioClickListener,
openInBrowserClickListener = openInBrowserClickListener,
onOpenByDefaultClickListener = onOpenByDefaultClickListener,
onCloseMenuClickListener = onCloseMenuClickListener,
@@ -158,6 +166,7 @@
onToSplitScreenClickListener: () -> Unit,
onNewWindowClickListener: () -> Unit,
onManageWindowsClickListener: () -> Unit,
+ onChangeAspectRatioClickListener: () -> Unit,
openInBrowserClickListener: (Intent) -> Unit,
onOpenByDefaultClickListener: () -> Unit,
onCloseMenuClickListener: () -> Unit,
@@ -171,14 +180,16 @@
shouldShowWindowingPill = shouldShowWindowingPill,
shouldShowBrowserPill = shouldShowBrowserPill,
shouldShowNewWindowButton = shouldShowNewWindowButton,
- shouldShowManageWindowsButton = shouldShowManageWindowsButton
+ shouldShowManageWindowsButton = shouldShowManageWindowsButton,
+ shouldShowChangeAspectRatioButton = shouldShowChangeAspectRatioButton
).apply {
- bind(taskInfo, appIconBitmap, appName)
+ bind(taskInfo, appIconBitmap, appName, shouldShowMoreActionsPill)
this.onToDesktopClickListener = onToDesktopClickListener
this.onToFullscreenClickListener = onToFullscreenClickListener
this.onToSplitScreenClickListener = onToSplitScreenClickListener
this.onNewWindowClickListener = onNewWindowClickListener
this.onManageWindowsClickListener = onManageWindowsClickListener
+ this.onChangeAspectRatioClickListener = onChangeAspectRatioClickListener
this.onOpenInBrowserClickListener = {
openInBrowserClickListener.invoke(openInBrowserIntent!!)
}
@@ -392,8 +403,11 @@
R.dimen.desktop_mode_handle_menu_manage_windows_height
)
}
- if (!SHOULD_SHOW_SCREENSHOT_BUTTON && !shouldShowNewWindowButton
- && !shouldShowManageWindowsButton) {
+ if (!shouldShowChangeAspectRatioButton) {
+ menuHeight -= loadDimensionPixelSize(
+ R.dimen.desktop_mode_handle_menu_change_aspect_ratio_height)
+ }
+ if (!shouldShowMoreActionsPill) {
menuHeight -= pillTopMargin
}
if (!shouldShowBrowserPill) {
@@ -427,7 +441,8 @@
private val shouldShowWindowingPill: Boolean,
private val shouldShowBrowserPill: Boolean,
private val shouldShowNewWindowButton: Boolean,
- private val shouldShowManageWindowsButton: Boolean
+ private val shouldShowManageWindowsButton: Boolean,
+ private val shouldShowChangeAspectRatioButton: Boolean
) {
val rootView = LayoutInflater.from(context)
.inflate(R.layout.desktop_mode_window_decor_handle_menu, null /* root */) as View
@@ -454,6 +469,8 @@
private val newWindowBtn = moreActionsPill.requireViewById<Button>(R.id.new_window_button)
private val manageWindowBtn = moreActionsPill
.requireViewById<Button>(R.id.manage_windows_button)
+ private val changeAspectRatioBtn = moreActionsPill
+ .requireViewById<Button>(R.id.change_aspect_ratio_button)
// Open in Browser Pill.
private val openInBrowserPill = rootView.requireViewById<View>(R.id.open_in_browser_pill)
@@ -472,6 +489,7 @@
var onToSplitScreenClickListener: (() -> Unit)? = null
var onNewWindowClickListener: (() -> Unit)? = null
var onManageWindowsClickListener: (() -> Unit)? = null
+ var onChangeAspectRatioClickListener: (() -> Unit)? = null
var onOpenInBrowserClickListener: (() -> Unit)? = null
var onOpenByDefaultClickListener: (() -> Unit)? = null
var onCloseMenuClickListener: (() -> Unit)? = null
@@ -488,6 +506,7 @@
collapseMenuButton.setOnClickListener { onCloseMenuClickListener?.invoke() }
newWindowBtn.setOnClickListener { onNewWindowClickListener?.invoke() }
manageWindowBtn.setOnClickListener { onManageWindowsClickListener?.invoke() }
+ changeAspectRatioBtn.setOnClickListener { onChangeAspectRatioClickListener?.invoke() }
rootView.setOnTouchListener { _, event ->
if (event.actionMasked == ACTION_OUTSIDE) {
@@ -499,7 +518,12 @@
}
/** Binds the menu views to the new data. */
- fun bind(taskInfo: RunningTaskInfo, appIconBitmap: Bitmap?, appName: CharSequence?) {
+ fun bind(
+ taskInfo: RunningTaskInfo,
+ appIconBitmap: Bitmap?,
+ appName: CharSequence?,
+ shouldShowMoreActionsPill: Boolean
+ ) {
this.taskInfo = taskInfo
this.style = calculateMenuStyle(taskInfo)
@@ -507,7 +531,10 @@
if (shouldShowWindowingPill) {
bindWindowingPill(style)
}
- bindMoreActionsPill(style)
+ moreActionsPill.isGone = !shouldShowMoreActionsPill
+ if (shouldShowMoreActionsPill) {
+ bindMoreActionsPill(style)
+ }
bindOpenInBrowserPill(style)
}
@@ -616,27 +643,20 @@
}
private fun bindMoreActionsPill(style: MenuStyle) {
- moreActionsPill.apply {
- isGone = !shouldShowNewWindowButton && !SHOULD_SHOW_SCREENSHOT_BUTTON
- && !shouldShowManageWindowsButton
- }
- screenshotBtn.apply {
- isGone = !SHOULD_SHOW_SCREENSHOT_BUTTON
- background.setTint(style.backgroundColor)
- setTextColor(style.textColor)
- compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
- }
- newWindowBtn.apply {
- isGone = !shouldShowNewWindowButton
- background.setTint(style.backgroundColor)
- setTextColor(style.textColor)
- compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
- }
- manageWindowBtn.apply {
- isGone = !shouldShowManageWindowsButton
- background.setTint(style.backgroundColor)
- setTextColor(style.textColor)
- compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
+ arrayOf(
+ screenshotBtn to SHOULD_SHOW_SCREENSHOT_BUTTON,
+ newWindowBtn to shouldShowNewWindowButton,
+ manageWindowBtn to shouldShowManageWindowsButton,
+ changeAspectRatioBtn to shouldShowChangeAspectRatioButton,
+ ).forEach {
+ val button = it.first
+ val shouldShow = it.second
+ button.apply {
+ isGone = !shouldShow
+ background.setTint(style.backgroundColor)
+ setTextColor(style.textColor)
+ compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
+ }
}
}
@@ -664,6 +684,14 @@
companion object {
private const val TAG = "HandleMenu"
private const val SHOULD_SHOW_SCREENSHOT_BUTTON = false
+
+ /**
+ * Returns whether the aspect ratio button should be shown for the task. It usually means
+ * that the task is on a large screen with ignore-orientation-request.
+ */
+ fun shouldShowChangeAspectRatioButton(taskInfo: RunningTaskInfo): Boolean =
+ taskInfo.appCompatTaskInfo.eligibleForUserAspectRatioButton() &&
+ taskInfo.windowingMode == WindowConfiguration.WINDOWING_MODE_FULLSCREEN
}
}
@@ -679,6 +707,7 @@
shouldShowWindowingPill: Boolean,
shouldShowNewWindowButton: Boolean,
shouldShowManageWindowsButton: Boolean,
+ shouldShowChangeAspectRatioButton: Boolean,
openInBrowserIntent: Intent?,
captionWidth: Int,
captionHeight: Int,
@@ -699,6 +728,7 @@
shouldShowWindowingPill: Boolean,
shouldShowNewWindowButton: Boolean,
shouldShowManageWindowsButton: Boolean,
+ shouldShowChangeAspectRatioButton: Boolean,
openInBrowserIntent: Intent?,
captionWidth: Int,
captionHeight: Int,
@@ -715,6 +745,7 @@
shouldShowWindowingPill,
shouldShowNewWindowButton,
shouldShowManageWindowsButton,
+ shouldShowChangeAspectRatioButton,
openInBrowserIntent,
captionWidth,
captionHeight,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MinimizeAppWindowsTest.kt
similarity index 61%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MinimizeAppWindowsTest.kt
index 94b2bdf..e16159c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MinimizeAppWindowsTest.kt
@@ -14,9 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.util.settings
+package com.android.wm.shell.functional
-import com.android.systemui.kosmos.Kosmos
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.MinimizeAppWindows
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
-val Kosmos.userAwareSecureSettingsRepository by
- Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+/* Functional test for [MinimizeAppWindows]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class MinimizeAppWindowsTest : MinimizeAppWindows()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt
new file mode 100644
index 0000000..b548363
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.scenarios
+
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.NewTasksAppHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+/**
+ * Base scenario test for minimizing all the desktop app windows one-by-one by clicking their
+ * minimize buttons.
+ */
+@Ignore("Test Base Class")
+abstract class MinimizeAppWindows
+constructor(private val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val testApp1 = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+ private val testApp2 = DesktopModeAppHelper(NonResizeableAppHelper(instrumentation))
+ private val testApp3 = DesktopModeAppHelper(NewTasksAppHelper(instrumentation))
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, Rotation.ROTATION_0)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ Assume.assumeTrue(Flags.enableMinimizeButton())
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+ ChangeDisplayOrientationRule.setRotation(rotation)
+ testApp1.enterDesktopWithDrag(wmHelper, device)
+ testApp2.launchViaIntent(wmHelper)
+ testApp3.launchViaIntent(wmHelper)
+ }
+
+ @Test
+ open fun minimizeAllAppWindows() {
+ testApp3.minimizeDesktopApp(wmHelper, device)
+ testApp2.minimizeDesktopApp(wmHelper, device)
+ testApp1.minimizeDesktopApp(wmHelper, device)
+ }
+
+ @After
+ fun teardown() {
+ testApp1.exit(wmHelper)
+ testApp2.exit(wmHelper)
+ testApp3.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
index 266e484..2ed7d07 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
@@ -62,6 +62,7 @@
@Before
public void setUp() throws Exception {
+ mTargetProgressCalled = new CountDownLatch(1);
mMainThreadHandler = new Handler(Looper.getMainLooper());
final BackMotionEvent backEvent = backMotionEventFrom(0, 0);
mMainThreadHandler.post(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
index ecaf970..803e5d4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
@@ -43,38 +43,30 @@
.apply {
isTopActivityTransparent = true
numActivities = 1
- }))
- assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
- createFreeformTask(/* displayId */ 0)
- .apply {
- isTopActivityTransparent = true
- numActivities = 0
+ isTopActivityNoDisplay = false
}))
}
@Test
- fun testIsTopActivityExemptFromDesktopWindowing_singleTopActivity() {
- assertTrue(isTopActivityExemptFromDesktopWindowing(mContext,
- createFreeformTask(/* displayId */ 0)
- .apply {
- isTopActivityTransparent = true
- numActivities = 1
- }))
+ fun testIsTopActivityExemptFromDesktopWindowing_topActivityTransparent_multipleActivities() {
assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
createFreeformTask(/* displayId */ 0)
- .apply {
- isTopActivityTransparent = false
- numActivities = 1
- }))
+ .apply {
+ isTopActivityTransparent = true
+ numActivities = 2
+ isTopActivityNoDisplay = false
+ }))
}
@Test
- fun testIsTopActivityExemptFromDesktopWindowing__topActivityStyleFloating() {
+ fun testIsTopActivityExemptFromDesktopWindowing_topActivityTransparent_notDisplayed() {
assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
createFreeformTask(/* displayId */ 0)
- .apply {
- isTopActivityStyleFloating = true
- }))
+ .apply {
+ isTopActivityTransparent = true
+ numActivities = 1
+ isTopActivityNoDisplay = true
+ }))
}
@Test
@@ -85,6 +77,19 @@
createFreeformTask(/* displayId */ 0)
.apply {
baseActivity = baseComponent
+ isTopActivityNoDisplay = false
}))
}
+
+ @Test
+ fun testIsTopActivityExemptFromDesktopWindowing_systemUiTask_notDisplayed() {
+ val systemUIPackageName = context.resources.getString(R.string.config_systemUi)
+ val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
+ assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
+ createFreeformTask(/* displayId */ 0)
+ .apply {
+ baseActivity = baseComponent
+ isTopActivityNoDisplay = true
+ }))
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
new file mode 100644
index 0000000..fea8236
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.content.ContentResolver
+import android.os.Binder
+import android.provider.Settings
+import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
+import android.testing.AndroidTestingRunner
+import android.view.Display.DEFAULT_DISPLAY
+import android.view.IWindowManager
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.window.DisplayAreaInfo
+import android.window.WindowContainerTransaction
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.never
+import com.android.wm.shell.MockToken
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.Transitions
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.isNull
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.whenever
+
+/**
+ * Test class for [DesktopDisplayEventHandler]
+ *
+ * Usage: atest WMShellUnitTests:DesktopDisplayEventHandlerTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesktopDisplayEventHandlerTest : ShellTestCase() {
+
+ @Mock lateinit var testExecutor: ShellExecutor
+ @Mock lateinit var transitions: Transitions
+ @Mock lateinit var displayController: DisplayController
+ @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+ @Mock private lateinit var mockWindowManager: IWindowManager
+
+ private lateinit var shellInit: ShellInit
+ private lateinit var handler: DesktopDisplayEventHandler
+
+ @Before
+ fun setUp() {
+ shellInit = spy(ShellInit(testExecutor))
+ whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
+ val tda = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda)
+ handler =
+ DesktopDisplayEventHandler(
+ context,
+ shellInit,
+ transitions,
+ displayController,
+ rootTaskDisplayAreaOrganizer,
+ mockWindowManager,
+ )
+ shellInit.init()
+ }
+
+ private fun testDisplayWindowingModeSwitch(
+ defaultWindowingMode: Int,
+ extendedDisplayEnabled: Boolean,
+ expectTransition: Boolean
+ ) {
+ val externalDisplayId = 100
+ val captor = ArgumentCaptor.forClass(OnDisplaysChangedListener::class.java)
+ verify(displayController).addDisplayWindowListener(captor.capture())
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = defaultWindowingMode
+ whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer { defaultWindowingMode }
+ val settingsSession = ExtendedDisplaySettingsSession(
+ context.contentResolver, if (extendedDisplayEnabled) 1 else 0)
+
+ settingsSession.use {
+ // The external display connected.
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, externalDisplayId))
+ captor.value.onDisplayAdded(externalDisplayId)
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+ // The external display disconnected.
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY))
+ captor.value.onDisplayRemoved(externalDisplayId)
+
+ if (expectTransition) {
+ val arg = argumentCaptor<WindowContainerTransaction>()
+ verify(transitions, times(2)).startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
+ assertThat(arg.firstValue.changes[tda.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ assertThat(arg.secondValue.changes[tda.token.asBinder()]?.windowingMode)
+ .isEqualTo(defaultWindowingMode)
+ } else {
+ verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), any(), isNull())
+ }
+ }
+ }
+
+ @Test
+ fun displayWindowingModeSwitchOnDisplayConnected_extendedDisplayDisabled() {
+ testDisplayWindowingModeSwitch(
+ defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ extendedDisplayEnabled = false,
+ expectTransition = false
+ )
+ }
+
+ @Test
+ fun displayWindowingModeSwitchOnDisplayConnected_fullscreenDisplay() {
+ testDisplayWindowingModeSwitch(
+ defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ extendedDisplayEnabled = true,
+ expectTransition = true
+ )
+ }
+
+ @Test
+ fun displayWindowingModeSwitchOnDisplayConnected_freeformDisplay() {
+ testDisplayWindowingModeSwitch(
+ defaultWindowingMode = WINDOWING_MODE_FREEFORM,
+ extendedDisplayEnabled = true,
+ expectTransition = false
+ )
+ }
+
+ private class ExtendedDisplaySettingsSession(
+ private val contentResolver: ContentResolver,
+ private val overrideValue: Int
+ ) : AutoCloseable {
+ private val settingName = DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
+ private val initialValue = Settings.Global.getInt(contentResolver, settingName, 0)
+
+ init { Settings.Global.putInt(contentResolver, settingName, overrideValue) }
+
+ override fun close() {
+ Settings.Global.putInt(contentResolver, settingName, initialValue)
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index b157d55..315a46f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -1123,11 +1123,11 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- fun moveRunningTaskToDesktop_topActivityTranslucentWithStyleFloating_taskIsMovedToDesktop() {
+ fun moveRunningTaskToDesktop_topActivityTranslucentWithoutDisplay_taskIsMovedToDesktop() {
val task =
setUpFullscreenTask().apply {
isTopActivityTransparent = true
- isTopActivityStyleFloating = true
+ isTopActivityNoDisplay = true
numActivities = 1
}
@@ -1139,11 +1139,11 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- fun moveRunningTaskToDesktop_topActivityTranslucentWithoutStyleFloating_doesNothing() {
+ fun moveRunningTaskToDesktop_topActivityTranslucentWithDisplay_doesNothing() {
val task =
setUpFullscreenTask().apply {
isTopActivityTransparent = true
- isTopActivityStyleFloating = false
+ isTopActivityNoDisplay = false
numActivities = 1
}
@@ -1153,20 +1153,41 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- fun moveRunningTaskToDesktop_systemUIActivity_doesNothing() {
- val task = setUpFullscreenTask()
-
+ fun moveRunningTaskToDesktop_systemUIActivityWithDisplay_doesNothing() {
// Set task as systemUI package
val systemUIPackageName = context.resources.getString(
com.android.internal.R.string.config_systemUi)
val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
- task.baseActivity = baseComponent
+ val task =
+ setUpFullscreenTask().apply {
+ baseActivity = baseComponent
+ isTopActivityNoDisplay = false
+ }
controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
verifyEnterDesktopWCTNotExecuted()
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun moveRunningTaskToDesktop_systemUIActivityWithoutDisplay_doesNothing() {
+ // Set task as systemUI package
+ val systemUIPackageName = context.resources.getString(
+ com.android.internal.R.string.config_systemUi)
+ val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
+ val task =
+ setUpFullscreenTask().apply {
+ baseActivity = baseComponent
+ isTopActivityNoDisplay = true
+ }
+
+ controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
+
+ val wct = getLatestEnterDesktopWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
fun moveRunningTaskToDesktop_deviceSupported_taskIsMovedToDesktop() {
val task = setUpFullscreenTask()
@@ -2223,14 +2244,14 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- fun handleRequest_topActivityTransparentWithStyleFloating_returnSwitchToFreeformWCT() {
+ fun handleRequest_topActivityTransparentWithoutDisplay_returnSwitchToFreeformWCT() {
val freeformTask = setUpFreeformTask()
markTaskVisible(freeformTask)
val task =
setUpFullscreenTask().apply {
isTopActivityTransparent = true
- isTopActivityStyleFloating = true
+ isTopActivityNoDisplay = true
numActivities = 1
}
@@ -2241,11 +2262,14 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- fun handleRequest_topActivityTransparentWithoutStyleFloating_returnSwitchToFullscreenWCT() {
+ fun handleRequest_topActivityTransparentWithDisplay_returnSwitchToFullscreenWCT() {
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+
val task =
setUpFreeformTask().apply {
isTopActivityTransparent = true
- isTopActivityStyleFloating = false
+ isTopActivityNoDisplay = false
numActivities = 1
}
@@ -2256,14 +2280,19 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- fun handleRequest_systemUIActivity_returnSwitchToFullscreenWCT() {
- val task = setUpFreeformTask()
+ fun handleRequest_systemUIActivityWithDisplay_returnSwitchToFullscreenWCT() {
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
// Set task as systemUI package
val systemUIPackageName = context.resources.getString(
com.android.internal.R.string.config_systemUi)
val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
- task.baseActivity = baseComponent
+ val task =
+ setUpFreeformTask().apply {
+ baseActivity = baseComponent
+ isTopActivityNoDisplay = false
+ }
val result = controller.handleRequest(Binder(), createTransition(task))
assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
@@ -2271,6 +2300,27 @@
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun handleRequest_systemUIActivityWithoutDisplay_returnSwitchToFreeformWCT() {
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+
+ // Set task as systemUI package
+ val systemUIPackageName = context.resources.getString(
+ com.android.internal.R.string.config_systemUi)
+ val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
+ val task =
+ setUpFullscreenTask().apply {
+ baseActivity = baseComponent
+ isTopActivityNoDisplay = true
+ }
+
+ val result = controller.handleRequest(Binder(), createTransition(task))
+ assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,)
fun handleRequest_backTransition_singleTaskNoToken_noWallpaper_doesNotHandle() {
val task = setUpFreeformTask()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java
new file mode 100644
index 0000000..a4008c1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2.animation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+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.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Resources;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
+import com.android.wm.shell.pip2.phone.PipAppIconOverlay;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit test again {@link PipEnterAnimator}.
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class PipEnterAnimatorTest {
+
+ @Mock private Context mMockContext;
+
+ @Mock private Resources mMockResources;
+
+ @Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory;
+
+ @Mock private SurfaceControl.Transaction mMockAnimateTransaction;
+
+ @Mock private SurfaceControl.Transaction mMockStartTransaction;
+
+ @Mock private SurfaceControl.Transaction mMockFinishTransaction;
+
+ @Mock private Runnable mMockStartCallback;
+
+ @Mock private Runnable mMockEndCallback;
+
+ @Mock private PipAppIconOverlay mMockPipAppIconOverlay;
+
+ @Mock private SurfaceControl mMockAppIconOverlayLeash;
+
+ @Mock private ActivityInfo mMockActivityInfo;
+
+ @Surface.Rotation private int mRotation;
+ private SurfaceControl mTestLeash;
+ private Rect mEndBounds;
+ private PipEnterAnimator mPipEnterAnimator;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mMockContext.getResources()).thenReturn(mMockResources);
+ when(mMockResources.getInteger(anyInt())).thenReturn(0);
+ when(mMockFactory.getTransaction()).thenReturn(mMockAnimateTransaction);
+ when(mMockAnimateTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+ .thenReturn(mMockAnimateTransaction);
+ when(mMockStartTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+ .thenReturn(mMockStartTransaction);
+ when(mMockFinishTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+ .thenReturn(mMockFinishTransaction);
+ when(mMockPipAppIconOverlay.getLeash()).thenReturn(mMockAppIconOverlayLeash);
+
+ mTestLeash = new SurfaceControl.Builder()
+ .setContainerLayer()
+ .setName("PipExpandAnimatorTest")
+ .setCallsite("PipExpandAnimatorTest")
+ .build();
+ }
+
+ @Test
+ public void setAnimationStartCallback_enter_callbackStartCallback() {
+ mRotation = Surface.ROTATION_0;
+ mEndBounds = new Rect(100, 100, 500, 500);
+ mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash,
+ mMockStartTransaction, mMockFinishTransaction,
+ mEndBounds, mRotation);
+ mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+ mPipEnterAnimator.setAnimationStartCallback(mMockStartCallback);
+ mPipEnterAnimator.setAnimationEndCallback(mMockEndCallback);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipEnterAnimator.start();
+ mPipEnterAnimator.pause();
+ });
+
+ verify(mMockStartCallback).run();
+ verifyZeroInteractions(mMockEndCallback);
+ }
+
+ @Test
+ public void setAnimationEndCallback_enter_callbackStartAndEndCallback() {
+ mRotation = Surface.ROTATION_0;
+ mEndBounds = new Rect(100, 100, 500, 500);
+ mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash,
+ mMockStartTransaction, mMockFinishTransaction,
+ mEndBounds, mRotation);
+ mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+ mPipEnterAnimator.setAnimationStartCallback(mMockStartCallback);
+ mPipEnterAnimator.setAnimationEndCallback(mMockEndCallback);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipEnterAnimator.start();
+ mPipEnterAnimator.end();
+ });
+
+ verify(mMockStartCallback).run();
+ verify(mMockEndCallback).run();
+ }
+
+ @Test
+ public void setAppIconContentOverlay_thenGetContentOverlayLeash_returnOverlayLeash() {
+ mRotation = Surface.ROTATION_0;
+ mEndBounds = new Rect(100, 100, 500, 500);
+ mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash,
+ mMockStartTransaction, mMockFinishTransaction,
+ mEndBounds, mRotation);
+ mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+ mPipEnterAnimator.setPipAppIconOverlaySupplier(
+ (context, appBounds, endBounds, icon, iconSize) -> mMockPipAppIconOverlay);
+
+ mPipEnterAnimator.setAppIconContentOverlay(mMockContext, mEndBounds, mEndBounds,
+ mMockActivityInfo, 64 /* iconSize */);
+
+ assertEquals(mPipEnterAnimator.getContentOverlayLeash(), mMockAppIconOverlayLeash);
+ }
+
+ @Test
+ public void setAppIconContentOverlay_thenClearAppIconOverlay_returnNullLeash() {
+ mRotation = Surface.ROTATION_0;
+ mEndBounds = new Rect(100, 100, 500, 500);
+ mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash,
+ mMockStartTransaction, mMockFinishTransaction,
+ mEndBounds, mRotation);
+ mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+ mPipEnterAnimator.setPipAppIconOverlaySupplier(
+ (context, appBounds, endBounds, icon, iconSize) -> mMockPipAppIconOverlay);
+
+ mPipEnterAnimator.setAppIconContentOverlay(mMockContext, mEndBounds, mEndBounds,
+ mMockActivityInfo, 64 /* iconSize */);
+ mPipEnterAnimator.clearAppIconOverlay();
+
+ assertNull(mPipEnterAnimator.getContentOverlayLeash());
+ }
+
+ @Test
+ public void onEnterAnimationUpdate_withContentOverlay_animateOverlay() {
+ mRotation = Surface.ROTATION_0;
+ mEndBounds = new Rect(100, 100, 500, 500);
+ mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash,
+ mMockStartTransaction, mMockFinishTransaction,
+ mEndBounds, mRotation);
+ mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+ mPipEnterAnimator.setPipAppIconOverlaySupplier(
+ (context, appBounds, endBounds, icon, iconSize) -> mMockPipAppIconOverlay);
+
+ float fraction = 0.5f;
+ mPipEnterAnimator.setAppIconContentOverlay(mMockContext, mEndBounds, mEndBounds,
+ mMockActivityInfo, 64 /* iconSize */);
+ mPipEnterAnimator.onEnterAnimationUpdate(fraction, mMockAnimateTransaction);
+
+ verify(mMockPipAppIconOverlay).onAnimationUpdate(
+ eq(mMockAnimateTransaction), anyFloat(), eq(fraction), eq(mEndBounds));
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 03aab18..5626717 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -478,25 +478,10 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- fun testDecorationIsCreatedForTopTranslucentActivitiesWithStyleFloating() {
+ fun testDecorationIsNotCreatedForTopTranslucentActivities() {
val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN).apply {
isTopActivityTransparent = true
- isTopActivityStyleFloating = true
- numActivities = 1
- }
- doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(any()) }
- setUpMockDecorationsForTasks(task)
-
- onTaskOpening(task)
- assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- fun testDecorationIsNotCreatedForTopTranslucentActivitiesWithoutStyleFloating() {
- val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN).apply {
- isTopActivityTransparent = true
- isTopActivityStyleFloating = false
+ isTopActivityNoDisplay = false
numActivities = 1
}
onTaskOpening(task)
@@ -507,13 +492,14 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
fun testDecorationIsNotCreatedForSystemUIActivities() {
- val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN)
-
// Set task as systemUI package
val systemUIPackageName = context.resources.getString(
com.android.internal.R.string.config_systemUi)
val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
- task.baseActivity = baseComponent
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN).apply {
+ baseActivity = baseComponent
+ isTopActivityNoDisplay = false
+ }
onTaskOpening(task)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 86ec675..41f57ae 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -262,8 +262,8 @@
doReturn(defaultDisplay).when(mMockDisplayController).getDisplay(Display.DEFAULT_DISPLAY);
doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt());
when(mMockHandleMenuFactory.create(any(), any(), anyInt(), any(), any(), any(),
- anyBoolean(), anyBoolean(), anyBoolean(), any(), anyInt(), anyInt(), anyInt(),
- anyInt()))
+ anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), any(), anyInt(), anyInt(),
+ anyInt(), anyInt()))
.thenReturn(mMockHandleMenu);
when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any())).thenReturn(false);
when(mMockAppHeaderViewHolderFactory.create(any(), any(), any(), any(), any(), any(), any(),
@@ -1178,6 +1178,7 @@
any(),
any(),
any(),
+ any(),
openInBrowserCaptor.capture(),
any(),
any(),
@@ -1208,6 +1209,7 @@
any(),
any(),
any(),
+ any(),
openInBrowserCaptor.capture(),
any(),
any(),
@@ -1263,6 +1265,7 @@
any(),
any(),
any(),
+ any(),
closeClickListener.capture(),
any(),
anyBoolean()
@@ -1294,6 +1297,7 @@
any(),
any(),
any(),
+ any(),
/* forceShowSystemBars= */ eq(true)
);
}
@@ -1438,7 +1442,7 @@
private void verifyHandleMenuCreated(@Nullable Uri uri) {
verify(mMockHandleMenuFactory).create(any(), any(), anyInt(), any(), any(),
- any(), anyBoolean(), anyBoolean(), anyBoolean(),
+ any(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(),
argThat(intent -> (uri == null && intent == null) || intent.getData().equals(uri)),
anyInt(), anyInt(), anyInt(), anyInt());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
index 9544fa8..ade17c6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -266,6 +266,7 @@
WindowManagerWrapper(mockWindowManager),
layoutId, appIcon, appName, splitScreenController, shouldShowWindowingPill = true,
shouldShowNewWindowButton = true, shouldShowManageWindowsButton = false,
+ shouldShowChangeAspectRatioButton = false,
null /* openInBrowserLink */, captionWidth = HANDLE_WIDTH, captionHeight = 50,
captionX = captionX,
captionY = 0,
@@ -276,6 +277,7 @@
onToSplitScreenClickListener = mock(),
onNewWindowClickListener = mock(),
onManageWindowsClickListener = mock(),
+ onChangeAspectRatioClickListener = mock(),
openInBrowserClickListener = mock(),
onOpenByDefaultClickListener = mock(),
onCloseMenuClickListener = mock(),
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index fcb7efc..b71abdc 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -580,7 +580,6 @@
"utils/Color.cpp",
"utils/LinearAllocator.cpp",
"utils/StringUtils.cpp",
- "utils/StatsUtils.cpp",
"utils/TypefaceUtils.cpp",
"utils/VectorDrawableUtils.cpp",
"AnimationContext.cpp",
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index a9a5db8..e074a27 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -27,8 +27,8 @@
#include <SkColorSpace.h>
#include <SkColorType.h>
#include <SkEncodedOrigin.h>
-#include <SkGainmapInfo.h>
#include <SkImageInfo.h>
+#include <SkGainmapInfo.h>
#include <SkMatrix.h>
#include <SkPaint.h>
#include <SkPngChunkReader.h>
@@ -43,8 +43,6 @@
#include <memory>
-#include "modules/skcms/src/skcms_public.h"
-
using namespace android;
sk_sp<SkColorSpace> ImageDecoder::getDefaultColorSpace() const {
diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp
index 8b43f1d..49a7f73 100644
--- a/libs/hwui/jni/BitmapFactory.cpp
+++ b/libs/hwui/jni/BitmapFactory.cpp
@@ -10,7 +10,6 @@
#include <stdint.h>
#include <stdio.h>
#include <sys/stat.h>
-#include <utils/StatsUtils.h>
#include <memory>
@@ -631,7 +630,6 @@
}
bitmap::reinitBitmap(env, javaBitmap, outputBitmap.info(), isPremultiplied);
outputBitmap.notifyPixelsChanged();
- uirenderer::logBitmapDecode(*reuseBitmap);
// If a java bitmap was passed in for reuse, pass it back
return javaBitmap;
}
@@ -652,7 +650,6 @@
}
}
- uirenderer::logBitmapDecode(*hardwareBitmap);
return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags,
ninePatchChunk, ninePatchInsets, -1);
}
@@ -662,7 +659,6 @@
heapBitmap->setGainmap(std::move(gainmap));
}
- uirenderer::logBitmapDecode(*heapBitmap);
// now create the java bitmap
return bitmap::createBitmap(env, heapBitmap, bitmapCreateFlags, ninePatchChunk, ninePatchInsets,
-1);
diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp
index 5ffd5b9..f7e8e07 100644
--- a/libs/hwui/jni/BitmapRegionDecoder.cpp
+++ b/libs/hwui/jni/BitmapRegionDecoder.cpp
@@ -19,7 +19,6 @@
#include <HardwareBitmapUploader.h>
#include <androidfw/Asset.h>
#include <sys/stat.h>
-#include <utils/StatsUtils.h>
#include <memory>
@@ -377,7 +376,6 @@
recycledBitmap->setGainmap(std::move(gainmap));
}
bitmap::reinitBitmap(env, javaBitmap, recycledBitmap->info(), !requireUnpremul);
- uirenderer::logBitmapDecode(*recycledBitmap);
return javaBitmap;
}
@@ -394,14 +392,12 @@
hardwareBitmap->setGainmap(std::move(gm));
}
}
- uirenderer::logBitmapDecode(*hardwareBitmap);
return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags);
}
Bitmap* heapBitmap = heapAlloc.getStorageObjAndReset();
if (hasGainmap && heapBitmap != nullptr) {
heapBitmap->setGainmap(std::move(gainmap));
}
- uirenderer::logBitmapDecode(*heapBitmap);
return android::bitmap::createBitmap(env, heapBitmap, bitmapCreateFlags);
}
diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp
index 90fd3d8..aebc4db 100644
--- a/libs/hwui/jni/ImageDecoder.cpp
+++ b/libs/hwui/jni/ImageDecoder.cpp
@@ -37,7 +37,6 @@
#include <hwui/Bitmap.h>
#include <hwui/ImageDecoder.h>
#include <sys/stat.h>
-#include <utils/StatsUtils.h>
#include "Bitmap.h"
#include "BitmapFactory.h"
@@ -486,7 +485,6 @@
hwBitmap->setGainmap(std::move(gm));
}
}
- uirenderer::logBitmapDecode(*hwBitmap);
return bitmap::createBitmap(env, hwBitmap.release(), bitmapCreateFlags,
ninePatchChunk, ninePatchInsets);
}
@@ -500,8 +498,6 @@
nativeBitmap->setImmutable();
}
-
- uirenderer::logBitmapDecode(*nativeBitmap);
return bitmap::createBitmap(env, nativeBitmap.release(), bitmapCreateFlags, ninePatchChunk,
ninePatchInsets);
}
diff --git a/libs/hwui/libhwui.map.txt b/libs/hwui/libhwui.map.txt
index b559194..2414299 100644
--- a/libs/hwui/libhwui.map.txt
+++ b/libs/hwui/libhwui.map.txt
@@ -67,7 +67,6 @@
SkFILEStream::SkFILEStream*;
SkImageInfo::*;
SkMemoryStream::SkMemoryStream*;
- android::uirenderer::logBitmapDecode*;
};
local:
*;
diff --git a/libs/hwui/utils/StatsUtils.cpp b/libs/hwui/utils/StatsUtils.cpp
deleted file mode 100644
index 5c4027e..0000000
--- a/libs/hwui/utils/StatsUtils.cpp
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifdef __ANDROID__
-#include <dlfcn.h>
-#include <log/log.h>
-#include <statslog_hwui.h>
-#include <statssocket_lazy.h>
-#include <utils/Errors.h>
-
-#include <mutex>
-#endif
-
-#include <unistd.h>
-
-#include "StatsUtils.h"
-
-namespace android {
-namespace uirenderer {
-
-#ifdef __ANDROID__
-
-namespace {
-
-int32_t toStatsColorSpaceTransfer(skcms_TFType transferType) {
- switch (transferType) {
- case skcms_TFType_sRGBish:
- return stats::IMAGE_DECODED__COLOR_SPACE_TRANSFER__COLOR_SPACE_TRANSFER_SRGBISH;
- case skcms_TFType_PQish:
- return stats::IMAGE_DECODED__COLOR_SPACE_TRANSFER__COLOR_SPACE_TRANSFER_PQISH;
- case skcms_TFType_HLGish:
- return stats::IMAGE_DECODED__COLOR_SPACE_TRANSFER__COLOR_SPACE_TRANSFER_HLGISH;
- default:
- return stats::IMAGE_DECODED__COLOR_SPACE_TRANSFER__COLOR_SPACE_TRANSFER_UNKNOWN;
- }
-}
-
-int32_t toStatsBitmapFormat(SkColorType type) {
- switch (type) {
- case kAlpha_8_SkColorType:
- return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_A_8;
- case kRGB_565_SkColorType:
- return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_RGB_565;
- case kN32_SkColorType:
- return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_ARGB_8888;
- case kRGBA_F16_SkColorType:
- return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_RGBA_F16;
- case kRGBA_1010102_SkColorType:
- return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_RGBA_1010102;
- default:
- return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_UNKNOWN;
- }
-}
-
-} // namespace
-
-#endif
-
-void logBitmapDecode(const SkImageInfo& info, bool hasGainmap) {
-#ifdef __ANDROID__
-
- if (!statssocket::lazy::IsAvailable()) {
- std::once_flag once;
- std::call_once(once, []() { ALOGD("libstatssocket not available, dropping stats"); });
- return;
- }
-
- skcms_TFType tfnType = skcms_TFType_Invalid;
-
- if (info.colorSpace()) {
- skcms_TransferFunction tfn;
- info.colorSpace()->transferFn(&tfn);
- tfnType = skcms_TransferFunction_getType(&tfn);
- }
-
- auto status =
- stats::stats_write(uirenderer::stats::IMAGE_DECODED, static_cast<int32_t>(getuid()),
- uirenderer::toStatsColorSpaceTransfer(tfnType), hasGainmap,
- uirenderer::toStatsBitmapFormat(info.colorType()));
- ALOGW_IF(status != OK, "Image decoding logging dropped!");
-#endif
-}
-
-void logBitmapDecode(const Bitmap& bitmap) {
- logBitmapDecode(bitmap.info(), bitmap.hasGainmap());
-}
-
-} // namespace uirenderer
-} // namespace android
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 1db7198..5b90547 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -16,11 +16,15 @@
package android.media;
+import static android.media.audio.Flags.FLAG_SPEAKER_CLEANUP_USAGE;
+
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+// TODO switch from HIDL imports to AIDL
import android.audio.policy.configuration.V7_0.AudioUsage;
import android.compat.annotation.UnsupportedAppUsage;
import android.media.audiopolicy.AudioProductStrategy;
@@ -247,6 +251,16 @@
public static final int USAGE_ANNOUNCEMENT = SYSTEM_USAGE_OFFSET + 3;
/**
+ * @hide
+ * Usage value to use when a system application plays a signal intended to clean up the
+ * speaker transducers and free them of deposits of dust or water.
+ */
+ @FlaggedApi(FLAG_SPEAKER_CLEANUP_USAGE)
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public static final int USAGE_SPEAKER_CLEANUP = SYSTEM_USAGE_OFFSET + 4;
+
+ /**
* IMPORTANT: when adding new usage types, add them to SDK_USAGES and update SUPPRESSIBLE_USAGES
* if applicable, as well as audioattributes.proto.
* Also consider adding them to <aaudio/AAudio.h> for the NDK.
@@ -1521,6 +1535,8 @@
return "USAGE_VEHICLE_STATUS";
case USAGE_ANNOUNCEMENT:
return "USAGE_ANNOUNCEMENT";
+ case USAGE_SPEAKER_CLEANUP:
+ return "USAGE_SPEAKER_CLEANUP";
default:
return "unknown usage " + usage;
}
@@ -1661,12 +1677,8 @@
}
/**
- * @param usage one of {@link AttributeSystemUsage},
- * {@link AttributeSystemUsage#USAGE_CALL_ASSISTANT},
- * {@link AttributeSystemUsage#USAGE_EMERGENCY},
- * {@link AttributeSystemUsage#USAGE_SAFETY},
- * {@link AttributeSystemUsage#USAGE_VEHICLE_STATUS},
- * {@link AttributeSystemUsage#USAGE_ANNOUNCEMENT}
+ * Returns whether the given usage can only be used by system-privileged components
+ * @param usage one of {@link AttributeSystemUsage}.
* @return boolean indicating if the usage is a system usage or not
* @hide
*/
@@ -1676,7 +1688,8 @@
|| usage == USAGE_EMERGENCY
|| usage == USAGE_SAFETY
|| usage == USAGE_VEHICLE_STATUS
- || usage == USAGE_ANNOUNCEMENT);
+ || usage == USAGE_ANNOUNCEMENT
+ || usage == USAGE_SPEAKER_CLEANUP);
}
/**
@@ -1790,6 +1803,7 @@
case USAGE_SAFETY:
case USAGE_VEHICLE_STATUS:
case USAGE_ANNOUNCEMENT:
+ case USAGE_SPEAKER_CLEANUP:
case USAGE_UNKNOWN:
return AudioSystem.STREAM_MUSIC;
default:
@@ -1829,7 +1843,8 @@
USAGE_EMERGENCY,
USAGE_SAFETY,
USAGE_VEHICLE_STATUS,
- USAGE_ANNOUNCEMENT
+ USAGE_ANNOUNCEMENT,
+ USAGE_SPEAKER_CLEANUP
})
@Retention(RetentionPolicy.SOURCE)
public @interface AttributeSystemUsage {}
@@ -1879,6 +1894,7 @@
USAGE_SAFETY,
USAGE_VEHICLE_STATUS,
USAGE_ANNOUNCEMENT,
+ USAGE_SPEAKER_CLEANUP,
})
@Retention(RetentionPolicy.SOURCE)
public @interface AttributeUsage {}
diff --git a/media/java/android/media/flags/editing.aconfig b/media/java/android/media/flags/editing.aconfig
index 185f579..0adc478 100644
--- a/media/java/android/media/flags/editing.aconfig
+++ b/media/java/android/media/flags/editing.aconfig
@@ -15,3 +15,10 @@
description: "Enable B frames for Stagefright recorder."
bug: "341121900"
}
+
+flag {
+ name: "muxer_mp4_enable_apv"
+ namespace: "media_solutions"
+ description: "Enable APV support in mp4 writer."
+ bug: "370061501"
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/media/java/android/media/quality/AmbientBacklightEvent.aidl
similarity index 75%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to media/java/android/media/quality/AmbientBacklightEvent.aidl
index 94b2bdf..174cd46 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/media/java/android/media/quality/AmbientBacklightEvent.aidl
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-package com.android.systemui.util.settings
+package android.media.quality;
-import com.android.systemui.kosmos.Kosmos
-
-val Kosmos.userAwareSecureSettingsRepository by
- Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+parcelable AmbientBacklightEvent;
diff --git a/media/java/android/media/quality/AmbientBacklightEvent.java b/media/java/android/media/quality/AmbientBacklightEvent.java
new file mode 100644
index 0000000..3bc6b86
--- /dev/null
+++ b/media/java/android/media/quality/AmbientBacklightEvent.java
@@ -0,0 +1,136 @@
+/*
+ * 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.media.quality;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+public final class AmbientBacklightEvent implements Parcelable {
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({AMBIENT_BACKLIGHT_EVENT_ENABLED, AMBIENT_BACKLIGHT_EVENT_DISABLED,
+ AMBIENT_BACKLIGHT_EVENT_METADATA,
+ AMBIENT_BACKLIGHT_EVENT_INTERRUPTED})
+ public @interface AmbientBacklightEventTypes {}
+
+ /**
+ * Event type for ambient backlight events. The ambient backlight is enabled.
+ */
+ public static final int AMBIENT_BACKLIGHT_EVENT_ENABLED = 1;
+
+ /**
+ * Event type for ambient backlight events. The ambient backlight is disabled.
+ */
+ public static final int AMBIENT_BACKLIGHT_EVENT_DISABLED = 2;
+
+ /**
+ * Event type for ambient backlight events. The ambient backlight metadata is
+ * available.
+ */
+ public static final int AMBIENT_BACKLIGHT_EVENT_METADATA = 3;
+
+ /**
+ * Event type for ambient backlight events. The ambient backlight event is preempted by another
+ * application.
+ */
+ public static final int AMBIENT_BACKLIGHT_EVENT_INTERRUPTED = 4;
+
+ private final int mEventType;
+ @Nullable
+ private final AmbientBacklightMetadata mMetadata;
+
+ public AmbientBacklightEvent(int eventType,
+ @Nullable AmbientBacklightMetadata metadata) {
+ mEventType = eventType;
+ mMetadata = metadata;
+ }
+
+ private AmbientBacklightEvent(Parcel in) {
+ mEventType = in.readInt();
+ mMetadata = in.readParcelable(AmbientBacklightMetadata.class.getClassLoader());
+ }
+
+ public int getEventType() {
+ return mEventType;
+ }
+
+ @Nullable
+ public AmbientBacklightMetadata getMetadata() {
+ return mMetadata;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mEventType);
+ dest.writeParcelable(mMetadata, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @NonNull Parcelable.Creator<AmbientBacklightEvent> CREATOR =
+ new Parcelable.Creator<AmbientBacklightEvent>() {
+ public AmbientBacklightEvent createFromParcel(Parcel in) {
+ return new AmbientBacklightEvent(in);
+ }
+
+ public AmbientBacklightEvent[] newArray(int size) {
+ return new AmbientBacklightEvent[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof AmbientBacklightEvent)) {
+ return false;
+ }
+
+ AmbientBacklightEvent other = (AmbientBacklightEvent) obj;
+ return mEventType == other.mEventType
+ && Objects.equals(mMetadata, other.mMetadata);
+ }
+
+ @Override
+ public int hashCode() {
+ return mEventType * 31 + (mMetadata != null ? mMetadata.hashCode() : 0);
+ }
+
+ @Override
+ public String toString() {
+ return "AmbientBacklightEvent{"
+ + "mEventType=" + mEventType
+ + ", mMetadata=" + mMetadata
+ + '}';
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/media/java/android/media/quality/AmbientBacklightMetadata.aidl
similarity index 75%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to media/java/android/media/quality/AmbientBacklightMetadata.aidl
index 94b2bdf..b95a474f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/media/java/android/media/quality/AmbientBacklightMetadata.aidl
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-package com.android.systemui.util.settings
+package android.media.quality;
-import com.android.systemui.kosmos.Kosmos
-
-val Kosmos.userAwareSecureSettingsRepository by
- Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+parcelable AmbientBacklightMetadata;
\ No newline at end of file
diff --git a/media/java/android/media/quality/AmbientBacklightMetadata.java b/media/java/android/media/quality/AmbientBacklightMetadata.java
new file mode 100644
index 0000000..fc77934
--- /dev/null
+++ b/media/java/android/media/quality/AmbientBacklightMetadata.java
@@ -0,0 +1,127 @@
+/*
+ * 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.media.quality;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import java.util.Arrays;
+
+/**
+ * @hide
+ */
+public class AmbientBacklightMetadata implements Parcelable {
+ @NonNull
+ private final String mPackageName;
+ private final int mCompressAlgorithm;
+ private final int mSource;
+ private final int mColorFormat;
+ private final int mHorizontalZonesNumber;
+ private final int mVerticalZonesNumber;
+ @NonNull
+ private final int[] mZonesColors;
+
+ public AmbientBacklightMetadata(@NonNull String packageName, int compressAlgorithm,
+ int source, int colorFormat, int horizontalZonesNumber, int verticalZonesNumber,
+ @NonNull int[] zonesColors) {
+ mPackageName = packageName;
+ mCompressAlgorithm = compressAlgorithm;
+ mSource = source;
+ mColorFormat = colorFormat;
+ mHorizontalZonesNumber = horizontalZonesNumber;
+ mVerticalZonesNumber = verticalZonesNumber;
+ mZonesColors = zonesColors;
+ }
+
+ private AmbientBacklightMetadata(Parcel in) {
+ mPackageName = in.readString();
+ mCompressAlgorithm = in.readInt();
+ mSource = in.readInt();
+ mColorFormat = in.readInt();
+ mHorizontalZonesNumber = in.readInt();
+ mVerticalZonesNumber = in.readInt();
+ mZonesColors = in.createIntArray();
+ }
+
+ @NonNull
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public int getCompressAlgorithm() {
+ return mCompressAlgorithm;
+ }
+
+ public int getSource() {
+ return mSource;
+ }
+
+ public int getColorFormat() {
+ return mColorFormat;
+ }
+
+ public int getHorizontalZonesNumber() {
+ return mHorizontalZonesNumber;
+ }
+
+ public int getVerticalZonesNumber() {
+ return mVerticalZonesNumber;
+ }
+
+ @NonNull
+ public int[] getZonesColors() {
+ return mZonesColors;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mPackageName);
+ dest.writeInt(mCompressAlgorithm);
+ dest.writeInt(mSource);
+ dest.writeInt(mColorFormat);
+ dest.writeInt(mHorizontalZonesNumber);
+ dest.writeInt(mVerticalZonesNumber);
+ dest.writeIntArray(mZonesColors);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @NonNull Parcelable.Creator<AmbientBacklightMetadata> CREATOR =
+ new Parcelable.Creator<AmbientBacklightMetadata>() {
+ public AmbientBacklightMetadata createFromParcel(Parcel in) {
+ return new AmbientBacklightMetadata(in);
+ }
+
+ public AmbientBacklightMetadata[] newArray(int size) {
+ return new AmbientBacklightMetadata[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "AmbientBacklightMetadata{packageName=" + mPackageName
+ + ", compressAlgorithm=" + mCompressAlgorithm + ", source=" + mSource
+ + ", colorFormat=" + mColorFormat + ", horizontalZonesNumber="
+ + mHorizontalZonesNumber + ", verticalZonesNumber=" + mVerticalZonesNumber
+ + ", zonesColors=" + Arrays.toString(mZonesColors) + "}";
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/media/java/android/media/quality/AmbientBacklightSettings.aidl
similarity index 75%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to media/java/android/media/quality/AmbientBacklightSettings.aidl
index 94b2bdf..e2cdd03 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/media/java/android/media/quality/AmbientBacklightSettings.aidl
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-package com.android.systemui.util.settings
+package android.media.quality;
-import com.android.systemui.kosmos.Kosmos
-
-val Kosmos.userAwareSecureSettingsRepository by
- Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+parcelable AmbientBacklightSettings;
diff --git a/media/java/android/media/quality/AmbientBacklightSettings.java b/media/java/android/media/quality/AmbientBacklightSettings.java
new file mode 100644
index 0000000..391eb22
--- /dev/null
+++ b/media/java/android/media/quality/AmbientBacklightSettings.java
@@ -0,0 +1,203 @@
+/*
+ * 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.media.quality;
+
+import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * @hide
+ */
+public class AmbientBacklightSettings implements Parcelable {
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({SOURCE_NONE, SOURCE_AUDIO, SOURCE_VIDEO, SOURCE_AUDIO_VIDEO})
+ public @interface Source {}
+
+ /**
+ * The detection is disabled.
+ */
+ public static final int SOURCE_NONE = 0;
+
+ /**
+ * The detection is enabled for audio.
+ */
+ public static final int SOURCE_AUDIO = 1;
+
+ /**
+ * The detection is enabled for video.
+ */
+ public static final int SOURCE_VIDEO = 2;
+
+ /**
+ * The detection is enabled for audio and video.
+ */
+ public static final int SOURCE_AUDIO_VIDEO = 3;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({COLOR_FORMAT_RGB888})
+ public @interface ColorFormat {}
+
+ /**
+ * The color format is RGB888.
+ */
+ public static final int COLOR_FORMAT_RGB888 = 1;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({ALGORITHM_NONE, ALGORITHM_RLE})
+ public @interface CompressAlgorithm {}
+
+ /**
+ * The compress algorithm is disabled.
+ */
+ public static final int ALGORITHM_NONE = 0;
+
+ /**
+ * The compress algorithm is RLE.
+ */
+ public static final int ALGORITHM_RLE = 1;
+
+ /**
+ * The source of the ambient backlight.
+ */
+ private final int mSource;
+
+ /**
+ * The maximum framerate for the ambient backlight.
+ */
+ private final int mMaxFps;
+
+ /**
+ * The color format for the ambient backlight.
+ */
+ private final int mColorFormat;
+
+ /**
+ * The number of zones in horizontal direction.
+ */
+ private final int mHorizontalZonesNumber;
+
+ /**
+ * The number of zones in vertical direction.
+ */
+ private final int mVerticalZonesNumber;
+
+ /**
+ * The flag to indicate whether the letterbox is omitted.
+ */
+ private final boolean mIsLetterboxOmitted;
+
+ /**
+ * The color threshold for the ambient backlight.
+ */
+ private final int mThreshold;
+
+ public AmbientBacklightSettings(int source, int maxFps, int colorFormat,
+ int horizontalZonesNumber, int verticalZonesNumber, boolean isLetterboxOmitted,
+ int threshold) {
+ mSource = source;
+ mMaxFps = maxFps;
+ mColorFormat = colorFormat;
+ mHorizontalZonesNumber = horizontalZonesNumber;
+ mVerticalZonesNumber = verticalZonesNumber;
+ mIsLetterboxOmitted = isLetterboxOmitted;
+ mThreshold = threshold;
+ }
+
+ private AmbientBacklightSettings(Parcel in) {
+ mSource = in.readInt();
+ mMaxFps = in.readInt();
+ mColorFormat = in.readInt();
+ mHorizontalZonesNumber = in.readInt();
+ mVerticalZonesNumber = in.readInt();
+ mIsLetterboxOmitted = in.readBoolean();
+ mThreshold = in.readInt();
+ }
+
+ @Source
+ public int getSource() {
+ return mSource;
+ }
+
+ public int getMaxFps() {
+ return mMaxFps;
+ }
+
+ @ColorFormat
+ public int getColorFormat() {
+ return mColorFormat;
+ }
+
+ public int getHorizontalZonesNumber() {
+ return mHorizontalZonesNumber;
+ }
+
+ public int getVerticalZonesNumber() {
+ return mVerticalZonesNumber;
+ }
+
+ public boolean isLetterboxOmitted() {
+ return mIsLetterboxOmitted;
+ }
+
+ public int getThreshold() {
+ return mThreshold;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mSource);
+ dest.writeInt(mMaxFps);
+ dest.writeInt(mColorFormat);
+ dest.writeInt(mHorizontalZonesNumber);
+ dest.writeInt(mVerticalZonesNumber);
+ dest.writeBoolean(mIsLetterboxOmitted);
+ dest.writeInt(mThreshold);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @NonNull Parcelable.Creator<AmbientBacklightSettings> CREATOR =
+ new Parcelable.Creator<AmbientBacklightSettings>() {
+ public AmbientBacklightSettings createFromParcel(Parcel in) {
+ return new AmbientBacklightSettings(in);
+ }
+
+ public AmbientBacklightSettings[] newArray(int size) {
+ return new AmbientBacklightSettings[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "AmbientBacklightSettings{Source=" + mSource + ", MaxFps=" + mMaxFps
+ + ", ColorFormat=" + mColorFormat + ", HorizontalZonesNumber="
+ + mHorizontalZonesNumber + ", VerticalZonesNumber=" + mVerticalZonesNumber
+ + ", IsLetterboxOmitted=" + mIsLetterboxOmitted + ", Threshold=" + mThreshold + "}";
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/media/java/android/media/quality/IAmbientBacklightCallback.aidl
similarity index 74%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to media/java/android/media/quality/IAmbientBacklightCallback.aidl
index 94b2bdf..159f5b7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/media/java/android/media/quality/IAmbientBacklightCallback.aidl
@@ -14,9 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.util.settings
+package android.media.quality;
-import com.android.systemui.kosmos.Kosmos
+import android.media.quality.AmbientBacklightEvent;
-val Kosmos.userAwareSecureSettingsRepository by
- Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+/** @hide */
+oneway interface IAmbientBacklightCallback {
+ void onAmbientBacklightEvent(in AmbientBacklightEvent event);
+}
diff --git a/media/java/android/media/quality/IMediaQualityManager.aidl b/media/java/android/media/quality/IMediaQualityManager.aidl
index e413a50..e6c79dd 100644
--- a/media/java/android/media/quality/IMediaQualityManager.aidl
+++ b/media/java/android/media/quality/IMediaQualityManager.aidl
@@ -16,7 +16,13 @@
package android.media.quality;
+import android.media.quality.AmbientBacklightSettings;
+import android.media.quality.IAmbientBacklightCallback;
+import android.media.quality.IPictureProfileCallback;
+import android.media.quality.ISoundProfileCallback;
+import android.media.quality.ParamCapability;
import android.media.quality.PictureProfile;
+import android.media.quality.SoundProfile;
/**
* Interface for Media Quality Manager
@@ -24,8 +30,35 @@
*/
interface IMediaQualityManager {
PictureProfile createPictureProfile(in PictureProfile pp);
+ void updatePictureProfile(in long id, in PictureProfile pp);
+ void removePictureProfile(in long id);
PictureProfile getPictureProfileById(in long id);
List<PictureProfile> getPictureProfilesByPackage(in String packageName);
List<PictureProfile> getAvailablePictureProfiles();
- List<PictureProfile> getAvailableAllPictureProfiles();
-}
\ No newline at end of file
+ List<PictureProfile> getAllPictureProfiles();
+
+ SoundProfile createSoundProfile(in SoundProfile pp);
+ void updateSoundProfile(in long id, in SoundProfile pp);
+ void removeSoundProfile(in long id);
+ SoundProfile getSoundProfileById(in long id);
+ List<SoundProfile> getSoundProfilesByPackage(in String packageName);
+ List<SoundProfile> getAvailableSoundProfiles();
+ List<SoundProfile> getAllSoundProfiles();
+
+ void registerPictureProfileCallback(in IPictureProfileCallback cb);
+ void registerSoundProfileCallback(in ISoundProfileCallback cb);
+ void registerAmbientBacklightCallback(in IAmbientBacklightCallback cb);
+
+ List<ParamCapability> getParamCapabilities(in List<String> names);
+
+ boolean isSupported();
+ void setAutoPictureQualityEnabled(in boolean enabled);
+ boolean isAutoPictureQualityEnabled();
+ void setSuperResolutionEnabled(in boolean enabled);
+ boolean isSuperResolutionEnabled();
+ void setAutoSoundQualityEnabled(in boolean enabled);
+ boolean isAutoSoundQualityEnabled();
+
+ void setAmbientBacklightSettings(in AmbientBacklightSettings settings);
+ void setAmbientBacklightEnabled(in boolean enabled);
+}
diff --git a/media/java/android/media/quality/IPictureProfileCallback.aidl b/media/java/android/media/quality/IPictureProfileCallback.aidl
index 5deb029..05441cd 100644
--- a/media/java/android/media/quality/IPictureProfileCallback.aidl
+++ b/media/java/android/media/quality/IPictureProfileCallback.aidl
@@ -26,4 +26,5 @@
oneway interface IPictureProfileCallback {
void onPictureProfileAdded(in long id, in PictureProfile p);
void onPictureProfileUpdated(in long id, in PictureProfile p);
+ void onPictureProfileRemoved(in long id, in PictureProfile p);
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/media/java/android/media/quality/ISoundProfileCallback.aidl
similarity index 61%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to media/java/android/media/quality/ISoundProfileCallback.aidl
index 94b2bdf..72d1524 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/media/java/android/media/quality/ISoundProfileCallback.aidl
@@ -14,9 +14,17 @@
* limitations under the License.
*/
-package com.android.systemui.util.settings
-import com.android.systemui.kosmos.Kosmos
+package android.media.quality;
-val Kosmos.userAwareSecureSettingsRepository by
- Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+import android.media.quality.SoundProfile;
+
+/**
+ * Interface to receive callbacks from IMediaQuality.
+ * @hide
+ */
+oneway interface ISoundProfileCallback {
+ void onSoundProfileAdded(in long id, in SoundProfile p);
+ void onSoundProfileUpdated(in long id, in SoundProfile p);
+ void onSoundProfileRemoved(in long id, in SoundProfile p);
+}
diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java
index 03dc24d..1237d67 100644
--- a/media/java/android/media/quality/MediaQualityManager.java
+++ b/media/java/android/media/quality/MediaQualityManager.java
@@ -39,7 +39,7 @@
*/
@FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
@SystemService(Context.MEDIA_QUALITY_SERVICE)
-public class MediaQualityManager {
+public final class MediaQualityManager {
// TODO: unhide the APIs for api review
private static final String TAG = "MediaQualityManager";
@@ -48,6 +48,11 @@
private final Object mLock = new Object();
// @GuardedBy("mLock")
private final List<PictureProfileCallbackRecord> mPpCallbackRecords = new ArrayList<>();
+ // @GuardedBy("mLock")
+ private final List<SoundProfileCallbackRecord> mSpCallbackRecords = new ArrayList<>();
+ // @GuardedBy("mLock")
+ private final List<AmbientBacklightCallbackRecord> mAbCallbackRecords = new ArrayList<>();
+
/**
* @hide
@@ -55,7 +60,7 @@
public MediaQualityManager(Context context, IMediaQualityManager service) {
mContext = context;
mService = service;
- IPictureProfileCallback mqCallback = new IPictureProfileCallback.Stub() {
+ IPictureProfileCallback ppCallback = new IPictureProfileCallback.Stub() {
@Override
public void onPictureProfileAdded(long profileId, PictureProfile profile) {
synchronized (mLock) {
@@ -74,14 +79,72 @@
}
}
}
+ @Override
+ public void onPictureProfileRemoved(long profileId, PictureProfile profile) {
+ synchronized (mLock) {
+ for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
+ // TODO: filter callback record
+ record.postPictureProfileRemoved(profileId, profile);
+ }
+ }
+ }
};
+ ISoundProfileCallback spCallback = new ISoundProfileCallback.Stub() {
+ @Override
+ public void onSoundProfileAdded(long profileId, SoundProfile profile) {
+ synchronized (mLock) {
+ for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
+ // TODO: filter callback record
+ record.postSoundProfileAdded(profileId, profile);
+ }
+ }
+ }
+ @Override
+ public void onSoundProfileUpdated(long profileId, SoundProfile profile) {
+ synchronized (mLock) {
+ for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
+ // TODO: filter callback record
+ record.postSoundProfileUpdated(profileId, profile);
+ }
+ }
+ }
+ @Override
+ public void onSoundProfileRemoved(long profileId, SoundProfile profile) {
+ synchronized (mLock) {
+ for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
+ // TODO: filter callback record
+ record.postSoundProfileRemoved(profileId, profile);
+ }
+ }
+ }
+ };
+ IAmbientBacklightCallback abCallback = new IAmbientBacklightCallback.Stub() {
+ @Override
+ public void onAmbientBacklightEvent(AmbientBacklightEvent event) {
+ synchronized (mLock) {
+ for (AmbientBacklightCallbackRecord record : mAbCallbackRecords) {
+ record.postAmbientBacklightEvent(event);
+ }
+ }
+ }
+ };
+
+ try {
+ if (mService != null) {
+ mService.registerPictureProfileCallback(ppCallback);
+ mService.registerSoundProfileCallback(spCallback);
+ mService.registerAmbientBacklightCallback(abCallback);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
* Registers a {@link PictureProfileCallback}.
* @hide
*/
- public void registerCallback(
+ public void registerPictureProfileCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull PictureProfileCallback callback) {
Preconditions.checkNotNull(callback);
@@ -95,7 +158,7 @@
* Unregisters the existing {@link PictureProfileCallback}.
* @hide
*/
- public void unregisterCallback(@NonNull final PictureProfileCallback callback) {
+ public void unregisterPictureProfileCallback(@NonNull final PictureProfileCallback callback) {
Preconditions.checkNotNull(callback);
synchronized (mLock) {
for (Iterator<PictureProfileCallbackRecord> it = mPpCallbackRecords.iterator();
@@ -143,11 +206,11 @@
}
}
- /** @SystemApi all stored picture profiles */
+ /** @SystemApi all stored picture profiles of all packages */
@RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
- public List<PictureProfile> getAvailableAllPictureProfiles() {
+ public List<PictureProfile> getAllPictureProfiles() {
try {
- return mService.getAvailableAllPictureProfiles();
+ return mService.getAllPictureProfiles();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -167,6 +230,308 @@
}
}
+
+ /**
+ * Updates an existing picture profile and store it in the system.
+ */
+ public void updatePictureProfile(long profileId, PictureProfile pp) {
+ try {
+ mService.updatePictureProfile(profileId, pp);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+
+ /**
+ * Removes a picture profile from the system.
+ */
+ public void removePictureProfile(long profileId) {
+ try {
+ mService.removePictureProfile(profileId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Registers a {@link SoundProfileCallback}.
+ * @hide
+ */
+ public void registerSoundProfileCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull SoundProfileCallback callback) {
+ Preconditions.checkNotNull(callback);
+ Preconditions.checkNotNull(executor);
+ synchronized (mLock) {
+ mSpCallbackRecords.add(new SoundProfileCallbackRecord(callback, executor));
+ }
+ }
+
+ /**
+ * Unregisters the existing {@link SoundProfileCallback}.
+ * @hide
+ */
+ public void unregisterSoundProfileCallback(@NonNull final SoundProfileCallback callback) {
+ Preconditions.checkNotNull(callback);
+ synchronized (mLock) {
+ for (Iterator<SoundProfileCallbackRecord> it = mSpCallbackRecords.iterator();
+ it.hasNext(); ) {
+ SoundProfileCallbackRecord record = it.next();
+ if (record.getCallback() == callback) {
+ it.remove();
+ break;
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Gets sound profile by given profile ID.
+ * @return the corresponding sound profile if available; {@code null} if the ID doesn't
+ * exist or the profile is not accessible to the caller.
+ */
+ public SoundProfile getSoundProfileById(long profileId) {
+ try {
+ return mService.getSoundProfileById(profileId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+
+ /** @SystemApi gets profiles that available to the given package */
+ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
+ public List<SoundProfile> getSoundProfilesByPackage(String packageName) {
+ try {
+ return mService.getSoundProfilesByPackage(packageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** Gets profiles that available to the caller package */
+ public List<SoundProfile> getAvailableSoundProfiles() {
+ try {
+ return mService.getAvailableSoundProfiles();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @SystemApi all stored sound profiles of all packages */
+ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
+ public List<SoundProfile> getAllSoundProfiles() {
+ try {
+ return mService.getAllSoundProfiles();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+
+ /**
+ * Creates a sound profile and store it in the system.
+ *
+ * @return the stored profile with an assigned profile ID.
+ */
+ public SoundProfile createSoundProfile(SoundProfile sp) {
+ try {
+ return mService.createSoundProfile(sp);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+
+ /**
+ * Updates an existing sound profile and store it in the system.
+ */
+ public void updateSoundProfile(long profileId, SoundProfile sp) {
+ try {
+ mService.updateSoundProfile(profileId, sp);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+
+ /**
+ * Removes a sound profile from the system.
+ */
+ public void removeSoundProfile(long profileId) {
+ try {
+ mService.removeSoundProfile(profileId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets capability information of the given parameters.
+ */
+ public List<ParamCapability> getParamCapabilities(List<String> names) {
+ try {
+ return mService.getParamCapabilities(names);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns {@code true} if media quality HAL is implemented; {@code false} otherwise.
+ */
+ public boolean isSupported() {
+ try {
+ return mService.isSupported();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Enables or disables auto picture quality.
+ * <p>If enabled, picture quality parameters can be adjusted dynamically by hardware based on
+ * different use cases.
+ *
+ * @param enabled {@code true} to enable, {@code false} to disable.
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
+ public void setAutoPictureQualityEnabled(boolean enabled) {
+ try {
+ mService.setAutoPictureQualityEnabled(enabled);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns {@code true} if auto picture quality is enabled; {@code false} otherwise.
+ */
+ public boolean isAutoPictureQualityEnabled() {
+ try {
+ return mService.isAutoPictureQualityEnabled();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Enables or disables super resolution.
+ * <p>Super resolution is a feature to improve resolution.
+ *
+ * @param enabled {@code true} to enable, {@code false} to disable.
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
+ public void setSuperResolutionEnabled(boolean enabled) {
+ try {
+ mService.setSuperResolutionEnabled(enabled);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns {@code true} if super resolution is enabled; {@code false} otherwise.
+ */
+ public boolean isSuperResolutionEnabled() {
+ try {
+ return mService.isSuperResolutionEnabled();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Enables or disables auto sound quality.
+ * <p>If enabled, sound quality parameters can be adjusted dynamically by hardware based on
+ * different use cases.
+ *
+ * @param enabled {@code true} to enable, {@code false} to disable.
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
+ public void setAutoSoundQualityEnabled(boolean enabled) {
+ try {
+ mService.setAutoSoundQualityEnabled(enabled);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns {@code true} if auto sound quality is enabled; {@code false} otherwise.
+ */
+ public boolean isAutoSoundQualityEnabled() {
+ try {
+ return mService.isAutoSoundQualityEnabled();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Registers a {@link AmbientBacklightCallback}.
+ * @hide
+ */
+ public void registerAmbientBacklightCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull AmbientBacklightCallback callback) {
+ Preconditions.checkNotNull(callback);
+ Preconditions.checkNotNull(executor);
+ synchronized (mLock) {
+ mAbCallbackRecords.add(new AmbientBacklightCallbackRecord(callback, executor));
+ }
+ }
+
+ /**
+ * Unregisters the existing {@link AmbientBacklightCallback}.
+ * @hide
+ */
+ public void unregisterAmbientBacklightCallback(
+ @NonNull final AmbientBacklightCallback callback) {
+ Preconditions.checkNotNull(callback);
+ synchronized (mLock) {
+ for (Iterator<AmbientBacklightCallbackRecord> it = mAbCallbackRecords.iterator();
+ it.hasNext(); ) {
+ AmbientBacklightCallbackRecord record = it.next();
+ if (record.getCallback() == callback) {
+ it.remove();
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Set the ambient backlight settings.
+ *
+ * @param settings The settings to use for the backlight detector.
+ */
+ public void setAmbientBacklightSettings(
+ @NonNull AmbientBacklightSettings settings) {
+ Preconditions.checkNotNull(settings);
+ try {
+ mService.setAmbientBacklightSettings(settings);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Enables or disables the ambient backlight detection.
+ *
+ * @param enabled {@code true} to enable, {@code false} to disable.
+ */
+ public void setAmbientBacklightEnabled(boolean enabled) {
+ try {
+ mService.setAmbientBacklightEnabled(enabled);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+
private static final class PictureProfileCallbackRecord {
private final PictureProfileCallback mCallback;
private final Executor mExecutor;
@@ -198,6 +563,80 @@
}
});
}
+
+ public void postPictureProfileRemoved(final long id, PictureProfile profile) {
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onPictureProfileRemoved(id, profile);
+ }
+ });
+ }
+ }
+
+ private static final class SoundProfileCallbackRecord {
+ private final SoundProfileCallback mCallback;
+ private final Executor mExecutor;
+
+ SoundProfileCallbackRecord(SoundProfileCallback callback, Executor executor) {
+ mCallback = callback;
+ mExecutor = executor;
+ }
+
+ public SoundProfileCallback getCallback() {
+ return mCallback;
+ }
+
+ public void postSoundProfileAdded(final long id, SoundProfile profile) {
+
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onSoundProfileAdded(id, profile);
+ }
+ });
+ }
+
+ public void postSoundProfileUpdated(final long id, SoundProfile profile) {
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onSoundProfileUpdated(id, profile);
+ }
+ });
+ }
+
+ public void postSoundProfileRemoved(final long id, SoundProfile profile) {
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onSoundProfileRemoved(id, profile);
+ }
+ });
+ }
+ }
+
+ private static final class AmbientBacklightCallbackRecord {
+ private final AmbientBacklightCallback mCallback;
+ private final Executor mExecutor;
+
+ AmbientBacklightCallbackRecord(AmbientBacklightCallback callback, Executor executor) {
+ mCallback = callback;
+ mExecutor = executor;
+ }
+
+ public AmbientBacklightCallback getCallback() {
+ return mCallback;
+ }
+
+ public void postAmbientBacklightEvent(AmbientBacklightEvent event) {
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onAmbientBacklightEvent(event);
+ }
+ });
+ }
}
/**
@@ -215,5 +654,54 @@
*/
public void onPictureProfileUpdated(long id, PictureProfile profile) {
}
+ /**
+ * @hide
+ */
+ public void onPictureProfileRemoved(long id, PictureProfile profile) {
+ }
+ /**
+ * @hide
+ */
+ public void onError(int errorCode) {
+ }
+ }
+
+ /**
+ * Callback used to monitor status of sound profiles.
+ * @hide
+ */
+ public abstract static class SoundProfileCallback {
+ /**
+ * @hide
+ */
+ public void onSoundProfileAdded(long id, SoundProfile profile) {
+ }
+ /**
+ * @hide
+ */
+ public void onSoundProfileUpdated(long id, SoundProfile profile) {
+ }
+ /**
+ * @hide
+ */
+ public void onSoundProfileRemoved(long id, SoundProfile profile) {
+ }
+ /**
+ * @hide
+ */
+ public void onError(int errorCode) {
+ }
+ }
+
+ /**
+ * Callback used to monitor status of ambient backlight.
+ * @hide
+ */
+ public abstract static class AmbientBacklightCallback {
+ /**
+ * Called when new ambient backlight event is emitted.
+ */
+ public void onAmbientBacklightEvent(AmbientBacklightEvent event) {
+ }
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/media/java/android/media/quality/ParamCapability.aidl
similarity index 75%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to media/java/android/media/quality/ParamCapability.aidl
index 94b2bdf..b43409d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/media/java/android/media/quality/ParamCapability.aidl
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-package com.android.systemui.util.settings
+package android.media.quality;
-import com.android.systemui.kosmos.Kosmos
-
-val Kosmos.userAwareSecureSettingsRepository by
- Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+parcelable ParamCapability;
diff --git a/media/java/android/media/quality/ParamCapability.java b/media/java/android/media/quality/ParamCapability.java
new file mode 100644
index 0000000..70e8592
--- /dev/null
+++ b/media/java/android/media/quality/ParamCapability.java
@@ -0,0 +1,187 @@
+/*
+ * 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.media.quality;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.StringDef;
+import android.media.tv.flags.Flags;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Capability info of media quality parameters
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
+public class ParamCapability implements Parcelable {
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "TYPE_" }, value = {
+ TYPE_INT,
+ TYPE_LONG,
+ TYPE_DOUBLE,
+ TYPE_STRING,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ParamType {}
+
+ /**
+ * Integer parameter type
+ */
+ public static final int TYPE_INT = 1;
+
+ /**
+ * Long integer parameter type
+ */
+ public static final int TYPE_LONG = 2;
+
+ /**
+ * Double parameter type
+ */
+ public static final int TYPE_DOUBLE = 3;
+
+ /**
+ * String parameter type
+ */
+ public static final int TYPE_STRING = 4;
+
+ /** @hide */
+ @StringDef(prefix = { "CAPABILITY_" }, value = {
+ CAPABILITY_MAX,
+ CAPABILITY_MIN,
+ CAPABILITY_DEFAULT,
+ CAPABILITY_ENUM,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Capability {}
+
+ /**
+ * The key for the max possible value of this parameter.
+ */
+ public static final String CAPABILITY_MAX = "max";
+
+ /**
+ * The key for the min possible value of this parameter.
+ */
+ public static final String CAPABILITY_MIN = "min";
+
+ /**
+ * The key for the default value of this parameter.
+ */
+ public static final String CAPABILITY_DEFAULT = "default";
+
+ /**
+ * The key for the enumeration of this parameter.
+ */
+ public static final String CAPABILITY_ENUM = "enum";
+
+ @NonNull
+ private final String mName;
+ private final boolean mIsSupported;
+ @ParamType
+ private final int mType;
+ @NonNull
+ private final Bundle mCaps;
+
+ protected ParamCapability(Parcel in) {
+ mName = in.readString();
+ mIsSupported = in.readBoolean();
+ mType = in.readInt();
+ mCaps = in.readBundle();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mName);
+ dest.writeBoolean(mIsSupported);
+ dest.writeInt(mType);
+ dest.writeBundle(mCaps);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<ParamCapability> CREATOR = new Creator<ParamCapability>() {
+ @Override
+ public ParamCapability createFromParcel(Parcel in) {
+ return new ParamCapability(in);
+ }
+
+ @Override
+ public ParamCapability[] newArray(int size) {
+ return new ParamCapability[size];
+ }
+ };
+
+
+ /**
+ * Creates a new ParamCapability.
+ *
+ * @hide
+ */
+ public ParamCapability(
+ @NonNull String name,
+ boolean isSupported,
+ int type,
+ @NonNull Bundle caps) {
+ this.mName = name;
+ this.mIsSupported = isSupported;
+ this.mType = type;
+ this.mCaps = caps;
+ }
+
+ /**
+ * Gets parameter name.
+ */
+ @NonNull
+ public String getParamName() {
+ return mName;
+ }
+
+ /**
+ * Returns whether this parameter is supported or not.
+ */
+ public boolean isSupported() {
+ return mIsSupported;
+ }
+
+ /**
+ * Gets parameter type.
+ */
+ @ParamType
+ public int getParamType() {
+ return mType;
+ }
+
+ /**
+ * Gets capability information.
+ * <p>e.g. use the key {@link #CAPABILITY_MAX} to get the max value.
+ */
+ @NonNull
+ public Bundle getCapabilities() {
+ return new Bundle(mCaps);
+ }
+}
diff --git a/media/java/android/media/quality/SoundProfile.java b/media/java/android/media/quality/SoundProfile.java
index e0fcf9d..20d117b 100644
--- a/media/java/android/media/quality/SoundProfile.java
+++ b/media/java/android/media/quality/SoundProfile.java
@@ -18,6 +18,7 @@
import android.annotation.FlaggedApi;
import android.media.tv.flags.Flags;
+import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -37,6 +38,8 @@
private final String mInputId;
@Nullable
private final String mPackageName;
+ @NonNull
+ private final Bundle mParams;
protected SoundProfile(Parcel in) {
if (in.readByte() == 0) {
@@ -47,6 +50,7 @@
mName = in.readString();
mInputId = in.readString();
mPackageName = in.readString();
+ mParams = in.readBundle();
}
@Override
@@ -60,6 +64,7 @@
dest.writeString(mName);
dest.writeString(mInputId);
dest.writeString(mPackageName);
+ dest.writeBundle(mParams);
}
@Override
@@ -89,12 +94,14 @@
@Nullable Long id,
@NonNull String name,
@Nullable String inputId,
- @Nullable String packageName) {
+ @Nullable String packageName,
+ @NonNull Bundle params) {
this.mId = id;
this.mName = name;
com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, name);
this.mInputId = inputId;
this.mPackageName = packageName;
+ this.mParams = params;
}
@Nullable
@@ -116,6 +123,10 @@
public String getPackageName() {
return mPackageName;
}
+ @NonNull
+ public Bundle getParameters() {
+ return new Bundle(mParams);
+ }
/**
* A builder for {@link SoundProfile}
@@ -129,6 +140,8 @@
private String mInputId;
@Nullable
private String mPackageName;
+ @NonNull
+ private Bundle mParams;
/**
* Creates a new Builder.
@@ -181,6 +194,14 @@
}
/**
+ * Sets profile parameters.
+ */
+ @NonNull
+ public Builder setParameters(@NonNull Bundle params) {
+ mParams = new Bundle(params);
+ return this;
+ }
+ /**
* Builds the instance.
*/
@NonNull
@@ -190,7 +211,8 @@
mId,
mName,
mInputId,
- mPackageName);
+ mPackageName,
+ mParams);
return o;
}
}
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 6658918..abfc244 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -16,6 +16,8 @@
package android.media.tv;
+import static android.media.tv.flags.Flags.tifExtensionStandardization;
+
import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.IntDef;
@@ -159,6 +161,11 @@
new RemoteCallbackList<>();
private TvInputManager mTvInputManager;
+ /**
+ * @hide
+ */
+ protected TvInputServiceExtensionManager mTvInputServiceExtensionManager =
+ new TvInputServiceExtensionManager();
@Override
public final IBinder onBind(Intent intent) {
@@ -211,12 +218,23 @@
}
@Override
- public List<String> getAvailableExtensionInterfaceNames() {
- return TvInputService.this.getAvailableExtensionInterfaceNames();
+ public List<String> getAvailableExtensionInterfaceNames() {
+ List<String> extensionNames =
+ TvInputService.this.getAvailableExtensionInterfaceNames();
+ if (tifExtensionStandardization()) {
+ extensionNames.addAll(
+ TvInputServiceExtensionManager.getStandardExtensionInterfaceNames());
+ }
+ return extensionNames;
}
@Override
public IBinder getExtensionInterface(String name) {
+ if (tifExtensionStandardization() && name != null) {
+ if (TvInputServiceExtensionManager.checkIsStandardizedInterfaces(name)) {
+ return mTvInputServiceExtensionManager.getExtensionIBinder(name);
+ }
+ }
return TvInputService.this.getExtensionInterface(name);
}
diff --git a/media/java/android/media/tv/TvInputServiceExtensionManager.java b/media/java/android/media/tv/TvInputServiceExtensionManager.java
new file mode 100644
index 0000000..0e98488
--- /dev/null
+++ b/media/java/android/media/tv/TvInputServiceExtensionManager.java
@@ -0,0 +1,614 @@
+/*
+ * 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.media.tv;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.tv.flags.Flags;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_TIF_EXTENSION_STANDARDIZATION)
+public class TvInputServiceExtensionManager {
+ private static final String TAG = "TvInputServiceExtensionManager";
+ private static final String SCAN_PACKAGE = "android.media.tv.extension.scan.";
+ private static final String OAD_PACKAGE = "android.media.tv.extension.oad.";
+ private static final String CAM_PACKAGE = "android.media.tv.extension.cam.";
+ private static final String RATING_PACKAGE = "android.media.tv.extension.rating.";
+ private static final String TIME_PACKAGE = "android.media.tv.extension.time.";
+ private static final String TELETEXT_PACKAGE = "android.media.tv.extension.teletext.";
+ private static final String SCAN_BSU_PACKAGE = "android.media.tv.extension.scanbsu.";
+ private static final String CLIENT_TOKEN_PACKAGE = "android.media.tv.extension.clienttoken.";
+ private static final String SCREEN_MODE_PACKAGE = "android.media.tv.extension.screenmode.";
+ private static final String SIGNAL_PACKAGE = "android.media.tv.extension.signal.";
+ private static final String SERVICE_DATABASE_PACKAGE = "android.media.tv.extension.servicedb.";
+ private static final String PVR_PACKAGE = "android.media.tv.extension.pvr.";
+ private static final String EVENT_PACKAGE = "android.media.tv.extension.event.";
+ private static final String ANALOG_PACKAGE = "android.media.tv.extension.analog.";
+ private static final String TUNE_PACKAGE = "android.media.tv.extension.tune.";
+
+ /** Register binder returns success when it abides standardized interface structure */
+ public static final int REGISTER_SUCCESS = 0;
+ /** Register binder returns fail when the extension name is not in the standardization list */
+ public static final int REGISTER_FAIL_NAME_NOT_STANDARDIZED = 1;
+ /** Register binder returns fail when the IBinder does not implement standardized interface */
+ public static final int REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED = 2;
+ /** Register binder returns fail when remote server not available */
+ public static final int REGISTER_FAIL_REMOTE_EXCEPTION = 3;
+
+ /**
+ * Interface responsible for creating scan session and obtain parameters.
+ */
+ public static final String ISCAN_INTERFACE = SCAN_PACKAGE + "IScanInterface";
+ /**
+ * Interface that handles scan session and get/store related information.
+ */
+ public static final String ISCAN_SESSION = SCAN_PACKAGE + "IScanSession";
+ /**
+ * Interface that notifies changes related to scan session.
+ */
+ public static final String ISCAN_LISTENER = SCAN_PACKAGE + "IScanListener";
+ /**
+ * Interface for setting HDPlus information.
+ */
+ public static final String IHDPLUS_INFO = SCAN_PACKAGE + "IHDPlusInfo";
+ /**
+ * Interface for handling operator detection for scanning.
+ */
+ public static final String IOPERATOR_DETECTION = SCAN_PACKAGE + "IOperatorDetection";
+ /**
+ * Interface for changes related to operator detection searches.
+ */
+ public static final String IOPERATOR_DETECTION_LISTENER = SCAN_PACKAGE
+ + "IOperatorDetectionListener";
+ /**
+ * Interface for handling region channel list for scanning.
+ */
+ public static final String IREGION_CHANNEL_LIST = SCAN_PACKAGE + "IRegionChannelList";
+ /**
+ * Interface for changes related to changes in region channel list search.
+ */
+ public static final String IREGION_CHANNEL_LIST_LISTENER = SCAN_PACKAGE
+ + "IRegionChannelListListener";
+ /**
+ * Interface for handling target region information.
+ */
+ public static final String ITARGET_REGION = SCAN_PACKAGE + "ITargetRegion";
+ /**
+ * Interface for changes related to target regions during scanning.
+ */
+ public static final String ITARGET_REGION_LISTENER = SCAN_PACKAGE + "ITargetRegionListener";
+ /**
+ * Interface for handling LCN conflict groups.
+ */
+ public static final String ILCN_CONFLICT = SCAN_PACKAGE + "ILcnConflict";
+ /**
+ * Interface for detecting LCN conflicts during scanning.
+ */
+ public static final String ILCN_CONFLICT_LISTENER = SCAN_PACKAGE + "ILcnConflictListener";
+ /**
+ * Interface for handling LCN V2 channel list information.
+ */
+ public static final String ILCNV2_CHANNEL_LIST = SCAN_PACKAGE + "ILcnV2ChannelList";
+ /**
+ * Interface for detecting LCN V2 channel list during scanning.
+ */
+ public static final String ILCNV2_CHANNEL_LIST_LISTENER = SCAN_PACKAGE
+ + "ILcnV2ChannelListListener";
+ /**
+ * Interface for handling favorite network related information.
+ */
+ public static final String IFAVORITE_NETWORK = SCAN_PACKAGE + "IFavoriteNetwork";
+ /**
+ * Interface for detecting favorite network during scanning.
+ */
+ public static final String IFAVORITE_NETWORK_LISTENER = SCAN_PACKAGE
+ + "IFavoriteNetworkListener";
+ /**
+ * Interface for handling Turksat channel update system service.
+ */
+ public static final String ITKGS_INFO = SCAN_PACKAGE + "ITkgsInfo";
+ /**
+ * Interface for changes related to TKGS information.
+ */
+ public static final String ITKGS_INFO_LISTENER = SCAN_PACKAGE + "ITkgsInfoListener";
+ /**
+ * Interface for satellite search related to low noise block downconverter.
+ */
+ public static final String ISCAN_SAT_SEARCH = SCAN_PACKAGE + "IScanSatSearch";
+ /**
+ * Interface for Over-the-Air Download.
+ */
+ public static final String IOAD_UPDATE_INTERFACE = OAD_PACKAGE + "IOadUpdateInterface";
+ /**
+ * Interface for handling conditional access module app related information.
+ */
+ public static final String ICAM_APP_INFO_SERVICE = CAM_PACKAGE + "ICamAppInfoService";
+ /**
+ * Interface for changes on conditional access module app related information.
+ */
+ public static final String ICAM_APP_INFO_LISTENER = CAM_PACKAGE + "ICamAppInfoListener";
+ /**
+ * Interface for handling conditional access module related information.
+ */
+ public static final String ICAM_MONITORING_SERVICE = CAM_PACKAGE + "ICamMonitoringService";
+ /**
+ * Interface for changes on conditional access module related information.
+ */
+ public static final String ICAM_INFO_LISTENER = CAM_PACKAGE + "ICamInfoListener";
+ /**
+ * Interface for handling control of CI+ operations.
+ */
+ public static final String ICI_OPERATOR_INTERFACE = CAM_PACKAGE + "ICiOperatorInterface";
+ /**
+ * Interfaces for changes on CI+ operations.
+ */
+ public static final String ICI_OPERATOR_LISTENER = CAM_PACKAGE + "ICiOperatorListener";
+ /**
+ * Interface for handling conditional access module profile related information.
+ */
+ public static final String ICAM_PROFILE_INTERFACE = CAM_PACKAGE + "ICamProfileInterface";
+ /**
+ * Interface for handling conditional access module DRM related information.
+ */
+ public static final String ICONTENT_CONTROL_SERVICE = CAM_PACKAGE + "IContentControlService";
+ /**
+ * Interface for changes on DRM.
+ */
+ public static final String ICAM_DRM_INFO_LISTENER = CAM_PACKAGE + "ICamDrmInfoListener";
+ /**
+ * Interface for handling conditional access module pin related information.
+ */
+ public static final String ICAM_PIN_SERVICE = CAM_PACKAGE + "ICamPinService";
+ /**
+ * Interface for changes on conditional access module pin capability.
+ */
+ public static final String ICAM_PIN_CAPABILITY_LISTENER = CAM_PACKAGE
+ + "ICamPinCapabilityListener";
+ /**
+ * Interface for changes on conditional access module pin status.
+ */
+ public static final String ICAM_PIN_STATUS_LISTENER = CAM_PACKAGE + "ICamPinStatusListener";
+ /**
+ * Interface for handling conditional access module host control service.
+ */
+ public static final String ICAM_HOST_CONTROL_SERVICE = CAM_PACKAGE + "ICamHostControlService";
+ /**
+ * Interface for handling conditional access module ask release reply.
+ */
+ public static final String ICAM_HOST_CONTROL_ASK_RELEASE_REPLY_CALLBACK = CAM_PACKAGE
+ + "ICamHostControlAskReleaseReplyCallback";
+ /**
+ * Interface for changes on conditional access module host control service.
+ */
+ public static final String ICAM_HOST_CONTROL_INFO_LISTENER = CAM_PACKAGE
+ + "ICamHostControlInfoListener";
+ /**
+ * Interface for handling conditional access module host control service tune_quietly_flag.
+ */
+ public static final String ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG = CAM_PACKAGE
+ + "ICamHostControlTuneQuietlyFlag";
+ /**
+ * Interface for changes on conditional access module host control service tune_quietly_flag.
+ */
+ public static final String ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG_LISTENER = CAM_PACKAGE
+ + "ICamHostControlTuneQuietlyFlagListener";
+ /**
+ * Interface for handling conditional access module multi media interface.
+ */
+ public static final String IMMI_INTERFACE = CAM_PACKAGE + "IMmiInterface";
+ /**
+ * Interface for controlling conditional access module multi media session.
+ */
+ public static final String IMMI_SESSION = CAM_PACKAGE + "IMmiSession";
+ /**
+ * Interface for changes on conditional access module multi media session status.
+ */
+ public static final String IMMI_STATUS_CALLBACK = CAM_PACKAGE + "IMmiStatusCallback";
+ /**
+ * Interface for changes on conditional access app info related to entering menu.
+ */
+ public static final String IENTER_MENU_ERROR_CALLBACK = CAM_PACKAGE + "IEnterMenuErrorCallback";
+ /**
+ * Interface for handling RRT downloadable rating data.
+ */
+ public static final String IDOWNLOADABLE_RATING_TABLE_MONITOR = RATING_PACKAGE
+ + "IDownloadableRatingTableMonitor";
+ /**
+ * Interface for handling RRT rating related information.
+ */
+ public static final String IRATING_INTERFACE = RATING_PACKAGE + "IRatingInterface";
+ /**
+ * Interface for handling PMT rating related information.
+ */
+ public static final String IPMT_RATING_INTERFACE = RATING_PACKAGE + "IPmtRatingInterface";
+ /**
+ * Interface for changes on PMT rating related information.
+ */
+ public static final String IPMT_RATING_LISTENER = RATING_PACKAGE + "IPmtRatingListener";
+ /**
+ * Interface for handling IVBI rating related information.
+ */
+ public static final String IVBI_RATING_INTERFACE = RATING_PACKAGE + "IVbiRatingInterface";
+ /**
+ * Interface for changes on IVBI rating related information.
+ */
+ public static final String IVBI_RATING_LISTENER = RATING_PACKAGE + "IVbiRatingListener";
+ /**
+ * Interface for handling program rating related information.
+ */
+ public static final String IPROGRAM_INFO = RATING_PACKAGE + "IProgramInfo";
+ /**
+ * Interface for changes on program rating related information.
+ */
+ public static final String IPROGRAM_INFO_LISTENER = RATING_PACKAGE + "IProgramInfoListener";
+ /**
+ * Interface for getting broadcast time related information.
+ */
+ public static final String BROADCAST_TIME = TIME_PACKAGE + "BroadcastTime";
+ /**
+ * Interface for handling data service signal information on teletext.
+ */
+ public static final String IDATA_SERVICE_SIGNAL_INFO = TELETEXT_PACKAGE
+ + "IDataServiceSignalInfo";
+ /**
+ * Interface for changes on data service signal information on teletext.
+ */
+ public static final String IDATA_SERVICE_SIGNAL_INFO_LISTENER = TELETEXT_PACKAGE
+ + "IDataServiceSignalInfoListener";
+ /**
+ * Interface for handling teletext page information.
+ */
+ public static final String ITELETEXT_PAGE_SUB_CODE = TELETEXT_PACKAGE + "ITeletextPageSubCode";
+ /**
+ * Interface for handling scan background service update.
+ */
+ public static final String ISCAN_BACKGROUND_SERVICE_UPDATE = SCAN_BSU_PACKAGE
+ + "IScanBackgroundServiceUpdate";
+ /**
+ * Interface for changes on background service update
+ */
+ public static final String ISCAN_BACKGROUND_SERVICE_UPDATE_LISTENER = SCAN_BSU_PACKAGE
+ + "IScanBackgroundServiceUpdateListener";
+ /**
+ * Interface for generating client token.
+ */
+ public static final String ICLIENT_TOKEN = CLIENT_TOKEN_PACKAGE + "IClientToken";
+ /**
+ * Interfaces for handling screen mode information.
+ */
+ public static final String ISCREEN_MODE_SETTINGS = SCREEN_MODE_PACKAGE + "IScreenModeSettings";
+ /**
+ * Interfaces for handling HDMI signal information update.
+ */
+ public static final String IHDMI_SIGNAL_INTERFACE = SIGNAL_PACKAGE + "IHdmiSignalInterface";
+ /**
+ * Interfaces for changes on HDMI signal information update.
+ */
+ public static final String IHDMI_SIGNAL_INFO_LISTENER = SIGNAL_PACKAGE
+ + "IHdmiSignalInfoListener";
+ /**
+ * Interfaces for handling audio signal information update.
+ */
+ public static final String IAUDIO_SIGNAL_INFO = SIGNAL_PACKAGE + "IAudioSignalInfo";
+ /**
+ * Interfaces for handling analog audio signal information update.
+ */
+ public static final String IANALOG_AUDIO_INFO = SIGNAL_PACKAGE + "IAnalogAudioInfo";
+ /**
+ * Interfaces for change on audio signal information update.
+ */
+ public static final String IAUDIO_SIGNAL_INFO_LISTENER = SIGNAL_PACKAGE
+ + "IAudioSignalInfoListener";
+ /**
+ * Interfaces for handling video signal information update.
+ */
+ public static final String IVIDEO_SIGNAL_INFO = SIGNAL_PACKAGE + "IVideoSignalInfo";
+ /**
+ * Interfaces for changes on video signal information update.
+ */
+ public static final String IVIDEO_SIGNAL_INFO_LISTENER = SIGNAL_PACKAGE
+ + "IVideoSignalInfoListener";
+ /**
+ * Interfaces for handling service database updates.
+ */
+ public static final String ISERVICE_LIST_EDIT = SERVICE_DATABASE_PACKAGE + "IServiceListEdit";
+ /**
+ * Interfaces for changes on service database updates.
+ */
+ public static final String ISERVICE_LIST_EDIT_LISTENER = SERVICE_DATABASE_PACKAGE
+ + "IServiceListEditListener";
+ /**
+ * Interfaces for getting service database related information.
+ */
+ public static final String ISERVICE_LIST = SERVICE_DATABASE_PACKAGE + "IServiceList";
+ /**
+ * Interfaces for transferring service database related information.
+ */
+ public static final String ISERVICE_LIST_TRANSFER_INTERFACE = SERVICE_DATABASE_PACKAGE
+ + "IServiceListTransferInterface";
+ /**
+ * Interfaces for exporting service database session.
+ */
+ public static final String ISERVICE_LIST_EXPORT_SESSION = SERVICE_DATABASE_PACKAGE
+ + "IServiceListExportSession";
+ /**
+ * Interfaces for changes on exporting service database session.
+ */
+ public static final String ISERVICE_LIST_EXPORT_LISTENER = SERVICE_DATABASE_PACKAGE
+ + "IServiceListExportListener";
+ /**
+ * Interfaces for importing service database session.
+ */
+ public static final String ISERVICE_LIST_IMPORT_SESSION = SERVICE_DATABASE_PACKAGE
+ + "IServiceListImportSession";
+ /**
+ * Interfaces for changes on importing service database session.
+ */
+ public static final String ISERVICE_LIST_IMPORT_LISTENER = SERVICE_DATABASE_PACKAGE
+ + "IServiceListImportListener";
+ /**
+ * Interfaces for setting channel list resources.
+ */
+ public static final String ISERVICE_LIST_SET_CHANNEL_LIST_SESSION = SERVICE_DATABASE_PACKAGE
+ + "IServiceListSetChannelListSession";
+ /**
+ * Interfaces for changes on setting channel list resources.
+ */
+ public static final String ISERVICE_LIST_SET_CHANNEL_LIST_LISTENER = SERVICE_DATABASE_PACKAGE
+ + "IServiceListSetChannelListListener";
+ /**
+ * Interfaces for transferring channel list resources.
+ */
+ public static final String ICHANNEL_LIST_TRANSFER = SERVICE_DATABASE_PACKAGE
+ + "IChannelListTransfer";
+ /**
+ * Interfaces for record contents updates.
+ */
+ public static final String IRECORDED_CONTENTS = PVR_PACKAGE + "IRecordedContents";
+ /**
+ * Interfaces for changes on deleting record contents.
+ */
+ public static final String IDELETE_RECORDED_CONTENTS_CALLBACK = PVR_PACKAGE
+ + "IDeleteRecordedContentsCallback";
+ /**
+ * Interfaces for changes on getting record contents.
+ */
+ public static final String IGET_INFO_RECORDED_CONTENTS_CALLBACK = PVR_PACKAGE
+ + "IGetInfoRecordedContentsCallback";
+ /**
+ * Interfaces for monitoring present event information.
+ */
+ public static final String IEVENT_MONITOR = EVENT_PACKAGE + "IEventMonitor";
+ /**
+ * Interfaces for changes on present event information.
+ */
+ public static final String IEVENT_MONITOR_LISTENER = EVENT_PACKAGE + "IEventMonitorListener";
+ /**
+ * Interfaces for handling download event information.
+ */
+ public static final String IEVENT_DOWNLOAD = EVENT_PACKAGE + "IEventDownload";
+ /**
+ * Interfaces for changes on downloading event information.
+ */
+ public static final String IEVENT_DOWNLOAD_LISTENER = EVENT_PACKAGE + "IEventDownloadListener";
+ /**
+ * Interfaces for handling download event information for DVB and DTMB.
+ */
+ public static final String IEVENT_DOWNLOAD_SESSION = EVENT_PACKAGE + "IEventDownloadSession";
+ /**
+ * Interfaces for handling analog color system.
+ */
+ public static final String IANALOG_ATTRIBUTE_INTERFACE = ANALOG_PACKAGE
+ + "IAnalogAttributeInterface";
+ /**
+ * Interfaces for monitoring channel tuned information.
+ */
+ public static final String ICHANNEL_TUNED_INTERFACE = TUNE_PACKAGE + "IChannelTunedInterface";
+ /**
+ * Interfaces for changes on channel tuned information.
+ */
+ public static final String ICHANNEL_TUNED_LISTENER = TUNE_PACKAGE + "IChannelTunedListener";
+ /**
+ * Interfaces for handling tuner frontend signal info.
+ */
+ public static final String ITUNER_FRONTEND_SIGNAL_INFO_INTERFACE = SIGNAL_PACKAGE
+ + "ITunerFrontendSignalInfoInterface";
+ /**
+ * Interfaces for changes on tuner frontend signal info.
+ */
+ public static final String ITUNER_FRONTEND_SIGNAL_INFO_LISTENER = SIGNAL_PACKAGE
+ + "ITunerFrontendSignalInfoListener";
+ /**
+ * Interfaces for handling mux tune operations.
+ */
+ public static final String IMUX_TUNE_SESSION = TUNE_PACKAGE + "IMuxTuneSession";
+ /**
+ * Interfaces for initing mux tune session.
+ */
+ public static final String IMUX_TUNE = TUNE_PACKAGE + "IMuxTune";
+
+ // Set of standardized AIDL interface canonical names
+ private static final Set<String> sTisExtensions = new HashSet<>(Set.of(
+ ISCAN_INTERFACE,
+ ISCAN_SESSION,
+ ISCAN_LISTENER,
+ IHDPLUS_INFO,
+ IOPERATOR_DETECTION,
+ IOPERATOR_DETECTION_LISTENER,
+ IREGION_CHANNEL_LIST,
+ IREGION_CHANNEL_LIST_LISTENER,
+ ITARGET_REGION,
+ ITARGET_REGION_LISTENER,
+ ILCN_CONFLICT,
+ ILCN_CONFLICT_LISTENER,
+ ILCNV2_CHANNEL_LIST,
+ ILCNV2_CHANNEL_LIST_LISTENER,
+ IFAVORITE_NETWORK,
+ IFAVORITE_NETWORK_LISTENER,
+ ITKGS_INFO,
+ ITKGS_INFO_LISTENER,
+ ISCAN_SAT_SEARCH,
+ IOAD_UPDATE_INTERFACE,
+ ICAM_APP_INFO_SERVICE,
+ ICAM_APP_INFO_LISTENER,
+ ICAM_MONITORING_SERVICE,
+ ICAM_INFO_LISTENER,
+ ICI_OPERATOR_INTERFACE,
+ ICI_OPERATOR_LISTENER,
+ ICAM_PROFILE_INTERFACE,
+ ICONTENT_CONTROL_SERVICE,
+ ICAM_DRM_INFO_LISTENER,
+ ICAM_PIN_SERVICE,
+ ICAM_PIN_CAPABILITY_LISTENER,
+ ICAM_PIN_STATUS_LISTENER,
+ ICAM_HOST_CONTROL_SERVICE,
+ ICAM_HOST_CONTROL_ASK_RELEASE_REPLY_CALLBACK,
+ ICAM_HOST_CONTROL_INFO_LISTENER,
+ ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG,
+ ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG_LISTENER,
+ IMMI_INTERFACE,
+ IMMI_SESSION,
+ IMMI_STATUS_CALLBACK,
+ IENTER_MENU_ERROR_CALLBACK,
+ IDOWNLOADABLE_RATING_TABLE_MONITOR,
+ IRATING_INTERFACE,
+ IPMT_RATING_INTERFACE,
+ IPMT_RATING_LISTENER,
+ IVBI_RATING_INTERFACE,
+ IVBI_RATING_LISTENER,
+ IPROGRAM_INFO,
+ IPROGRAM_INFO_LISTENER,
+ BROADCAST_TIME,
+ IDATA_SERVICE_SIGNAL_INFO,
+ IDATA_SERVICE_SIGNAL_INFO_LISTENER,
+ ITELETEXT_PAGE_SUB_CODE,
+ ISCAN_BACKGROUND_SERVICE_UPDATE,
+ ISCAN_BACKGROUND_SERVICE_UPDATE_LISTENER,
+ ICLIENT_TOKEN,
+ ISCREEN_MODE_SETTINGS,
+ IHDMI_SIGNAL_INTERFACE,
+ IHDMI_SIGNAL_INFO_LISTENER,
+ IAUDIO_SIGNAL_INFO,
+ IANALOG_AUDIO_INFO,
+ IAUDIO_SIGNAL_INFO_LISTENER,
+ IVIDEO_SIGNAL_INFO,
+ IVIDEO_SIGNAL_INFO_LISTENER,
+ ISERVICE_LIST_EDIT,
+ ISERVICE_LIST_EDIT_LISTENER,
+ ISERVICE_LIST,
+ ISERVICE_LIST_TRANSFER_INTERFACE,
+ ISERVICE_LIST_EXPORT_SESSION,
+ ISERVICE_LIST_EXPORT_LISTENER,
+ ISERVICE_LIST_IMPORT_SESSION,
+ ISERVICE_LIST_IMPORT_LISTENER,
+ ISERVICE_LIST_SET_CHANNEL_LIST_SESSION,
+ ISERVICE_LIST_SET_CHANNEL_LIST_LISTENER,
+ ICHANNEL_LIST_TRANSFER,
+ IRECORDED_CONTENTS,
+ IDELETE_RECORDED_CONTENTS_CALLBACK,
+ IGET_INFO_RECORDED_CONTENTS_CALLBACK,
+ IEVENT_MONITOR,
+ IEVENT_MONITOR_LISTENER,
+ IEVENT_DOWNLOAD,
+ IEVENT_DOWNLOAD_LISTENER,
+ IEVENT_DOWNLOAD_SESSION,
+ IANALOG_ATTRIBUTE_INTERFACE,
+ ICHANNEL_TUNED_INTERFACE,
+ ICHANNEL_TUNED_LISTENER,
+ ITUNER_FRONTEND_SIGNAL_INFO_INTERFACE,
+ ITUNER_FRONTEND_SIGNAL_INFO_LISTENER,
+ IMUX_TUNE_SESSION,
+ IMUX_TUNE
+ ));
+
+ // Store the mapping between interface names and IBinder
+ private Map<String, IBinder> mExtensionInterfaceIBinderMapping = new HashMap<>();
+
+ TvInputServiceExtensionManager() {
+ }
+
+ /**
+ * Function to return available extension interface names
+ *
+ * @hide
+ */
+ public static @NonNull List<String> getStandardExtensionInterfaceNames() {
+ return new ArrayList<>(sTisExtensions);
+ }
+
+ /**
+ * Function to check if the extension is in the standardization list
+ */
+ static boolean checkIsStandardizedInterfaces(@NonNull String extensionName) {
+ return sTisExtensions.contains(extensionName);
+ }
+
+ /**
+ * This function should be used by OEM to register IBinder objects that implement
+ * standardized AIDL interfaces.
+ *
+ * @param extensionName Extension Interface Name
+ * @param binder IBinder object to be registered
+ * @return REGISTER_SUCCESS on success of registering IBinder object
+ * REGISTER_FAIL_NAME_NOT_STANDARDIZED on failure due to registering extension with
+ * non-standardized name
+ * REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED on failure due to IBinder not
+ * implementing standardized AIDL interface
+ * REGISTER_FAIL_REMOTE_EXCEPTION on failure due to remote exception
+ *
+ * @hide
+ */
+ public int registerExtensionIBinder(@NonNull String extensionName,
+ @NonNull IBinder binder) {
+ if (!checkIsStandardizedInterfaces(extensionName)) {
+ return REGISTER_FAIL_NAME_NOT_STANDARDIZED;
+ }
+ try {
+ if (binder.getInterfaceDescriptor().equals(extensionName)) {
+ mExtensionInterfaceIBinderMapping.put(extensionName, binder);
+ return REGISTER_SUCCESS;
+ } else {
+ return REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Fetching IBinder object failure due to " + e);
+ return REGISTER_FAIL_REMOTE_EXCEPTION;
+ }
+ }
+
+ /**
+ * Function to get corresponding IBinder object
+ */
+ @Nullable IBinder getExtensionIBinder(@NonNull String extensionName) {
+ return mExtensionInterfaceIBinderMapping.get(extensionName);
+ }
+
+}
diff --git a/media/java/android/mtp/OWNERS b/media/java/android/mtp/OWNERS
index 6b5336e..77ed08b 100644
--- a/media/java/android/mtp/OWNERS
+++ b/media/java/android/mtp/OWNERS
@@ -1,10 +1,9 @@
set noparent
-aprasath@google.com
anothermark@google.com
-kumarashishg@google.com
-sarup@google.com
+febinthattil@google.com
+aprasath@google.com
jsharkey@android.com
jameswei@google.com
rmojumder@google.com
-
+kumarashishg@google.com
diff --git a/media/tests/MtpTests/OWNERS b/media/tests/MtpTests/OWNERS
index 6b5336e..bdb6cdb 100644
--- a/media/tests/MtpTests/OWNERS
+++ b/media/tests/MtpTests/OWNERS
@@ -1,10 +1,9 @@
set noparent
-aprasath@google.com
anothermark@google.com
-kumarashishg@google.com
-sarup@google.com
+febinthattil@google.com
+aprasath@google.com
jsharkey@android.com
jameswei@google.com
rmojumder@google.com
-
+kumarashishg@google.com
\ No newline at end of file
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 23dd9b7..0fb3049 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -48,9 +48,7 @@
"libhwui_internal_headers",
],
- static_libs: [
- "libarect",
- ],
+ static_libs: ["libarect"],
host_supported: true,
target: {
@@ -62,11 +60,6 @@
shared_libs: [
"libandroid",
],
- static_libs: [
- "libstatslog_hwui",
- "libstatspull_lazy",
- "libstatssocket_lazy",
- ],
version_script: "libjnigraphics.map.txt",
},
host: {
diff --git a/native/graphics/jni/imagedecoder.cpp b/native/graphics/jni/imagedecoder.cpp
index cb95bcf..e18b4a9 100644
--- a/native/graphics/jni/imagedecoder.cpp
+++ b/native/graphics/jni/imagedecoder.cpp
@@ -14,9 +14,18 @@
* limitations under the License.
*/
+#include "aassetstreamadaptor.h"
+
+#include <android/asset_manager.h>
+#include <android/bitmap.h>
+#include <android/data_space.h>
+#include <android/imagedecoder.h>
#include <MimeType.h>
-#include <SkAlphaType.h>
+#include <android/rect.h>
+#include <hwui/ImageDecoder.h>
+#include <log/log.h>
#include <SkAndroidCodec.h>
+#include <SkAlphaType.h>
#include <SkCodec.h>
#include <SkCodecAnimation.h>
#include <SkColorSpace.h>
@@ -26,24 +35,14 @@
#include <SkRefCnt.h>
#include <SkSize.h>
#include <SkStream.h>
-#include <android/asset_manager.h>
-#include <android/bitmap.h>
-#include <android/data_space.h>
-#include <android/imagedecoder.h>
-#include <android/rect.h>
+#include <utils/Color.h>
+
#include <fcntl.h>
-#include <hwui/ImageDecoder.h>
-#include <log/log.h>
+#include <limits>
+#include <optional>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
-#include <utils/Color.h>
-#include <utils/StatsUtils.h>
-
-#include <limits>
-#include <optional>
-
-#include "aassetstreamadaptor.h"
using namespace android;
@@ -401,7 +400,9 @@
return info.minRowBytes();
}
-int AImageDecoder_decodeImage(AImageDecoder* decoder, void* pixels, size_t stride, size_t size) {
+int AImageDecoder_decodeImage(AImageDecoder* decoder,
+ void* pixels, size_t stride,
+ size_t size) {
if (!decoder || !pixels || !stride) {
return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
}
@@ -418,13 +419,7 @@
return ANDROID_IMAGE_DECODER_FINISHED;
}
- const auto result = ResultToErrorCode(imageDecoder->decode(pixels, stride));
-
- if (result == ANDROID_IMAGE_DECODER_SUCCESS) {
- uirenderer::logBitmapDecode(imageDecoder->getOutputInfo(), false);
- }
-
- return result;
+ return ResultToErrorCode(imageDecoder->decode(pixels, stride));
}
void AImageDecoder_delete(AImageDecoder* decoder) {
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 96b7c13..00812042 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -207,6 +207,7 @@
method public boolean isDefaultServiceForCategory(android.content.ComponentName, String);
method @FlaggedApi("android.nfc.enable_card_emulation_euicc") public boolean isEuiccSupported();
method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>);
+ method @FlaggedApi("android.nfc.nfc_event_listener") public void registerNfcEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.cardemulation.CardEmulation.NfcEventListener);
method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopFilterForService(@NonNull android.content.ComponentName, @NonNull String, boolean);
method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopPatternFilterForService(@NonNull android.content.ComponentName, @NonNull String, boolean);
method public boolean removeAidsForService(android.content.ComponentName, String);
@@ -216,6 +217,7 @@
method public boolean setPreferredService(android.app.Activity, android.content.ComponentName);
method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setShouldDefaultToObserveModeForService(@NonNull android.content.ComponentName, boolean);
method public boolean supportsAidPrefixRegistration();
+ method @FlaggedApi("android.nfc.nfc_event_listener") public void unregisterNfcEventListener(@NonNull android.nfc.cardemulation.CardEmulation.NfcEventListener);
method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean unsetOffHostForService(@NonNull android.content.ComponentName);
method public boolean unsetPreferredService(android.app.Activity);
field @Deprecated public static final String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
@@ -233,13 +235,16 @@
field public static final int SELECTION_MODE_PREFER_DEFAULT = 0; // 0x0
}
+ @FlaggedApi("android.nfc.nfc_event_listener") public static interface CardEmulation.NfcEventListener {
+ method @FlaggedApi("android.nfc.nfc_event_listener") public default void onObserveModeStateChanged(boolean);
+ method @FlaggedApi("android.nfc.nfc_event_listener") public default void onPreferredServiceChanged(boolean);
+ }
+
public abstract class HostApduService extends android.app.Service {
ctor public HostApduService();
method public final void notifyUnhandled();
method public final android.os.IBinder onBind(android.content.Intent);
method public abstract void onDeactivated(int);
- method @FlaggedApi("android.nfc.nfc_event_listener") public void onObserveModeStateChanged(boolean);
- method @FlaggedApi("android.nfc.nfc_event_listener") public void onPreferredServiceChanged(boolean);
method public abstract byte[] processCommandApdu(byte[], android.os.Bundle);
method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void processPollingFrames(@NonNull java.util.List<android.nfc.cardemulation.PollingFrame>);
method public final void sendResponseApdu(byte[]);
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 24e14e6..6aa8a2b 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -91,6 +91,7 @@
method public void onDisable(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public void onDisableFinished(int);
method public void onDisableStarted();
+ method public void onEeListenActivated(boolean);
method public void onEnable(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public void onEnableFinished(int);
method public void onEnableStarted();
@@ -105,7 +106,7 @@
method public void onRfFieldActivated(boolean);
method public void onRoutingChanged();
method public void onStateUpdated(int);
- method public void onTagConnected(boolean, @NonNull android.nfc.Tag);
+ method public void onTagConnected(boolean);
method public void onTagDispatch(@NonNull java.util.function.Consumer<java.lang.Boolean>);
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/nfc/java/android/nfc/ComponentNameAndUser.aidl
similarity index 75%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to nfc/java/android/nfc/ComponentNameAndUser.aidl
index 94b2bdf..e677998 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/nfc/java/android/nfc/ComponentNameAndUser.aidl
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-package com.android.systemui.util.settings
+package android.nfc;
-import com.android.systemui.kosmos.Kosmos
-
-val Kosmos.userAwareSecureSettingsRepository by
- Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+parcelable ComponentNameAndUser;
\ No newline at end of file
diff --git a/nfc/java/android/nfc/ComponentNameAndUser.java b/nfc/java/android/nfc/ComponentNameAndUser.java
new file mode 100644
index 0000000..59e6c62
--- /dev/null
+++ b/nfc/java/android/nfc/ComponentNameAndUser.java
@@ -0,0 +1,100 @@
+/*
+ * 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.nfc;
+
+import android.annotation.UserIdInt;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+public class ComponentNameAndUser implements Parcelable {
+ @UserIdInt private final int mUserId;
+ private ComponentName mComponentName;
+
+ public ComponentNameAndUser(@UserIdInt int userId, ComponentName componentName) {
+ mUserId = userId;
+ mComponentName = componentName;
+ }
+
+ /**
+ * @hide
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * @hide
+ */
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mUserId);
+ out.writeParcelable(mComponentName, flags);
+ }
+
+ public static final Parcelable.Creator<ComponentNameAndUser> CREATOR =
+ new Parcelable.Creator<ComponentNameAndUser>() {
+ public ComponentNameAndUser createFromParcel(Parcel in) {
+ return new ComponentNameAndUser(in);
+ }
+
+ public ComponentNameAndUser[] newArray(int size) {
+ return new ComponentNameAndUser[size];
+ }
+ };
+
+ private ComponentNameAndUser(Parcel in) {
+ mUserId = in.readInt();
+ mComponentName = in.readParcelable(null, ComponentName.class);
+ }
+
+ @UserIdInt
+ public int getUserId() {
+ return mUserId;
+ }
+
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+ @Override
+ public String toString() {
+ return mComponentName + " for user id: " + mUserId;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj != null && obj instanceof ComponentNameAndUser) {
+ ComponentNameAndUser other = (ComponentNameAndUser) obj;
+ return other.getUserId() == mUserId
+ && Objects.equals(other.getComponentName(), mComponentName);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ if (mComponentName == null) {
+ return mUserId;
+ }
+ return mComponentName.hashCode() + mUserId;
+ }
+}
diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl
index 8535e4a..5e2e92d 100644
--- a/nfc/java/android/nfc/INfcCardEmulation.aidl
+++ b/nfc/java/android/nfc/INfcCardEmulation.aidl
@@ -17,6 +17,8 @@
package android.nfc;
import android.content.ComponentName;
+import android.nfc.INfcEventListener;
+
import android.nfc.cardemulation.AidGroup;
import android.nfc.cardemulation.ApduServiceInfo;
import android.os.RemoteCallback;
@@ -55,4 +57,7 @@
boolean isAutoChangeEnabled();
List<String> getRoutingStatus();
void overwriteRoutingTable(int userHandle, String emptyAid, String protocol, String tech, String sc);
+
+ void registerNfcEventListener(in INfcEventListener listener);
+ void unregisterNfcEventListener(in INfcEventListener listener);
}
diff --git a/nfc/java/android/nfc/INfcEventListener.aidl b/nfc/java/android/nfc/INfcEventListener.aidl
new file mode 100644
index 0000000..5162c26
--- /dev/null
+++ b/nfc/java/android/nfc/INfcEventListener.aidl
@@ -0,0 +1,11 @@
+package android.nfc;
+
+import android.nfc.ComponentNameAndUser;
+
+/**
+ * @hide
+ */
+oneway interface INfcEventListener {
+ void onPreferredServiceChanged(in ComponentNameAndUser ComponentNameAndUser);
+ void onObserveModeStateChanged(boolean isEnabled);
+}
\ No newline at end of file
diff --git a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
index 48c7ee6..7f1fd15 100644
--- a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
+++ b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
@@ -27,7 +27,7 @@
* @hide
*/
interface INfcOemExtensionCallback {
- void onTagConnected(boolean connected, in Tag tag);
+ void onTagConnected(boolean connected);
void onStateUpdated(int state);
void onApplyRouting(in ResultReceiver isSkipped);
void onNdefRead(in ResultReceiver isSkipped);
@@ -46,6 +46,7 @@
void onCardEmulationActivated(boolean isActivated);
void onRfFieldActivated(boolean isActivated);
void onRfDiscoveryStarted(boolean isDiscoveryStarted);
+ void onEeListenActivated(boolean isActivated);
void onGetOemAppSearchIntent(in List<String> firstPackage, in ResultReceiver intentConsumer);
void onNdefMessage(in Tag tag, in NdefMessage message, in ResultReceiver hasOemExecutableContent);
void onLaunchHceAppChooserActivity(in String selectedAid, in List<ApduServiceInfo> services, in ComponentName failedComponent, in String category);
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index 474ff8c..1d2085c 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -80,6 +80,7 @@
private boolean mCardEmulationActivated = false;
private boolean mRfFieldActivated = false;
private boolean mRfDiscoveryStarted = false;
+ private boolean mEeListenActivated = false;
/**
* Broadcast Action: Sent on NFC stack initialization when NFC OEM extensions are enabled.
@@ -195,9 +196,8 @@
* ex - if tag is connected notify cover and Nfctest app if app is in testing mode
*
* @param connected status of the tag true if tag is connected otherwise false
- * @param tag Tag details
*/
- void onTagConnected(boolean connected, @NonNull Tag tag);
+ void onTagConnected(boolean connected);
/**
* Update the Nfc Adapter State
@@ -327,6 +327,13 @@
void onRfDiscoveryStarted(boolean isDiscoveryStarted);
/**
+ * Notifies the NFCEE (NFC Execution Environment) Listen has been activated.
+ *
+ * @param isActivated true, if EE Listen is ON, else EE Listen is OFF.
+ */
+ void onEeListenActivated(boolean isActivated);
+
+ /**
* Gets the intent to find the OEM package in the OEM App market. If the consumer returns
* {@code null} or a timeout occurs, the intent from the first available package will be
* used instead.
@@ -437,6 +444,7 @@
callback.onCardEmulationActivated(mCardEmulationActivated);
callback.onRfFieldActivated(mRfFieldActivated);
callback.onRfDiscoveryStarted(mRfDiscoveryStarted);
+ callback.onEeListenActivated(mEeListenActivated);
});
}
}
@@ -684,9 +692,9 @@
private final class NfcOemExtensionCallback extends INfcOemExtensionCallback.Stub {
@Override
- public void onTagConnected(boolean connected, Tag tag) throws RemoteException {
+ public void onTagConnected(boolean connected) throws RemoteException {
mCallbackMap.forEach((cb, ex) ->
- handleVoid2ArgCallback(connected, tag, cb::onTagConnected, ex));
+ handleVoidCallback(connected, cb::onTagConnected, ex));
}
@Override
@@ -711,6 +719,13 @@
}
@Override
+ public void onEeListenActivated(boolean isActivated) throws RemoteException {
+ mEeListenActivated = isActivated;
+ mCallbackMap.forEach((cb, ex) ->
+ handleVoidCallback(isActivated, cb::onEeListenActivated, ex));
+ }
+
+ @Override
public void onStateUpdated(int state) throws RemoteException {
mCallbackMap.forEach((cb, ex) ->
handleVoidCallback(state, cb::onStateUpdated, ex));
diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
index d75318f..9ff83fe 100644
--- a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -52,10 +52,12 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.TreeMap;
import java.util.regex.Pattern;
/**
@@ -204,7 +206,8 @@
this(info, onHost, description, staticAidGroups, dynamicAidGroups,
requiresUnlock, requiresScreenOn, bannerResource, uid,
settingsActivityName, offHost, staticOffHost, isEnabled,
- new HashMap<String, Boolean>(), new HashMap<Pattern, Boolean>());
+ new HashMap<String, Boolean>(), new TreeMap<>(
+ Comparator.comparing(Pattern::toString)));
}
/**
@@ -340,7 +343,8 @@
mStaticAidGroups = new HashMap<String, AidGroup>();
mDynamicAidGroups = new HashMap<String, AidGroup>();
mAutoTransact = new HashMap<String, Boolean>();
- mAutoTransactPatterns = new HashMap<Pattern, Boolean>();
+ mAutoTransactPatterns = new TreeMap<Pattern, Boolean>(
+ Comparator.comparing(Pattern::toString));
mOnHost = onHost;
final int depth = parser.getDepth();
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index d8f04c5..eb28c3b 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -17,6 +17,7 @@
package android.nfc.cardemulation;
import android.Manifest;
+import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -33,15 +34,18 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.nfc.ComponentNameAndUser;
import android.nfc.Constants;
import android.nfc.Flags;
import android.nfc.INfcCardEmulation;
+import android.nfc.INfcEventListener;
import android.nfc.NfcAdapter;
import android.os.Build;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
+import android.util.ArrayMap;
import android.util.Log;
import java.lang.annotation.Retention;
@@ -50,6 +54,8 @@
import java.util.HexFormat;
import java.util.List;
import java.util.Locale;
+import java.util.Objects;
+import java.util.concurrent.Executor;
import java.util.regex.Pattern;
/**
@@ -1076,4 +1082,107 @@
default -> throw new IllegalStateException("Unexpected value: " + route);
};
}
+
+ /** Listener for preferred service state changes. */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
+ public interface NfcEventListener {
+ /**
+ * This method is called when this package gains or loses preferred Nfc service status,
+ * either the Default Wallet Role holder (see {@link
+ * android.app.role.RoleManager#ROLE_WALLET}) or the preferred service of the foreground
+ * activity set with {@link #setPreferredService(Activity, ComponentName)}
+ *
+ * @param isPreferred true is this service has become the preferred Nfc service, false if it
+ * is no longer the preferred service
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
+ default void onPreferredServiceChanged(boolean isPreferred) {}
+
+ /**
+ * This method is called when observe mode has been enabled or disabled.
+ *
+ * @param isEnabled true if observe mode has been enabled, false if it has been disabled
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
+ default void onObserveModeStateChanged(boolean isEnabled) {}
+ }
+
+ private final ArrayMap<NfcEventListener, Executor> mNfcEventListeners = new ArrayMap<>();
+
+ final INfcEventListener mINfcEventListener =
+ new INfcEventListener.Stub() {
+ public void onPreferredServiceChanged(ComponentNameAndUser componentNameAndUser) {
+ if (!android.nfc.Flags.nfcEventListener()) {
+ return;
+ }
+ boolean isPreferred =
+ componentNameAndUser != null
+ && componentNameAndUser.getUserId()
+ == mContext.getUser().getIdentifier()
+ && componentNameAndUser.getComponentName() != null
+ && Objects.equals(
+ mContext.getPackageName(),
+ componentNameAndUser.getComponentName()
+ .getPackageName());
+ synchronized (mNfcEventListeners) {
+ mNfcEventListeners.forEach(
+ (listener, executor) -> {
+ executor.execute(
+ () -> listener.onPreferredServiceChanged(isPreferred));
+ });
+ }
+ }
+
+ public void onObserveModeStateChanged(boolean isEnabled) {
+ if (!android.nfc.Flags.nfcEventListener()) {
+ return;
+ }
+ synchronized (mNfcEventListeners) {
+ mNfcEventListeners.forEach(
+ (listener, executor) -> {
+ executor.execute(
+ () -> listener.onObserveModeStateChanged(isEnabled));
+ });
+ }
+ }
+ };
+
+ /**
+ * Register a listener for NFC Events.
+ *
+ * @param executor The Executor to run the call back with
+ * @param listener The listener to register
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
+ public void registerNfcEventListener(
+ @NonNull @CallbackExecutor Executor executor, @NonNull NfcEventListener listener) {
+ if (!android.nfc.Flags.nfcEventListener()) {
+ return;
+ }
+ synchronized (mNfcEventListeners) {
+ mNfcEventListeners.put(listener, executor);
+ if (mNfcEventListeners.size() == 1) {
+ callService(() -> sService.registerNfcEventListener(mINfcEventListener));
+ }
+ }
+ }
+
+ /**
+ * Unregister a preferred service listener that was previously registered with {@link
+ * #registerNfcEventListener(Executor, NfcEventListener)}
+ *
+ * @param listener The previously registered listener to unregister
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
+ public void unregisterNfcEventListener(@NonNull NfcEventListener listener) {
+ if (!android.nfc.Flags.nfcEventListener()) {
+ return;
+ }
+ synchronized (mNfcEventListeners) {
+ mNfcEventListeners.remove(listener);
+ if (mNfcEventListeners.size() == 0) {
+ callService(() -> sService.unregisterNfcEventListener(mINfcEventListener));
+ }
+ }
+ }
}
diff --git a/nfc/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java
index cd8e19c..4f601f0 100644
--- a/nfc/java/android/nfc/cardemulation/HostApduService.java
+++ b/nfc/java/android/nfc/cardemulation/HostApduService.java
@@ -239,15 +239,6 @@
*/
public static final int MSG_POLLING_LOOP = 4;
- /**
- * @hide
- */
- public static final int MSG_OBSERVE_MODE_CHANGE = 5;
-
- /**
- * @hide
- */
- public static final int MSG_PREFERRED_SERVICE_CHANGED = 6;
/**
* @hide
@@ -343,16 +334,6 @@
processPollingFrames(pollingFrames);
}
break;
- case MSG_OBSERVE_MODE_CHANGE:
- if (android.nfc.Flags.nfcEventListener()) {
- onObserveModeStateChanged(msg.arg1 == 1);
- }
- break;
- case MSG_PREFERRED_SERVICE_CHANGED:
- if (android.nfc.Flags.nfcEventListener()) {
- onPreferredServiceChanged(msg.arg1 == 1);
- }
- break;
default:
super.handleMessage(msg);
}
@@ -462,25 +443,4 @@
*/
public abstract void onDeactivated(int reason);
-
- /**
- * This method is called when this service is the preferred Nfc service and
- * Observe mode has been enabled or disabled.
- *
- * @param isEnabled true if observe mode has been enabled, false if it has been disabled
- */
- @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
- public void onObserveModeStateChanged(boolean isEnabled) {
-
- }
-
- /**
- * This method is called when this service gains or loses preferred Nfc service status.
- *
- * @param isPreferred true is this service has become the preferred Nfc service,
- * false if it is no longer the preferred service
- */
- @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
- public void onPreferredServiceChanged(boolean isPreferred) {
- }
}
diff --git a/packages/SettingsLib/Graph/graph.proto b/packages/SettingsLib/Graph/graph.proto
index cbe602e..6b93cd7 100644
--- a/packages/SettingsLib/Graph/graph.proto
+++ b/packages/SettingsLib/Graph/graph.proto
@@ -77,6 +77,8 @@
// Intent to show and locate the preference (might have highlight animation on
// the preference).
optional IntentProto launch_intent = 14;
+ // Descriptor of the preference value.
+ optional PreferenceValueDescriptorProto value_descriptor = 15;
// Target of an Intent
message ActionTarget {
@@ -103,9 +105,28 @@
message PreferenceValueProto {
oneof value {
bool boolean_value = 1;
+ int32 int_value = 2;
}
}
+// Proto of preference value descriptor.
+message PreferenceValueDescriptorProto {
+ oneof type {
+ bool boolean_type = 1;
+ RangeValueProto range_value = 2;
+ }
+}
+
+// Proto of preference value that is between a range.
+message RangeValueProto {
+ // The lower bound (inclusive) of the range.
+ optional int32 min = 1;
+ // The upper bound (inclusive) of the range.
+ optional int32 max = 2;
+ // The increment step within the range. 0 means unset, which implies step size is 1.
+ optional int32 step = 3;
+}
+
// Proto of android.content.Intent
message IntentProto {
// The action of the Intent.
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt
index fdffe5d..5ceee6d 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt
@@ -65,6 +65,7 @@
val visitedScreens: Set<String> = setOf(),
val locale: Locale? = null,
val includeValue: Boolean = true,
+ val includeValueDescriptor: Boolean = true,
)
object GetPreferenceGraphRequestCodec : MessageCodec<GetPreferenceGraphRequest> {
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
index 9cb872a..2256bb3 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
@@ -49,6 +49,7 @@
import com.android.settingslib.metadata.PreferenceScreenRegistry
import com.android.settingslib.metadata.PreferenceSummaryProvider
import com.android.settingslib.metadata.PreferenceTitleProvider
+import com.android.settingslib.metadata.RangeValue
import com.android.settingslib.preference.PreferenceScreenFactory
import com.android.settingslib.preference.PreferenceScreenProvider
import java.util.Locale
@@ -66,6 +67,7 @@
private val builder by lazy { PreferenceGraphProto.newBuilder() }
private val visitedScreens = mutableSetOf<String>().apply { addAll(request.visitedScreens) }
private val includeValue = request.includeValue
+ private val includeValueDescriptor = request.includeValueDescriptor
private suspend fun init() {
for (key in request.screenKeys) {
@@ -270,7 +272,8 @@
summary = textProto { string = it.toString() }
}
}
- if (metadata.icon != 0) icon = metadata.icon
+ val metadataIcon = metadata.getPreferenceIcon(context)
+ if (metadataIcon != 0) icon = metadataIcon
if (metadata.keywords != 0) keywords = metadata.keywords
val preferenceExtras = metadata.extras(context)
preferenceExtras?.let { extras = it.toProto() }
@@ -283,14 +286,37 @@
restricted = metadata.isRestricted(context)
}
persistent = metadata.isPersistent(context)
- if (
- includeValue &&
- persistent &&
- metadata is BooleanValue &&
- metadata is PersistentPreference<*>
- ) {
- metadata.storage(context).getValue(metadata.key, Boolean::class.javaObjectType)?.let {
- value = preferenceValueProto { booleanValue = it }
+ if (persistent) {
+ if (includeValue && metadata is PersistentPreference<*>) {
+ value = preferenceValueProto {
+ when (metadata) {
+ is BooleanValue ->
+ metadata
+ .storage(context)
+ .getValue(metadata.key, Boolean::class.javaObjectType)
+ ?.let { booleanValue = it }
+ is RangeValue -> {
+ metadata
+ .storage(context)
+ .getValue(metadata.key, Int::class.javaObjectType)
+ ?.let { intValue = it }
+ }
+ else -> {}
+ }
+ }
+ }
+ if (includeValueDescriptor) {
+ valueDescriptor = preferenceValueDescriptorProto {
+ when (metadata) {
+ is BooleanValue -> booleanType = true
+ is RangeValue -> rangeValue = rangeValueProto {
+ min = metadata.getMinValue(context)
+ max = metadata.getMaxValue(context)
+ step = metadata.getIncrementStep(context)
+ }
+ else -> {}
+ }
+ }
}
}
if (metadata is PreferenceScreenMetadata) {
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
index d8db1bb..6e4db1d 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
@@ -27,8 +27,10 @@
import com.android.settingslib.metadata.BooleanValue
import com.android.settingslib.metadata.PersistentPreference
import com.android.settingslib.metadata.PreferenceAvailabilityProvider
+import com.android.settingslib.metadata.PreferenceMetadata
import com.android.settingslib.metadata.PreferenceRestrictionProvider
import com.android.settingslib.metadata.PreferenceScreenRegistry
+import com.android.settingslib.metadata.RangeValue
import com.android.settingslib.metadata.ReadWritePermit
/** Request to set preference value. */
@@ -114,27 +116,39 @@
if (metadata is PreferenceAvailabilityProvider && !metadata.isAvailable(application)) {
return PreferenceSetterResult.UNAVAILABLE
}
+
+ fun <T> PreferenceMetadata.checkWritePermit(value: T): Int {
+ @Suppress("UNCHECKED_CAST") val preference = (this as PersistentPreference<T>)
+ return when (preference.getWritePermit(application, value, myUid, callingUid)) {
+ ReadWritePermit.ALLOW -> PreferenceSetterResult.OK
+ ReadWritePermit.DISALLOW -> PreferenceSetterResult.DISALLOW
+ ReadWritePermit.REQUIRE_APP_PERMISSION ->
+ PreferenceSetterResult.REQUIRE_APP_PERMISSION
+ ReadWritePermit.REQUIRE_USER_AGREEMENT ->
+ PreferenceSetterResult.REQUIRE_USER_AGREEMENT
+ else -> PreferenceSetterResult.INTERNAL_ERROR
+ }
+ }
+
val storage = metadata.storage(application)
val value = request.value
try {
if (value.hasBooleanValue()) {
if (metadata !is BooleanValue) return PreferenceSetterResult.INVALID_REQUEST
val booleanValue = value.booleanValue
- @Suppress("UNCHECKED_CAST")
- val booleanPreference = metadata as PersistentPreference<Boolean>
- when (
- booleanPreference.getWritePermit(application, booleanValue, myUid, callingUid)
- ) {
- ReadWritePermit.ALLOW -> {}
- ReadWritePermit.DISALLOW -> return PreferenceSetterResult.DISALLOW
- ReadWritePermit.REQUIRE_APP_PERMISSION ->
- return PreferenceSetterResult.REQUIRE_APP_PERMISSION
- ReadWritePermit.REQUIRE_USER_AGREEMENT ->
- return PreferenceSetterResult.REQUIRE_USER_AGREEMENT
- else -> return PreferenceSetterResult.INTERNAL_ERROR
- }
+ val resultCode = metadata.checkWritePermit(booleanValue)
+ if (resultCode != PreferenceSetterResult.OK) return resultCode
storage.setValue(key, Boolean::class.javaObjectType, booleanValue)
return PreferenceSetterResult.OK
+ } else if (value.hasIntValue()) {
+ val intValue = value.intValue
+ val resultCode = metadata.checkWritePermit(intValue)
+ if (resultCode != PreferenceSetterResult.OK) return resultCode
+ if (metadata is RangeValue && !metadata.isValidValue(application, intValue)) {
+ return PreferenceSetterResult.INVALID_REQUEST
+ }
+ storage.setValue(key, Int::class.javaObjectType, intValue)
+ return PreferenceSetterResult.OK
}
} catch (e: Exception) {
return PreferenceSetterResult.INTERNAL_ERROR
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt
index d7dae77..dee32d9 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt
@@ -24,7 +24,9 @@
import com.android.settingslib.graph.proto.PreferenceProto
import com.android.settingslib.graph.proto.PreferenceProto.ActionTarget
import com.android.settingslib.graph.proto.PreferenceScreenProto
+import com.android.settingslib.graph.proto.PreferenceValueDescriptorProto
import com.android.settingslib.graph.proto.PreferenceValueProto
+import com.android.settingslib.graph.proto.RangeValueProto
import com.android.settingslib.graph.proto.TextProto
/** Returns root or null. */
@@ -89,6 +91,16 @@
inline fun preferenceValueProto(init: PreferenceValueProto.Builder.() -> Unit) =
PreferenceValueProto.newBuilder().also(init).build()
+/** Kotlin DSL-style builder for [PreferenceValueDescriptorProto]. */
+@JvmSynthetic
+inline fun preferenceValueDescriptorProto(init: PreferenceValueDescriptorProto.Builder.() -> Unit) =
+ PreferenceValueDescriptorProto.newBuilder().also(init).build()
+
+/** Kotlin DSL-style builder for [RangeValueProto]. */
+@JvmSynthetic
+inline fun rangeValueProto(init: RangeValueProto.Builder.() -> Unit) =
+ RangeValueProto.newBuilder().also(init).build()
+
/** Kotlin DSL-style builder for [TextProto]. */
@JvmSynthetic
inline fun textProto(init: TextProto.Builder.() -> Unit) = TextProto.newBuilder().also(init).build()
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
index d40a6f6..762f5ea 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
@@ -23,7 +23,6 @@
import androidx.preference.SwitchPreferenceCompat
import androidx.preference.TwoStatePreference
import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_KEY
-import com.android.settingslib.metadata.PersistentPreference
import com.android.settingslib.metadata.PreferenceMetadata
import com.android.settingslib.metadata.PreferenceScreenMetadata
import com.android.settingslib.metadata.PreferenceTitleProvider
@@ -71,10 +70,10 @@
override fun bind(preference: Preference, metadata: PreferenceMetadata) {
super.bind(preference, metadata)
- (metadata as? PersistentPreference<*>)
- ?.storage(preference.context)
- ?.getValue(metadata.key, Boolean::class.javaObjectType)
- ?.let { (preference as TwoStatePreference).isChecked = it }
+ (preference as TwoStatePreference).apply {
+ // "false" is kind of placeholder, metadata datastore should provide the default value
+ isChecked = preferenceDataStore!!.getBoolean(key, false)
+ }
}
}
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceHierarchyInflater.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceHierarchyInflater.kt
index c26ba18..657f69a 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceHierarchyInflater.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceHierarchyInflater.kt
@@ -40,11 +40,11 @@
addPreference(preferenceGroup)
preferenceGroup.inflatePreferenceHierarchy(preferenceBindingFactory, it)
} else {
- preferenceBindingFactory.bind(widget, it, preferenceBinding)
(metadata as? PersistentPreference<*>)?.storage(context)?.let { storage ->
widget.preferenceDataStore =
storages.getOrPut(storage) { PreferenceDataStoreAdapter(storage) }
}
+ preferenceBindingFactory.bind(widget, it, preferenceBinding)
// MUST add preference after binding for persistent preference to get initial value
// (preference key is set within bind method)
addPreference(widget)
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
index d75f3e8..022fb1d 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
@@ -236,12 +236,12 @@
preference.bindRecursively(preferenceBindingFactory, preferences, storages)
} else {
preferences[preference.key]?.let {
- preferenceBindingFactory.bind(preference, it)
val metadata = it.metadata
(metadata as? PersistentPreference<*>)?.storage(context)?.let { storage ->
preference.preferenceDataStore =
storages.getOrPut(storage) { PreferenceDataStoreAdapter(storage) }
}
+ preferenceBindingFactory.bind(preference, it)
}
}
}
diff --git a/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/PreferenceBindingTestUtils.kt b/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/PreferenceBindingTestUtils.kt
new file mode 100644
index 0000000..f3142d0
--- /dev/null
+++ b/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/PreferenceBindingTestUtils.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.preference
+
+import android.content.Context
+import androidx.annotation.VisibleForTesting
+import androidx.preference.Preference
+import com.android.settingslib.metadata.PersistentPreference
+import com.android.settingslib.metadata.PreferenceMetadata
+
+/** Creates [Preference] widget and binds with metadata. */
+@VisibleForTesting
+fun <P : Preference> PreferenceMetadata.createAndBindWidget(context: Context): P {
+ val binding = DefaultPreferenceBindingFactory.getPreferenceBinding(this)
+ return (binding.createWidget(context) as P).also {
+ if (this is PersistentPreference<*>) {
+ storage(context)?.let { keyValueStore ->
+ it.preferenceDataStore = PreferenceDataStoreAdapter(keyValueStore)
+ }
+ }
+ binding.bind(it, this)
+ }
+}
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml
index cc42dab..944bef6 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml
@@ -21,6 +21,7 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingVertical="@dimen/settingslib_expressive_space_small1"
+ android:paddingHorizontal="@dimen/settingslib_expressive_space_small1"
android:filterTouchesWhenObscured="false">
<TextView
@@ -40,7 +41,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@android:id/title"
- android:layout_alignLeft="@android:id/title"
android:layout_alignStart="@android:id/title"
android:layout_gravity="start"
android:textAlignment="viewStart"
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPendingIntentAction.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPendingIntentAction.java
index 3d4282c..b997e4d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPendingIntentAction.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPendingIntentAction.java
@@ -17,7 +17,6 @@
package com.android.settingslib.bluetooth.devicesettings;
import android.app.PendingIntent;
-import android.content.Intent;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -47,7 +46,7 @@
/** Read a {@link DeviceSettingPendingIntentAction} instance from {@link Parcel} */
@NonNull
public static DeviceSettingPendingIntentAction readFromParcel(@NonNull Parcel in) {
- PendingIntent pendingIntent = in.readParcelable(Intent.class.getClassLoader());
+ PendingIntent pendingIntent = in.readParcelable(PendingIntent.class.getClassLoader());
Bundle extras = in.readBundle(Bundle.class.getClassLoader());
return new DeviceSettingPendingIntentAction(pendingIntent, extras);
}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index a18b6c1..bffda8b 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -536,6 +536,8 @@
"androidx.room_room-runtime",
"androidx.room_room-ktx",
"androidx.datastore_datastore-preferences",
+ "androidx.media3.media3-common",
+ "androidx.media3.media3-session",
"com.google.android.material_material",
"device_state_flags_lib",
"kotlinx_coroutines_android",
@@ -703,6 +705,8 @@
"androidx.room_room-testing",
"androidx.room_room-ktx",
"androidx.datastore_datastore-preferences",
+ "androidx.media3.media3-common",
+ "androidx.media3.media3-session",
"device_state_flags_lib",
"kotlinx-coroutines-android",
"kotlinx-coroutines-core",
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 5c5edb1..0938167 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -468,6 +468,15 @@
}
flag {
+ name: "status_bar_notification_chips_test"
+ namespace: "systemui"
+ description: "Flag to enable certain features that let us test the status bar notification "
+ "chips with teamfooders. This flag should *never* be released to trunkfood or nextfood."
+ bug: "361346412"
+}
+
+
+flag {
name: "compose_bouncer"
namespace: "systemui"
description: "Use the new compose bouncer in SystemUI"
@@ -1515,6 +1524,16 @@
}
flag {
+ namespace: "systemui"
+ name: "user_aware_settings_repositories"
+ description: "Provide user-aware versions of SecureSettingsRepository and SystemSettingsRepository in SystemUI modules (see doc linked from b/356099784)."
+ bug: "356099784"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "notify_password_text_view_user_activity_in_background"
namespace: "systemui"
description: "Decide whether to notify the user activity in password text view, to power manager in the background thread."
@@ -1708,3 +1727,9 @@
bug: "365064144"
}
+flag {
+ name: "touchpad_three_finger_tap_customization"
+ namespace: "systemui"
+ description: "Customize touchpad three finger tap"
+ bug: "365063048"
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index 6fc51e4..e78862e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -53,6 +53,7 @@
import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace
import com.android.systemui.res.R
import com.android.systemui.shade.LargeScreenHeaderHelper
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker
@@ -84,7 +85,7 @@
stackScrollLayout: NotificationStackScrollLayout,
sharedNotificationContainerBinder: SharedNotificationContainerBinder,
private val keyguardRootViewModel: KeyguardRootViewModel,
- private val configurationState: ConfigurationState,
+ @ShadeDisplayAware private val configurationState: ConfigurationState,
private val iconBindingFailureTracker: StatusBarIconViewBindingFailureTracker,
private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore,
@@ -127,7 +128,7 @@
}
val burnIn = rememberBurnIn(clockInteractor)
AnimatedVisibility(
- visibleState = transitionState,
+ visibleState = transitionState,
enter = fadeIn(),
exit = fadeOut(),
modifier =
@@ -150,7 +151,7 @@
)
}
}
- },
+ }
)
}
}
@@ -172,7 +173,7 @@
areNotificationsVisible: Boolean,
isShadeLayoutWide: Boolean,
burnInParams: BurnInParameters?,
- modifier: Modifier = Modifier
+ modifier: Modifier = Modifier,
) {
if (!areNotificationsVisible) {
return
@@ -192,10 +193,7 @@
if (burnInParams == null) {
it
} else {
- it.burnInAware(
- viewModel = aodBurnInViewModel,
- params = burnInParams,
- )
+ it.burnInAware(viewModel = aodBurnInViewModel, params = burnInParams)
}
},
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
index 2a91bd8..26c827a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
@@ -43,6 +43,7 @@
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.lifecycle.rememberViewModel
+import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace
import com.android.systemui.qs.composefragment.ui.GridAnchor
import com.android.systemui.qs.panels.ui.compose.EditMode
import com.android.systemui.qs.panels.ui.compose.TileGrid
@@ -53,8 +54,11 @@
import com.android.systemui.scene.ui.composable.Overlay
import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
import com.android.systemui.shade.ui.composable.OverlayShade
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import com.android.systemui.statusbar.phone.ui.StatusBarIconController
import com.android.systemui.statusbar.phone.ui.TintedIconManager
+import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -67,6 +71,8 @@
private val tintedIconManagerFactory: TintedIconManager.Factory,
private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
private val statusBarIconController: StatusBarIconController,
+ private val notificationStackScrollView: Lazy<NotificationScrollView>,
+ private val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
) : Overlay {
override val key = Overlays.QuickSettingsShade
@@ -98,6 +104,14 @@
ShadeBody(viewModel = viewModel.quickSettingsContainerViewModel)
}
+
+ SnoozeableHeadsUpNotificationSpace(
+ stackScrollView = notificationStackScrollView.get(),
+ viewModel =
+ rememberViewModel("QuickSettingsShadeOverlay") {
+ notificationsPlaceholderViewModelFactory.create()
+ },
+ )
}
}
}
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 7872ffa..041cd15 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
@@ -36,12 +36,11 @@
internal interface DraggableHandler {
/**
- * Start a drag in the given [startedPosition], with the given [overSlop] and number of
- * [pointersDown].
+ * Start a drag with the given [pointersInfo] and [overSlop].
*
* The returned [DragController] should be used to continue or stop the drag.
*/
- fun onDragStarted(startedPosition: Offset?, overSlop: Float, pointersDown: Int): DragController
+ fun onDragStarted(pointersInfo: PointersInfo?, overSlop: Float): DragController
}
/**
@@ -96,7 +95,7 @@
* Note: if this returns true, then [onDragStarted] will be called with overSlop equal to 0f,
* indicating that the transition should be intercepted.
*/
- internal fun shouldImmediatelyIntercept(startedPosition: Offset?): Boolean {
+ internal fun shouldImmediatelyIntercept(pointersInfo: PointersInfo?): Boolean {
// We don't intercept the touch if we are not currently driving the transition.
val dragController = dragController
if (dragController?.isDrivingTransition != true) {
@@ -107,7 +106,7 @@
// Only intercept the current transition if one of the 2 swipes results is also a transition
// between the same pair of contents.
- val swipes = computeSwipes(startedPosition, pointersDown = 1)
+ val swipes = computeSwipes(pointersInfo)
val fromContent = layoutImpl.content(swipeAnimation.currentContent)
val (upOrLeft, downOrRight) = swipes.computeSwipesResults(fromContent)
val currentScene = layoutImpl.state.currentScene
@@ -124,11 +123,7 @@
))
}
- override fun onDragStarted(
- startedPosition: Offset?,
- overSlop: Float,
- pointersDown: Int,
- ): DragController {
+ override fun onDragStarted(pointersInfo: PointersInfo?, overSlop: Float): DragController {
if (overSlop == 0f) {
val oldDragController = dragController
check(oldDragController != null && oldDragController.isDrivingTransition) {
@@ -153,7 +148,7 @@
return updateDragController(swipes, swipeAnimation)
}
- val swipes = computeSwipes(startedPosition, pointersDown)
+ val swipes = computeSwipes(pointersInfo)
val fromContent = layoutImpl.contentForUserActions()
swipes.updateSwipesResults(fromContent)
@@ -190,8 +185,7 @@
return createSwipeAnimation(layoutImpl, result, isUpOrLeft, orientation)
}
- internal fun resolveSwipeSource(startedPosition: Offset?): SwipeSource.Resolved? {
- if (startedPosition == null) return null
+ internal fun resolveSwipeSource(startedPosition: Offset): SwipeSource.Resolved? {
return layoutImpl.swipeSourceDetector.source(
layoutSize = layoutImpl.lastSize,
position = startedPosition.round(),
@@ -200,57 +194,44 @@
)
}
- internal fun resolveSwipe(
- pointersDown: Int,
- fromSource: SwipeSource.Resolved?,
- isUpOrLeft: Boolean,
- ): Swipe.Resolved {
- return Swipe.Resolved(
- direction =
- when (orientation) {
- Orientation.Horizontal ->
- if (isUpOrLeft) {
- SwipeDirection.Resolved.Left
- } else {
- SwipeDirection.Resolved.Right
- }
-
- Orientation.Vertical ->
- if (isUpOrLeft) {
- SwipeDirection.Resolved.Up
- } else {
- SwipeDirection.Resolved.Down
- }
- },
- pointerCount = pointersDown,
- fromSource = fromSource,
+ private fun computeSwipes(pointersInfo: PointersInfo?): Swipes {
+ val fromSource = pointersInfo?.let { resolveSwipeSource(it.startedPosition) }
+ return Swipes(
+ upOrLeft = resolveSwipe(orientation, isUpOrLeft = true, pointersInfo, fromSource),
+ downOrRight = resolveSwipe(orientation, isUpOrLeft = false, pointersInfo, fromSource),
)
}
+}
- private fun computeSwipes(startedPosition: Offset?, pointersDown: Int): Swipes {
- val fromSource = resolveSwipeSource(startedPosition)
- val upOrLeft = resolveSwipe(pointersDown, fromSource, isUpOrLeft = true)
- val downOrRight = resolveSwipe(pointersDown, fromSource, isUpOrLeft = false)
- return if (fromSource == null) {
- Swipes(
- upOrLeft = null,
- downOrRight = null,
- upOrLeftNoSource = upOrLeft,
- downOrRightNoSource = downOrRight,
- )
- } else {
- Swipes(
- upOrLeft = upOrLeft,
- downOrRight = downOrRight,
- upOrLeftNoSource = upOrLeft.copy(fromSource = null),
- downOrRightNoSource = downOrRight.copy(fromSource = null),
- )
- }
- }
+private fun resolveSwipe(
+ orientation: Orientation,
+ isUpOrLeft: Boolean,
+ pointersInfo: PointersInfo?,
+ fromSource: SwipeSource.Resolved?,
+): Swipe.Resolved {
+ return Swipe.Resolved(
+ direction =
+ when (orientation) {
+ Orientation.Horizontal ->
+ if (isUpOrLeft) {
+ SwipeDirection.Resolved.Left
+ } else {
+ SwipeDirection.Resolved.Right
+ }
- companion object {
- private const val TAG = "DraggableHandlerImpl"
- }
+ Orientation.Vertical ->
+ if (isUpOrLeft) {
+ SwipeDirection.Resolved.Up
+ } else {
+ SwipeDirection.Resolved.Down
+ }
+ },
+ // If the number of pointers is not specified, 1 is assumed.
+ pointerCount = pointersInfo?.pointersDown ?: 1,
+ // Resolves the pointer type only if all pointers are of the same type.
+ pointersType = pointersInfo?.pointersDownByType?.keys?.singleOrNull(),
+ fromSource = fromSource,
+ )
}
/** @param swipes The [Swipes] associated to the current gesture. */
@@ -498,24 +479,14 @@
}
/** The [Swipe] associated to a given fromScene, startedPosition and pointersDown. */
-internal class Swipes(
- val upOrLeft: Swipe.Resolved?,
- val downOrRight: Swipe.Resolved?,
- val upOrLeftNoSource: Swipe.Resolved?,
- val downOrRightNoSource: Swipe.Resolved?,
-) {
+internal class Swipes(val upOrLeft: Swipe.Resolved, val downOrRight: Swipe.Resolved) {
/** The [UserActionResult] associated to up and down swipes. */
var upOrLeftResult: UserActionResult? = null
var downOrRightResult: UserActionResult? = null
fun computeSwipesResults(fromContent: Content): Pair<UserActionResult?, UserActionResult?> {
- val userActions = fromContent.userActions
- fun result(swipe: Swipe.Resolved?): UserActionResult? {
- return userActions[swipe ?: return null]
- }
-
- val upOrLeftResult = result(upOrLeft) ?: result(upOrLeftNoSource)
- val downOrRightResult = result(downOrRight) ?: result(downOrRightNoSource)
+ val upOrLeftResult = fromContent.findActionResultBestMatch(swipe = upOrLeft)
+ val downOrRightResult = fromContent.findActionResultBestMatch(swipe = downOrRight)
return upOrLeftResult to downOrRightResult
}
@@ -569,11 +540,13 @@
val connection: PriorityNestedScrollConnection = nestedScrollConnection()
- private fun PointersInfo.resolveSwipe(isUpOrLeft: Boolean): Swipe.Resolved {
- return draggableHandler.resolveSwipe(
- pointersDown = pointersDown,
- fromSource = draggableHandler.resolveSwipeSource(startedPosition),
+ private fun resolveSwipe(isUpOrLeft: Boolean, pointersInfo: PointersInfo?): Swipe.Resolved {
+ return resolveSwipe(
+ orientation = draggableHandler.orientation,
isUpOrLeft = isUpOrLeft,
+ pointersInfo = pointersInfo,
+ fromSource =
+ pointersInfo?.let { draggableHandler.resolveSwipeSource(it.startedPosition) },
)
}
@@ -582,12 +555,7 @@
// moving on to the next scene.
var canChangeScene = false
- var _lastPointersInfo: PointersInfo? = null
- fun pointersInfo(): PointersInfo {
- return checkNotNull(_lastPointersInfo) {
- "PointersInfo should be initialized before the transition begins."
- }
- }
+ var lastPointersInfo: PointersInfo? = null
fun hasNextScene(amount: Float): Boolean {
val transitionState = layoutState.transitionState
@@ -595,17 +563,11 @@
val fromScene = layoutImpl.scene(scene)
val resolvedSwipe =
when {
- amount < 0f -> pointersInfo().resolveSwipe(isUpOrLeft = true)
- amount > 0f -> pointersInfo().resolveSwipe(isUpOrLeft = false)
+ amount < 0f -> resolveSwipe(isUpOrLeft = true, lastPointersInfo)
+ amount > 0f -> resolveSwipe(isUpOrLeft = false, lastPointersInfo)
else -> null
}
- val nextScene =
- resolvedSwipe?.let {
- fromScene.userActions[it]
- ?: if (it.fromSource != null) {
- fromScene.userActions[it.copy(fromSource = null)]
- } else null
- }
+ val nextScene = resolvedSwipe?.let { fromScene.findActionResultBestMatch(it) }
if (nextScene != null) return true
if (transitionState !is TransitionState.Idle) return false
@@ -619,13 +581,14 @@
return PriorityNestedScrollConnection(
orientation = orientation,
canStartPreScroll = { offsetAvailable, offsetBeforeStart, _ ->
+ val pointersInfo = pointersInfoOwner.pointersInfo()
canChangeScene =
if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f
val canInterceptSwipeTransition =
canChangeScene &&
offsetAvailable != 0f &&
- draggableHandler.shouldImmediatelyIntercept(startedPosition = null)
+ draggableHandler.shouldImmediatelyIntercept(pointersInfo)
if (!canInterceptSwipeTransition) return@PriorityNestedScrollConnection false
val threshold = layoutImpl.transitionInterceptionThreshold
@@ -636,13 +599,11 @@
return@PriorityNestedScrollConnection false
}
- val pointersInfo = pointersInfoOwner.pointersInfo()
-
- if (pointersInfo.isMouseWheel) {
+ if (pointersInfo?.isMouseWheel == true) {
// Do not support mouse wheel interactions
return@PriorityNestedScrollConnection false
}
- _lastPointersInfo = pointersInfo
+ lastPointersInfo = pointersInfo
// If the current swipe transition is *not* closed to 0f or 1f, then we want the
// scroll events to intercept the current transition to continue the scene
@@ -662,11 +623,11 @@
if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f
val pointersInfo = pointersInfoOwner.pointersInfo()
- if (pointersInfo.isMouseWheel) {
+ if (pointersInfo?.isMouseWheel == true) {
// Do not support mouse wheel interactions
return@PriorityNestedScrollConnection false
}
- _lastPointersInfo = pointersInfo
+ lastPointersInfo = pointersInfo
val canStart =
when (behavior) {
@@ -704,11 +665,11 @@
canChangeScene = false
val pointersInfo = pointersInfoOwner.pointersInfo()
- if (pointersInfo.isMouseWheel) {
+ if (pointersInfo?.isMouseWheel == true) {
// Do not support mouse wheel interactions
return@PriorityNestedScrollConnection false
}
- _lastPointersInfo = pointersInfo
+ lastPointersInfo = pointersInfo
val canStart = behavior.canStartOnPostFling && hasNextScene(velocityAvailable)
if (canStart) {
@@ -718,12 +679,11 @@
canStart
},
onStart = { firstScroll ->
- val pointersInfo = pointersInfo()
+ val pointersInfo = lastPointersInfo
scrollController(
dragController =
draggableHandler.onDragStarted(
- pointersDown = pointersInfo.pointersDown,
- startedPosition = pointersInfo.startedPosition,
+ pointersInfo = pointersInfo,
overSlop = if (isIntercepting) 0f else firstScroll,
),
canChangeScene = canChangeScene,
@@ -742,7 +702,7 @@
return object : ScrollController {
override fun onScroll(deltaScroll: Float, source: NestedScrollSource): Float {
val pointersInfo = pointersInfoOwner.pointersInfo()
- if (pointersInfo.isMouseWheel) {
+ if (pointersInfo?.isMouseWheel == true) {
// Do not support mouse wheel interactions
return 0f
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index 8613f6d..ab2324a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -33,6 +33,7 @@
import androidx.compose.ui.input.pointer.PointerId
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.PointerInputScope
+import androidx.compose.ui.input.pointer.PointerType
import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode
import androidx.compose.ui.input.pointer.changedToDown
import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
@@ -52,6 +53,7 @@
import androidx.compose.ui.util.fastAny
import androidx.compose.ui.util.fastFilter
import androidx.compose.ui.util.fastFirstOrNull
+import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastSumBy
import com.android.compose.ui.util.SpaceVectorConverter
import kotlin.coroutines.cancellation.CancellationException
@@ -78,8 +80,8 @@
@Stable
internal fun Modifier.multiPointerDraggable(
orientation: Orientation,
- startDragImmediately: (startedPosition: Offset) -> Boolean,
- onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+ startDragImmediately: (pointersInfo: PointersInfo) -> Boolean,
+ onDragStarted: (pointersInfo: PointersInfo, overSlop: Float) -> DragController,
onFirstPointerDown: () -> Unit = {},
swipeDetector: SwipeDetector = DefaultSwipeDetector,
dispatcher: NestedScrollDispatcher,
@@ -97,9 +99,8 @@
private data class MultiPointerDraggableElement(
private val orientation: Orientation,
- private val startDragImmediately: (startedPosition: Offset) -> Boolean,
- private val onDragStarted:
- (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+ private val startDragImmediately: (pointersInfo: PointersInfo) -> Boolean,
+ private val onDragStarted: (pointersInfo: PointersInfo, overSlop: Float) -> DragController,
private val onFirstPointerDown: () -> Unit,
private val swipeDetector: SwipeDetector,
private val dispatcher: NestedScrollDispatcher,
@@ -125,9 +126,8 @@
internal class MultiPointerDraggableNode(
orientation: Orientation,
- var startDragImmediately: (startedPosition: Offset) -> Boolean,
- var onDragStarted:
- (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+ var startDragImmediately: (pointersInfo: PointersInfo) -> Boolean,
+ var onDragStarted: (pointersInfo: PointersInfo, overSlop: Float) -> DragController,
var onFirstPointerDown: () -> Unit,
swipeDetector: SwipeDetector = DefaultSwipeDetector,
private val dispatcher: NestedScrollDispatcher,
@@ -183,17 +183,22 @@
pointerInput.onPointerEvent(pointerEvent, pass, bounds)
}
+ private var lastPointerEvent: PointerEvent? = null
private var startedPosition: Offset? = null
private var pointersDown: Int = 0
- private var isMouseWheel: Boolean = false
- internal fun pointersInfo(): PointersInfo {
- return PointersInfo(
+ internal fun pointersInfo(): PointersInfo? {
+ val startedPosition = startedPosition
+ val lastPointerEvent = lastPointerEvent
+ if (startedPosition == null || lastPointerEvent == null) {
// This may be null, i.e. when the user uses TalkBack
+ return null
+ }
+
+ return PointersInfo(
startedPosition = startedPosition,
- // We could have 0 pointers during fling or for other reasons.
- pointersDown = pointersDown.coerceAtLeast(1),
- isMouseWheel = isMouseWheel,
+ pointersDown = pointersDown,
+ lastPointerEvent = lastPointerEvent,
)
}
@@ -212,8 +217,8 @@
if (pointerEvent.type == PointerEventType.Enter) continue
val changes = pointerEvent.changes
+ lastPointerEvent = pointerEvent
pointersDown = changes.countDown()
- isMouseWheel = pointerEvent.type == PointerEventType.Scroll
when {
// There are no more pointers down.
@@ -285,8 +290,8 @@
detectDragGestures(
orientation = orientation,
startDragImmediately = startDragImmediately,
- onDragStart = { startedPosition, overSlop, pointersDown ->
- onDragStarted(startedPosition, overSlop, pointersDown)
+ onDragStart = { pointersInfo, overSlop ->
+ onDragStarted(pointersInfo, overSlop)
},
onDrag = { controller, amount ->
dispatchScrollEvents(
@@ -435,9 +440,8 @@
*/
private suspend fun AwaitPointerEventScope.detectDragGestures(
orientation: Orientation,
- startDragImmediately: (startedPosition: Offset) -> Boolean,
- onDragStart:
- (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+ startDragImmediately: (pointersInfo: PointersInfo) -> Boolean,
+ onDragStart: (pointersInfo: PointersInfo, overSlop: Float) -> DragController,
onDrag: (controller: DragController, dragAmount: Float) -> Unit,
onDragEnd: (controller: DragController) -> Unit,
onDragCancel: (controller: DragController) -> Unit,
@@ -462,8 +466,13 @@
.first()
var overSlop = 0f
+ var lastPointersInfo =
+ checkNotNull(pointersInfo()) {
+ "We should have pointers down, last event: $currentEvent"
+ }
+
val drag =
- if (startDragImmediately(consumablePointer.position)) {
+ if (startDragImmediately(lastPointersInfo)) {
consumablePointer.consume()
consumablePointer
} else {
@@ -488,14 +497,18 @@
consumablePointer.id,
onSlopReached,
)
- }
+ } ?: return
+ lastPointersInfo =
+ checkNotNull(pointersInfo()) {
+ "We should have pointers down, last event: $currentEvent"
+ }
// Make sure that overSlop is not 0f. This can happen when the user drags by exactly
// the touch slop. However, the overSlop we pass to onDragStarted() is used to
// compute the direction we are dragging in, so overSlop should never be 0f unless
// we intercept an ongoing swipe transition (i.e. startDragImmediately() returned
// true).
- if (drag != null && overSlop == 0f) {
+ if (overSlop == 0f) {
val delta = (drag.position - consumablePointer.position).toFloat()
check(delta != 0f) { "delta is equal to 0" }
overSlop = delta.sign
@@ -503,49 +516,38 @@
drag
}
- if (drag != null) {
- val controller =
- onDragStart(
- // The startedPosition is the starting position when a gesture begins (when the
- // first pointer touches the screen), not the point where we begin dragging.
- // For example, this could be different if one of our children intercepts the
- // gesture first and then we do.
- requireNotNull(startedPosition),
- overSlop,
- pointersDown,
+ val controller = onDragStart(lastPointersInfo, overSlop)
+
+ val successful: Boolean
+ try {
+ onDrag(controller, overSlop)
+
+ successful =
+ drag(
+ initialPointerId = drag.id,
+ hasDragged = { it.positionChangeIgnoreConsumed().toFloat() != 0f },
+ onDrag = {
+ onDrag(controller, it.positionChange().toFloat())
+ it.consume()
+ },
+ onIgnoredEvent = {
+ // We are still dragging an object, but this event is not of interest to the
+ // caller.
+ // This event will not trigger the onDrag event, but we will consume the
+ // event to prevent another pointerInput from interrupting the current
+ // gesture just because the event was ignored.
+ it.consume()
+ },
)
+ } catch (t: Throwable) {
+ onDragCancel(controller)
+ throw t
+ }
- val successful: Boolean
- try {
- onDrag(controller, overSlop)
-
- successful =
- drag(
- initialPointerId = drag.id,
- hasDragged = { it.positionChangeIgnoreConsumed().toFloat() != 0f },
- onDrag = {
- onDrag(controller, it.positionChange().toFloat())
- it.consume()
- },
- onIgnoredEvent = {
- // We are still dragging an object, but this event is not of interest to
- // the caller.
- // This event will not trigger the onDrag event, but we will consume the
- // event to prevent another pointerInput from interrupting the current
- // gesture just because the event was ignored.
- it.consume()
- },
- )
- } catch (t: Throwable) {
- onDragCancel(controller)
- throw t
- }
-
- if (successful) {
- onDragEnd(controller)
- } else {
- onDragCancel(controller)
- }
+ if (successful) {
+ onDragEnd(controller)
+ } else {
+ onDragCancel(controller)
}
}
@@ -655,11 +657,57 @@
}
internal fun interface PointersInfoOwner {
- fun pointersInfo(): PointersInfo
+ /**
+ * Provides information about the pointers interacting with this composable.
+ *
+ * @return A [PointersInfo] object containing details about the pointers, including the starting
+ * position and the number of pointers down, or `null` if there are no pointers down.
+ */
+ fun pointersInfo(): PointersInfo?
}
+/**
+ * Holds information about pointer interactions within a composable.
+ *
+ * This class stores details such as the starting position of a gesture, the number of pointers
+ * down, and whether the last pointer event was a mouse wheel scroll.
+ *
+ * @param startedPosition The starting position of the gesture. This is the position where the first
+ * pointer touched the screen, not necessarily the point where dragging begins. This may be
+ * different from the initial touch position if a child composable intercepts the gesture before
+ * this one.
+ * @param pointersDown The number of pointers currently down.
+ * @param isMouseWheel Indicates whether the last pointer event was a mouse wheel scroll.
+ * @param pointersDownByType Provide a map of pointer types to the count of pointers of that type
+ * currently down/pressed.
+ */
internal data class PointersInfo(
- val startedPosition: Offset?,
+ val startedPosition: Offset,
val pointersDown: Int,
val isMouseWheel: Boolean,
-)
+ val pointersDownByType: Map<PointerType, Int>,
+) {
+ init {
+ check(pointersDown > 0) { "We should have at least 1 pointer down, $pointersDown instead" }
+ }
+}
+
+private fun PointersInfo(
+ startedPosition: Offset,
+ pointersDown: Int,
+ lastPointerEvent: PointerEvent,
+): PointersInfo {
+ return PointersInfo(
+ startedPosition = startedPosition,
+ pointersDown = pointersDown,
+ isMouseWheel = lastPointerEvent.type == PointerEventType.Scroll,
+ pointersDownByType =
+ buildMap {
+ lastPointerEvent.changes.fastForEach { change ->
+ if (!change.pressed) return@fastForEach
+ val newValue = (get(change.type) ?: 0) + 1
+ put(change.type, newValue)
+ }
+ },
+ )
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
index 077927d..5bf77ae 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
@@ -233,6 +233,12 @@
(to == null || this.toContent == to)
}
+ fun isTransitioningSets(from: Set<ContentKey>? = null, to: Set<ContentKey>? = null): Boolean {
+ return this is Transition &&
+ (from == null || from.contains(this.fromContent)) &&
+ (to == null || to.contains(this.toContent))
+ }
+
/** Whether we are transitioning from [content] to [other], or from [other] to [content]. */
fun isTransitioningBetween(content: ContentKey, other: ContentKey): Boolean {
return isTransitioning(from = content, to = other) ||
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 5042403..21d87e1 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
@@ -27,6 +27,7 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.pointer.PointerType
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.Density
@@ -407,6 +408,7 @@
data class Swipe(
val direction: SwipeDirection,
val pointerCount: Int = 1,
+ val pointersType: PointerType? = null,
val fromSource: SwipeSource? = null,
) : UserAction() {
companion object {
@@ -422,6 +424,7 @@
return Resolved(
direction = direction.resolve(layoutDirection),
pointerCount = pointerCount,
+ pointersType = pointersType,
fromSource = fromSource?.resolve(layoutDirection),
)
}
@@ -431,6 +434,7 @@
val direction: SwipeDirection.Resolved,
val pointerCount: Int,
val fromSource: SwipeSource.Resolved?,
+ val pointersType: PointerType?,
) : UserAction.Resolved()
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index fdf01cc..ba5f414 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -19,7 +19,6 @@
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollDispatcher
import androidx.compose.ui.input.nestedscroll.nestedScrollModifierNode
import androidx.compose.ui.input.pointer.PointerEvent
@@ -65,6 +64,52 @@
return userActions.keys.any { it is Swipe.Resolved && it.direction.orientation == orientation }
}
+/**
+ * Finds the best matching [UserActionResult] for the given [swipe] within this [Content].
+ * Prioritizes actions with matching [Swipe.Resolved.fromSource].
+ *
+ * @param swipe The swipe to match against.
+ * @return The best matching [UserActionResult], or `null` if no match is found.
+ */
+internal fun Content.findActionResultBestMatch(swipe: Swipe.Resolved): UserActionResult? {
+ var bestPoints = Int.MIN_VALUE
+ var bestMatch: UserActionResult? = null
+ userActions.forEach { (actionSwipe, actionResult) ->
+ if (
+ actionSwipe !is Swipe.Resolved ||
+ // The direction must match.
+ actionSwipe.direction != swipe.direction ||
+ // The number of pointers down must match.
+ actionSwipe.pointerCount != swipe.pointerCount ||
+ // The action requires a specific fromSource.
+ (actionSwipe.fromSource != null && actionSwipe.fromSource != swipe.fromSource) ||
+ // The action requires a specific pointerType.
+ (actionSwipe.pointersType != null && actionSwipe.pointersType != swipe.pointersType)
+ ) {
+ // This action is not eligible.
+ return@forEach
+ }
+
+ val sameFromSource = actionSwipe.fromSource == swipe.fromSource
+ val samePointerType = actionSwipe.pointersType == swipe.pointersType
+ // Prioritize actions with a perfect match.
+ if (sameFromSource && samePointerType) {
+ return actionResult
+ }
+
+ var points = 0
+ if (sameFromSource) points++
+ if (samePointerType) points++
+
+ // Otherwise, keep track of the best eligible action.
+ if (points > bestPoints) {
+ bestPoints = points
+ bestMatch = actionResult
+ }
+ }
+ return bestMatch
+}
+
private data class SwipeToSceneElement(
val draggableHandler: DraggableHandlerImpl,
val swipeDetector: SwipeDetector,
@@ -155,10 +200,10 @@
override fun onCancelPointerInput() = multiPointerDraggableNode.onCancelPointerInput()
- private fun startDragImmediately(startedPosition: Offset): Boolean {
+ private fun startDragImmediately(pointersInfo: PointersInfo): Boolean {
// Immediately start the drag if the user can't swipe in the other direction and the gesture
// handler can intercept it.
- return !canOppositeSwipe() && draggableHandler.shouldImmediatelyIntercept(startedPosition)
+ return !canOppositeSwipe() && draggableHandler.shouldImmediatelyIntercept(pointersInfo)
}
private fun canOppositeSwipe(): 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 f24d93f..5dad0d7 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
@@ -23,6 +23,7 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.UserInput
+import androidx.compose.ui.input.pointer.PointerType
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
@@ -51,6 +52,20 @@
private const val SCREEN_SIZE = 100f
private val LAYOUT_SIZE = IntSize(SCREEN_SIZE.toInt(), SCREEN_SIZE.toInt())
+private fun pointersInfo(
+ startedPosition: Offset = Offset.Zero,
+ pointersDown: Int = 1,
+ isMouseWheel: Boolean = false,
+ pointersDownByType: Map<PointerType, Int> = mapOf(PointerType.Touch to pointersDown),
+): PointersInfo {
+ return PointersInfo(
+ startedPosition = startedPosition,
+ pointersDown = pointersDown,
+ isMouseWheel = isMouseWheel,
+ pointersDownByType = pointersDownByType,
+ )
+}
+
@RunWith(AndroidJUnit4::class)
class DraggableHandlerTest {
private class TestGestureScope(val testScope: MonotonicClockTestScope) {
@@ -126,9 +141,7 @@
val draggableHandler = layoutImpl.draggableHandler(Orientation.Vertical)
val horizontalDraggableHandler = layoutImpl.draggableHandler(Orientation.Horizontal)
- var pointerInfoOwner: () -> PointersInfo = {
- PointersInfo(startedPosition = Offset.Zero, pointersDown = 1, isMouseWheel = false)
- }
+ var pointerInfoOwner: () -> PointersInfo = { pointersInfo() }
fun nestedScrollConnection(
nestedScrollBehavior: NestedScrollBehavior,
@@ -211,42 +224,32 @@
}
fun onDragStarted(
- startedPosition: Offset = Offset.Zero,
+ pointersInfo: PointersInfo = pointersInfo(),
overSlop: Float,
- pointersDown: Int = 1,
expectedConsumedOverSlop: Float = overSlop,
): DragController {
// overSlop should be 0f only if the drag gesture starts with startDragImmediately
if (overSlop == 0f) error("Consider using onDragStartedImmediately()")
return onDragStarted(
draggableHandler = draggableHandler,
- startedPosition = startedPosition,
+ pointersInfo = pointersInfo,
overSlop = overSlop,
- pointersDown = pointersDown,
expectedConsumedOverSlop = expectedConsumedOverSlop,
)
}
- fun onDragStartedImmediately(
- startedPosition: Offset = Offset.Zero,
- pointersDown: Int = 1,
- ): DragController {
- return onDragStarted(draggableHandler, startedPosition, overSlop = 0f, pointersDown)
+ fun onDragStartedImmediately(pointersInfo: PointersInfo = pointersInfo()): DragController {
+ return onDragStarted(draggableHandler, pointersInfo, overSlop = 0f)
}
fun onDragStarted(
draggableHandler: DraggableHandler,
- startedPosition: Offset = Offset.Zero,
+ pointersInfo: PointersInfo = pointersInfo(),
overSlop: Float = 0f,
- pointersDown: Int = 1,
expectedConsumedOverSlop: Float = overSlop,
): DragController {
val dragController =
- draggableHandler.onDragStarted(
- startedPosition = startedPosition,
- overSlop = overSlop,
- pointersDown = pointersDown,
- )
+ draggableHandler.onDragStarted(pointersInfo = pointersInfo, overSlop = overSlop)
// MultiPointerDraggable will always call onDelta with the initial overSlop right after
dragController.onDragDelta(pixels = overSlop, expectedConsumedOverSlop)
@@ -528,7 +531,8 @@
mapOf(Swipe.Up to UserActionResult(SceneB, isIrreversible = true), Swipe.Down to SceneC)
val dragController =
onDragStarted(
- startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE * 0.5f),
+ pointersInfo =
+ pointersInfo(startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE * 0.5f)),
overSlop = up(fractionOfScreen = 0.2f),
)
assertTransition(
@@ -554,7 +558,7 @@
// Start dragging from the bottom
onDragStarted(
- startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE),
+ pointersInfo = pointersInfo(startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE)),
overSlop = up(fractionOfScreen = 0.1f),
)
assertTransition(
@@ -1051,8 +1055,8 @@
navigateToSceneC()
// Swipe up from the middle to transition to scene B.
- val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
- onDragStarted(startedPosition = middle, overSlop = up(0.1f))
+ val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+ onDragStarted(pointersInfo = middle, overSlop = up(0.1f))
assertTransition(
currentScene = SceneC,
fromScene = SceneC,
@@ -1067,7 +1071,7 @@
// should intercept it. Because it is intercepted, the overSlop passed to onDragStarted()
// should be 0f.
assertThat(draggableHandler.shouldImmediatelyIntercept(middle)).isTrue()
- onDragStartedImmediately(startedPosition = middle)
+ onDragStartedImmediately(pointersInfo = middle)
// We should have intercepted the transition, so the transition should be the same object.
assertTransition(
@@ -1083,9 +1087,9 @@
// Start a new gesture from the bottom of the screen. Because swiping up from the bottom of
// C leads to scene A (and not B), the previous transitions is *not* intercepted and we
// instead animate from C to A.
- val bottom = Offset(SCREEN_SIZE / 2, SCREEN_SIZE)
+ val bottom = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2, SCREEN_SIZE))
assertThat(draggableHandler.shouldImmediatelyIntercept(bottom)).isFalse()
- onDragStarted(startedPosition = bottom, overSlop = up(0.1f))
+ onDragStarted(pointersInfo = bottom, overSlop = up(0.1f))
assertTransition(
currentScene = SceneC,
@@ -1102,8 +1106,8 @@
navigateToSceneC()
// Swipe up from the middle to transition to scene B.
- val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
- onDragStarted(startedPosition = middle, overSlop = up(0.1f))
+ val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+ onDragStarted(pointersInfo = middle, overSlop = up(0.1f))
assertTransition(fromScene = SceneC, toScene = SceneB, isUserInputOngoing = true)
// The current transition can be intercepted.
@@ -1119,15 +1123,15 @@
@Test
fun interruptedTransitionCanNotBeImmediatelyIntercepted() = runGestureTest {
- assertThat(draggableHandler.shouldImmediatelyIntercept(startedPosition = null)).isFalse()
+ assertThat(draggableHandler.shouldImmediatelyIntercept(pointersInfo = null)).isFalse()
onDragStarted(overSlop = up(0.1f))
- assertThat(draggableHandler.shouldImmediatelyIntercept(startedPosition = null)).isTrue()
+ assertThat(draggableHandler.shouldImmediatelyIntercept(pointersInfo = null)).isTrue()
layoutState.startTransitionImmediately(
animationScope = testScope.backgroundScope,
transition(SceneA, SceneB),
)
- assertThat(draggableHandler.shouldImmediatelyIntercept(startedPosition = null)).isFalse()
+ assertThat(draggableHandler.shouldImmediatelyIntercept(pointersInfo = null)).isFalse()
}
@Test
@@ -1159,7 +1163,7 @@
assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
// Intercept the transition and swipe down back to scene A.
- assertThat(draggableHandler.shouldImmediatelyIntercept(startedPosition = null)).isTrue()
+ assertThat(draggableHandler.shouldImmediatelyIntercept(pointersInfo = null)).isTrue()
val dragController2 = onDragStartedImmediately()
// Block the transition when the user release their finger.
@@ -1203,9 +1207,7 @@
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways)
// Drag from the **top** of the screen
- pointerInfoOwner = {
- PointersInfo(startedPosition = Offset(0f, 0f), pointersDown = 1, isMouseWheel = false)
- }
+ pointerInfoOwner = { pointersInfo() }
assertIdle(currentScene = SceneC)
nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f))
@@ -1222,13 +1224,7 @@
advanceUntilIdle()
// Drag from the **bottom** of the screen
- pointerInfoOwner = {
- PointersInfo(
- startedPosition = Offset(0f, SCREEN_SIZE),
- pointersDown = 1,
- isMouseWheel = false,
- )
- }
+ pointerInfoOwner = { pointersInfo(startedPosition = Offset(0f, SCREEN_SIZE)) }
assertIdle(currentScene = SceneC)
nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f))
@@ -1248,9 +1244,7 @@
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways)
// Use mouse wheel
- pointerInfoOwner = {
- PointersInfo(startedPosition = Offset(0f, 0f), pointersDown = 1, isMouseWheel = true)
- }
+ pointerInfoOwner = { pointersInfo(isMouseWheel = true) }
assertIdle(currentScene = SceneC)
nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f))
@@ -1260,8 +1254,8 @@
@Test
fun transitionIsImmediatelyUpdatedWhenReleasingFinger() = runGestureTest {
// Swipe up from the middle to transition to scene B.
- val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
- val dragController = onDragStarted(startedPosition = middle, overSlop = up(0.1f))
+ val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+ val dragController = onDragStarted(pointersInfo = middle, overSlop = up(0.1f))
assertTransition(fromScene = SceneA, toScene = SceneB, isUserInputOngoing = true)
dragController.onDragStoppedAnimateLater(velocity = 0f)
@@ -1274,10 +1268,10 @@
layoutState.transitions = transitions { overscrollDisabled(SceneB, Orientation.Vertical) }
// Swipe up to scene B at progress = 200%.
- val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
+ val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
val dragController =
onDragStarted(
- startedPosition = middle,
+ pointersInfo = middle,
overSlop = up(2f),
// Overscroll is disabled, it will scroll up to 100%
expectedConsumedOverSlop = up(1f),
@@ -1305,8 +1299,8 @@
layoutState.transitions = transitions { overscrollDisabled(SceneB, Orientation.Vertical) }
// Swipe up to scene B at progress = 200%.
- val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
- val dragController = onDragStarted(startedPosition = middle, overSlop = up(0.99f))
+ val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+ val dragController = onDragStarted(pointersInfo = middle, overSlop = up(0.99f))
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.99f)
// Release the finger.
@@ -1351,9 +1345,9 @@
overscroll(SceneB, Orientation.Vertical) { fade(TestElements.Foo) }
}
- val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
+ val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
- val dragController = onDragStarted(startedPosition = middle, overSlop = up(0.5f))
+ val dragController = onDragStarted(pointersInfo = middle, overSlop = up(0.5f))
val transition = assertThat(transitionState).isSceneTransition()
assertThat(transition).hasFromScene(SceneA)
assertThat(transition).hasToScene(SceneB)
@@ -1383,9 +1377,9 @@
overscroll(SceneC, Orientation.Vertical) { fade(TestElements.Foo) }
}
- val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
+ val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
- val dragController = onDragStarted(startedPosition = middle, overSlop = down(0.5f))
+ val dragController = onDragStarted(pointersInfo = middle, overSlop = down(0.5f))
val transition = assertThat(transitionState).isSceneTransition()
assertThat(transition).hasFromScene(SceneA)
assertThat(transition).hasToScene(SceneC)
@@ -1414,9 +1408,9 @@
overscroll(SceneB, Orientation.Vertical) { fade(TestElements.Foo) }
}
- val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
+ val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
- val dragController = onDragStarted(startedPosition = middle, overSlop = up(1.5f))
+ val dragController = onDragStarted(pointersInfo = middle, overSlop = up(1.5f))
val transition = assertThat(transitionState).isSceneTransition()
assertThat(transition).hasFromScene(SceneA)
assertThat(transition).hasToScene(SceneB)
@@ -1446,9 +1440,9 @@
overscroll(SceneC, Orientation.Vertical) { fade(TestElements.Foo) }
}
- val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
+ val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
- val dragController = onDragStarted(startedPosition = middle, overSlop = down(1.5f))
+ val dragController = onDragStarted(pointersInfo = middle, overSlop = down(1.5f))
val transition = assertThat(transitionState).isSceneTransition()
assertThat(transition).hasFromScene(SceneA)
assertThat(transition).hasToScene(SceneC)
@@ -1480,8 +1474,8 @@
mutableUserActionsA = mapOf(Swipe.Up to UserActionResult(SceneB))
- val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
- val dragController = onDragStarted(startedPosition = middle, overSlop = down(1f))
+ val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+ val dragController = onDragStarted(pointersInfo = middle, overSlop = down(1f))
val transition = assertThat(transitionState).isSceneTransition()
assertThat(transition).hasFromScene(SceneA)
assertThat(transition).hasToScene(SceneB)
@@ -1513,8 +1507,8 @@
mutableUserActionsA = mapOf(Swipe.Down to UserActionResult(SceneC))
- val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
- val dragController = onDragStarted(startedPosition = middle, overSlop = up(1f))
+ val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+ val dragController = onDragStarted(pointersInfo = middle, overSlop = up(1f))
val transition = assertThat(transitionState).isSceneTransition()
assertThat(transition).hasFromScene(SceneA)
assertThat(transition).hasToScene(SceneC)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
index 3df6087..5ec74f8 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
@@ -98,7 +98,7 @@
Modifier.multiPointerDraggable(
orientation = Orientation.Vertical,
startDragImmediately = { false },
- onDragStarted = { _, _, _ ->
+ onDragStarted = { _, _ ->
started = true
SimpleDragController(
onDrag = { dragged = true },
@@ -167,7 +167,7 @@
orientation = Orientation.Vertical,
// We want to start a drag gesture immediately
startDragImmediately = { true },
- onDragStarted = { _, _, _ ->
+ onDragStarted = { _, _ ->
started = true
SimpleDragController(
onDrag = { dragged = true },
@@ -239,7 +239,7 @@
.multiPointerDraggable(
orientation = Orientation.Vertical,
startDragImmediately = { false },
- onDragStarted = { _, _, _ ->
+ onDragStarted = { _, _ ->
started = true
SimpleDragController(
onDrag = { dragged = true },
@@ -358,7 +358,7 @@
.multiPointerDraggable(
orientation = Orientation.Vertical,
startDragImmediately = { false },
- onDragStarted = { _, _, _ ->
+ onDragStarted = { _, _ ->
started = true
SimpleDragController(
onDrag = { dragged = true },
@@ -463,7 +463,7 @@
.multiPointerDraggable(
orientation = Orientation.Vertical,
startDragImmediately = { false },
- onDragStarted = { _, _, _ ->
+ onDragStarted = { _, _ ->
verticalStarted = true
SimpleDragController(
onDrag = { verticalDragged = true },
@@ -475,7 +475,7 @@
.multiPointerDraggable(
orientation = Orientation.Horizontal,
startDragImmediately = { false },
- onDragStarted = { _, _, _ ->
+ onDragStarted = { _, _ ->
horizontalStarted = true
SimpleDragController(
onDrag = { horizontalDragged = true },
@@ -574,7 +574,7 @@
return swipeConsume
}
},
- onDragStarted = { _, _, _ ->
+ onDragStarted = { _, _ ->
started = true
SimpleDragController(
onDrag = { /* do nothing */ },
@@ -668,7 +668,7 @@
.multiPointerDraggable(
orientation = Orientation.Vertical,
startDragImmediately = { false },
- onDragStarted = { _, _, _ ->
+ onDragStarted = { _, _ ->
SimpleDragController(
onDrag = { consumedOnDrag = it },
onStop = { consumedOnDragStop = it },
@@ -739,7 +739,7 @@
.multiPointerDraggable(
orientation = Orientation.Vertical,
startDragImmediately = { false },
- onDragStarted = { _, _, _ ->
+ onDragStarted = { _, _ ->
SimpleDragController(
onDrag = { /* do nothing */ },
onStop = {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index 2bc9b38..aaeaba9 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -38,6 +38,7 @@
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.input.pointer.PointerType
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.platform.testTag
@@ -61,6 +62,7 @@
import com.android.compose.animation.scene.TestScenes.SceneA
import com.android.compose.animation.scene.TestScenes.SceneB
import com.android.compose.animation.scene.TestScenes.SceneC
+import com.android.compose.animation.scene.TestScenes.SceneD
import com.android.compose.animation.scene.subjects.assertThat
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
@@ -127,6 +129,7 @@
mapOf(
Swipe.Down to SceneA,
Swipe(SwipeDirection.Down, pointerCount = 2) to SceneB,
+ Swipe(SwipeDirection.Down, pointersType = PointerType.Mouse) to SceneD,
Swipe(SwipeDirection.Right, fromSource = Edge.Left) to SceneB,
Swipe(SwipeDirection.Down, fromSource = Edge.Top) to SceneB,
)
@@ -134,6 +137,12 @@
) {
Box(Modifier.fillMaxSize())
}
+ scene(
+ key = SceneD,
+ userActions = if (swipesEnabled()) mapOf(Swipe.Up to SceneC) else emptyMap(),
+ ) {
+ Box(Modifier.fillMaxSize())
+ }
}
}
@@ -502,6 +511,45 @@
}
@Test
+ fun mousePointerSwipe() {
+ // Start at scene C.
+ val layoutState = layoutState(SceneC)
+
+ // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
+ // detected as a drag event.
+ var touchSlop = 0f
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ TestContent(layoutState)
+ }
+
+ assertThat(layoutState.transitionState).isIdle()
+ assertThat(layoutState.transitionState).hasCurrentScene(SceneC)
+
+ rule.onRoot().performMouseInput {
+ enter(middle)
+ press()
+ moveBy(Offset(0f, touchSlop + 10.dp.toPx()), 1_000)
+ }
+
+ // We are transitioning to D because we are moving the mouse while the primary button is
+ // pressed.
+ val transition = assertThat(layoutState.transitionState).isSceneTransition()
+ assertThat(transition).hasFromScene(SceneC)
+ assertThat(transition).hasToScene(SceneD)
+
+ rule.onRoot().performMouseInput {
+ release()
+ exit(middle)
+ }
+ // Release the mouse primary button and wait for the animation to end. We are back to C
+ // because we only swiped 10dp.
+ rule.waitForIdle()
+ assertThat(layoutState.transitionState).isIdle()
+ assertThat(layoutState.transitionState).hasCurrentScene(SceneC)
+ }
+
+ @Test
fun mouseWheel_pointerInputApi_ignoredByStl() {
val layoutState = layoutState()
var touchSlop = 0f
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt
index f39dd67..95ef2ce 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt
@@ -65,4 +65,6 @@
}
from(TestScenes.SceneC, to = TestScenes.SceneA) { spec = snap() }
+
+ from(TestScenes.SceneC, to = TestScenes.SceneD) { spec = snap() }
}
diff --git a/packages/SystemUI/customization/res/drawable/clock_default_thumbnail.xml b/packages/SystemUI/customization/res/drawable/clock_default_thumbnail.xml
index be72d0b..3951e4c 100644
--- a/packages/SystemUI/customization/res/drawable/clock_default_thumbnail.xml
+++ b/packages/SystemUI/customization/res/drawable/clock_default_thumbnail.xml
@@ -14,7 +14,22 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <solid android:color="#FFFF00FF" />
-</shape>
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="36dp"
+ android:height="50dp"
+ android:viewportWidth="36"
+ android:viewportHeight="50">
+
+ <path
+ android:pathData="M8.592,22.712C11.052,22.712 13.063,21.635 14.628,19.48C16.193,17.326 16.976,14.623 16.976,11.372C16.976,8.059 16.198,5.346 14.644,3.232C13.099,1.109 11.082,0.047 8.592,0.047C6.113,0.047 4.096,1.109 2.541,3.232C0.997,5.346 0.225,8.059 0.225,11.372C0.225,14.664 0.997,17.377 2.541,19.511C4.096,21.645 6.113,22.712 8.592,22.712ZM8.592,18.901C7.322,18.901 6.301,18.225 5.529,16.874C4.757,15.523 4.37,13.699 4.37,11.402C4.37,9.045 4.757,7.206 5.529,5.885C6.301,4.553 7.322,3.888 8.592,3.888C9.873,3.888 10.899,4.543 11.671,5.854C12.444,7.155 12.83,9.004 12.83,11.402C12.83,13.78 12.449,15.624 11.686,16.935C10.924,18.246 9.893,18.901 8.592,18.901Z"
+ android:fillColor="#FFFFFF"/>
+ <path
+ android:pathData="M8.141,39.462C9.289,39.462 10.168,39.742 10.778,40.301C11.387,40.849 11.692,41.627 11.692,42.633C11.692,43.7 11.377,44.538 10.747,45.148C10.117,45.747 9.254,46.047 8.156,46.047C7.14,46.047 6.353,45.778 5.794,45.239C5.245,44.701 4.859,44.132 4.635,43.532C4.483,43.115 4.249,42.831 3.934,42.679C3.629,42.516 3.182,42.521 2.593,42.694C2.034,42.856 1.638,43.136 1.404,43.532C1.18,43.918 1.155,44.375 1.328,44.904C1.694,46.113 2.486,47.2 3.706,48.166C4.925,49.121 6.49,49.598 8.4,49.598C10.585,49.598 12.343,48.948 13.674,47.647C15.005,46.336 15.67,44.695 15.67,42.724C15.67,41.383 15.279,40.209 14.497,39.203C13.714,38.197 12.703,37.583 11.464,37.359V37.298C12.51,36.973 13.323,36.373 13.902,35.5C14.482,34.616 14.771,33.574 14.771,32.375C14.771,30.597 14.192,29.225 13.034,28.26C11.885,27.284 10.336,26.796 8.385,26.796C6.789,26.796 5.443,27.172 4.346,27.924C3.258,28.666 2.496,29.57 2.059,30.637C1.826,31.135 1.795,31.587 1.968,31.994C2.151,32.4 2.501,32.715 3.02,32.939C3.579,33.183 4.015,33.244 4.33,33.122C4.656,32.99 4.92,32.741 5.123,32.375C5.469,31.704 5.87,31.196 6.327,30.851C6.784,30.495 7.409,30.317 8.202,30.317H8.217C9.152,30.317 9.879,30.581 10.397,31.11C10.925,31.638 11.189,32.38 11.189,33.335C11.189,34.209 10.89,34.915 10.29,35.454C9.691,35.992 8.923,36.262 7.989,36.262H6.906C6.378,36.262 5.971,36.389 5.687,36.643C5.402,36.887 5.26,37.278 5.26,37.816C5.26,38.334 5.402,38.741 5.687,39.036C5.971,39.32 6.378,39.462 6.906,39.462H8.141Z"
+ android:fillColor="#FFFFFF"/>
+ <path
+ android:pathData="M23.263,7.531C23.263,6.372 23.644,5.432 24.406,4.711C25.178,3.979 26.153,3.613 27.332,3.613C28.531,3.613 29.507,3.984 30.259,4.726C31.021,5.458 31.402,6.393 31.402,7.531C31.402,8.679 30.99,9.639 30.167,10.411C29.354,11.184 28.404,11.57 27.317,11.57C26.169,11.57 25.203,11.194 24.421,10.442C23.649,9.68 23.263,8.709 23.263,7.531ZM26.936,22.026C27.271,21.558 27.759,20.867 28.399,19.953C29.049,19.038 30.04,17.595 31.371,15.624C32.144,14.476 32.956,13.236 33.81,11.905C34.664,10.574 35.09,9.116 35.09,7.531C35.09,5.468 34.333,3.705 32.819,2.242C31.315,0.778 29.466,0.047 27.271,0.047C25.198,0.047 23.4,0.778 21.875,2.242C20.361,3.705 19.604,5.498 19.604,7.622C19.604,9.756 20.331,11.524 21.784,12.926C23.237,14.318 24.959,15.014 26.951,15.014C27.754,15.014 28.303,14.959 28.597,14.847C28.892,14.725 29.151,14.603 29.375,14.481L28.536,13.292L28.277,13.643C27.932,14.12 27.545,14.659 27.119,15.258C26.702,15.858 26.285,16.468 25.869,17.087C25.351,17.829 24.929,18.429 24.604,18.886C24.289,19.343 24.06,19.678 23.918,19.892C23.573,20.4 23.456,20.872 23.567,21.309C23.689,21.736 24.004,22.107 24.512,22.422C25.01,22.767 25.457,22.905 25.854,22.834C26.26,22.773 26.621,22.503 26.936,22.026Z"
+ android:fillColor="#FFFFFF"/>
+ <path
+ android:pathData="M27.406,49.537C29.865,49.537 31.877,48.46 33.442,46.306C35.007,44.152 35.789,41.449 35.789,38.197C35.789,34.885 35.012,32.172 33.457,30.058C31.912,27.934 29.895,26.873 27.406,26.873C24.927,26.873 22.91,27.934 21.355,30.058C19.81,32.172 19.038,34.885 19.038,38.197C19.038,41.49 19.81,44.203 21.355,46.336C22.91,48.47 24.927,49.537 27.406,49.537ZM27.406,45.727C26.136,45.727 25.115,45.051 24.342,43.7C23.57,42.348 23.184,40.524 23.184,38.228C23.184,35.87 23.57,34.031 24.342,32.71C25.115,31.379 26.136,30.713 27.406,30.713C28.686,30.713 29.712,31.369 30.485,32.68C31.257,33.98 31.643,35.83 31.643,38.228C31.643,40.605 31.262,42.45 30.5,43.761C29.738,45.071 28.707,45.727 27.406,45.727Z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 9b3c9bf..e9b58b0 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -94,7 +94,6 @@
DEFAULT_CLOCK_ID,
resources.getString(R.string.clock_default_name),
resources.getString(R.string.clock_default_description),
- // TODO(b/352049256): Update placeholder to actual resource
resources.getDrawable(R.drawable.clock_default_thumbnail, null),
isReactiveToTone = true,
axes = fontAxes,
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
index 2a87452..ae18aac 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
@@ -16,102 +16,19 @@
package com.android.systemui.shared.settings.data.repository
-import android.content.ContentResolver
-import android.database.ContentObserver
import android.provider.Settings
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.withContext
-/**
- * Defines interface for classes that can provide access to data from [Settings.Secure].
- * This repository doesn't guarantee to provide value across different users. For that
- * see: [UserAwareSecureSettingsRepository]
- */
+/** Defines interface for classes that can provide access to data from [Settings.Secure]. */
interface SecureSettingsRepository {
/** Returns a [Flow] tracking the value of a setting as an [Int]. */
- fun intSetting(
- name: String,
- defaultValue: Int = 0,
- ): Flow<Int>
+ fun intSetting(name: String, defaultValue: Int = 0): Flow<Int>
/** Updates the value of the setting with the given name. */
- suspend fun setInt(
- name: String,
- value: Int,
- )
+ suspend fun setInt(name: String, value: Int)
- suspend fun getInt(
- name: String,
- defaultValue: Int = 0,
- ): Int
+ suspend fun getInt(name: String, defaultValue: Int = 0): Int
suspend fun getString(name: String): String?
}
-
-class SecureSettingsRepositoryImpl(
- private val contentResolver: ContentResolver,
- private val backgroundDispatcher: CoroutineDispatcher,
-) : SecureSettingsRepository {
-
- override fun intSetting(
- name: String,
- defaultValue: Int,
- ): Flow<Int> {
- return callbackFlow {
- val observer =
- object : ContentObserver(null) {
- override fun onChange(selfChange: Boolean) {
- trySend(Unit)
- }
- }
-
- contentResolver.registerContentObserver(
- Settings.Secure.getUriFor(name),
- /* notifyForDescendants= */ false,
- observer,
- )
- send(Unit)
-
- awaitClose { contentResolver.unregisterContentObserver(observer) }
- }
- .map { Settings.Secure.getInt(contentResolver, name, defaultValue) }
- // The above work is done on the background thread (which is important for accessing
- // settings through the content resolver).
- .flowOn(backgroundDispatcher)
- }
-
- override suspend fun setInt(name: String, value: Int) {
- withContext(backgroundDispatcher) {
- Settings.Secure.putInt(
- contentResolver,
- name,
- value,
- )
- }
- }
-
- override suspend fun getInt(name: String, defaultValue: Int): Int {
- return withContext(backgroundDispatcher) {
- Settings.Secure.getInt(
- contentResolver,
- name,
- defaultValue,
- )
- }
- }
-
- override suspend fun getString(name: String): String? {
- return withContext(backgroundDispatcher) {
- Settings.Secure.getString(
- contentResolver,
- name,
- )
- }
- }
-}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryImpl.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryImpl.kt
new file mode 100644
index 0000000..8b9fcb4
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryImpl.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 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.shared.settings.data.repository
+
+import android.content.ContentResolver
+import android.database.ContentObserver
+import android.provider.Settings
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.withContext
+
+/**
+ * Simple implementation of [SecureSettingsRepository].
+ *
+ * This repository doesn't guarantee to provide value across different users, and therefore
+ * shouldn't be used in SystemUI. For that see: [UserAwareSecureSettingsRepository]
+ */
+// TODO: b/377244768 - Move to Theme/WallpaperPicker, away from SystemUI.
+class SecureSettingsRepositoryImpl(
+ private val contentResolver: ContentResolver,
+ private val backgroundDispatcher: CoroutineDispatcher,
+) : SecureSettingsRepository {
+
+ override fun intSetting(name: String, defaultValue: Int): Flow<Int> {
+ return callbackFlow {
+ val observer =
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ trySend(Unit)
+ }
+ }
+
+ contentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(name),
+ /* notifyForDescendants= */ false,
+ observer,
+ )
+ send(Unit)
+
+ awaitClose { contentResolver.unregisterContentObserver(observer) }
+ }
+ .map { Settings.Secure.getInt(contentResolver, name, defaultValue) }
+ // The above work is done on the background thread (which is important for accessing
+ // settings through the content resolver).
+ .flowOn(backgroundDispatcher)
+ }
+
+ override suspend fun setInt(name: String, value: Int) {
+ withContext(backgroundDispatcher) { Settings.Secure.putInt(contentResolver, name, value) }
+ }
+
+ override suspend fun getInt(name: String, defaultValue: Int): Int {
+ return withContext(backgroundDispatcher) {
+ Settings.Secure.getInt(contentResolver, name, defaultValue)
+ }
+ }
+
+ override suspend fun getString(name: String): String? {
+ return withContext(backgroundDispatcher) {
+ Settings.Secure.getString(contentResolver, name)
+ }
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepository.kt
index afe82fb..8cda9b3 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepository.kt
@@ -16,102 +16,19 @@
package com.android.systemui.shared.settings.data.repository
-import android.content.ContentResolver
-import android.database.ContentObserver
import android.provider.Settings
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.withContext
-/**
- * Defines interface for classes that can provide access to data from [Settings.System]. This
- * repository doesn't guarantee to provide value across different users. For that see:
- * [UserAwareSecureSettingsRepository] which does that for secure settings.
- */
+/** Interface for classes that can provide access to data from [Settings.System]. */
interface SystemSettingsRepository {
/** Returns a [Flow] tracking the value of a setting as an [Int]. */
- fun intSetting(
- name: String,
- defaultValue: Int = 0,
- ): Flow<Int>
+ fun intSetting(name: String, defaultValue: Int = 0): Flow<Int>
/** Updates the value of the setting with the given name. */
- suspend fun setInt(
- name: String,
- value: Int,
- )
+ suspend fun setInt(name: String, value: Int)
- suspend fun getInt(
- name: String,
- defaultValue: Int = 0,
- ): Int
+ suspend fun getInt(name: String, defaultValue: Int = 0): Int
suspend fun getString(name: String): String?
}
-
-class SystemSettingsRepositoryImpl(
- private val contentResolver: ContentResolver,
- private val backgroundDispatcher: CoroutineDispatcher,
-) : SystemSettingsRepository {
-
- override fun intSetting(
- name: String,
- defaultValue: Int,
- ): Flow<Int> {
- return callbackFlow {
- val observer =
- object : ContentObserver(null) {
- override fun onChange(selfChange: Boolean) {
- trySend(Unit)
- }
- }
-
- contentResolver.registerContentObserver(
- Settings.System.getUriFor(name),
- /* notifyForDescendants= */ false,
- observer,
- )
- send(Unit)
-
- awaitClose { contentResolver.unregisterContentObserver(observer) }
- }
- .map { Settings.System.getInt(contentResolver, name, defaultValue) }
- // The above work is done on the background thread (which is important for accessing
- // settings through the content resolver).
- .flowOn(backgroundDispatcher)
- }
-
- override suspend fun setInt(name: String, value: Int) {
- withContext(backgroundDispatcher) {
- Settings.System.putInt(
- contentResolver,
- name,
- value,
- )
- }
- }
-
- override suspend fun getInt(name: String, defaultValue: Int): Int {
- return withContext(backgroundDispatcher) {
- Settings.System.getInt(
- contentResolver,
- name,
- defaultValue,
- )
- }
- }
-
- override suspend fun getString(name: String): String? {
- return withContext(backgroundDispatcher) {
- Settings.System.getString(
- contentResolver,
- name,
- )
- }
- }
-}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepositoryImpl.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepositoryImpl.kt
new file mode 100644
index 0000000..b039a32
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepositoryImpl.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.shared.settings.data.repository
+
+import android.content.ContentResolver
+import android.database.ContentObserver
+import android.provider.Settings
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.withContext
+
+/**
+ * Defines interface for classes that can provide access to data from [Settings.System].
+ *
+ * This repository doesn't guarantee to provide value across different users. For that see:
+ * [UserAwareSystemSettingsRepository].
+ */
+// TODO: b/377244768 - Move to Theme/WallpaperPicker, away from SystemUI.
+class SystemSettingsRepositoryImpl(
+ private val contentResolver: ContentResolver,
+ private val backgroundDispatcher: CoroutineDispatcher,
+) : SystemSettingsRepository {
+
+ override fun intSetting(name: String, defaultValue: Int): Flow<Int> {
+ return callbackFlow {
+ val observer =
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ trySend(Unit)
+ }
+ }
+
+ contentResolver.registerContentObserver(
+ Settings.System.getUriFor(name),
+ /* notifyForDescendants= */ false,
+ observer,
+ )
+ send(Unit)
+
+ awaitClose { contentResolver.unregisterContentObserver(observer) }
+ }
+ .map { Settings.System.getInt(contentResolver, name, defaultValue) }
+ // The above work is done on the background thread (which is important for accessing
+ // settings through the content resolver).
+ .flowOn(backgroundDispatcher)
+ }
+
+ override suspend fun setInt(name: String, value: Int) {
+ withContext(backgroundDispatcher) { Settings.System.putInt(contentResolver, name, value) }
+ }
+
+ override suspend fun getInt(name: String, defaultValue: Int): Int {
+ return withContext(backgroundDispatcher) {
+ Settings.System.getInt(contentResolver, name, defaultValue)
+ }
+ }
+
+ override suspend fun getString(name: String): String? {
+ return withContext(backgroundDispatcher) {
+ Settings.System.getString(contentResolver, name)
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/OnTeardownRuleTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/OnTeardownRuleTest.kt
new file mode 100644
index 0000000..8635bb0
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/OnTeardownRuleTest.kt
@@ -0,0 +1,124 @@
+/*
+ * 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
+
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.JUnitCore
+
+@Suppress("JUnitMalformedDeclaration")
+@SmallTest
+class OnTeardownRuleTest : SysuiTestCase() {
+ // None of these inner classes should be run except as part of this utilities-testing test
+ class HasTeardown {
+ @get:Rule val teardownRule = OnTeardownRule()
+
+ @Before
+ fun setUp() {
+ teardownWasRun = false
+ teardownRule.onTeardown { teardownWasRun = true }
+ }
+
+ @Test fun doTest() {}
+
+ companion object {
+ var teardownWasRun = false
+ }
+ }
+
+ @Test
+ fun teardownRuns() {
+ val result = JUnitCore().run(HasTeardown::class.java)
+ assertThat(result.failures).isEmpty()
+ assertThat(HasTeardown.teardownWasRun).isTrue()
+ }
+
+ class FirstTeardownFails {
+ @get:Rule val teardownRule = OnTeardownRule()
+
+ @Before
+ fun setUp() {
+ teardownWasRun = false
+ teardownRule.onTeardown { fail("One fails") }
+ teardownRule.onTeardown { teardownWasRun = true }
+ }
+
+ @Test fun doTest() {}
+
+ companion object {
+ var teardownWasRun = false
+ }
+ }
+
+ @Test
+ fun allTeardownsRun() {
+ val result = JUnitCore().run(FirstTeardownFails::class.java)
+ assertThat(result.failures.map { it.message }).isEqualTo(listOf("One fails"))
+ assertThat(FirstTeardownFails.teardownWasRun).isTrue()
+ }
+
+ class ThreeTeardowns {
+ @get:Rule val teardownRule = OnTeardownRule()
+
+ @Before
+ fun setUp() {
+ messages.clear()
+ }
+
+ @Test
+ fun doTest() {
+ teardownRule.onTeardown { messages.add("A") }
+ teardownRule.onTeardown { messages.add("B") }
+ teardownRule.onTeardown { messages.add("C") }
+ }
+
+ companion object {
+ val messages = mutableListOf<String>()
+ }
+ }
+
+ @Test
+ fun reverseOrder() {
+ val result = JUnitCore().run(ThreeTeardowns::class.java)
+ assertThat(result.failures).isEmpty()
+ assertThat(ThreeTeardowns.messages).isEqualTo(listOf("C", "B", "A"))
+ }
+
+ class TryToDoABadThing {
+ @get:Rule val teardownRule = OnTeardownRule()
+
+ @Test
+ fun doTest() {
+ teardownRule.onTeardown {
+ teardownRule.onTeardown {
+ // do nothing
+ }
+ }
+ }
+ }
+
+ @Test
+ fun prohibitTeardownDuringTeardown() {
+ val result = JUnitCore().run(TryToDoABadThing::class.java)
+ assertThat(result.failures.map { it.message })
+ .isEqualTo(listOf("Cannot add new teardown routines after test complete."))
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
index 176c3ac..2594472 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
@@ -22,12 +22,13 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -40,8 +41,6 @@
@RunWith(AndroidJUnit4::class)
class AccessibilityQsShortcutsRepositoryImplTest : SysuiTestCase() {
private val kosmos = testKosmos()
- private val testDispatcher = kosmos.testDispatcher
- private val testScope = kosmos.testScope
private val secureSettings = kosmos.fakeSettings
@Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule()
@@ -55,8 +54,8 @@
return UserA11yQsShortcutsRepository(
userId,
secureSettings,
- testScope.backgroundScope,
- testDispatcher,
+ kosmos.testScope.backgroundScope,
+ kosmos.testDispatcher,
)
}
}
@@ -69,13 +68,13 @@
AccessibilityQsShortcutsRepositoryImpl(
a11yManager,
userA11yQsShortcutsRepositoryFactory,
- testDispatcher
+ kosmos.testDispatcher,
)
}
@Test
fun a11yQsShortcutTargetsForCorrectUsers() =
- testScope.runTest {
+ kosmos.runTest {
val user0 = 0
val targetsForUser0 = setOf("a", "b", "c")
val user1 = 1
@@ -94,7 +93,7 @@
secureSettings.putStringForUser(
SETTING_NAME,
a11yQsTargets.joinToString(separator = ":"),
- forUser
+ forUser,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
index 8c7cd61..cdda9cc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
@@ -47,8 +47,7 @@
import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
-import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.util.concurrency.FakeExecutor
@@ -61,7 +60,6 @@
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.runCurrent
import org.junit.Before
import org.junit.Rule
@@ -95,9 +93,6 @@
@Mock private lateinit var onBackInvokedDispatcher: WindowOnBackInvokedDispatcher
@Mock private lateinit var iStatusBarService: IStatusBarService
@Mock private lateinit var headsUpManager: HeadsUpManager
- private val activeNotificationsRepository = ActiveNotificationListRepository()
- private val activeNotificationsInteractor =
- ActiveNotificationsInteractor(activeNotificationsRepository, StandardTestDispatcher())
private val keyguardRepository = FakeKeyguardRepository()
private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy {
@@ -107,7 +102,7 @@
keyguardRepository,
headsUpManager,
powerInteractor,
- activeNotificationsInteractor,
+ kosmos.activeNotificationsInteractor,
kosmos::sceneInteractor,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
index 72e0726..5994afa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
@@ -55,7 +55,7 @@
testableResources.overrideConfiguration(configuration)
configurationRepository = FakeConfigurationRepository()
testScope = TestScope()
- underTest = ConfigurationInteractor(configurationRepository)
+ underTest = ConfigurationInteractorImpl(configurationRepository)
}
@Test
@@ -207,7 +207,7 @@
updateDisplay(
width = DISPLAY_HEIGHT,
height = DISPLAY_WIDTH,
- rotation = Surface.ROTATION_90
+ rotation = Surface.ROTATION_90,
)
runCurrent()
@@ -217,7 +217,7 @@
private fun updateDisplay(
width: Int = DISPLAY_WIDTH,
height: Int = DISPLAY_HEIGHT,
- @Surface.Rotation rotation: Int = Surface.ROTATION_0
+ @Surface.Rotation rotation: Int = Surface.ROTATION_0,
) {
configuration.windowConfiguration.maxBounds.set(Rect(0, 0, width, height))
configuration.windowConfiguration.displayRotation = rotation
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceTest.kt
index 44ce085..c3c958c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceTest.kt
@@ -20,6 +20,8 @@
import android.appwidget.AppWidgetProviderInfo
import android.content.ComponentName
import android.content.Intent
+import android.content.IntentSender
+import android.os.Binder
import android.os.UserHandle
import android.testing.TestableLooper
import android.widget.RemoteViews
@@ -29,6 +31,7 @@
import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.shared.model.fakeGlanceableHubMultiUserHelper
+import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IConfigureWidgetCallback
import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IGlanceableHubWidgetsListener
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
@@ -43,11 +46,13 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -164,7 +169,7 @@
}
@Test
- fun addWidget_getWidgetUpdate() =
+ fun addWidget_noConfigurationCallback_getWidgetUpdate() =
testScope.runTest {
setupWidgets()
@@ -180,7 +185,7 @@
assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
// Add a widget
- service.addWidget(ComponentName("pkg_4", "cls_4"), UserHandle.of(0), 3)
+ service.addWidget(ComponentName("pkg_4", "cls_4"), UserHandle.of(0), 3, null)
runCurrent()
// Verify an update pushed with widget 4 added
@@ -192,6 +197,71 @@
}
@Test
+ fun addWidget_withConfigurationCallback_configurationFails_doNotAddWidget() =
+ testScope.runTest {
+ setupWidgets()
+
+ // Bind service
+ val binder = underTest.onBind(Intent())
+ val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder)
+
+ // Verify the update is as expected
+ val widgets by collectLastValue(service.listenForWidgetUpdates())
+ assertThat(widgets).hasSize(3)
+ assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue()
+ assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+ assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+
+ // Add a widget with a configuration callback that fails
+ service.addWidget(
+ ComponentName("pkg_4", "cls_4"),
+ UserHandle.of(0),
+ 3,
+ createConfigureWidgetCallback(success = false),
+ )
+ runCurrent()
+
+ // Verify that widget 4 is not added
+ assertThat(widgets).hasSize(3)
+ assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue()
+ assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+ assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+ }
+
+ @Test
+ fun addWidget_withConfigurationCallback_configurationSucceeds_addWidget() =
+ testScope.runTest {
+ setupWidgets()
+
+ // Bind service
+ val binder = underTest.onBind(Intent())
+ val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder)
+
+ // Verify the update is as expected
+ val widgets by collectLastValue(service.listenForWidgetUpdates())
+ assertThat(widgets).hasSize(3)
+ assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue()
+ assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+ assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+
+ // Add a widget with a configuration callback that fails
+ service.addWidget(
+ ComponentName("pkg_4", "cls_4"),
+ UserHandle.of(0),
+ 3,
+ createConfigureWidgetCallback(success = true),
+ )
+ runCurrent()
+
+ // Verify that widget 4 is added
+ assertThat(widgets).hasSize(4)
+ assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue()
+ assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+ assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+ assertThat(widgets?.get(3)?.has(4, "pkg_4/cls_4", 3, 3)).isTrue()
+ }
+
+ @Test
fun deleteWidget_getWidgetUpdate() =
testScope.runTest {
setupWidgets()
@@ -271,6 +341,21 @@
assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
}
+ @Test
+ fun getIntentSenderForConfigureActivity() =
+ testScope.runTest {
+ val expected = IntentSender(Binder())
+ whenever(appWidgetHost.getIntentSenderForConfigureActivity(anyInt(), anyInt()))
+ .thenReturn(expected)
+
+ // Bind service
+ val binder = underTest.onBind(Intent())
+ val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder)
+
+ val actual = service.getIntentSenderForConfigureActivity(1)
+ assertThat(actual).isEqualTo(expected)
+ }
+
private fun setupWidgets() {
widgetRepository.addWidget(
appWidgetId = 1,
@@ -293,7 +378,7 @@
}
private fun IGlanceableHubWidgetManagerService.listenForWidgetUpdates() =
- conflatedCallbackFlow<List<CommunalWidgetContentModel>> {
+ conflatedCallbackFlow {
val listener =
object : IGlanceableHubWidgetsListener.Stub() {
override fun onWidgetsUpdated(widgets: List<CommunalWidgetContentModel>) {
@@ -316,4 +401,15 @@
this.rank == rank &&
this.spanY == spanY
}
+
+ private fun createConfigureWidgetCallback(success: Boolean): IConfigureWidgetCallback {
+ return object : IConfigureWidgetCallback.Stub() {
+ override fun onConfigureWidget(
+ appWidgetId: Int,
+ resultReceiver: IConfigureWidgetCallback.IResultReceiver?,
+ ) {
+ resultReceiver?.onResult(success)
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetConfigurationControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetConfigurationControllerTest.kt
index 5d4eaf0..e1bdf1c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetConfigurationControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetConfigurationControllerTest.kt
@@ -18,17 +18,18 @@
import android.app.Activity
import android.content.ActivityNotFoundException
+import android.content.IntentSender
+import android.os.Binder
+import android.os.OutcomeReceiver
import androidx.activity.ComponentActivity
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.shared.model.fakeGlanceableHubMultiUserHelper
+import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async
@@ -38,15 +39,22 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class WidgetConfigurationControllerTest : SysuiTestCase() {
- @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
- @Mock private lateinit var ownerActivity: ComponentActivity
+ private val appWidgetHost = mock<CommunalAppWidgetHost>()
+ private val ownerActivity = mock<ComponentActivity>()
+
+ private val outcomeReceiverCaptor = argumentCaptor<OutcomeReceiver<IntentSender?, Throwable>>()
private val kosmos = testKosmos()
@@ -54,18 +62,19 @@
@Before
fun setUp() {
- MockitoAnnotations.initMocks(this)
underTest =
WidgetConfigurationController(
ownerActivity,
{ appWidgetHost },
kosmos.testDispatcher,
kosmos.fakeGlanceableHubMultiUserHelper,
+ { kosmos.mockGlanceableHubWidgetManager },
+ kosmos.fakeExecutor,
)
}
@Test
- fun configurationFailsWhenActivityNotFound() =
+ fun configureWidget_activityNotFound_returnsFalse() =
with(kosmos) {
testScope.runTest {
whenever(
@@ -84,7 +93,7 @@
}
@Test
- fun configurationFails() =
+ fun configureWidget_configurationFails_returnsFalse() =
with(kosmos) {
testScope.runTest {
val result = async { underTest.configureWidget(123) }
@@ -100,7 +109,7 @@
}
@Test
- fun configurationSuccessful() =
+ fun configureWidget_configurationSucceeds_returnsTrue() =
with(kosmos) {
testScope.runTest {
val result = async { underTest.configureWidget(123) }
@@ -114,4 +123,116 @@
result.cancel()
}
}
+
+ @Test
+ fun configureWidget_headlessSystemUser_activityNotFound_returnsFalse() =
+ with(kosmos) {
+ testScope.runTest {
+ fakeGlanceableHubMultiUserHelper.setIsInHeadlessSystemUser(true)
+
+ // Activity not found
+ whenever(
+ mockGlanceableHubWidgetManager.getIntentSenderForConfigureActivity(
+ anyInt(),
+ outcomeReceiverCaptor.capture(),
+ any(),
+ )
+ )
+ .then { outcomeReceiverCaptor.firstValue.onError(ActivityNotFoundException()) }
+
+ val result = async { underTest.configureWidget(123) }
+ runCurrent()
+
+ assertThat(result.await()).isFalse()
+ result.cancel()
+ }
+ }
+
+ @Test
+ fun configureWidget_headlessSystemUser_intentSenderNull_returnsFalse() =
+ with(kosmos) {
+ testScope.runTest {
+ fakeGlanceableHubMultiUserHelper.setIsInHeadlessSystemUser(true)
+
+ prepareIntentSender(null)
+
+ assertThat(underTest.configureWidget(123)).isFalse()
+ }
+ }
+
+ @Test
+ fun configureWidget_headlessSystemUser_configurationFails_returnsFalse() =
+ with(kosmos) {
+ testScope.runTest {
+ fakeGlanceableHubMultiUserHelper.setIsInHeadlessSystemUser(true)
+
+ val intentSender = IntentSender(Binder())
+ prepareIntentSender(intentSender)
+
+ val result = async { underTest.configureWidget(123) }
+ runCurrent()
+ assertThat(result.isCompleted).isFalse()
+
+ verify(ownerActivity)
+ .startIntentSenderForResult(
+ eq(intentSender),
+ eq(WidgetConfigurationController.REQUEST_CODE),
+ anyOrNull(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ any(),
+ )
+
+ underTest.setConfigurationResult(Activity.RESULT_CANCELED)
+ runCurrent()
+
+ assertThat(result.await()).isFalse()
+ result.cancel()
+ }
+ }
+
+ @Test
+ fun configureWidget_headlessSystemUser_configurationSucceeds_returnsTrue() =
+ with(kosmos) {
+ testScope.runTest {
+ fakeGlanceableHubMultiUserHelper.setIsInHeadlessSystemUser(true)
+
+ val intentSender = IntentSender(Binder())
+ prepareIntentSender(intentSender)
+
+ val result = async { underTest.configureWidget(123) }
+ runCurrent()
+ assertThat(result.isCompleted).isFalse()
+
+ verify(ownerActivity)
+ .startIntentSenderForResult(
+ eq(intentSender),
+ eq(WidgetConfigurationController.REQUEST_CODE),
+ anyOrNull(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ any(),
+ )
+
+ underTest.setConfigurationResult(Activity.RESULT_OK)
+ runCurrent()
+
+ assertThat(result.await()).isTrue()
+ result.cancel()
+ }
+ }
+
+ private fun prepareIntentSender(intentSender: IntentSender?) =
+ with(kosmos) {
+ whenever(
+ mockGlanceableHubWidgetManager.getIntentSenderForConfigureActivity(
+ anyInt(),
+ outcomeReceiverCaptor.capture(),
+ any(),
+ )
+ )
+ .then { outcomeReceiverCaptor.firstValue.onResult(intentSender) }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteServiceBinderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteServiceBinderTest.kt
index f8a45e8..b343def 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteServiceBinderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteServiceBinderTest.kt
@@ -31,7 +31,8 @@
import com.android.systemui.controls.panels.selectedComponentRepository
import com.android.systemui.controls.settings.FakeControlsSettingsRepository
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.dreams.homecontrols.shared.IOnControlsSettingsChangeListener
+import com.android.systemui.dreams.homecontrols.shared.controlsSettings
+import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsComponentInfo
import com.android.systemui.dreams.homecontrols.system.domain.interactor.controlsComponent
import com.android.systemui.dreams.homecontrols.system.domain.interactor.controlsListingController
import com.android.systemui.dreams.homecontrols.system.domain.interactor.homeControlsComponentInteractor
@@ -42,13 +43,10 @@
import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
-import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -56,6 +54,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito
+import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -90,13 +89,13 @@
fun testRegisterSingleListener() =
testScope.runTest {
setup()
- val controlsSettings by collectLastValue(addCallback())
+ val controlsSettings by collectLastValue(underTest.controlsSettings)
runServicesUpdate()
assertThat(controlsSettings)
.isEqualTo(
- CallbackArgs(
- panelComponent = TEST_COMPONENT,
+ HomeControlsComponentInfo(
+ componentName = TEST_COMPONENT,
allowTrivialControlsOnLockscreen = false,
)
)
@@ -106,21 +105,21 @@
fun testRegisterMultipleListeners() =
testScope.runTest {
setup()
- val controlsSettings1 by collectLastValue(addCallback())
- val controlsSettings2 by collectLastValue(addCallback())
+ val controlsSettings1 by collectLastValue(underTest.controlsSettings)
+ val controlsSettings2 by collectLastValue(underTest.controlsSettings)
runServicesUpdate()
assertThat(controlsSettings1)
.isEqualTo(
- CallbackArgs(
- panelComponent = TEST_COMPONENT,
+ HomeControlsComponentInfo(
+ componentName = TEST_COMPONENT,
allowTrivialControlsOnLockscreen = false,
)
)
assertThat(controlsSettings2)
.isEqualTo(
- CallbackArgs(
- panelComponent = TEST_COMPONENT,
+ HomeControlsComponentInfo(
+ componentName = TEST_COMPONENT,
allowTrivialControlsOnLockscreen = false,
)
)
@@ -130,13 +129,13 @@
fun testListenerCalledWhenStateChanges() =
testScope.runTest {
setup()
- val controlsSettings by collectLastValue(addCallback())
+ val controlsSettings by collectLastValue(underTest.controlsSettings)
runServicesUpdate()
assertThat(controlsSettings)
.isEqualTo(
- CallbackArgs(
- panelComponent = TEST_COMPONENT,
+ HomeControlsComponentInfo(
+ componentName = TEST_COMPONENT,
allowTrivialControlsOnLockscreen = false,
)
)
@@ -146,13 +145,47 @@
// Updated with null component now that we are no longer authorized.
assertThat(controlsSettings)
.isEqualTo(
- CallbackArgs(panelComponent = null, allowTrivialControlsOnLockscreen = false)
+ HomeControlsComponentInfo(
+ componentName = null,
+ allowTrivialControlsOnLockscreen = false,
+ )
)
}
+ @Test
+ fun testDestroy() =
+ testScope.runTest {
+ setup()
+ val controlsSettings1 by collectLastValue(underTest.controlsSettings)
+
+ assertThat(controlsSettings1)
+ .isEqualTo(
+ HomeControlsComponentInfo(
+ componentName = null,
+ allowTrivialControlsOnLockscreen = false,
+ )
+ )
+
+ underTest.onDestroy()
+ runServicesUpdate()
+ fakeControlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true)
+
+ // Existing callback is not triggered if destroyed.
+ assertThat(controlsSettings1)
+ .isEqualTo(
+ HomeControlsComponentInfo(
+ componentName = null,
+ allowTrivialControlsOnLockscreen = false,
+ )
+ )
+ // New callbacks cannot be added.
+ val controlsSettings2 by collectLastValue(underTest.controlsSettings)
+ assertThat(controlsSettings2).isNull()
+ }
+
private fun TestScope.runServicesUpdate() {
runCurrent()
- val listings = listOf(ControlsServiceInfo(TEST_COMPONENT, "panel", hasPanel = true))
+ val listings = listOf(buildControlsServiceInfo(TEST_COMPONENT, "panel", hasPanel = true))
val callback = withArgCaptor {
Mockito.verify(kosmos.controlsListingController).addCallback(capture())
}
@@ -160,20 +193,6 @@
runCurrent()
}
- private fun addCallback() = conflatedCallbackFlow {
- val callback =
- object : IOnControlsSettingsChangeListener.Stub() {
- override fun onControlsSettingsChanged(
- panelComponent: ComponentName?,
- allowTrivialControlsOnLockscreen: Boolean,
- ) {
- trySend(CallbackArgs(panelComponent, allowTrivialControlsOnLockscreen))
- }
- }
- underTest.registerListenerForCurrentUser(callback)
- awaitClose { underTest.unregisterListenerForCurrentUser(callback) }
- }
-
private suspend fun TestScope.setup() {
kosmos.fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
kosmos.fakeUserTracker.set(listOf(PRIMARY_USER), 0)
@@ -182,12 +201,7 @@
runCurrent()
}
- private data class CallbackArgs(
- val panelComponent: ComponentName?,
- val allowTrivialControlsOnLockscreen: Boolean,
- )
-
- private fun ControlsServiceInfo(
+ private fun buildControlsServiceInfo(
componentName: ComponentName,
label: CharSequence,
hasPanel: Boolean,
@@ -225,7 +239,7 @@
UserInfo(
/* id= */ PRIMARY_USER_ID,
/* name= */ "primary user",
- /* flags= */ UserInfo.FLAG_PRIMARY,
+ /* flags= */ UserInfo.FLAG_MAIN,
)
private const val TEST_PACKAGE = "pkg"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpManagerTest.kt
index f331060..5827c7b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpManagerTest.kt
@@ -23,6 +23,7 @@
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.table.TableLogBuffer
import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test
@@ -134,6 +135,21 @@
}
@Test
+ fun registerDumpable_supportsAnonymousDumpables() {
+ val anonDumpable =
+ object : Dumpable {
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("AnonDumpable")
+ }
+ }
+
+ // THEN registration with implicit names should succeed
+ dumpManager.registerCriticalDumpable(anonDumpable)
+
+ // No exception thrown
+ }
+
+ @Test
fun getDumpables_returnsSafeCollection() {
// GIVEN a variety of registered dumpables
dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModelTest.kt
index 2735d2f..a0bef72 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModelTest.kt
@@ -24,7 +24,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
import com.android.systemui.keyboard.docking.domain.interactor.KeyboardDockingIndicationInteractor
import com.google.common.truth.Truth.assertThat
@@ -59,7 +59,7 @@
val keyboardDockingIndicationInteractor =
KeyboardDockingIndicationInteractor(keyboardRepository)
- val configurationInteractor = ConfigurationInteractor(configurationRepository)
+ val configurationInteractor = ConfigurationInteractorImpl(configurationRepository)
underTest =
KeyboardDockingIndicationViewModel(
@@ -67,7 +67,7 @@
context,
keyboardDockingIndicationInteractor,
configurationInteractor,
- testScope.backgroundScope
+ testScope.backgroundScope,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
index 26ce67d..7ec53df 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
@@ -40,8 +40,8 @@
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository
import com.android.systemui.util.settings.fakeSettings
-import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepositoryImpl
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
@@ -72,14 +72,13 @@
@Before
fun setup() {
- val settingsRepository =
- UserAwareSecureSettingsRepositoryImpl(secureSettings, userRepository, dispatcher)
+ val settingsRepository = kosmos.userAwareSecureSettingsRepository
val stickyKeysRepository =
StickyKeysRepositoryImpl(
inputManager,
dispatcher,
settingsRepository,
- mock<StickyKeysLogger>()
+ mock<StickyKeysLogger>(),
)
setStickyKeySetting(enabled = false)
viewModel =
@@ -114,7 +113,7 @@
verify(inputManager)
.registerStickyModifierStateListener(
any(),
- any(InputManager.StickyModifierStateListener::class.java)
+ any(InputManager.StickyModifierStateListener::class.java),
)
}
}
@@ -187,11 +186,7 @@
assertThat(stickyKeys)
.isEqualTo(
- mapOf(
- ALT to Locked(false),
- META to Locked(false),
- SHIFT to Locked(false),
- )
+ mapOf(ALT to Locked(false), META to Locked(false), SHIFT to Locked(false))
)
}
}
@@ -218,7 +213,7 @@
mapOf(
META to false,
SHIFT to false, // shift is sticky but not locked
- CTRL to false
+ CTRL to false,
)
)
val previousShiftIndex = stickyKeys?.toList()?.indexOf(SHIFT to Locked(false))
@@ -228,7 +223,7 @@
SHIFT to false,
SHIFT to true, // shift is now locked
META to false,
- CTRL to false
+ CTRL to false,
)
)
assertThat(stickyKeys?.toList()?.indexOf(SHIFT to Locked(true)))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
index 6e16705..7f31356 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
@@ -34,6 +34,8 @@
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.Transition
import com.android.systemui.scene.data.repository.sceneContainerRepository
import com.android.systemui.scene.data.repository.setSceneTransition
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -115,9 +117,9 @@
assertEquals(
listOf(
- false, // We should start with the surface invisible on LOCKSCREEN.
+ false // We should start with the surface invisible on LOCKSCREEN.
),
- values
+ values,
)
val lockscreenSpecificSurfaceVisibility = true
@@ -134,13 +136,7 @@
// We started a transition from LOCKSCREEN, we should be using the value emitted by the
// lockscreenSurfaceVisibilityFlow.
- assertEquals(
- listOf(
- false,
- lockscreenSpecificSurfaceVisibility,
- ),
- values
- )
+ assertEquals(listOf(false, lockscreenSpecificSurfaceVisibility), values)
// Go back to LOCKSCREEN, since we won't emit 'true' twice in a row.
transitionRepository.sendTransitionStep(
@@ -166,7 +162,7 @@
lockscreenSpecificSurfaceVisibility,
false, // FINISHED (LOCKSCREEN)
),
- values
+ values,
)
val bouncerSpecificVisibility = true
@@ -191,7 +187,7 @@
false,
bouncerSpecificVisibility,
),
- values
+ values,
)
}
@@ -362,20 +358,14 @@
kosmos.sceneInteractor.changeScene(Scenes.Gone, "")
assertThat(currentScene).isEqualTo(Scenes.Gone)
- listOf(
- Scenes.Shade,
- Scenes.QuickSettings,
- Scenes.Shade,
- Scenes.Gone,
- )
- .forEach { scene ->
- kosmos.setSceneTransition(ObservableTransitionState.Idle(scene))
- kosmos.sceneInteractor.changeScene(scene, "")
- assertThat(currentScene).isEqualTo(scene)
- assertWithMessage("Unexpected visibility for scene \"${scene.debugName}\"")
- .that(isSurfaceBehindVisible)
- .isTrue()
- }
+ listOf(Scenes.Shade, Scenes.QuickSettings, Scenes.Shade, Scenes.Gone).forEach { scene ->
+ kosmos.setSceneTransition(ObservableTransitionState.Idle(scene))
+ kosmos.sceneInteractor.changeScene(scene, "")
+ assertThat(currentScene).isEqualTo(scene)
+ assertWithMessage("Unexpected visibility for scene \"${scene.debugName}\"")
+ .that(isSurfaceBehindVisible)
+ .isTrue()
+ }
}
@Test
@@ -386,19 +376,14 @@
val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene)
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
- listOf(
- Scenes.Shade,
- Scenes.QuickSettings,
- Scenes.Shade,
- Scenes.Lockscreen,
- )
- .forEach { scene ->
- kosmos.setSceneTransition(ObservableTransitionState.Idle(scene))
- kosmos.sceneInteractor.changeScene(scene, "")
- assertWithMessage("Unexpected visibility for scene \"${scene.debugName}\"")
- .that(isSurfaceBehindVisible)
- .isFalse()
- }
+ listOf(Scenes.Shade, Scenes.QuickSettings, Scenes.Shade, Scenes.Lockscreen).forEach {
+ scene ->
+ kosmos.setSceneTransition(ObservableTransitionState.Idle(scene))
+ kosmos.sceneInteractor.changeScene(scene, "")
+ assertWithMessage("Unexpected visibility for scene \"${scene.debugName}\"")
+ .that(isSurfaceBehindVisible)
+ .isFalse()
+ }
}
@Test
@@ -427,9 +412,9 @@
assertEquals(
listOf(
- false, // Not using the animation when we're just sitting on LOCKSCREEN.
+ false // Not using the animation when we're just sitting on LOCKSCREEN.
),
- values
+ values,
)
surfaceBehindIsAnimatingFlow.emit(true)
@@ -437,7 +422,7 @@
transitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.GONE,
- testScope
+ testScope,
)
runCurrent()
@@ -446,7 +431,7 @@
false,
true, // Still true when we're FINISHED -> GONE, since we're still animating.
),
- values
+ values,
)
surfaceBehindIsAnimatingFlow.emit(false)
@@ -458,7 +443,7 @@
true,
false, // False once the animation ends.
),
- values
+ values,
)
}
@@ -488,9 +473,9 @@
assertEquals(
listOf(
- false, // Not using the animation when we're just sitting on LOCKSCREEN.
+ false // Not using the animation when we're just sitting on LOCKSCREEN.
),
- values
+ values,
)
surfaceBehindIsAnimatingFlow.emit(true)
@@ -509,7 +494,7 @@
false,
true, // We're happily animating while transitioning to gone.
),
- values
+ values,
)
// Oh no, we're still surfaceBehindAnimating=true, but no longer transitioning to GONE.
@@ -536,7 +521,7 @@
true,
false, // Despite the animator still running, this should be false.
),
- values
+ values,
)
surfaceBehindIsAnimatingFlow.emit(false)
@@ -548,7 +533,7 @@
true,
false, // The animator ending should have no effect.
),
- values
+ values,
)
}
@@ -579,10 +564,10 @@
assertEquals(
listOf(
- true, // Unsurprisingly, we should start with the lockscreen visible on
+ true // Unsurprisingly, we should start with the lockscreen visible on
// LOCKSCREEN.
),
- values
+ values,
)
transitionRepository.sendTransitionStep(
@@ -596,9 +581,9 @@
assertEquals(
listOf(
- true, // Lockscreen remains visible while we're transitioning to GONE.
+ true // Lockscreen remains visible while we're transitioning to GONE.
),
- values
+ values,
)
transitionRepository.sendTransitionStep(
@@ -615,7 +600,7 @@
true,
false, // Once we're fully GONE, the lockscreen should not be visible.
),
- values
+ values,
)
}
@@ -628,7 +613,7 @@
transitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.GONE,
- testScope
+ testScope,
)
runCurrent()
@@ -640,7 +625,7 @@
// Then, false, since we finish in GONE.
false,
),
- values
+ values,
)
transitionRepository.sendTransitionStep(
@@ -665,7 +650,7 @@
// Should remain false as we transition from GONE.
false,
),
- values
+ values,
)
transitionRepository.sendTransitionStep(
@@ -693,7 +678,7 @@
// visibility of the from state (LS).
true,
),
- values
+ values,
)
transitionRepository.sendTransitionStep(
@@ -706,14 +691,7 @@
runCurrent()
- assertEquals(
- listOf(
- true,
- false,
- true,
- ),
- values
- )
+ assertEquals(listOf(true, false, true), values)
}
/**
@@ -730,7 +708,7 @@
transitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.GONE,
- testScope
+ testScope,
)
runCurrent()
@@ -740,7 +718,7 @@
// Not visible since we're GONE.
false,
),
- values
+ values,
)
transitionRepository.sendTransitionStep(
@@ -803,7 +781,7 @@
// STARTED to GONE after a CANCELED from GONE.
false,
),
- values
+ values,
)
transitionRepository.sendTransitionSteps(
@@ -820,7 +798,7 @@
// visible again once we're finished in LOCKSCREEN.
true,
),
- values
+ values,
)
}
@@ -833,7 +811,7 @@
transitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.GONE,
- testScope
+ testScope,
)
runCurrent()
@@ -843,7 +821,7 @@
// Not visible when finished in GONE.
false,
),
- values
+ values,
)
transitionRepository.sendTransitionStep(
@@ -869,7 +847,7 @@
// Still not visible during GONE -> AOD.
false,
),
- values
+ values,
)
transitionRepository.sendTransitionStep(
@@ -886,9 +864,9 @@
true,
false,
// Visible now that we're FINISHED in AOD.
- true
+ true,
),
- values
+ values,
)
transitionRepository.sendTransitionStep(
@@ -914,9 +892,9 @@
true,
false,
// Remains visible from AOD during transition.
- true
+ true,
),
- values
+ values,
)
transitionRepository.sendTransitionStep(
@@ -934,15 +912,15 @@
false,
true,
// Until we're finished in GONE again.
- false
+ false,
),
- values
+ values,
)
}
@Test
@EnableSceneContainer
- fun lockscreenVisibility() =
+ fun lockscreenVisibilityWithScenes() =
testScope.runTest {
val isDeviceUnlocked by
collectLastValue(
@@ -956,32 +934,69 @@
val lockscreenVisibility by collectLastValue(underTest.value.lockscreenVisibility)
assertThat(lockscreenVisibility).isTrue()
+ kosmos.setSceneTransition(Idle(Scenes.Shade))
+ kosmos.sceneInteractor.changeScene(Scenes.Shade, "")
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+ assertThat(lockscreenVisibility).isTrue()
+
+ kosmos.setSceneTransition(Transition(from = Scenes.Shade, to = Scenes.QuickSettings))
+ assertThat(lockscreenVisibility).isTrue()
+
+ kosmos.setSceneTransition(Idle(Scenes.QuickSettings))
+ kosmos.sceneInteractor.changeScene(Scenes.QuickSettings, "")
+ assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
+ assertThat(lockscreenVisibility).isTrue()
+
+ kosmos.setSceneTransition(Transition(from = Scenes.QuickSettings, to = Scenes.Shade))
+ assertThat(lockscreenVisibility).isTrue()
+
+ kosmos.setSceneTransition(Idle(Scenes.Shade))
+ kosmos.sceneInteractor.changeScene(Scenes.Shade, "")
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+ assertThat(lockscreenVisibility).isTrue()
+
+ kosmos.setSceneTransition(Idle(Scenes.Bouncer))
kosmos.sceneInteractor.changeScene(Scenes.Bouncer, "")
assertThat(currentScene).isEqualTo(Scenes.Bouncer)
assertThat(lockscreenVisibility).isTrue()
+ kosmos.setSceneTransition(Transition(from = Scenes.Bouncer, to = Scenes.Gone))
+ assertThat(lockscreenVisibility).isTrue()
+
+ kosmos.setSceneTransition(Idle(Scenes.Gone))
kosmos.authenticationInteractor.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)
assertThat(isDeviceUnlocked).isTrue()
kosmos.sceneInteractor.changeScene(Scenes.Gone, "")
assertThat(currentScene).isEqualTo(Scenes.Gone)
assertThat(lockscreenVisibility).isFalse()
+ kosmos.setSceneTransition(Idle(Scenes.Shade))
kosmos.sceneInteractor.changeScene(Scenes.Shade, "")
assertThat(currentScene).isEqualTo(Scenes.Shade)
assertThat(lockscreenVisibility).isFalse()
+ kosmos.setSceneTransition(Transition(from = Scenes.Shade, to = Scenes.QuickSettings))
+ assertThat(lockscreenVisibility).isFalse()
+
+ kosmos.setSceneTransition(Idle(Scenes.QuickSettings))
kosmos.sceneInteractor.changeScene(Scenes.QuickSettings, "")
assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
assertThat(lockscreenVisibility).isFalse()
+ kosmos.setSceneTransition(Idle(Scenes.Shade))
kosmos.sceneInteractor.changeScene(Scenes.Shade, "")
assertThat(currentScene).isEqualTo(Scenes.Shade)
assertThat(lockscreenVisibility).isFalse()
+ kosmos.setSceneTransition(Idle(Scenes.Gone))
kosmos.sceneInteractor.changeScene(Scenes.Gone, "")
assertThat(currentScene).isEqualTo(Scenes.Gone)
assertThat(lockscreenVisibility).isFalse()
+ kosmos.setSceneTransition(Transition(from = Scenes.Gone, to = Scenes.Lockscreen))
+ assertThat(lockscreenVisibility).isFalse()
+
+ kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
kosmos.sceneInteractor.changeScene(Scenes.Lockscreen, "")
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
assertThat(lockscreenVisibility).isTrue()
@@ -1037,7 +1052,7 @@
flowOf(Scenes.Lockscreen),
progress,
false,
- flowOf(false)
+ flowOf(false),
)
private val goneToLs =
@@ -1047,7 +1062,7 @@
flowOf(Scenes.Lockscreen),
progress,
false,
- flowOf(false)
+ flowOf(false),
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
index 9c58e2b..92764ae 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
@@ -29,11 +29,13 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -112,4 +114,20 @@
verify(activityTaskManagerService).setLockScreenShown(true, false)
verifyNoMoreInteractions(activityTaskManagerService)
}
+
+ @Test
+ fun setSurfaceBehindVisibility_goesAwayFirst_andIgnoresSecondCall() {
+ underTest.setLockscreenShown(true)
+ underTest.setSurfaceBehindVisibility(true)
+ verify(activityTaskManagerService).keyguardGoingAway(0)
+
+ underTest.setSurfaceBehindVisibility(true)
+ verifyNoMoreInteractions(keyguardTransitions)
+ }
+
+ @Test
+ fun setSurfaceBehindVisibility_falseSetsLockscreenVisibility() {
+ underTest.setSurfaceBehindVisibility(false)
+ verify(activityTaskManagerService).setLockScreenShown(eq(true), any())
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryTest.kt
new file mode 100644
index 0000000..9e3fdf3
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryTest.kt
@@ -0,0 +1,335 @@
+/*
+ * 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.media.controls.domain.pipeline
+
+import android.media.session.MediaSession
+import android.os.Bundle
+import android.os.Handler
+import android.os.looper
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import androidx.media.utils.MediaConstants
+import androidx.media3.common.Player
+import androidx.media3.session.CommandButton
+import androidx.media3.session.MediaController as Media3Controller
+import androidx.media3.session.SessionCommand
+import androidx.media3.session.SessionToken
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.graphics.imageLoader
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.shared.mediaLogger
+import com.android.systemui.media.controls.shared.model.MediaButton
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
+import com.android.systemui.media.controls.util.fakeSessionTokenFactory
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.google.common.collect.ImmutableList
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+private const val PACKAGE_NAME = "package_name"
+private const val CUSTOM_ACTION_NAME = "Custom Action"
+private const val CUSTOM_ACTION_COMMAND = "custom-action"
+
+@SmallTest
+@RunWithLooper
+@RunWith(AndroidJUnit4::class)
+class Media3ActionFactoryTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val controllerFactory = kosmos.fakeMediaControllerFactory
+ private val tokenFactory = kosmos.fakeSessionTokenFactory
+ private lateinit var testableLooper: TestableLooper
+
+ private var commandCaptor = argumentCaptor<SessionCommand>()
+ private var runnableCaptor = argumentCaptor<Runnable>()
+
+ private val legacyToken = MediaSession.Token(1, null)
+ private val token = mock<SessionToken>()
+ private val handler =
+ mock<Handler> {
+ on { post(runnableCaptor.capture()) } doAnswer
+ {
+ runnableCaptor.lastValue.run()
+ true
+ }
+ }
+ private val customLayout = ImmutableList.of<CommandButton>()
+ private val media3Controller =
+ mock<Media3Controller> {
+ on { customLayout } doReturn customLayout
+ on { sessionExtras } doReturn Bundle()
+ on { isCommandAvailable(any()) } doReturn true
+ on { isSessionCommandAvailable(any<SessionCommand>()) } doReturn true
+ }
+
+ private lateinit var underTest: Media3ActionFactory
+
+ @Before
+ fun setup() {
+ testableLooper = TestableLooper.get(this)
+
+ underTest =
+ Media3ActionFactory(
+ context,
+ kosmos.imageLoader,
+ controllerFactory,
+ tokenFactory,
+ kosmos.mediaLogger,
+ kosmos.looper,
+ handler,
+ kosmos.testScope,
+ )
+
+ controllerFactory.setMedia3Controller(media3Controller)
+ tokenFactory.setMedia3SessionToken(token)
+ }
+
+ @Test
+ fun media3Actions_playingState_withCustomActions() =
+ testScope.runTest {
+ // Media is playing, all commands available, with custom actions
+ val customLayout = ImmutableList.copyOf((0..1).map { createCustomCommandButton(it) })
+ whenever(media3Controller.customLayout).thenReturn(customLayout)
+ whenever(media3Controller.isPlaying).thenReturn(true)
+ val result = getActions()
+
+ assertThat(result).isNotNull()
+
+ val actions = result!!
+ assertThat(actions.playOrPause!!.contentDescription)
+ .isEqualTo(context.getString(R.string.controls_media_button_pause))
+ actions.playOrPause!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).pause()
+ verify(media3Controller).release()
+ clearInvocations(media3Controller)
+
+ assertThat(actions.prevOrCustom!!.contentDescription)
+ .isEqualTo(context.getString(R.string.controls_media_button_prev))
+ actions.prevOrCustom!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).seekToPrevious()
+ verify(media3Controller).release()
+ clearInvocations(media3Controller)
+
+ assertThat(actions.nextOrCustom!!.contentDescription)
+ .isEqualTo(context.getString(R.string.controls_media_button_next))
+ actions.nextOrCustom!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).seekToNext()
+ verify(media3Controller).release()
+ clearInvocations(media3Controller)
+
+ assertThat(actions.custom0!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 0")
+ actions.custom0!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+ assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 0")
+ verify(media3Controller).release()
+ clearInvocations(media3Controller)
+
+ assertThat(actions.custom1!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 1")
+ actions.custom1!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+ assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 1")
+ verify(media3Controller).release()
+ }
+
+ @Test
+ fun media3Actions_pausedState_hasPauseAction() =
+ testScope.runTest {
+ whenever(media3Controller.isPlaying).thenReturn(false)
+ val result = getActions()
+
+ assertThat(result).isNotNull()
+ val actions = result!!
+ assertThat(actions.playOrPause!!.contentDescription)
+ .isEqualTo(context.getString(R.string.controls_media_button_play))
+ clearInvocations(media3Controller)
+
+ actions.playOrPause!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).play()
+ verify(media3Controller).release()
+ clearInvocations(media3Controller)
+ }
+
+ @Test
+ fun media3Actions_bufferingState_hasLoadingSpinner() =
+ testScope.runTest {
+ whenever(media3Controller.isPlaying).thenReturn(false)
+ whenever(media3Controller.playbackState).thenReturn(Player.STATE_BUFFERING)
+ val result = getActions()
+
+ assertThat(result).isNotNull()
+ val actions = result!!
+ assertThat(actions.playOrPause!!.contentDescription)
+ .isEqualTo(context.getString(R.string.controls_media_button_connecting))
+ assertThat(actions.playOrPause!!.action).isNull()
+ assertThat(actions.playOrPause!!.rebindId)
+ .isEqualTo(com.android.internal.R.drawable.progress_small_material)
+ }
+
+ @Test
+ fun media3Actions_noPrevNext_usesCustom() =
+ testScope.runTest {
+ val customLayout = ImmutableList.copyOf((0..4).map { createCustomCommandButton(it) })
+ whenever(media3Controller.customLayout).thenReturn(customLayout)
+ whenever(media3Controller.isPlaying).thenReturn(true)
+ whenever(media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_PREVIOUS)))
+ .thenReturn(false)
+ whenever(
+ media3Controller.isCommandAvailable(
+ eq(Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
+ )
+ )
+ .thenReturn(false)
+ whenever(media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_NEXT)))
+ .thenReturn(false)
+ whenever(
+ media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM))
+ )
+ .thenReturn(false)
+ val result = getActions()
+
+ assertThat(result).isNotNull()
+ val actions = result!!
+
+ assertThat(actions.prevOrCustom!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 0")
+ actions.prevOrCustom!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+ assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 0")
+ verify(media3Controller).release()
+ clearInvocations(media3Controller)
+
+ assertThat(actions.nextOrCustom!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 1")
+ actions.nextOrCustom!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+ assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 1")
+ verify(media3Controller).release()
+ clearInvocations(media3Controller)
+
+ assertThat(actions.custom0!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 2")
+ actions.custom0!!.action!!.run()
+ runCurrent()
+ testableLooper.processAllMessages()
+ verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+ assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 2")
+ verify(media3Controller).release()
+ clearInvocations(media3Controller)
+
+ assertThat(actions.custom1!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 3")
+ actions.custom1!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+ assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 3")
+ verify(media3Controller).release()
+ }
+
+ @Test
+ fun media3Actions_noPrevNext_reservedSpace() =
+ testScope.runTest {
+ val customLayout = ImmutableList.copyOf((0..4).map { createCustomCommandButton(it) })
+ whenever(media3Controller.customLayout).thenReturn(customLayout)
+ whenever(media3Controller.isPlaying).thenReturn(true)
+ whenever(media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_PREVIOUS)))
+ .thenReturn(false)
+ whenever(
+ media3Controller.isCommandAvailable(
+ eq(Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
+ )
+ )
+ .thenReturn(false)
+ whenever(media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_NEXT)))
+ .thenReturn(false)
+ whenever(
+ media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM))
+ )
+ .thenReturn(false)
+ val extras =
+ Bundle().apply {
+ putBoolean(
+ MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV,
+ true,
+ )
+ putBoolean(
+ MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT,
+ true,
+ )
+ }
+ whenever(media3Controller.sessionExtras).thenReturn(extras)
+ val result = getActions()
+
+ assertThat(result).isNotNull()
+ val actions = result!!
+
+ assertThat(actions.prevOrCustom).isNull()
+ assertThat(actions.nextOrCustom).isNull()
+
+ assertThat(actions.custom0!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 0")
+ actions.custom0!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+ assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 0")
+ verify(media3Controller).release()
+ clearInvocations(media3Controller)
+
+ assertThat(actions.custom1!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 1")
+ actions.custom1!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+ assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 1")
+ verify(media3Controller).release()
+ }
+
+ private suspend fun getActions(): MediaButton? {
+ val result = underTest.createActionsFromSession(PACKAGE_NAME, legacyToken)
+ testScope.runCurrent()
+ verify(media3Controller).release()
+
+ // Clear so tests can verify the correct number of release() calls in later operations
+ clearInvocations(media3Controller)
+ return result
+ }
+
+ private fun createCustomCommandButton(id: Int): CommandButton {
+ return CommandButton.Builder()
+ .setDisplayName("$CUSTOM_ACTION_NAME $id")
+ .setSessionCommand(SessionCommand("$CUSTOM_ACTION_COMMAND $id", Bundle()))
+ .build()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt
index fc9e595..1a7265b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt
@@ -29,6 +29,7 @@
import android.media.session.PlaybackState
import android.os.Bundle
import android.service.notification.StatusBarNotification
+import android.testing.TestableLooper.RunWithLooper
import androidx.media.utils.MediaConstants
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -69,6 +70,7 @@
private const val SESSION_EMPTY_TITLE = ""
@SmallTest
+@RunWithLooper
@RunWith(AndroidJUnit4::class)
class MediaDataLoaderTest : SysuiTestCase() {
@@ -80,6 +82,7 @@
private val fakeFeatureFlags = kosmos.fakeFeatureFlagsClassic
private val mediaFlags = kosmos.mediaFlags
private val mediaControllerFactory = kosmos.fakeMediaControllerFactory
+ private val media3ActionFactory = kosmos.media3ActionFactory
private val session = MediaSession(context, "MediaDataLoaderTestSession")
private val metadataBuilder =
MediaMetadata.Builder().apply {
@@ -87,21 +90,25 @@
putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
}
- private val underTest: MediaDataLoader =
- MediaDataLoader(
- context,
- testDispatcher,
- testScope,
- mediaControllerFactory,
- mediaFlags,
- kosmos.imageLoader,
- statusBarManager,
- )
+ private lateinit var underTest: MediaDataLoader
@Before
fun setUp() {
mediaControllerFactory.setControllerForToken(session.sessionToken, mediaController)
+ whenever(mediaController.sessionToken).thenReturn(session.sessionToken)
whenever(mediaController.metadata).then { metadataBuilder.build() }
+
+ underTest =
+ MediaDataLoader(
+ context,
+ testDispatcher,
+ testScope,
+ mediaControllerFactory,
+ mediaFlags,
+ kosmos.imageLoader,
+ statusBarManager,
+ kosmos.media3ActionFactory,
+ )
}
@Test
@@ -394,6 +401,7 @@
mediaFlags,
mockImageLoader,
statusBarManager,
+ media3ActionFactory,
)
metadataBuilder.putString(
MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
@@ -422,6 +430,7 @@
mediaFlags,
mockImageLoader,
statusBarManager,
+ media3ActionFactory,
)
metadataBuilder.putString(
MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
index 2905a73..646722b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
@@ -116,6 +116,7 @@
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.data.repository.LightBarControllerStore;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.LightBarController;
@@ -208,7 +209,7 @@
@Mock
private LightBarController mLightBarController;
@Mock
- private LightBarController.Factory mLightBarcontrollerFactory;
+ private LightBarControllerStore mLightBarControllerStore;
@Mock
private AutoHideController mAutoHideController;
@Mock
@@ -257,7 +258,7 @@
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
- when(mLightBarcontrollerFactory.create(any(Context.class))).thenReturn(mLightBarController);
+ when(mLightBarControllerStore.forDisplay(anyInt())).thenReturn(mLightBarController);
when(mAutoHideControllerFactory.create(any(Context.class))).thenReturn(mAutoHideController);
when(mNavigationBarView.getHomeButton()).thenReturn(mHomeButton);
when(mNavigationBarView.getRecentsButton()).thenReturn(mRecentsButton);
@@ -649,8 +650,7 @@
mFakeExecutor,
mUiEventLogger,
mNavBarHelper,
- mLightBarController,
- mLightBarcontrollerFactory,
+ mLightBarControllerStore,
mAutoHideController,
mAutoHideControllerFactory,
Optional.of(mTelecomManager),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
index ff40e43..a063531 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
@@ -115,7 +115,7 @@
underTest.logUserActionPipeline(
TileSpec.create("test_spec"),
QSTileUserAction.Click(null),
- QSTileState.build({ Icon.Resource(0, ContentDescription.Resource(0)) }, "") {},
+ QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {},
"test_data",
)
@@ -141,7 +141,7 @@
fun testLogStateUpdate() {
underTest.logStateUpdate(
TileSpec.create("test_spec"),
- QSTileState.build({ Icon.Resource(0, ContentDescription.Resource(0)) }, "") {},
+ QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {},
"test_data",
)
@@ -162,18 +162,14 @@
@Test
fun testLogForceUpdate() {
- underTest.logForceUpdate(
- TileSpec.create("test_spec"),
- )
+ underTest.logForceUpdate(TileSpec.create("test_spec"))
assertThat(logBuffer.getStringBuffer()).contains("tile data force update")
}
@Test
fun testLogInitialUpdate() {
- underTest.logInitialRequest(
- TileSpec.create("test_spec"),
- )
+ underTest.logInitialRequest(TileSpec.create("test_spec"))
assertThat(logBuffer.getStringBuffer()).contains("tile data initial update")
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
index c918ed8..056efb3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
@@ -85,8 +85,8 @@
object : QSTileDataToStateMapper<Any> {
override fun map(config: QSTileConfig, data: Any): QSTileState =
QSTileState.build(
- { Icon.Resource(0, ContentDescription.Resource(0)) },
- data.toString()
+ Icon.Resource(0, ContentDescription.Resource(0)),
+ data.toString(),
) {}
}
},
@@ -116,7 +116,7 @@
.isEqualTo(
"test_spec:\n" +
" QSTileState(" +
- "icon=() -> com.android.systemui.common.shared.model.Icon?, " +
+ "icon=Resource(res=0, contentDescription=Resource(res=0)), " +
"iconRes=null, " +
"label=test_data, " +
"activationState=INACTIVE, " +
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt
index 5a73fe2..00460bf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt
@@ -66,7 +66,7 @@
createAirplaneModeState(
QSTileState.ActivationState.ACTIVE,
context.resources.getStringArray(R.array.tile_states_airplane)[Tile.STATE_ACTIVE],
- R.drawable.qs_airplane_icon_on
+ R.drawable.qs_airplane_icon_on,
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -81,7 +81,7 @@
createAirplaneModeState(
QSTileState.ActivationState.INACTIVE,
context.resources.getStringArray(R.array.tile_states_airplane)[Tile.STATE_INACTIVE],
- R.drawable.qs_airplane_icon_off
+ R.drawable.qs_airplane_icon_off,
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -89,11 +89,11 @@
private fun createAirplaneModeState(
activationState: QSTileState.ActivationState,
secondaryLabel: String,
- iconRes: Int
+ iconRes: Int,
): QSTileState {
val label = context.getString(R.string.airplane_mode)
return QSTileState(
- { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ Icon.Loaded(context.getDrawable(iconRes)!!, null),
iconRes,
label,
activationState,
@@ -103,7 +103,7 @@
null,
QSTileState.SideViewIcon.None,
QSTileState.EnabledState.ENABLED,
- Switch::class.qualifiedName
+ Switch::class.qualifiedName,
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
index 79e4fef..632aae0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
@@ -51,7 +51,7 @@
.apply { addOverride(R.drawable.ic_alarm, TestStubDrawable()) }
.resources,
context.theme,
- fakeClock
+ fakeClock,
)
}
@@ -69,7 +69,7 @@
val expectedState =
createAlarmTileState(
QSTileState.ActivationState.INACTIVE,
- context.getString(R.string.qs_alarm_tile_no_alarm)
+ context.getString(R.string.qs_alarm_tile_no_alarm),
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -85,7 +85,7 @@
val localDateTime =
LocalDateTime.ofInstant(
Instant.ofEpochMilli(triggerTime),
- TimeZone.getDefault().toZoneId()
+ TimeZone.getDefault().toZoneId(),
)
val expectedSecondaryLabel = AlarmTileMapper.formatter24Hour.format(localDateTime)
val expectedState =
@@ -104,7 +104,7 @@
val localDateTime =
LocalDateTime.ofInstant(
Instant.ofEpochMilli(triggerTime),
- TimeZone.getDefault().toZoneId()
+ TimeZone.getDefault().toZoneId(),
)
val expectedSecondaryLabel = AlarmTileMapper.formatter12Hour.format(localDateTime)
val expectedState =
@@ -124,7 +124,7 @@
val localDateTime =
LocalDateTime.ofInstant(
Instant.ofEpochMilli(triggerTime),
- TimeZone.getDefault().toZoneId()
+ TimeZone.getDefault().toZoneId(),
)
val expectedSecondaryLabel = AlarmTileMapper.formatterDateOnly.format(localDateTime)
val expectedState =
@@ -144,7 +144,7 @@
val localDateTime =
LocalDateTime.ofInstant(
Instant.ofEpochMilli(triggerTime),
- TimeZone.getDefault().toZoneId()
+ TimeZone.getDefault().toZoneId(),
)
val expectedSecondaryLabel = AlarmTileMapper.formatter12Hour.format(localDateTime)
val expectedState =
@@ -164,7 +164,7 @@
val localDateTime =
LocalDateTime.ofInstant(
Instant.ofEpochMilli(triggerTime),
- TimeZone.getDefault().toZoneId()
+ TimeZone.getDefault().toZoneId(),
)
val expectedSecondaryLabel = AlarmTileMapper.formatterDateOnly.format(localDateTime)
val expectedState =
@@ -174,11 +174,11 @@
private fun createAlarmTileState(
activationState: QSTileState.ActivationState,
- secondaryLabel: String
+ secondaryLabel: String,
): QSTileState {
val label = context.getString(R.string.status_bar_alarm)
return QSTileState(
- { Icon.Loaded(context.getDrawable(R.drawable.ic_alarm)!!, null) },
+ Icon.Loaded(context.getDrawable(R.drawable.ic_alarm)!!, null),
R.drawable.ic_alarm,
label,
activationState,
@@ -188,7 +188,7 @@
null,
QSTileState.SideViewIcon.Chevron,
QSTileState.EnabledState.ENABLED,
- Switch::class.qualifiedName
+ Switch::class.qualifiedName,
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
index a0d26c2..5385f94 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
@@ -253,7 +253,7 @@
): QSTileState {
val label = context.getString(R.string.battery_detail_switch_title)
return QSTileState(
- { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ Icon.Loaded(context.getDrawable(iconRes)!!, null),
iconRes,
label,
activationState,
@@ -265,7 +265,7 @@
stateDescription,
QSTileState.SideViewIcon.None,
QSTileState.EnabledState.ENABLED,
- Switch::class.qualifiedName
+ Switch::class.qualifiedName,
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt
index ea7b7c5..356b98e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt
@@ -45,7 +45,7 @@
context.orCreateTestableResources
.apply { addOverride(R.drawable.ic_qs_color_correction, TestStubDrawable()) }
.resources,
- context.theme
+ context.theme,
)
}
@@ -73,11 +73,11 @@
private fun createColorCorrectionTileState(
activationState: QSTileState.ActivationState,
- secondaryLabel: String
+ secondaryLabel: String,
): QSTileState {
val label = context.getString(R.string.quick_settings_color_correction_label)
return QSTileState(
- { Icon.Loaded(context.getDrawable(R.drawable.ic_qs_color_correction)!!, null) },
+ Icon.Loaded(context.getDrawable(R.drawable.ic_qs_color_correction)!!, null),
R.drawable.ic_qs_color_correction,
label,
activationState,
@@ -87,7 +87,7 @@
null,
QSTileState.SideViewIcon.None,
QSTileState.EnabledState.ENABLED,
- Switch::class.qualifiedName
+ Switch::class.qualifiedName,
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
index f1d08c0..8236c4c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
@@ -57,10 +57,7 @@
private val kosmos =
testKosmos().apply { customTileSpec = TileSpec.Companion.create(TEST_COMPONENT) }
private val underTest by lazy {
- CustomTileMapper(
- context = mockContext,
- uriGrantsManager = uriGrantsManager,
- )
+ CustomTileMapper(context = mockContext, uriGrantsManager = uriGrantsManager)
}
@Test
@@ -68,10 +65,7 @@
with(kosmos) {
testScope.runTest {
val actual =
- underTest.map(
- customTileQsTileConfig,
- createModel(hasPendingBind = true),
- )
+ underTest.map(customTileQsTileConfig, createModel(hasPendingBind = true))
val expected =
createTileState(
activationState = QSTileState.ActivationState.UNAVAILABLE,
@@ -91,10 +85,7 @@
customTileQsTileConfig,
createModel(tileState = Tile.STATE_ACTIVE),
)
- val expected =
- createTileState(
- activationState = QSTileState.ActivationState.ACTIVE,
- )
+ val expected = createTileState(activationState = QSTileState.ActivationState.ACTIVE)
assertThat(actual).isEqualTo(expected)
}
@@ -110,9 +101,7 @@
createModel(tileState = Tile.STATE_INACTIVE),
)
val expected =
- createTileState(
- activationState = QSTileState.ActivationState.INACTIVE,
- )
+ createTileState(activationState = QSTileState.ActivationState.INACTIVE)
assertThat(actual).isEqualTo(expected)
}
@@ -142,10 +131,7 @@
with(kosmos) {
testScope.runTest {
val actual =
- underTest.map(
- customTileQsTileConfig,
- createModel(isToggleable = false),
- )
+ underTest.map(customTileQsTileConfig, createModel(isToggleable = false))
val expected =
createTileState(
sideIcon = QSTileState.SideViewIcon.Chevron,
@@ -184,7 +170,7 @@
customTileQsTileConfig,
createModel(
tileIcon = createIcon(RuntimeException(), false),
- defaultTileIcon = createIcon(null, true)
+ defaultTileIcon = createIcon(null, true),
),
)
val expected =
@@ -266,7 +252,7 @@
a11yClass: String? = Switch::class.qualifiedName,
): QSTileState {
return QSTileState(
- { icon?.let { com.android.systemui.common.shared.model.Icon.Loaded(icon, null) } },
+ icon?.let { com.android.systemui.common.shared.model.Icon.Loaded(icon, null) },
null,
"test label",
activationState,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
index 63fb67d..587585c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
@@ -44,7 +44,7 @@
addOverride(R.drawable.qs_flashlight_icon_on, TestStubDrawable())
}
.resources,
- context.theme
+ context.theme,
)
}
@@ -74,7 +74,7 @@
val expectedIcon =
Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_on)!!, null)
- val actualIcon = tileState.icon()
+ val actualIcon = tileState.icon
assertThat(actualIcon).isEqualTo(expectedIcon)
}
@@ -85,7 +85,7 @@
val expectedIcon =
Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_off)!!, null)
- val actualIcon = tileState.icon()
+ val actualIcon = tileState.icon
assertThat(actualIcon).isEqualTo(expectedIcon)
}
@@ -96,7 +96,7 @@
val expectedIcon =
Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_off)!!, null)
- val actualIcon = tileState.icon()
+ val actualIcon = tileState.icon
assertThat(actualIcon).isEqualTo(expectedIcon)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt
index f8e01be..e81771e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt
@@ -42,7 +42,7 @@
context.orCreateTestableResources
.apply { addOverride(R.drawable.ic_qs_font_scaling, TestStubDrawable()) }
.resources,
- context.theme
+ context.theme,
)
}
@@ -58,14 +58,7 @@
private fun createFontScalingTileState(): QSTileState =
QSTileState(
- {
- Icon.Loaded(
- context.getDrawable(
- R.drawable.ic_qs_font_scaling,
- )!!,
- null
- )
- },
+ Icon.Loaded(context.getDrawable(R.drawable.ic_qs_font_scaling)!!, null),
R.drawable.ic_qs_font_scaling,
context.getString(R.string.quick_settings_font_scaling_label),
QSTileState.ActivationState.ACTIVE,
@@ -75,6 +68,6 @@
null,
QSTileState.SideViewIcon.Chevron,
QSTileState.EnabledState.ENABLED,
- Switch::class.qualifiedName
+ Switch::class.qualifiedName,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt
index cdf6bda..12d604f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt
@@ -102,7 +102,7 @@
val label = context.getString(R.string.quick_settings_hearing_devices_label)
val iconRes = R.drawable.qs_hearing_devices_icon
return QSTileState(
- { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ Icon.Loaded(context.getDrawable(iconRes)!!, null),
iconRes,
label,
activationState,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
index d32ba47..9dcf49e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
@@ -187,7 +187,7 @@
): QSTileState {
val label = context.getString(R.string.quick_settings_internet_label)
return QSTileState(
- { icon },
+ icon,
iconRes,
label,
activationState,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt
index a7bd697..30fce73 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt
@@ -49,7 +49,7 @@
addOverride(R.drawable.qs_invert_colors_icon_on, TestStubDrawable())
}
.resources,
- context.theme
+ context.theme,
)
}
@@ -63,7 +63,7 @@
createColorInversionTileState(
QSTileState.ActivationState.INACTIVE,
subtitleArray[1],
- R.drawable.qs_invert_colors_icon_off
+ R.drawable.qs_invert_colors_icon_off,
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -78,7 +78,7 @@
createColorInversionTileState(
QSTileState.ActivationState.ACTIVE,
subtitleArray[2],
- R.drawable.qs_invert_colors_icon_on
+ R.drawable.qs_invert_colors_icon_on,
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -90,7 +90,7 @@
): QSTileState {
val label = context.getString(R.string.quick_settings_inversion_label)
return QSTileState(
- { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ Icon.Loaded(context.getDrawable(iconRes)!!, null),
iconRes,
label,
activationState,
@@ -100,7 +100,7 @@
null,
QSTileState.SideViewIcon.None,
QSTileState.EnabledState.ENABLED,
- Switch::class.qualifiedName
+ Switch::class.qualifiedName,
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt
index ea74a4c..37e8a60 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt
@@ -45,7 +45,7 @@
addOverride(R.drawable.qs_location_icon_on, TestStubDrawable())
}
.resources,
- context.theme
+ context.theme,
)
}
@@ -70,7 +70,7 @@
val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(true))
val expectedIcon = Icon.Loaded(context.getDrawable(R.drawable.qs_location_icon_on)!!, null)
- val actualIcon = tileState.icon()
+ val actualIcon = tileState.icon
Truth.assertThat(actualIcon).isEqualTo(expectedIcon)
}
@@ -79,7 +79,7 @@
val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(false))
val expectedIcon = Icon.Loaded(context.getDrawable(R.drawable.qs_location_icon_off)!!, null)
- val actualIcon = tileState.icon()
+ val actualIcon = tileState.icon
Truth.assertThat(actualIcon).isEqualTo(expectedIcon)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
index c3d45db..4e91d16 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
@@ -59,34 +59,24 @@
@Test
fun inactiveState() {
val icon = TestStubDrawable("res123").asIcon()
- val model =
- ModesTileModel(
- isActivated = false,
- activeModes = emptyList(),
- icon = icon,
- )
+ val model = ModesTileModel(isActivated = false, activeModes = emptyList(), icon = icon)
val state = underTest.map(config, model)
assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.INACTIVE)
- assertThat(state.icon()).isEqualTo(icon)
+ assertThat(state.icon).isEqualTo(icon)
assertThat(state.secondaryLabel).isEqualTo("No active modes")
}
@Test
fun activeState_oneMode() {
val icon = TestStubDrawable("res123").asIcon()
- val model =
- ModesTileModel(
- isActivated = true,
- activeModes = listOf("DND"),
- icon = icon,
- )
+ val model = ModesTileModel(isActivated = true, activeModes = listOf("DND"), icon = icon)
val state = underTest.map(config, model)
assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.ACTIVE)
- assertThat(state.icon()).isEqualTo(icon)
+ assertThat(state.icon).isEqualTo(icon)
assertThat(state.secondaryLabel).isEqualTo("DND is active")
}
@@ -103,7 +93,7 @@
val state = underTest.map(config, model)
assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.ACTIVE)
- assertThat(state.icon()).isEqualTo(icon)
+ assertThat(state.icon).isEqualTo(icon)
assertThat(state.secondaryLabel).isEqualTo("3 modes are active")
}
@@ -115,12 +105,12 @@
isActivated = false,
activeModes = emptyList(),
icon = icon,
- iconResId = 123
+ iconResId = 123,
)
val state = underTest.map(config, model)
- assertThat(state.icon()).isEqualTo(icon)
+ assertThat(state.icon).isEqualTo(icon)
assertThat(state.iconRes).isEqualTo(123)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt
index 75273f2..1457f53 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt
@@ -73,7 +73,7 @@
val expectedState =
createNightDisplayTileState(
QSTileState.ActivationState.INACTIVE,
- context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE]
+ context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE],
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -88,7 +88,7 @@
val expectedState =
createNightDisplayTileState(
QSTileState.ActivationState.INACTIVE,
- context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE]
+ context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE],
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -102,7 +102,7 @@
val expectedState =
createNightDisplayTileState(
QSTileState.ActivationState.ACTIVE,
- context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE]
+ context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE],
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -116,7 +116,7 @@
val expectedState =
createNightDisplayTileState(
QSTileState.ActivationState.ACTIVE,
- context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE]
+ context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE],
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -140,7 +140,7 @@
val expectedState =
createNightDisplayTileState(
QSTileState.ActivationState.ACTIVE,
- context.getString(R.string.quick_settings_night_secondary_label_until_sunrise)
+ context.getString(R.string.quick_settings_night_secondary_label_until_sunrise),
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -154,7 +154,7 @@
val expectedState =
createNightDisplayTileState(
QSTileState.ActivationState.INACTIVE,
- context.getString(R.string.quick_settings_night_secondary_label_on_at_sunset)
+ context.getString(R.string.quick_settings_night_secondary_label_on_at_sunset),
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -181,8 +181,8 @@
QSTileState.ActivationState.INACTIVE,
context.getString(
R.string.quick_settings_night_secondary_label_on_at,
- formatter24Hour.format(testStartTime)
- )
+ formatter24Hour.format(testStartTime),
+ ),
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -199,8 +199,8 @@
QSTileState.ActivationState.INACTIVE,
context.getString(
R.string.quick_settings_night_secondary_label_on_at,
- formatter12Hour.format(testStartTime)
- )
+ formatter12Hour.format(testStartTime),
+ ),
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -218,8 +218,8 @@
QSTileState.ActivationState.INACTIVE,
context.getString(
R.string.quick_settings_night_secondary_label_on_at,
- formatter12Hour.format(testStartTime)
- )
+ formatter12Hour.format(testStartTime),
+ ),
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -235,8 +235,8 @@
QSTileState.ActivationState.ACTIVE,
context.getString(
R.string.quick_settings_secondary_label_until,
- formatter24Hour.format(testEndTime)
- )
+ formatter24Hour.format(testEndTime),
+ ),
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -252,8 +252,8 @@
QSTileState.ActivationState.ACTIVE,
context.getString(
R.string.quick_settings_secondary_label_until,
- formatter12Hour.format(testEndTime)
- )
+ formatter12Hour.format(testEndTime),
+ ),
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -270,15 +270,15 @@
QSTileState.ActivationState.ACTIVE,
context.getString(
R.string.quick_settings_secondary_label_until,
- formatter24Hour.format(testEndTime)
- )
+ formatter24Hour.format(testEndTime),
+ ),
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
private fun createNightDisplayTileState(
activationState: QSTileState.ActivationState,
- secondaryLabel: String?
+ secondaryLabel: String?,
): QSTileState {
val label = context.getString(R.string.quick_settings_night_display_label)
val iconRes =
@@ -289,7 +289,7 @@
if (TextUtils.isEmpty(secondaryLabel)) label
else TextUtils.concat(label, ", ", secondaryLabel)
return QSTileState(
- { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ Icon.Loaded(context.getDrawable(iconRes)!!, null),
iconRes,
label,
activationState,
@@ -299,7 +299,7 @@
null,
QSTileState.SideViewIcon.None,
QSTileState.EnabledState.ENABLED,
- Switch::class.qualifiedName
+ Switch::class.qualifiedName,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt
index 3189a9e..7782d2b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt
@@ -51,11 +51,11 @@
.apply {
addOverride(
com.android.internal.R.drawable.ic_qs_one_handed_mode,
- TestStubDrawable()
+ TestStubDrawable(),
)
}
.resources,
- context.theme
+ context.theme,
)
}
@@ -69,7 +69,7 @@
createOneHandedModeTileState(
QSTileState.ActivationState.INACTIVE,
subtitleArray[1],
- com.android.internal.R.drawable.ic_qs_one_handed_mode
+ com.android.internal.R.drawable.ic_qs_one_handed_mode,
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -84,7 +84,7 @@
createOneHandedModeTileState(
QSTileState.ActivationState.ACTIVE,
subtitleArray[2],
- com.android.internal.R.drawable.ic_qs_one_handed_mode
+ com.android.internal.R.drawable.ic_qs_one_handed_mode,
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -96,7 +96,7 @@
): QSTileState {
val label = context.getString(R.string.quick_settings_onehanded_label)
return QSTileState(
- { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ Icon.Loaded(context.getDrawable(iconRes)!!, null),
iconRes,
label,
activationState,
@@ -106,7 +106,7 @@
null,
QSTileState.SideViewIcon.None,
QSTileState.EnabledState.ENABLED,
- Switch::class.qualifiedName
+ Switch::class.qualifiedName,
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt
index 08e5cbe..ed33250 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt
@@ -49,11 +49,11 @@
.apply {
addOverride(
com.android.systemui.res.R.drawable.ic_qr_code_scanner,
- TestStubDrawable()
+ TestStubDrawable(),
)
}
.resources,
- context.theme
+ context.theme,
)
}
@@ -64,11 +64,7 @@
val outputState = mapper.map(config, inputModel)
- val expectedState =
- createQRCodeScannerTileState(
- QSTileState.ActivationState.INACTIVE,
- null,
- )
+ val expectedState = createQRCodeScannerTileState(QSTileState.ActivationState.INACTIVE, null)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -83,7 +79,7 @@
QSTileState.ActivationState.UNAVAILABLE,
context.getString(
com.android.systemui.res.R.string.qr_code_scanner_updating_secondary_label
- )
+ ),
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -94,12 +90,10 @@
): QSTileState {
val label = context.getString(com.android.systemui.res.R.string.qr_code_scanner_title)
return QSTileState(
- {
- Icon.Loaded(
- context.getDrawable(com.android.systemui.res.R.drawable.ic_qr_code_scanner)!!,
- null
- )
- },
+ Icon.Loaded(
+ context.getDrawable(com.android.systemui.res.R.drawable.ic_qr_code_scanner)!!,
+ null,
+ ),
com.android.systemui.res.R.drawable.ic_qr_code_scanner,
label,
activationState,
@@ -109,7 +103,7 @@
null,
QSTileState.SideViewIcon.Chevron,
QSTileState.EnabledState.ENABLED,
- Switch::class.qualifiedName
+ Switch::class.qualifiedName,
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt
index ca30e9c..85111fd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt
@@ -51,7 +51,7 @@
addOverride(R.drawable.qs_extra_dim_icon_off, TestStubDrawable())
}
.resources,
- context.theme
+ context.theme,
)
}
@@ -61,10 +61,7 @@
val outputState = mapper.map(config, inputModel)
- val expectedState =
- createReduceBrightColorsTileState(
- QSTileState.ActivationState.INACTIVE,
- )
+ val expectedState = createReduceBrightColorsTileState(QSTileState.ActivationState.INACTIVE)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -79,7 +76,7 @@
}
private fun createReduceBrightColorsTileState(
- activationState: QSTileState.ActivationState,
+ activationState: QSTileState.ActivationState
): QSTileState {
val label =
context.getString(com.android.internal.R.string.reduce_bright_colors_feature_name)
@@ -88,7 +85,7 @@
R.drawable.qs_extra_dim_icon_on
else R.drawable.qs_extra_dim_icon_off
return QSTileState(
- { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ Icon.Loaded(context.getDrawable(iconRes)!!, null),
iconRes,
label,
activationState,
@@ -101,7 +98,7 @@
null,
QSTileState.SideViewIcon.None,
QSTileState.EnabledState.ENABLED,
- Switch::class.qualifiedName
+ Switch::class.qualifiedName,
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt
index 3e40c5c..53671ba 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt
@@ -66,13 +66,13 @@
addOverride(com.android.internal.R.bool.config_allowRotationResolver, true)
addOverride(
com.android.internal.R.array.config_foldedDeviceStates,
- intArrayOf() // empty array <=> device is not foldable
+ intArrayOf(), // empty array <=> device is not foldable
)
}
.resources,
context.theme,
devicePostureController,
- deviceStateManager
+ deviceStateManager,
)
}
@@ -86,7 +86,7 @@
createRotationLockTileState(
QSTileState.ActivationState.ACTIVE,
EMPTY_SECONDARY_STRING,
- R.drawable.qs_auto_rotate_icon_on
+ R.drawable.qs_auto_rotate_icon_on,
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -101,7 +101,7 @@
createRotationLockTileState(
QSTileState.ActivationState.ACTIVE,
context.getString(R.string.rotation_lock_camera_rotation_on),
- R.drawable.qs_auto_rotate_icon_on
+ R.drawable.qs_auto_rotate_icon_on,
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -116,7 +116,7 @@
createRotationLockTileState(
QSTileState.ActivationState.INACTIVE,
EMPTY_SECONDARY_STRING,
- R.drawable.qs_auto_rotate_icon_off
+ R.drawable.qs_auto_rotate_icon_off,
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -167,7 +167,7 @@
mapper.apply {
overrideResource(
com.android.internal.R.array.config_foldedDeviceStates,
- intArrayOf(1, 2, 3)
+ intArrayOf(1, 2, 3),
)
}
whenever(deviceStateManager.supportedDeviceStates).thenReturn(kosmos.foldedDeviceStateList)
@@ -176,11 +176,11 @@
private fun createRotationLockTileState(
activationState: QSTileState.ActivationState,
secondaryLabel: String,
- iconRes: Int
+ iconRes: Int,
): QSTileState {
val label = context.getString(R.string.quick_settings_rotation_unlocked_label)
return QSTileState(
- { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ Icon.Loaded(context.getDrawable(iconRes)!!, null),
iconRes,
label,
activationState,
@@ -190,7 +190,7 @@
secondaryLabel,
QSTileState.SideViewIcon.None,
QSTileState.EnabledState.ENABLED,
- Switch::class.qualifiedName
+ Switch::class.qualifiedName,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt
index 9bb6141..9a45065 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt
@@ -46,7 +46,7 @@
addOverride(R.drawable.qs_data_saver_icon_on, TestStubDrawable())
}
.resources,
- context.theme
+ context.theme,
)
}
@@ -59,7 +59,7 @@
val expectedState =
createDataSaverTileState(
QSTileState.ActivationState.ACTIVE,
- R.drawable.qs_data_saver_icon_on
+ R.drawable.qs_data_saver_icon_on,
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -73,14 +73,14 @@
val expectedState =
createDataSaverTileState(
QSTileState.ActivationState.INACTIVE,
- R.drawable.qs_data_saver_icon_off
+ R.drawable.qs_data_saver_icon_off,
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
private fun createDataSaverTileState(
activationState: QSTileState.ActivationState,
- iconRes: Int
+ iconRes: Int,
): QSTileState {
val label = context.getString(R.string.data_saver)
val secondaryLabel =
@@ -91,7 +91,7 @@
else context.resources.getStringArray(R.array.tile_states_saver)[0]
return QSTileState(
- { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ Icon.Loaded(context.getDrawable(iconRes)!!, null),
iconRes,
label,
activationState,
@@ -101,7 +101,7 @@
null,
QSTileState.SideViewIcon.None,
QSTileState.EnabledState.ENABLED,
- Switch::class.qualifiedName
+ Switch::class.qualifiedName,
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt
index 336b566..cd683c4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt
@@ -52,7 +52,7 @@
addOverride(R.drawable.qs_screen_record_icon_off, TestStubDrawable())
}
.resources,
- context.theme
+ context.theme,
)
}
@@ -82,7 +82,7 @@
createScreenRecordTileState(
QSTileState.ActivationState.ACTIVE,
R.drawable.qs_screen_record_icon_on,
- String.format("%d...", timeLeft)
+ String.format("%d...", timeLeft),
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -110,7 +110,7 @@
val label = context.getString(R.string.quick_settings_screen_record_label)
return QSTileState(
- { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ Icon.Loaded(context.getDrawable(iconRes)!!, null),
iconRes,
label,
activationState,
@@ -123,7 +123,7 @@
QSTileState.SideViewIcon.Chevron
else QSTileState.SideViewIcon.None,
QSTileState.EnabledState.ENABLED,
- Switch::class.qualifiedName
+ Switch::class.qualifiedName,
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt
index b08f39b..c569403 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt
@@ -56,7 +56,7 @@
context.getString(R.string.quick_settings_camera_mic_available),
R.drawable.qs_camera_access_icon_on,
null,
- CAMERA
+ CAMERA,
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -74,7 +74,7 @@
context.getString(R.string.quick_settings_camera_mic_blocked),
R.drawable.qs_camera_access_icon_off,
null,
- CAMERA
+ CAMERA,
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -92,7 +92,7 @@
context.getString(R.string.quick_settings_camera_mic_available),
R.drawable.qs_mic_access_on,
null,
- MICROPHONE
+ MICROPHONE,
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -110,7 +110,7 @@
context.getString(R.string.quick_settings_camera_mic_blocked),
R.drawable.qs_mic_access_off,
null,
- MICROPHONE
+ MICROPHONE,
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -146,7 +146,7 @@
else context.getString(R.string.quick_settings_mic_label)
return QSTileState(
- { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ Icon.Loaded(context.getDrawable(iconRes)!!, null),
iconRes,
label,
activationState,
@@ -156,7 +156,7 @@
stateDescription,
QSTileState.SideViewIcon.None,
QSTileState.EnabledState.ENABLED,
- Switch::class.qualifiedName
+ Switch::class.qualifiedName,
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt
index c021caa..0d2ebe4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt
@@ -49,7 +49,7 @@
addOverride(R.drawable.qs_light_dark_theme_icon_on, TestStubDrawable())
}
.resources,
- context.theme
+ context.theme,
)
}
@@ -69,7 +69,7 @@
expandedAccessibilityClass: KClass<out View>? = Switch::class,
): QSTileState {
return QSTileState(
- { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ Icon.Loaded(context.getDrawable(iconRes)!!, null),
iconRes,
label,
activationState,
@@ -79,7 +79,7 @@
stateDescription,
sideViewIcon,
enabledState,
- expandedAccessibilityClass?.qualifiedName
+ expandedAccessibilityClass?.qualifiedName,
)
}
@@ -98,7 +98,7 @@
createUiNightModeTileState(
activationState = QSTileState.ActivationState.UNAVAILABLE,
secondaryLabel = expectedSecondaryLabel,
- contentDescription = expectedContentDescription
+ contentDescription = expectedContentDescription,
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -118,7 +118,7 @@
createUiNightModeTileState(
activationState = QSTileState.ActivationState.UNAVAILABLE,
secondaryLabel = expectedSecondaryLabel,
- contentDescription = expectedContentDescription
+ contentDescription = expectedContentDescription,
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -136,7 +136,7 @@
activationState = QSTileState.ActivationState.INACTIVE,
label = expectedLabel,
secondaryLabel = expectedSecondaryLabel,
- contentDescription = expectedLabel
+ contentDescription = expectedLabel,
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -155,7 +155,7 @@
label = expectedLabel,
secondaryLabel = expectedSecondaryLabel,
activationState = QSTileState.ActivationState.ACTIVE,
- contentDescription = expectedLabel
+ contentDescription = expectedLabel,
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -174,7 +174,7 @@
label = expectedLabel,
secondaryLabel = expectedSecondaryLabel,
activationState = QSTileState.ActivationState.ACTIVE,
- contentDescription = expectedLabel
+ contentDescription = expectedLabel,
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -193,7 +193,7 @@
label = expectedLabel,
secondaryLabel = expectedSecondaryLabel,
activationState = QSTileState.ActivationState.INACTIVE,
- contentDescription = expectedLabel
+ contentDescription = expectedLabel,
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -214,7 +214,7 @@
activationState = QSTileState.ActivationState.ACTIVE,
contentDescription = expectedLabel,
supportedActions =
- setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -237,7 +237,7 @@
secondaryLabel = expectedSecondaryLabel,
activationState = QSTileState.ActivationState.UNAVAILABLE,
contentDescription = expectedContentDescription,
- supportedActions = setOf(QSTileState.UserAction.LONG_CLICK)
+ supportedActions = setOf(QSTileState.UserAction.LONG_CLICK),
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -258,7 +258,7 @@
activationState = QSTileState.ActivationState.INACTIVE,
contentDescription = expectedLabel,
supportedActions =
- setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -279,7 +279,7 @@
secondaryLabel = expectedSecondaryLabel,
activationState = QSTileState.ActivationState.UNAVAILABLE,
contentDescription = TextUtils.concat(expectedLabel, ", ", expectedSecondaryLabel),
- supportedActions = setOf(QSTileState.UserAction.LONG_CLICK)
+ supportedActions = setOf(QSTileState.UserAction.LONG_CLICK),
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -300,7 +300,7 @@
secondaryLabel = expectedSecondaryLabel,
activationState = QSTileState.ActivationState.UNAVAILABLE,
contentDescription = TextUtils.concat(expectedLabel, ", ", expectedSecondaryLabel),
- supportedActions = setOf(QSTileState.UserAction.LONG_CLICK)
+ supportedActions = setOf(QSTileState.UserAction.LONG_CLICK),
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -312,7 +312,7 @@
nightMode = true,
powerSave = false,
isLocationEnabled = true,
- uiMode = UiModeManager.MODE_NIGHT_AUTO
+ uiMode = UiModeManager.MODE_NIGHT_AUTO,
)
val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
@@ -328,7 +328,7 @@
activationState = QSTileState.ActivationState.ACTIVE,
contentDescription = TextUtils.concat(expectedLabel, ", ", expectedSecondaryLabel),
supportedActions =
- setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -340,7 +340,7 @@
nightMode = false,
powerSave = false,
isLocationEnabled = true,
- uiMode = UiModeManager.MODE_NIGHT_AUTO
+ uiMode = UiModeManager.MODE_NIGHT_AUTO,
)
val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
@@ -356,7 +356,7 @@
activationState = QSTileState.ActivationState.INACTIVE,
contentDescription = TextUtils.concat(expectedLabel, ", ", expectedSecondaryLabel),
supportedActions =
- setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -379,7 +379,7 @@
activationState = QSTileState.ActivationState.ACTIVE,
contentDescription = expectedLabel,
supportedActions =
- setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -401,7 +401,7 @@
activationState = QSTileState.ActivationState.INACTIVE,
contentDescription = expectedLabel,
supportedActions =
- setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -413,7 +413,7 @@
nightMode = false,
powerSave = false,
uiMode = UiModeManager.MODE_NIGHT_CUSTOM,
- nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN
+ nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN,
)
val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
@@ -428,7 +428,7 @@
activationState = QSTileState.ActivationState.INACTIVE,
contentDescription = expectedLabel,
supportedActions =
- setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -440,7 +440,7 @@
nightMode = true,
powerSave = false,
uiMode = UiModeManager.MODE_NIGHT_CUSTOM,
- nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN
+ nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN,
)
val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
@@ -455,7 +455,7 @@
activationState = QSTileState.ActivationState.ACTIVE,
contentDescription = expectedLabel,
supportedActions =
- setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -467,7 +467,7 @@
nightMode = false,
powerSave = true,
uiMode = UiModeManager.MODE_NIGHT_CUSTOM,
- nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN
+ nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN,
)
val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
@@ -484,7 +484,7 @@
secondaryLabel = expectedSecondaryLabel,
activationState = QSTileState.ActivationState.UNAVAILABLE,
contentDescription = expectedContentDescription,
- supportedActions = setOf(QSTileState.UserAction.LONG_CLICK)
+ supportedActions = setOf(QSTileState.UserAction.LONG_CLICK),
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt
index e7bde681..86321ea 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt
@@ -56,7 +56,7 @@
whenever(
devicePolicyResourceManager.getString(
eq(DevicePolicyResources.Strings.SystemUi.QS_WORK_PROFILE_LABEL),
- any()
+ any(),
)
)
.thenReturn(testLabel)
@@ -66,12 +66,12 @@
.apply {
addOverride(
com.android.internal.R.drawable.stat_sys_managed_profile_status,
- TestStubDrawable()
+ TestStubDrawable(),
)
}
.resources,
context.theme,
- devicePolicyManager
+ devicePolicyManager,
)
}
@@ -105,13 +105,11 @@
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
- private fun createWorkModeTileState(
- activationState: QSTileState.ActivationState,
- ): QSTileState {
+ private fun createWorkModeTileState(activationState: QSTileState.ActivationState): QSTileState {
val label = testLabel
val iconRes = com.android.internal.R.drawable.stat_sys_managed_profile_status
return QSTileState(
- icon = { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ icon = Icon.Loaded(context.getDrawable(iconRes)!!, null),
iconRes = iconRes,
label = label,
activationState = activationState,
@@ -134,7 +132,7 @@
stateDescription = null,
sideViewIcon = QSTileState.SideViewIcon.None,
enabledState = QSTileState.EnabledState.ENABLED,
- expandedAccessibilityClassName = Switch::class.qualifiedName
+ expandedAccessibilityClassName = Switch::class.qualifiedName,
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
index c33e2a4..954215ee 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
@@ -184,10 +184,7 @@
{
object : QSTileDataToStateMapper<String> {
override fun map(config: QSTileConfig, data: String): QSTileState =
- QSTileState.build(
- { Icon.Resource(0, ContentDescription.Resource(0)) },
- data
- ) {}
+ QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), data) {}
}
},
disabledByPolicyInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
index 7955f2f..0219a4c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
@@ -104,7 +104,7 @@
eq(tileConfig.tileSpec),
eq(userAction),
any(),
- eq("initial_data")
+ eq("initial_data"),
)
verify(qsTileAnalytics).trackUserAction(eq(tileConfig), eq(userAction))
}
@@ -130,7 +130,7 @@
.logUserActionRejectedByPolicy(
eq(userAction),
eq(tileConfig.tileSpec),
- eq(DISABLED_RESTRICTION)
+ eq(DISABLED_RESTRICTION),
)
verify(qsTileAnalytics, never()).trackUserAction(any(), any())
}
@@ -159,7 +159,7 @@
.logUserActionRejectedByPolicy(
eq(userAction),
eq(tileConfig.tileSpec),
- eq(DISABLED_RESTRICTION)
+ eq(DISABLED_RESTRICTION),
)
verify(qsTileAnalytics, never()).trackUserAction(any(), any())
}
@@ -174,7 +174,7 @@
QSTilePolicy.Restricted(
listOf(
DISABLED_RESTRICTION,
- FakeDisabledByPolicyInteractor.DISABLED_RESTRICTION_2
+ FakeDisabledByPolicyInteractor.DISABLED_RESTRICTION_2,
)
)
}
@@ -194,13 +194,13 @@
.logUserActionRejectedByPolicy(
eq(userAction),
eq(tileConfig.tileSpec),
- eq(DISABLED_RESTRICTION)
+ eq(DISABLED_RESTRICTION),
)
verify(qsTileLogger, never())
.logUserActionRejectedByPolicy(
eq(userAction),
eq(tileConfig.tileSpec),
- eq(FakeDisabledByPolicyInteractor.DISABLED_RESTRICTION_2)
+ eq(FakeDisabledByPolicyInteractor.DISABLED_RESTRICTION_2),
)
verify(qsTileAnalytics, never()).trackUserAction(any(), any())
}
@@ -243,10 +243,7 @@
{
object : QSTileDataToStateMapper<String> {
override fun map(config: QSTileConfig, data: String): QSTileState =
- QSTileState.build(
- { Icon.Resource(0, ContentDescription.Resource(0)) },
- data
- ) {}
+ QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), data) {}
}
},
disabledByPolicyInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
index 22913f1..8769022 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
@@ -26,7 +26,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.domain.interactor.displayStateInteractor
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.display.data.repository.displayStateRepository
import com.android.systemui.dump.DumpManager
@@ -101,7 +101,7 @@
private val fakeConfigurationRepository =
FakeConfigurationRepository().apply { onConfigurationChange(configuration) }
- private val configurationInteractor = ConfigurationInteractor(fakeConfigurationRepository)
+ private val configurationInteractor = ConfigurationInteractorImpl(fakeConfigurationRepository)
private val mockAsyncLayoutInflater =
mock<AsyncLayoutInflater>() {
@@ -151,10 +151,7 @@
inOrder.verify(qsImpl!!).onCreate(nullable())
inOrder
.verify(qsImpl!!)
- .onComponentCreated(
- eq(qsSceneComponentFactory.components[0]),
- any(),
- )
+ .onComponentCreated(eq(qsSceneComponentFactory.components[0]), any())
}
@Test
@@ -422,10 +419,7 @@
inOrder.verify(newQSImpl).onCreate(nullable())
inOrder
.verify(newQSImpl)
- .onComponentCreated(
- qsSceneComponentFactory.components[1],
- bundleArgCaptor.value,
- )
+ .onComponentCreated(qsSceneComponentFactory.components[1], bundleArgCaptor.value)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 319f1e5..3be8a38 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -44,6 +44,7 @@
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.ui.viewmodel.lockscreenUserActionsViewModel
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
@@ -153,7 +154,7 @@
@Test
fun swipeUpOnLockscreen_enterCorrectPin_unlocksDevice() =
kosmos.runTest {
- val actions by testScope.collectLastValue(kosmos.lockscreenUserActionsViewModel.actions)
+ val actions by collectLastValue(kosmos.lockscreenUserActionsViewModel.actions)
val upDestinationSceneKey =
(actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
@@ -170,7 +171,7 @@
kosmos.runTest {
setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
- val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions)
+ val actions by collectLastValue(lockscreenUserActionsViewModel.actions)
val upDestinationSceneKey =
(actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
@@ -180,7 +181,7 @@
@Test
fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
kosmos.runTest {
- val actions by testScope.collectLastValue(shadeUserActionsViewModel.actions)
+ val actions by collectLastValue(shadeUserActionsViewModel.actions)
setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
assertCurrentScene(Scenes.Lockscreen)
@@ -197,8 +198,8 @@
@Test
fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenDismissed_goesToGone() =
kosmos.runTest {
- val actions by testScope.collectLastValue(shadeUserActionsViewModel.actions)
- val canSwipeToEnter by testScope.collectLastValue(deviceEntryInteractor.canSwipeToEnter)
+ val actions by collectLastValue(shadeUserActionsViewModel.actions)
+ val canSwipeToEnter by collectLastValue(deviceEntryInteractor.canSwipeToEnter)
setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
@@ -279,7 +280,7 @@
fun swipeUpOnLockscreenWhileUnlocked_dismissesLockscreen() =
kosmos.runTest {
unlockDevice()
- val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions)
+ val actions by collectLastValue(lockscreenUserActionsViewModel.actions)
val upDestinationSceneKey =
(actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
@@ -302,7 +303,7 @@
fun dismissingIme_whileOnPasswordBouncer_navigatesToLockscreen() =
kosmos.runTest {
setAuthMethod(AuthenticationMethodModel.Password)
- val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions)
+ val actions by collectLastValue(lockscreenUserActionsViewModel.actions)
val upDestinationSceneKey =
(actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
@@ -319,14 +320,13 @@
fun bouncerActionButtonClick_opensEmergencyServicesDialer() =
kosmos.runTest {
setAuthMethod(AuthenticationMethodModel.Password)
- val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions)
+ val actions by collectLastValue(lockscreenUserActionsViewModel.actions)
val upDestinationSceneKey =
(actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
emulateUserDrivenTransition(to = upDestinationSceneKey)
- val bouncerActionButton by
- testScope.collectLastValue(bouncerSceneContentViewModel.actionButton)
+ val bouncerActionButton by collectLastValue(bouncerSceneContentViewModel.actionButton)
assertWithMessage("Bouncer action button not visible")
.that(bouncerActionButton)
.isNotNull()
@@ -341,14 +341,13 @@
kosmos.runTest {
setAuthMethod(AuthenticationMethodModel.Password)
startPhoneCall()
- val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions)
+ val actions by collectLastValue(lockscreenUserActionsViewModel.actions)
val upDestinationSceneKey =
(actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
emulateUserDrivenTransition(to = upDestinationSceneKey)
- val bouncerActionButton by
- testScope.collectLastValue(bouncerSceneContentViewModel.actionButton)
+ val bouncerActionButton by collectLastValue(bouncerSceneContentViewModel.actionButton)
assertWithMessage("Bouncer action button not visible during call")
.that(bouncerActionButton)
.isNotNull()
@@ -574,7 +573,7 @@
.that(getCurrentSceneInUi())
.isEqualTo(Scenes.Bouncer)
val authMethodViewModel by
- testScope.collectLastValue(bouncerSceneContentViewModel.authMethodViewModel)
+ collectLastValue(bouncerSceneContentViewModel.authMethodViewModel)
assertWithMessage("Cannot enter PIN when not using a PIN authentication method!")
.that(authMethodViewModel)
.isInstanceOf(PinBouncerViewModel::class.java)
@@ -603,7 +602,7 @@
.that(getCurrentSceneInUi())
.isEqualTo(Scenes.Bouncer)
val authMethodViewModel by
- testScope.collectLastValue(bouncerSceneContentViewModel.authMethodViewModel)
+ collectLastValue(bouncerSceneContentViewModel.authMethodViewModel)
assertWithMessage("Cannot enter PIN when not using a PIN authentication method!")
.that(authMethodViewModel)
.isInstanceOf(PinBouncerViewModel::class.java)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
index bfe5ef7..db2297c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
@@ -32,7 +32,7 @@
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
import com.android.systemui.statusbar.NotificationPresenter
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.init.NotificationsController
@@ -69,7 +69,7 @@
private val notificationPresenter = mock<NotificationPresenter>()
private val notificationsController = mock<NotificationsController>()
private val powerInteractor = PowerInteractorFactory.create().powerInteractor
- private val activeNotificationsRepository = ActiveNotificationListRepository()
+ private val activeNotificationsRepository = kosmos.activeNotificationListRepository
private val activeNotificationsInteractor =
ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt
index 4d71dc4..4871564 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt
@@ -18,6 +18,8 @@
import android.content.ComponentName
import android.graphics.Rect
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FREEFORM_FULL_SCREEN
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FREEFORM_MAXIMIZED
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FREE_FORM
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FULL_SCREEN
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.PIP
@@ -153,11 +155,23 @@
fun freeFormApps(
vararg tasks: TaskSpec,
focusedTaskId: Int,
+ maximizedTaskId: Int = -1,
shadeExpanded: Boolean = false,
): DisplayContentModel {
val freeFormTasks =
tasks
- .map { freeForm(it) }
+ .map {
+ freeForm(
+ task = it,
+ bounds =
+ if (it.taskId == maximizedTaskId) {
+ FREEFORM_MAXIMIZED
+ } else {
+ FREE_FORM
+ },
+ maxBounds = FREEFORM_FULL_SCREEN,
+ )
+ }
// Root tasks are ordered top-down in List<RootTaskInfo>.
// Sort 'focusedTaskId' last (Boolean natural ordering: [false, true])
.sortedBy { it.childTaskIds[0] != focusedTaskId }
@@ -180,9 +194,9 @@
val PIP = Rect(440, 1458, 1038, 1794)
val SPLIT_TOP = Rect(0, 0, 1080, 1187)
val SPLIT_BOTTOM = Rect(0, 1213, 1080, 2400)
- val FREE_FORM = Rect(119, 332, 1000, 1367)
// "Tablet" size
+ val FREE_FORM = Rect(119, 332, 1000, 1367)
val FREEFORM_FULL_SCREEN = Rect(0, 0, 2560, 1600)
val FREEFORM_MAXIMIZED = Rect(0, 48, 2560, 1480)
val FREEFORM_SPLIT_LEFT = Rect(0, 0, 1270, 1600)
@@ -301,11 +315,12 @@
}
/** An activity in FreeForm mode */
- fun freeForm(task: TaskSpec, bounds: Rect = FREE_FORM) =
+ fun freeForm(task: TaskSpec, bounds: Rect = FREE_FORM, maxBounds: Rect = bounds) =
newRootTaskInfo(
taskId = task.taskId,
userId = task.userId,
bounds = bounds,
+ maxBounds = maxBounds,
windowingMode = WindowingMode.Freeform,
topActivity = ComponentName.unflattenFromString(task.name),
) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/NewRootTaskInfo.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/NewRootTaskInfo.kt
index cedf0c8..4f6871e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/NewRootTaskInfo.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/NewRootTaskInfo.kt
@@ -84,6 +84,7 @@
activityType: ActivityType = Standard,
windowingMode: WindowingMode = FullScreen,
bounds: Rect = Rect(),
+ maxBounds: Rect = bounds,
topActivity: ComponentName? = null,
topActivityType: ActivityType = Standard,
numActivities: Int? = null,
@@ -94,6 +95,7 @@
setWindowingMode(windowingMode.toInt())
setActivityType(activityType.toInt())
setBounds(bounds)
+ setMaxBounds(maxBounds)
}
this.bounds = bounds
this.displayId = displayId
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt
index b7f565d..c884b9a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt
@@ -90,9 +90,11 @@
Matched(
PrivateProfilePolicy.NAME,
PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
- CaptureParameters(
+ LegacyCaptureParameters(
type = FullScreen(displayId = 0),
- component = ComponentName.unflattenFromString(YOUTUBE),
+ component =
+ ComponentName.unflattenFromString(YOUTUBE)
+ ?: error("Invalid component name"),
owner = UserHandle.of(PRIVATE),
),
)
@@ -142,7 +144,7 @@
Matched(
PrivateProfilePolicy.NAME,
PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
- CaptureParameters(
+ LegacyCaptureParameters(
type = FullScreen(displayId = 0),
component = ComponentName.unflattenFromString(YOUTUBE),
owner = UserHandle.of(PRIVATE),
@@ -167,7 +169,7 @@
Matched(
PrivateProfilePolicy.NAME,
PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
- CaptureParameters(
+ LegacyCaptureParameters(
type = FullScreen(displayId = 0),
component = ComponentName.unflattenFromString(FILES),
owner = UserHandle.of(PRIVATE),
@@ -188,7 +190,7 @@
Matched(
PrivateProfilePolicy.NAME,
PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
- CaptureParameters(
+ LegacyCaptureParameters(
type = FullScreen(displayId = 0),
component = ComponentName.unflattenFromString(YOUTUBE_PIP),
owner = UserHandle.of(PRIVATE),
@@ -212,7 +214,7 @@
Matched(
PrivateProfilePolicy.NAME,
PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
- CaptureParameters(
+ LegacyCaptureParameters(
type = FullScreen(displayId = 0),
component = ComponentName.unflattenFromString(YOUTUBE_PIP),
owner = UserHandle.of(PRIVATE),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/ScreenshotPolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/ScreenshotPolicyTest.kt
index 28eb9fc..948c24e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/ScreenshotPolicyTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/ScreenshotPolicyTest.kt
@@ -17,11 +17,11 @@
package com.android.systemui.screenshot.policy
import android.content.ComponentName
+import android.graphics.Rect
import android.os.UserHandle
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.FILES
-import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.LAUNCHER
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.MESSAGES
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.YOUTUBE
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FREEFORM_FULL_SCREEN
@@ -32,10 +32,10 @@
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.pictureInPictureApp
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.singleFullScreen
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.splitScreenApps
+import com.android.systemui.screenshot.data.model.allTasks
import com.android.systemui.screenshot.data.repository.profileTypeRepository
import com.android.systemui.screenshot.policy.CaptureType.FullScreen
import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask
-import com.android.systemui.screenshot.policy.CaptureType.RootTask
import com.android.systemui.screenshot.policy.TestUserIds.PERSONAL
import com.android.systemui.screenshot.policy.TestUserIds.PRIVATE
import com.android.systemui.screenshot.policy.TestUserIds.WORK
@@ -50,69 +50,81 @@
private val defaultComponent = ComponentName("default", "default")
private val defaultOwner = UserHandle.SYSTEM
+ private val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
@Test
fun fullScreen_work() = runTest {
- val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
+ val displayContent = singleFullScreen(TaskSpec(taskId = 1002, name = FILES, userId = WORK))
+ val expectedFocusedTask =
+ displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
- val result =
- policy.apply(
- singleFullScreen(TaskSpec(taskId = 1002, name = FILES, userId = WORK)),
- defaultComponent,
- defaultOwner,
- )
+ val result = policy.apply(displayContent, defaultComponent, defaultOwner)
assertThat(result)
.isEqualTo(
CaptureParameters(
type = IsolatedTask(taskId = 1002, taskBounds = FULL_SCREEN),
- component = ComponentName.unflattenFromString(FILES),
- owner = UserHandle.of(WORK),
+ contentTask =
+ TaskReference(
+ taskId = expectedFocusedTask.id,
+ component = expectedFocusedTask.componentName,
+ owner = UserHandle.of(expectedFocusedTask.userId),
+ bounds = expectedFocusedTask.bounds,
+ ),
+ owner = UserHandle.of(expectedFocusedTask.userId),
)
)
}
@Test
fun fullScreen_private() = runTest {
- val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
+ val displayContent =
+ singleFullScreen(TaskSpec(taskId = 1002, name = YOUTUBE, userId = PRIVATE))
+ val expectedFocusedTask =
+ displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
- val result =
- policy.apply(
- singleFullScreen(TaskSpec(taskId = 1002, name = YOUTUBE, userId = PRIVATE)),
- defaultComponent,
- defaultOwner,
- )
+ val result = policy.apply(displayContent, defaultComponent, defaultOwner)
assertThat(result)
.isEqualTo(
CaptureParameters(
type = FullScreen(displayId = 0),
- component = ComponentName.unflattenFromString(YOUTUBE),
- owner = UserHandle.of(PRIVATE),
+ contentTask =
+ TaskReference(
+ taskId = expectedFocusedTask.id,
+ component = expectedFocusedTask.componentName,
+ owner = UserHandle.of(expectedFocusedTask.userId),
+ bounds = expectedFocusedTask.bounds,
+ ),
+ owner = UserHandle.of(expectedFocusedTask.userId),
)
)
}
@Test
fun splitScreen_workAndPersonal() = runTest {
- val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
- val result =
- policy.apply(
- splitScreenApps(
- first = TaskSpec(taskId = 1002, name = FILES, userId = WORK),
- second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL),
- focusedTaskId = 1002,
- ),
- defaultComponent,
- defaultOwner,
+ val displayContent =
+ splitScreenApps(
+ first = TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+ second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL),
+ focusedTaskId = 1002,
)
+ val expectedFocusedTask =
+ displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
+
+ val result = policy.apply(displayContent, defaultComponent, defaultOwner)
assertThat(result)
.isEqualTo(
CaptureParameters(
type = FullScreen(displayId = 0),
- component = ComponentName.unflattenFromString(YOUTUBE),
+ contentTask =
+ TaskReference(
+ taskId = expectedFocusedTask.id,
+ component = expectedFocusedTask.componentName,
+ owner = UserHandle.of(expectedFocusedTask.userId),
+ bounds = expectedFocusedTask.bounds,
+ ),
owner = UserHandle.of(PERSONAL),
)
)
@@ -120,24 +132,28 @@
@Test
fun splitScreen_personalAndPrivate() = runTest {
- val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
- val result =
- policy.apply(
- splitScreenApps(
- first = TaskSpec(taskId = 1002, name = FILES, userId = PERSONAL),
- second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
- focusedTaskId = 1002,
- ),
- defaultComponent,
- defaultOwner,
+ val displayContent =
+ splitScreenApps(
+ first = TaskSpec(taskId = 1002, name = FILES, userId = PERSONAL),
+ second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
+ focusedTaskId = 1002,
)
+ val expectedFocusedTask =
+ displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
+
+ val result = policy.apply(displayContent, defaultComponent, defaultOwner)
assertThat(result)
.isEqualTo(
CaptureParameters(
type = FullScreen(displayId = 0),
- component = ComponentName.unflattenFromString(YOUTUBE),
+ contentTask =
+ TaskReference(
+ taskId = expectedFocusedTask.id,
+ component = expectedFocusedTask.componentName,
+ owner = UserHandle.of(expectedFocusedTask.userId),
+ bounds = expectedFocusedTask.bounds,
+ ),
owner = UserHandle.of(PRIVATE),
)
)
@@ -145,24 +161,28 @@
@Test
fun splitScreen_workAndPrivate() = runTest {
- val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
- val result =
- policy.apply(
- splitScreenApps(
- first = TaskSpec(taskId = 1002, name = FILES, userId = WORK),
- second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
- focusedTaskId = 1002,
- ),
- defaultComponent,
- defaultOwner,
+ val displayContent =
+ splitScreenApps(
+ first = TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+ second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
+ focusedTaskId = 1002,
)
+ val expectedFocusedTask =
+ displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
+
+ val result = policy.apply(displayContent, defaultComponent, defaultOwner)
assertThat(result)
.isEqualTo(
CaptureParameters(
type = FullScreen(displayId = 0),
- component = ComponentName.unflattenFromString(YOUTUBE),
+ contentTask =
+ TaskReference(
+ taskId = expectedFocusedTask.id,
+ component = expectedFocusedTask.componentName,
+ owner = UserHandle.of(expectedFocusedTask.userId),
+ bounds = expectedFocusedTask.bounds,
+ ),
owner = UserHandle.of(PRIVATE),
)
)
@@ -170,32 +190,31 @@
@Test
fun splitScreen_twoWorkTasks() = runTest {
- val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
- val result =
- policy.apply(
- splitScreenApps(
- parentTaskId = 1,
- parentBounds = FREEFORM_FULL_SCREEN,
- orientation = VERTICAL,
- first = TaskSpec(taskId = 1002, name = FILES, userId = WORK),
- second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = WORK),
- focusedTaskId = 1002,
- ),
- defaultComponent,
- defaultOwner,
+ val displayContent =
+ splitScreenApps(
+ parentTaskId = 1,
+ parentBounds = FREEFORM_FULL_SCREEN,
+ orientation = VERTICAL,
+ first = TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+ second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = WORK),
+ focusedTaskId = 1002,
)
+ val expectedFocusedTask =
+ displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
+
+ val result = policy.apply(displayContent, defaultComponent, defaultOwner)
assertThat(result)
.isEqualTo(
CaptureParameters(
- type =
- RootTask(
- parentTaskId = 1,
- taskBounds = FREEFORM_FULL_SCREEN,
- childTaskIds = listOf(1002, 1003),
+ type = IsolatedTask(taskBounds = FREEFORM_FULL_SCREEN, taskId = 1),
+ contentTask =
+ TaskReference(
+ taskId = expectedFocusedTask.id,
+ component = expectedFocusedTask.componentName,
+ owner = UserHandle.of(expectedFocusedTask.userId),
+ bounds = expectedFocusedTask.bounds,
),
- component = ComponentName.unflattenFromString(FILES),
owner = UserHandle.of(WORK),
)
)
@@ -203,99 +222,112 @@
@Test
fun freeform_floatingWindows() = runTest {
- val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
- val result =
- policy.apply(
- freeFormApps(
- TaskSpec(taskId = 1002, name = FILES, userId = WORK),
- TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL),
- focusedTaskId = 1003,
- ),
- defaultComponent,
- defaultOwner,
+ val displayContent =
+ freeFormApps(
+ TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+ TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL),
+ focusedTaskId = 1003,
)
+ val expectedFocusedTask =
+ displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1003 }
+
+ val result = policy.apply(displayContent, defaultComponent, defaultOwner)
assertThat(result)
.isEqualTo(
CaptureParameters(
type = FullScreen(displayId = 0),
- component = ComponentName.unflattenFromString(YOUTUBE),
+ contentTask =
+ TaskReference(
+ taskId = expectedFocusedTask.id,
+ component = expectedFocusedTask.componentName,
+ owner = UserHandle.of(expectedFocusedTask.userId),
+ bounds = expectedFocusedTask.bounds,
+ ),
owner = UserHandle.of(PERSONAL),
)
)
}
@Test
- fun freeform_floatingWindows_maximized() = runTest {
- val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
- val result =
- policy.apply(
- freeFormApps(
- TaskSpec(taskId = 1002, name = FILES, userId = WORK),
- TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL),
- focusedTaskId = 1003,
- ),
- defaultComponent,
- defaultOwner,
+ fun freeform_floatingWindows_work_maximized() = runTest {
+ val displayContent =
+ freeFormApps(
+ TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+ TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL),
+ focusedTaskId = 1002,
+ maximizedTaskId = 1002,
)
+ val expectedFocusedTask =
+ displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
+
+ val result = policy.apply(displayContent, defaultComponent, defaultOwner)
assertThat(result)
.isEqualTo(
CaptureParameters(
- type = FullScreen(displayId = 0),
- component = ComponentName.unflattenFromString(YOUTUBE),
- owner = UserHandle.of(PERSONAL),
+ type = IsolatedTask(taskId = 1002, taskBounds = expectedFocusedTask.bounds),
+ contentTask =
+ TaskReference(
+ taskId = expectedFocusedTask.id,
+ component = expectedFocusedTask.componentName,
+ owner = UserHandle.of(expectedFocusedTask.userId),
+ bounds = expectedFocusedTask.bounds,
+ ),
+ owner = UserHandle.of(WORK),
)
)
}
@Test
fun freeform_floatingWindows_withPrivate() = runTest {
- val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
- val result =
- policy.apply(
- freeFormApps(
- TaskSpec(taskId = 1002, name = FILES, userId = WORK),
- TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
- TaskSpec(taskId = 1004, name = MESSAGES, userId = PERSONAL),
- focusedTaskId = 1004,
- ),
- defaultComponent,
- defaultOwner,
+ val displayContent =
+ freeFormApps(
+ TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+ TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
+ TaskSpec(taskId = 1004, name = MESSAGES, userId = PERSONAL),
+ focusedTaskId = 1004,
)
+ val expectedFocusedTask = displayContent.allTasks().single { it.id == 1004 }
+
+ val result = policy.apply(displayContent, defaultComponent, defaultOwner)
assertThat(result)
.isEqualTo(
CaptureParameters(
type = FullScreen(displayId = 0),
- component = ComponentName.unflattenFromString(YOUTUBE),
+ contentTask =
+ TaskReference(
+ taskId = expectedFocusedTask.id,
+ component = expectedFocusedTask.componentName,
+ owner = UserHandle.of(expectedFocusedTask.userId),
+ bounds = expectedFocusedTask.bounds,
+ ),
owner = UserHandle.of(PRIVATE),
)
)
}
@Test
- fun freeform_floating_workOnly() = runTest {
- val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
+ fun freeform_floating_work() = runTest {
+ val displayContent =
+ freeFormApps(TaskSpec(taskId = 1002, name = FILES, userId = WORK), focusedTaskId = 1002)
+ val expectedFocusedTask =
+ displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
- val result =
- policy.apply(
- freeFormApps(
- TaskSpec(taskId = 1002, name = FILES, userId = WORK),
- focusedTaskId = 1002,
- ),
- defaultComponent,
- defaultOwner,
- )
+ val result = policy.apply(displayContent, defaultComponent, defaultOwner)
assertThat(result)
.isEqualTo(
CaptureParameters(
type = FullScreen(displayId = 0),
- component = ComponentName.unflattenFromString(LAUNCHER),
+ contentTask =
+ TaskReference(
+ taskId = expectedFocusedTask.id,
+ component = expectedFocusedTask.componentName,
+ owner = UserHandle.of(expectedFocusedTask.userId),
+ bounds = expectedFocusedTask.bounds,
+ ),
owner = defaultOwner,
)
)
@@ -303,23 +335,27 @@
@Test
fun fullScreen_shadeExpanded() = runTest {
- val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
- val result =
- policy.apply(
- singleFullScreen(
- TaskSpec(taskId = 1002, name = FILES, userId = WORK),
- shadeExpanded = true,
- ),
- defaultComponent,
- defaultOwner,
+ val displayContent =
+ singleFullScreen(
+ TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+ shadeExpanded = true,
)
+ val expectedFocusedTask =
+ displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
+
+ val result = policy.apply(displayContent, defaultComponent, defaultOwner)
assertThat(result)
.isEqualTo(
CaptureParameters(
type = FullScreen(displayId = 0),
- component = defaultComponent,
+ contentTask =
+ TaskReference(
+ taskId = -1,
+ component = defaultComponent,
+ owner = defaultOwner,
+ bounds = Rect(),
+ ),
owner = defaultOwner,
)
)
@@ -327,25 +363,55 @@
@Test
fun fullScreen_with_PictureInPicture() = runTest {
- val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
- val result =
- policy.apply(
- pictureInPictureApp(
- pip = TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL),
- fullScreen = TaskSpec(taskId = 1003, name = FILES, userId = WORK),
- ),
- defaultComponent,
- defaultOwner,
+ val displayContent =
+ pictureInPictureApp(
+ pip = TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL),
+ fullScreen = TaskSpec(taskId = 1003, name = FILES, userId = WORK),
)
+ val expectedFocusedTask = displayContent.allTasks().single { it.id == 1003 }
+
+ val result = policy.apply(displayContent, defaultComponent, defaultOwner)
assertThat(result)
.isEqualTo(
CaptureParameters(
type = IsolatedTask(taskId = 1003, taskBounds = FULL_SCREEN),
- component = ComponentName.unflattenFromString(FILES),
+ contentTask =
+ TaskReference(
+ taskId = expectedFocusedTask.id,
+ component = expectedFocusedTask.componentName,
+ owner = UserHandle.of(expectedFocusedTask.userId),
+ bounds = expectedFocusedTask.bounds,
+ ),
owner = UserHandle.of(WORK),
)
)
}
+
+ // TODO: PiP tasks should affect ownership (e.g. Private)
+ @Test
+ fun fullScreen_with_PictureInPicture_private() = runTest {
+ val displayContent =
+ pictureInPictureApp(
+ pip = TaskSpec(taskId = 1002, name = YOUTUBE, userId = PRIVATE),
+ fullScreen = TaskSpec(taskId = 1003, name = FILES, userId = PERSONAL),
+ )
+ val expectedFocusedTask = displayContent.allTasks().single { it.id == 1003 }
+
+ val result = policy.apply(displayContent, defaultComponent, defaultOwner)
+ assertThat(result)
+ .isEqualTo(
+ CaptureParameters(
+ type = FullScreen(displayId = 0),
+ contentTask =
+ TaskReference(
+ taskId = expectedFocusedTask.id,
+ component = expectedFocusedTask.componentName,
+ owner = UserHandle.of(expectedFocusedTask.userId),
+ bounds = expectedFocusedTask.bounds,
+ ),
+ owner = UserHandle.of(PRIVATE),
+ )
+ )
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
index 30a786c..c1477fe 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
@@ -135,7 +135,7 @@
PolicyResult.Matched(
policy = WorkProfilePolicy.NAME,
reason = WORK_TASK_IS_TOP,
- CaptureParameters(
+ LegacyCaptureParameters(
type = IsolatedTask(taskId = 1002, taskBounds = FULL_SCREEN),
component = ComponentName.unflattenFromString(FILES),
owner = UserHandle.of(WORK),
@@ -162,7 +162,7 @@
PolicyResult.Matched(
policy = WorkProfilePolicy.NAME,
reason = WORK_TASK_IS_TOP,
- CaptureParameters(
+ LegacyCaptureParameters(
type = IsolatedTask(taskId = 1002, taskBounds = FULL_SCREEN.splitTop(20)),
component = ComponentName.unflattenFromString(FILES),
owner = UserHandle.of(WORK),
@@ -200,7 +200,7 @@
PolicyResult.Matched(
policy = WorkProfilePolicy.NAME,
reason = WORK_TASK_IS_TOP,
- CaptureParameters(
+ LegacyCaptureParameters(
type = IsolatedTask(taskId = 1003, taskBounds = FULL_SCREEN),
component = ComponentName.unflattenFromString(FILES),
owner = UserHandle.of(WORK),
@@ -226,7 +226,7 @@
PolicyResult.Matched(
policy = WorkProfilePolicy.NAME,
reason = WORK_TASK_IS_TOP,
- CaptureParameters(
+ LegacyCaptureParameters(
type = IsolatedTask(taskId = 1003, taskBounds = FREE_FORM),
component = ComponentName.unflattenFromString(FILES),
owner = UserHandle.of(WORK),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 59d0d70..041d1a6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -238,7 +238,7 @@
primaryBouncerInteractor,
alternateBouncerInteractor,
mock(BouncerViewBinder::class.java),
- mock(ConfigurationForwarder::class.java),
+ { mock(ConfigurationForwarder::class.java) },
brightnessMirrorShowingInteractor,
)
underTest.setupExpandedStatusBar()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 9b91fc7..5d1ce7c5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -203,7 +203,7 @@
primaryBouncerInteractor,
alternateBouncerInteractor,
mock(),
- configurationForwarder,
+ { configurationForwarder },
brightnessMirrorShowingInteractor,
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index 2e759a3..443595d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -17,7 +17,6 @@
package com.android.systemui.shade;
import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
-import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
@@ -63,8 +62,6 @@
import com.android.systemui.statusbar.QsFrameTranslateController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository;
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
-import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.DozeParameters;
@@ -159,8 +156,6 @@
protected SysuiStatusBarStateController mStatusBarStateController;
protected ShadeInteractor mShadeInteractor;
- protected ActiveNotificationsInteractor mActiveNotificationsInteractor;
-
protected Handler mMainHandler;
protected LockscreenShadeTransitionController.Callback mLockscreenShadeTransitionCallback;
@@ -204,11 +199,6 @@
),
mKosmos.getShadeModeInteractor());
- mActiveNotificationsInteractor = new ActiveNotificationsInteractor(
- new ActiveNotificationListRepository(),
- StandardTestDispatcher(/* scheduler = */ null, /* name = */ null)
- );
-
KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext);
keyguardStatusView.setId(R.id.keyguard_status_view);
@@ -277,7 +267,7 @@
mock(DeviceEntryFaceAuthInteractor.class),
mShadeRepository,
mShadeInteractor,
- mActiveNotificationsInteractor,
+ mKosmos.getActiveNotificationsInteractor(),
new JavaAdapter(mTestScope.getBackgroundScope()),
mCastController,
splitShadeStateController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
index 943fb62..0f476d0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
@@ -35,8 +35,7 @@
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.NotificationShadeWindowController
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
-import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.row.NotificationGutsManager
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.policy.DeviceProvisionedController
@@ -50,7 +49,6 @@
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import dagger.Lazy
-import kotlinx.coroutines.test.StandardTestDispatcher
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -65,8 +63,6 @@
@SmallTest
class ShadeControllerImplTest : SysuiTestCase() {
private val executor = FakeExecutor(FakeSystemClock())
- private val testDispatcher = StandardTestDispatcher()
- private val activeNotificationsRepository = ActiveNotificationListRepository()
private val kosmos = Kosmos()
private val testScope = kosmos.testScope
@@ -95,7 +91,7 @@
FakeKeyguardRepository(),
headsUpManager,
PowerInteractorFactory.create().powerInteractor,
- ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher),
+ kosmos.activeNotificationsInteractor,
kosmos::sceneInteractor,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
index ce9b3be..7842d75 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
@@ -31,6 +31,7 @@
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.concurrency.Execution
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.withArgCaptor
@@ -52,6 +53,10 @@
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class CommunalSmartspaceControllerTest : SysuiTestCase() {
+ @Mock private lateinit var userTracker: UserTracker
+
+ @Mock private lateinit var userContextPrimary: Context
+
@Mock private lateinit var smartspaceManager: SmartspaceManager
@Mock private lateinit var execution: Execution
@@ -113,15 +118,18 @@
MockitoAnnotations.initMocks(this)
`when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(session)
+ `when`(userTracker.userContext).thenReturn(userContextPrimary)
+ `when`(userContextPrimary.getSystemService(SmartspaceManager::class.java))
+ .thenReturn(smartspaceManager)
+
controller =
CommunalSmartspaceController(
- context,
- smartspaceManager,
+ userTracker,
execution,
uiExecutor,
precondition,
Optional.of(targetFilter),
- Optional.of(plugin)
+ Optional.of(plugin),
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
index e774aed..c83c82d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
@@ -33,6 +33,7 @@
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.settings.UserTracker
import com.android.systemui.smartspace.dagger.SmartspaceViewComponent
import com.android.systemui.util.concurrency.Execution
import com.android.systemui.util.mockito.any
@@ -46,66 +47,51 @@
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito
+import org.mockito.Mockito.anyInt
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
-import org.mockito.Mockito.anyInt
import org.mockito.MockitoAnnotations
-import org.mockito.Spy
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class DreamSmartspaceControllerTest : SysuiTestCase() {
- @Mock
- private lateinit var smartspaceManager: SmartspaceManager
+ @Mock private lateinit var userTracker: UserTracker
- @Mock
- private lateinit var execution: Execution
+ @Mock private lateinit var userContextPrimary: Context
- @Mock
- private lateinit var uiExecutor: Executor
+ @Mock private lateinit var smartspaceManager: SmartspaceManager
- @Mock
- private lateinit var viewComponentFactory: SmartspaceViewComponent.Factory
+ @Mock private lateinit var execution: Execution
- @Mock
- private lateinit var viewComponent: SmartspaceViewComponent
+ @Mock private lateinit var uiExecutor: Executor
- @Mock
- private lateinit var weatherViewComponent: SmartspaceViewComponent
+ @Mock private lateinit var viewComponentFactory: SmartspaceViewComponent.Factory
- private val weatherSmartspaceView: SmartspaceView by lazy {
- Mockito.spy(TestView(context))
- }
+ @Mock private lateinit var viewComponent: SmartspaceViewComponent
- @Mock
- private lateinit var targetFilter: SmartspaceTargetFilter
+ @Mock private lateinit var weatherViewComponent: SmartspaceViewComponent
- @Mock
- private lateinit var plugin: BcSmartspaceDataPlugin
+ private val weatherSmartspaceView: SmartspaceView by lazy { Mockito.spy(TestView(context)) }
- @Mock
- private lateinit var weatherPlugin: BcSmartspaceDataPlugin
+ @Mock private lateinit var targetFilter: SmartspaceTargetFilter
- @Mock
- private lateinit var precondition: SmartspacePrecondition
+ @Mock private lateinit var plugin: BcSmartspaceDataPlugin
- private val smartspaceView: SmartspaceView by lazy {
- Mockito.spy(TestView(context))
- }
+ @Mock private lateinit var weatherPlugin: BcSmartspaceDataPlugin
- @Mock
- private lateinit var listener: BcSmartspaceDataPlugin.SmartspaceTargetListener
+ @Mock private lateinit var precondition: SmartspacePrecondition
- @Mock
- private lateinit var session: SmartspaceSession
+ private val smartspaceView: SmartspaceView by lazy { Mockito.spy(TestView(context)) }
+
+ @Mock private lateinit var listener: BcSmartspaceDataPlugin.SmartspaceTargetListener
+
+ @Mock private lateinit var session: SmartspaceSession
private lateinit var controller: DreamSmartspaceController
// TODO(b/272811280): Remove usage of real view
- private val fakeParent by lazy {
- FrameLayout(context)
- }
+ private val fakeParent by lazy { FrameLayout(context) }
/**
* A class which implements SmartspaceView and extends View. This is mocked to provide the right
@@ -134,30 +120,44 @@
override fun setMediaTarget(target: SmartspaceTarget?) {}
- override fun getSelectedPage(): Int { return 0; }
+ override fun getSelectedPage(): Int {
+ return 0
+ }
- override fun getCurrentCardTopPadding(): Int { return 0; }
+ override fun getCurrentCardTopPadding(): Int {
+ return 0
+ }
}
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
`when`(viewComponentFactory.create(any(), eq(plugin), any(), eq(null)))
- .thenReturn(viewComponent)
+ .thenReturn(viewComponent)
`when`(viewComponent.getView()).thenReturn(smartspaceView)
`when`(viewComponentFactory.create(any(), eq(weatherPlugin), any(), any()))
.thenReturn(weatherViewComponent)
`when`(weatherViewComponent.getView()).thenReturn(weatherSmartspaceView)
`when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(session)
- controller = DreamSmartspaceController(context, smartspaceManager, execution, uiExecutor,
- viewComponentFactory, precondition, Optional.of(targetFilter), Optional.of(plugin),
- Optional.of(weatherPlugin))
+ `when`(userTracker.userContext).thenReturn(userContextPrimary)
+ `when`(userContextPrimary.getSystemService(SmartspaceManager::class.java))
+ .thenReturn(smartspaceManager)
+
+ controller =
+ DreamSmartspaceController(
+ userTracker,
+ execution,
+ uiExecutor,
+ viewComponentFactory,
+ precondition,
+ Optional.of(targetFilter),
+ Optional.of(plugin),
+ Optional.of(weatherPlugin),
+ )
}
- /**
- * Ensures smartspace session begins on a listener only flow.
- */
+ /** Ensures smartspace session begins on a listener only flow. */
@Test
fun testConnectOnListen() {
`when`(precondition.conditionsMet()).thenReturn(true)
@@ -165,18 +165,18 @@
verify(smartspaceManager).createSmartspaceSession(any())
- var targetListener = withArgCaptor<SmartspaceSession.OnTargetsAvailableListener> {
- verify(session).addOnTargetsAvailableListener(any(), capture())
- }
+ var targetListener =
+ withArgCaptor<SmartspaceSession.OnTargetsAvailableListener> {
+ verify(session).addOnTargetsAvailableListener(any(), capture())
+ }
`when`(targetFilter.filterSmartspaceTarget(any())).thenReturn(true)
var target = Mockito.mock(SmartspaceTarget::class.java)
targetListener.onTargetsAvailable(listOf(target))
- var targets = withArgCaptor<List<SmartspaceTarget>> {
- verify(plugin).onTargetsAvailable(capture())
- }
+ var targets =
+ withArgCaptor<List<SmartspaceTarget>> { verify(plugin).onTargetsAvailable(capture()) }
assertThat(targets.contains(target)).isTrue()
@@ -185,17 +185,16 @@
verify(session).close()
}
- /**
- * Ensures session begins when a view is attached.
- */
+ /** Ensures session begins when a view is attached. */
@Test
fun testConnectOnViewCreate() {
`when`(precondition.conditionsMet()).thenReturn(true)
controller.buildAndConnectView(Mockito.mock(ViewGroup::class.java))
- val stateChangeListener = withArgCaptor<View.OnAttachStateChangeListener> {
- verify(viewComponentFactory).create(any(), eq(plugin), capture(), eq(null))
- }
+ val stateChangeListener =
+ withArgCaptor<View.OnAttachStateChangeListener> {
+ verify(viewComponentFactory).create(any(), eq(plugin), capture(), eq(null))
+ }
val mockView = Mockito.mock(TestView::class.java)
`when`(precondition.conditionsMet()).thenReturn(true)
@@ -209,9 +208,7 @@
verify(session).close()
}
- /**
- * Ensures session is created when weather smartspace view is created and attached.
- */
+ /** Ensures session is created when weather smartspace view is created and attached. */
@Test
fun testConnectOnWeatherViewCreate() {
`when`(precondition.conditionsMet()).thenReturn(true)
@@ -223,8 +220,8 @@
// Then weather view is created with custom view and the default weatherPlugin.getView
// should not be called
- verify(viewComponentFactory).create(eq(fakeParent), eq(weatherPlugin), any(),
- eq(customView))
+ verify(viewComponentFactory)
+ .create(eq(fakeParent), eq(weatherPlugin), any(), eq(customView))
verify(weatherPlugin, Mockito.never()).getView(fakeParent)
// And then session is created
@@ -234,9 +231,7 @@
verify(weatherSmartspaceView).setDozeAmount(0f)
}
- /**
- * Ensures weather plugin registers target listener when it is added from the controller.
- */
+ /** Ensures weather plugin registers target listener when it is added from the controller. */
@Test
fun testAddListenerInController_registersListenerForWeatherPlugin() {
val customView = Mockito.mock(TestView::class.java)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
index 6e19096..32f4164 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
@@ -66,18 +66,34 @@
testScope.runTest {
val latest by collectLastValue(underTest.chips)
- setNotifs(listOf(activeNotificationModel(key = "notif", statusBarChipIcon = null)))
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = null,
+ isPromoted = true,
+ )
+ )
+ )
assertThat(latest).isEmpty()
}
@Test
- fun chips_oneNotif_statusBarIconViewMatches() =
+ fun chips_onePromotedNotif_statusBarIconViewMatches() =
testScope.runTest {
val latest by collectLastValue(underTest.chips)
val icon = mock<StatusBarIconView>()
- setNotifs(listOf(activeNotificationModel(key = "notif", statusBarChipIcon = icon)))
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = icon,
+ isPromoted = true,
+ )
+ )
+ )
assertThat(latest).hasSize(1)
val chip = latest!![0]
@@ -86,7 +102,7 @@
}
@Test
- fun chips_twoNotifs_twoChips() =
+ fun chips_onlyForPromotedNotifs() =
testScope.runTest {
val latest by collectLastValue(underTest.chips)
@@ -94,8 +110,21 @@
val secondIcon = mock<StatusBarIconView>()
setNotifs(
listOf(
- activeNotificationModel(key = "notif1", statusBarChipIcon = firstIcon),
- activeNotificationModel(key = "notif2", statusBarChipIcon = secondIcon),
+ activeNotificationModel(
+ key = "notif1",
+ statusBarChipIcon = firstIcon,
+ isPromoted = true,
+ ),
+ activeNotificationModel(
+ key = "notif2",
+ statusBarChipIcon = secondIcon,
+ isPromoted = true,
+ ),
+ activeNotificationModel(
+ key = "notif3",
+ statusBarChipIcon = mock<StatusBarIconView>(),
+ isPromoted = false,
+ ),
)
)
@@ -118,6 +147,7 @@
activeNotificationModel(
key = "clickTest",
statusBarChipIcon = mock<StatusBarIconView>(),
+ isPromoted = true,
)
)
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
index b12d7c5..25d5ce5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
@@ -293,19 +293,27 @@
}
@Test
- fun chips_singleNotifChip_primaryIsNotifSecondaryIsHidden() =
+ fun chips_singlePromotedNotif_primaryIsNotifSecondaryIsHidden() =
testScope.runTest {
val latest by collectLastValue(underTest.chips)
val icon = mock<StatusBarIconView>()
- setNotifs(listOf(activeNotificationModel(key = "notif", statusBarChipIcon = icon)))
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = icon,
+ isPromoted = true,
+ )
+ )
+ )
assertIsNotifChip(latest!!.primary, icon)
assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
}
@Test
- fun chips_twoNotifChips_primaryAndSecondaryAreNotifsInOrder() =
+ fun chips_twoPromotedNotifs_primaryAndSecondaryAreNotifsInOrder() =
testScope.runTest {
val latest by collectLastValue(underTest.chips)
@@ -313,8 +321,16 @@
val secondIcon = mock<StatusBarIconView>()
setNotifs(
listOf(
- activeNotificationModel(key = "firstNotif", statusBarChipIcon = firstIcon),
- activeNotificationModel(key = "secondNotif", statusBarChipIcon = secondIcon),
+ activeNotificationModel(
+ key = "firstNotif",
+ statusBarChipIcon = firstIcon,
+ isPromoted = true,
+ ),
+ activeNotificationModel(
+ key = "secondNotif",
+ statusBarChipIcon = secondIcon,
+ isPromoted = true,
+ ),
)
)
@@ -323,7 +339,7 @@
}
@Test
- fun chips_threeNotifChips_topTwoShown() =
+ fun chips_threePromotedNotifs_topTwoShown() =
testScope.runTest {
val latest by collectLastValue(underTest.chips)
@@ -332,9 +348,21 @@
val thirdIcon = mock<StatusBarIconView>()
setNotifs(
listOf(
- activeNotificationModel(key = "firstNotif", statusBarChipIcon = firstIcon),
- activeNotificationModel(key = "secondNotif", statusBarChipIcon = secondIcon),
- activeNotificationModel(key = "thirdNotif", statusBarChipIcon = thirdIcon),
+ activeNotificationModel(
+ key = "firstNotif",
+ statusBarChipIcon = firstIcon,
+ isPromoted = true,
+ ),
+ activeNotificationModel(
+ key = "secondNotif",
+ statusBarChipIcon = secondIcon,
+ isPromoted = true,
+ ),
+ activeNotificationModel(
+ key = "thirdNotif",
+ statusBarChipIcon = thirdIcon,
+ isPromoted = true,
+ ),
)
)
@@ -343,7 +371,7 @@
}
@Test
- fun chips_callAndNotifs_primaryIsCallSecondaryIsNotif() =
+ fun chips_callAndPromotedNotifs_primaryIsCallSecondaryIsNotif() =
testScope.runTest {
val latest by collectLastValue(underTest.chips)
@@ -351,10 +379,15 @@
val firstIcon = mock<StatusBarIconView>()
setNotifs(
listOf(
- activeNotificationModel(key = "firstNotif", statusBarChipIcon = firstIcon),
+ activeNotificationModel(
+ key = "firstNotif",
+ statusBarChipIcon = firstIcon,
+ isPromoted = true,
+ ),
activeNotificationModel(
key = "secondNotif",
statusBarChipIcon = mock<StatusBarIconView>(),
+ isPromoted = true,
),
)
)
@@ -364,7 +397,7 @@
}
@Test
- fun chips_screenRecordAndCallAndNotifs_notifsNotShown() =
+ fun chips_screenRecordAndCallAndPromotedNotifs_notifsNotShown() =
testScope.runTest {
val latest by collectLastValue(underTest.chips)
@@ -375,6 +408,7 @@
activeNotificationModel(
key = "notif",
statusBarChipIcon = mock<StatusBarIconView>(),
+ isPromoted = true,
)
)
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt
index 8dcc444..5be5fb4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt
@@ -17,11 +17,13 @@
package com.android.systemui.statusbar.core
import android.platform.test.annotations.EnableFlags
+import android.view.Display
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.data.repository.fakePrivacyDotWindowControllerStore
import com.android.systemui.testKosmos
import com.google.common.truth.Expect
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -30,6 +32,7 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.never
import org.mockito.kotlin.verify
@OptIn(ExperimentalCoroutinesApi::class)
@@ -39,16 +42,12 @@
class MultiDisplayStatusBarStarterTest : SysuiTestCase() {
@get:Rule val expect: Expect = Expect.create()
- private val kosmos =
- testKosmos().also {
- it.statusBarOrchestratorFactory = it.fakeStatusBarOrchestratorFactory
- it.statusBarInitializerStore = it.fakeStatusBarInitializerStore
- }
+ private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val fakeDisplayRepository = kosmos.displayRepository
private val fakeOrchestratorFactory = kosmos.fakeStatusBarOrchestratorFactory
private val fakeInitializerStore = kosmos.fakeStatusBarInitializerStore
-
+ private val fakePrivacyDotStore = kosmos.fakePrivacyDotWindowControllerStore
// Lazy, so that @EnableFlags is set before initializer is instantiated.
private val underTest by lazy { kosmos.multiDisplayStatusBarStarter }
@@ -83,6 +82,31 @@
}
@Test
+ fun start_startsPrivacyDotForCurrentDisplays() =
+ testScope.runTest {
+ fakeDisplayRepository.addDisplay(displayId = 1)
+ fakeDisplayRepository.addDisplay(displayId = 2)
+
+ underTest.start()
+ runCurrent()
+
+ verify(fakePrivacyDotStore.forDisplay(displayId = 1)).start()
+ verify(fakePrivacyDotStore.forDisplay(displayId = 2)).start()
+ }
+
+ @Test
+ fun start_doesNotStartPrivacyDotForDefaultDisplay() =
+ testScope.runTest {
+ fakeDisplayRepository.addDisplay(displayId = Display.DEFAULT_DISPLAY)
+
+ underTest.start()
+ runCurrent()
+
+ verify(fakePrivacyDotStore.forDisplay(displayId = Display.DEFAULT_DISPLAY), never())
+ .start()
+ }
+
+ @Test
fun displayAdded_orchestratorForNewDisplayIsStarted() =
testScope.runTest {
underTest.start()
@@ -109,6 +133,18 @@
}
@Test
+ fun displayAdded_privacyDotForNewDisplayIsStarted() =
+ testScope.runTest {
+ underTest.start()
+ runCurrent()
+
+ fakeDisplayRepository.addDisplay(displayId = 3)
+ runCurrent()
+
+ verify(fakePrivacyDotStore.forDisplay(displayId = 3)).start()
+ }
+
+ @Test
fun displayAddedDuringStart_initializerForNewDisplayIsStarted() =
testScope.runTest {
underTest.start()
@@ -129,8 +165,17 @@
fakeDisplayRepository.addDisplay(displayId = 3)
runCurrent()
- expect
- .that(fakeInitializerStore.forDisplay(displayId = 3).startedByCoreStartable)
- .isTrue()
+ verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = 3)!!).start()
+ }
+
+ @Test
+ fun displayAddedDuringStart_privacyDotForNewDisplayIsStarted() =
+ testScope.runTest {
+ underTest.start()
+
+ fakeDisplayRepository.addDisplay(displayId = 3)
+ runCurrent()
+
+ verify(fakePrivacyDotStore.forDisplay(displayId = 3)).start()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
index 20a19a9..938da88 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
@@ -26,11 +26,14 @@
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.fragments.FragmentHostManager
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.statusbar.data.repository.fakeStatusBarModePerDisplayRepository
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment
import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent
import com.android.systemui.statusbar.pipeline.shared.ui.composable.StatusBarRootFactory
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import org.junit.Assert.assertThrows
@@ -45,12 +48,14 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class StatusBarInitializerTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val windowController = mock(StatusBarWindowController::class.java)
private val windowControllerStore = mock(StatusBarWindowControllerStore::class.java)
private val transaction = mock(FragmentTransaction::class.java)
private val fragmentManager = mock(FragmentManager::class.java)
private val fragmentHostManager = mock(FragmentHostManager::class.java)
private val backgroundView = mock(ViewGroup::class.java)
+ private val statusBarModePerDisplayRepository = kosmos.fakeStatusBarModePerDisplayRepository
@Before
fun setup() {
@@ -72,6 +77,7 @@
statusBarRootFactory = mock(StatusBarRootFactory::class.java),
componentFactory = mock(HomeStatusBarComponent.Factory::class.java),
creationListeners = setOf(),
+ statusBarModePerDisplayRepository = statusBarModePerDisplayRepository,
)
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt
new file mode 100644
index 0000000..18eef33
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.data.repository
+
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LightBarControllerStoreImplTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val testScope = kosmos.testScope
+ private val fakeDisplayRepository = kosmos.displayRepository
+
+ private val underTest = kosmos.lightBarControllerStoreImpl
+
+ @Before
+ fun start() {
+ underTest.start()
+ }
+
+ @Before fun addDisplays() = runBlocking { fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY) }
+
+ @Test
+ fun forDisplay_startsInstance() =
+ testScope.runTest {
+ val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+ verify(instance).start()
+ }
+
+ @Test
+ fun beforeDisplayRemoved_doesNotStopInstances() =
+ testScope.runTest {
+ val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+ verify(instance, never()).stop()
+ }
+
+ @Test
+ fun displayRemoved_stopsInstance() =
+ testScope.runTest {
+ val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+ fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+
+ verify(instance).stop()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt
new file mode 100644
index 0000000..a9920ec5
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.data.repository
+
+import android.platform.test.annotations.EnableFlags
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+class MultiDisplayStatusBarModeRepositoryStoreTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val testScope = kosmos.testScope
+ private val fakeDisplayRepository = kosmos.displayRepository
+ private val underTest by lazy { kosmos.multiDisplayStatusBarModeRepositoryStore }
+
+ @Before
+ fun start() {
+ underTest.start()
+ }
+
+ @Before fun addDisplays() = runBlocking { fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY) }
+
+ @Test
+ fun forDisplay_startsInstance() =
+ testScope.runTest {
+ val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+ verify(instance).start()
+ }
+
+ @Test
+ fun displayRemoved_stopsInstance() =
+ testScope.runTest {
+ val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+ fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+
+ verify(instance).stop()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.kt
new file mode 100644
index 0000000..ae734b3
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.data.repository
+
+import android.platform.test.annotations.EnableFlags
+import android.view.Display
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+class PrivacyDotWindowControllerStoreImplTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val underTest by lazy { kosmos.privacyDotWindowControllerStoreImpl }
+
+ @Before
+ fun installDisplays() = runBlocking {
+ kosmos.displayRepository.addDisplay(displayId = Display.DEFAULT_DISPLAY)
+ kosmos.displayRepository.addDisplay(displayId = Display.DEFAULT_DISPLAY + 1)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun forDisplay_defaultDisplay_throws() {
+ underTest.forDisplay(displayId = Display.DEFAULT_DISPLAY)
+ }
+
+ @Test
+ fun forDisplay_nonDefaultDisplay_doesNotThrow() {
+ underTest.forDisplay(displayId = Display.DEFAULT_DISPLAY + 1)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt
new file mode 100644
index 0000000..e65c04c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.data.repository
+
+import android.platform.test.annotations.EnableFlags
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+class SystemEventChipAnimationControllerStoreImplTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val testScope = kosmos.testScope
+ private val fakeDisplayRepository = kosmos.displayRepository
+
+ // Lazy so that @EnableFlags has time to run before underTest is instantiated.
+ private val underTest by lazy { kosmos.systemEventChipAnimationControllerStoreImpl }
+
+ @Before
+ fun start() {
+ underTest.start()
+ }
+
+ @Before fun addDisplays() = runBlocking { fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY) }
+
+ @Test
+ fun beforeDisplayRemoved_doesNotStopInstances() =
+ testScope.runTest {
+ val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+ verify(instance, never()).stop()
+ }
+
+ @Test
+ fun displayRemoved_stopsInstance() =
+ testScope.runTest {
+ val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+ fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+
+ verify(instance).stop()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationControllerTest.kt
new file mode 100644
index 0000000..d4007d7
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationControllerTest.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.events
+
+import android.platform.test.annotations.EnableFlags
+import androidx.core.animation.AnimatorSet
+import androidx.core.animation.ValueAnimator
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.data.repository.systemEventChipAnimationControllerStore
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+class MultiDisplaySystemEventChipAnimationControllerTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val displayRepository = kosmos.displayRepository
+ private val store = kosmos.systemEventChipAnimationControllerStore
+
+ // Lazy so that @EnableFlags has time to switch the flags before the instance is created.
+ private val underTest by lazy { kosmos.multiDisplaySystemEventChipAnimationController }
+
+ @Before
+ fun installDisplays() = runBlocking {
+ INSTALLED_DISPLAY_IDS.forEach { displayRepository.addDisplay(displayId = it) }
+ }
+
+ @Test
+ fun init_forwardsToAllControllers() {
+ underTest.init()
+
+ INSTALLED_DISPLAY_IDS.forEach { verify(store.forDisplay(it)).init() }
+ }
+
+ @Test
+ fun stop_forwardsToAllControllers() {
+ underTest.stop()
+
+ INSTALLED_DISPLAY_IDS.forEach { verify(store.forDisplay(it)).stop() }
+ }
+
+ @Test
+ fun announceForAccessibility_forwardsToAllControllers() {
+ val contentDescription = "test content description"
+ underTest.announceForAccessibility(contentDescription)
+
+ INSTALLED_DISPLAY_IDS.forEach {
+ verify(store.forDisplay(it)).announceForAccessibility(contentDescription)
+ }
+ }
+
+ @Test
+ fun onSystemEventAnimationBegin_returnsAnimatorSetWithOneAnimatorPerDisplay() {
+ INSTALLED_DISPLAY_IDS.forEach {
+ val controller = store.forDisplay(it)
+ whenever(controller.onSystemEventAnimationBegin()).thenReturn(ValueAnimator.ofInt(0, 1))
+ }
+ val animator = underTest.onSystemEventAnimationBegin() as AnimatorSet
+
+ assertThat(animator.childAnimations).hasSize(INSTALLED_DISPLAY_IDS.size)
+ }
+
+ @Test
+ fun onSystemEventAnimationFinish_returnsAnimatorSetWithOneAnimatorPerDisplay() {
+ INSTALLED_DISPLAY_IDS.forEach {
+ val controller = store.forDisplay(it)
+ whenever(controller.onSystemEventAnimationFinish(any()))
+ .thenReturn(ValueAnimator.ofInt(0, 1))
+ }
+ val animator =
+ underTest.onSystemEventAnimationFinish(hasPersistentDot = true) as AnimatorSet
+
+ assertThat(animator.childAnimations).hasSize(INSTALLED_DISPLAY_IDS.size)
+ }
+
+ companion object {
+ private const val DISPLAY_ID_1 = 123
+ private const val DISPLAY_ID_2 = 456
+ private val INSTALLED_DISPLAY_IDS = listOf(DISPLAY_ID_1, DISPLAY_ID_2)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerTest.kt
new file mode 100644
index 0000000..6bcd735
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerTest.kt
@@ -0,0 +1,185 @@
+/*
+ * 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.events
+
+import android.view.Gravity.BOTTOM
+import android.view.Gravity.LEFT
+import android.view.Gravity.RIGHT
+import android.view.Gravity.TOP
+import android.view.Surface
+import android.view.View
+import android.view.WindowManager
+import android.view.fakeWindowManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.google.common.truth.Expect
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PrivacyDotWindowControllerTest : SysuiTestCase() {
+
+ @get:Rule val expect: Expect = Expect.create()
+
+ private val kosmos = testKosmos()
+ private val underTest = kosmos.privacyDotWindowController
+ private val viewController = kosmos.privacyDotViewController
+ private val windowManager = kosmos.fakeWindowManager
+ private val executor = kosmos.fakeExecutor
+
+ @After
+ fun cleanUpCustomDisplay() {
+ context.display = null
+ }
+
+ @Test
+ fun start_beforeUiThreadExecutes_doesNotAddWindows() {
+ underTest.start()
+
+ assertThat(windowManager.addedViews).isEmpty()
+ }
+
+ @Test
+ fun start_beforeUiThreadExecutes_doesNotInitializeViewController() {
+ underTest.start()
+
+ assertThat(viewController.isInitialized).isFalse()
+ }
+
+ @Test
+ fun start_afterUiThreadExecutes_addsWindowsOnUiThread() {
+ underTest.start()
+
+ executor.runAllReady()
+
+ assertThat(windowManager.addedViews).hasSize(4)
+ }
+
+ @Test
+ fun start_afterUiThreadExecutes_initializesViewController() {
+ underTest.start()
+
+ executor.runAllReady()
+
+ assertThat(viewController.isInitialized).isTrue()
+ }
+
+ @Test
+ fun start_initializesTopLeft() {
+ underTest.start()
+ executor.runAllReady()
+
+ assertThat(viewController.topLeft?.id).isEqualTo(R.id.privacy_dot_top_left_container)
+ }
+
+ @Test
+ fun start_initializesTopRight() {
+ underTest.start()
+ executor.runAllReady()
+
+ assertThat(viewController.topRight?.id).isEqualTo(R.id.privacy_dot_top_right_container)
+ }
+
+ @Test
+ fun start_initializesTopBottomLeft() {
+ underTest.start()
+ executor.runAllReady()
+
+ assertThat(viewController.bottomLeft?.id).isEqualTo(R.id.privacy_dot_bottom_left_container)
+ }
+
+ @Test
+ fun start_initializesBottomRight() {
+ underTest.start()
+ executor.runAllReady()
+
+ assertThat(viewController.bottomRight?.id)
+ .isEqualTo(R.id.privacy_dot_bottom_right_container)
+ }
+
+ @Test
+ fun start_viewsAddedInRespectiveCorners() {
+ context.display = mock { on { rotation } doReturn Surface.ROTATION_0 }
+
+ underTest.start()
+ executor.runAllReady()
+
+ expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(TOP or LEFT)
+ expect.that(gravityForView(viewController.topRight!!)).isEqualTo(TOP or RIGHT)
+ expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(BOTTOM or LEFT)
+ expect.that(gravityForView(viewController.bottomRight!!)).isEqualTo(BOTTOM or RIGHT)
+ }
+
+ @Test
+ fun start_rotation90_viewsPositionIsShifted90degrees() {
+ context.display = mock { on { rotation } doReturn Surface.ROTATION_90 }
+
+ underTest.start()
+ executor.runAllReady()
+
+ expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(BOTTOM or LEFT)
+ expect.that(gravityForView(viewController.topRight!!)).isEqualTo(TOP or LEFT)
+ expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(BOTTOM or RIGHT)
+ expect.that(gravityForView(viewController.bottomRight!!)).isEqualTo(TOP or RIGHT)
+ }
+
+ @Test
+ fun start_rotation180_viewsPositionIsShifted180degrees() {
+ context.display = mock { on { rotation } doReturn Surface.ROTATION_180 }
+
+ underTest.start()
+ executor.runAllReady()
+
+ expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(BOTTOM or RIGHT)
+ expect.that(gravityForView(viewController.topRight!!)).isEqualTo(BOTTOM or LEFT)
+ expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(TOP or RIGHT)
+ expect.that(gravityForView(viewController.bottomRight!!)).isEqualTo(TOP or LEFT)
+ }
+
+ @Test
+ fun start_rotation270_viewsPositionIsShifted270degrees() {
+ context.display = mock { on { rotation } doReturn Surface.ROTATION_270 }
+
+ underTest.start()
+ executor.runAllReady()
+
+ expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(TOP or RIGHT)
+ expect.that(gravityForView(viewController.topRight!!)).isEqualTo(BOTTOM or RIGHT)
+ expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(TOP or LEFT)
+ expect.that(gravityForView(viewController.bottomRight!!)).isEqualTo(BOTTOM or LEFT)
+ }
+
+ private fun paramsForView(view: View): WindowManager.LayoutParams {
+ return windowManager.addedViews.entries
+ .first { it.key == view || it.key.findViewById<View>(view.id) != null }
+ .value
+ }
+
+ private fun gravityForView(view: View): Int {
+ return paramsForView(view).gravity
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
index 9f40f60..99bda85 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
@@ -18,11 +18,14 @@
package com.android.systemui.statusbar.notification.domain.interactor
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.notification.collection.render.NotifStats
import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
@@ -44,7 +47,7 @@
private val testScope = kosmos.testScope
private val activeNotificationListRepository = kosmos.activeNotificationListRepository
- private val underTest = kosmos.activeNotificationsInteractor
+ private val underTest by lazy { kosmos.activeNotificationsInteractor }
@Test
fun testAllNotificationsCount() =
@@ -65,14 +68,8 @@
val normalNotifs =
listOf(
- activeNotificationModel(
- key = "notif1",
- callType = CallType.None,
- ),
- activeNotificationModel(
- key = "notif2",
- callType = CallType.None,
- )
+ activeNotificationModel(key = "notif1", callType = CallType.None),
+ activeNotificationModel(key = "notif2", callType = CallType.None),
)
activeNotificationListRepository.activeNotifications.value =
@@ -129,10 +126,7 @@
val latest by collectLastValue(underTest.ongoingCallNotification)
val ongoingNotif =
- activeNotificationModel(
- key = "ongoingNotif",
- callType = CallType.Ongoing,
- )
+ activeNotificationModel(key = "ongoingNotif", callType = CallType.Ongoing)
activeNotificationListRepository.activeNotifications.value =
ActiveNotificationsStore.Builder()
@@ -170,6 +164,62 @@
}
@Test
+ @DisableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun promotedOngoingNotifications_flagOff_empty() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.promotedOngoingNotifications)
+
+ val promoted1 = activeNotificationModel(key = "notif1", isPromoted = true)
+ val notPromoted2 = activeNotificationModel(key = "notif2", isPromoted = false)
+
+ activeNotificationListRepository.activeNotifications.value =
+ ActiveNotificationsStore.Builder()
+ .apply { addIndividualNotif(promoted1) }
+ .apply { addIndividualNotif(notPromoted2) }
+ .build()
+
+ assertThat(latest!!).isEmpty()
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun promotedOngoingNotifications_nonePromoted_empty() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.promotedOngoingNotifications)
+
+ activeNotificationListRepository.activeNotifications.value =
+ ActiveNotificationsStore.Builder()
+ .apply { activeNotificationModel(key = "notif1", isPromoted = false) }
+ .apply { activeNotificationModel(key = "notif2", isPromoted = false) }
+ .apply { activeNotificationModel(key = "notif3", isPromoted = false) }
+ .build()
+
+ assertThat(latest!!).isEmpty()
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun promotedOngoingNotifications_somePromoted_hasOnlyPromoted() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.promotedOngoingNotifications)
+
+ val promoted1 = activeNotificationModel(key = "notif1", isPromoted = true)
+ val notPromoted2 = activeNotificationModel(key = "notif2", isPromoted = false)
+ val notPromoted3 = activeNotificationModel(key = "notif3", isPromoted = false)
+ val promoted4 = activeNotificationModel(key = "notif4", isPromoted = true)
+
+ activeNotificationListRepository.activeNotifications.value =
+ ActiveNotificationsStore.Builder()
+ .apply { addIndividualNotif(promoted1) }
+ .apply { addIndividualNotif(notPromoted2) }
+ .apply { addIndividualNotif(notPromoted3) }
+ .apply { addIndividualNotif(promoted4) }
+ .build()
+
+ assertThat(latest!!).containsExactly(promoted1, promoted4)
+ }
+
+ @Test
fun areAnyNotificationsPresent_isTrue() =
testScope.runTest {
val areAnyNotificationsPresent by collectLastValue(underTest.areAnyNotificationsPresent)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
index 572a0c1..183f901 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
@@ -16,22 +16,25 @@
package com.android.systemui.statusbar.notification.domain.interactor
import android.app.Notification
-import android.os.Bundle
+import android.app.Notification.FLAG_PROMOTED_ONGOING
+import android.platform.test.annotations.EnableFlags
import android.service.notification.StatusBarNotification
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.RankingBuilder
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
+import com.android.systemui.statusbar.notification.promoted.promotedNotificationsProvider
import com.android.systemui.statusbar.notification.shared.byKey
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -39,16 +42,16 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class RenderNotificationsListInteractorTest : SysuiTestCase() {
- private val backgroundDispatcher = StandardTestDispatcher()
- private val testScope = TestScope(backgroundDispatcher)
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
- private val notifsRepository = ActiveNotificationListRepository()
- private val notifsInteractor =
- ActiveNotificationsInteractor(notifsRepository, backgroundDispatcher)
+ private val notifsRepository = kosmos.activeNotificationListRepository
+ private val notifsInteractor = kosmos.activeNotificationsInteractor
private val underTest =
RenderNotificationListInteractor(
notifsRepository,
sectionStyleProvider = mock(),
+ promotedNotificationsProvider = kosmos.promotedNotificationsProvider,
)
@Test
@@ -85,12 +88,7 @@
assertThat(ranks)
.containsExactlyEntriesIn(
- mapOf(
- "single" to 0,
- "summary" to 1,
- "child0" to 2,
- "child1" to 3,
- )
+ mapOf("single" to 0, "summary" to 1, "child0" to 2, "child1" to 3)
)
}
@@ -126,6 +124,53 @@
assertThat(actual).containsAtLeastEntriesIn(expected)
}
+
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME)
+ fun setRenderList_setsPromotionStatus() =
+ testScope.runTest {
+ val actual by collectLastValue(notifsInteractor.topLevelRepresentativeNotifications)
+
+ val notPromoted1 = mockNotificationEntry("key1", flag = null)
+ val promoted2 = mockNotificationEntry("key2", flag = FLAG_PROMOTED_ONGOING)
+
+ underTest.setRenderedList(listOf(notPromoted1, promoted2))
+
+ assertThat(actual!!.size).isEqualTo(2)
+
+ val first = actual!![0]
+ assertThat(first.key).isEqualTo("key1")
+ assertThat(first.isPromoted).isFalse()
+
+ val second = actual!![1]
+ assertThat(second.key).isEqualTo("key2")
+ assertThat(second.isPromoted).isTrue()
+ }
+
+ private fun mockNotificationEntry(
+ key: String,
+ rank: Int = 0,
+ flag: Int? = null,
+ ): NotificationEntry {
+ val nBuilder = Notification.Builder(context, "a")
+ if (flag != null) {
+ nBuilder.setFlag(flag, true)
+ }
+ val notification = nBuilder.build()
+
+ val mockSbn =
+ mock<StatusBarNotification>() {
+ whenever(this.notification).thenReturn(notification)
+ whenever(packageName).thenReturn("com.android")
+ }
+ return mock<NotificationEntry> {
+ whenever(this.key).thenReturn(key)
+ whenever(this.icons).thenReturn(mock())
+ whenever(this.representativeEntry).thenReturn(this)
+ whenever(this.ranking).thenReturn(RankingBuilder().setRank(rank).build())
+ whenever(this.sbn).thenReturn(mockSbn)
+ }
+ }
}
private fun mockGroupEntry(
@@ -139,19 +184,3 @@
whenever(this.children).thenReturn(children)
}
}
-
-private fun mockNotificationEntry(key: String, rank: Int = 0): NotificationEntry {
- val mockNotification = mock<Notification> { this.extras = Bundle() }
- val mockSbn =
- mock<StatusBarNotification>() {
- whenever(notification).thenReturn(mockNotification)
- whenever(packageName).thenReturn("com.android")
- }
- return mock<NotificationEntry> {
- whenever(this.key).thenReturn(key)
- whenever(this.icons).thenReturn(mock())
- whenever(this.representativeEntry).thenReturn(this)
- whenever(this.ranking).thenReturn(RankingBuilder().setRank(rank).build())
- whenever(this.sbn).thenReturn(mockSbn)
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderTest.kt
new file mode 100644
index 0000000..a9dbe63
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderTest.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.promoted
+
+import android.app.Notification
+import android.app.Notification.FLAG_PROMOTED_ONGOING
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+
+@SmallTest
+class PromotedNotificationsProviderTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
+ private val underTest = kosmos.promotedNotificationsProvider
+
+ @Test
+ @DisableFlags(PromotedNotificationUi.FLAG_NAME)
+ fun shouldPromote_uiFlagOff_false() {
+ val entry = createNotification(FLAG_PROMOTED_ONGOING)
+
+ assertThat(underTest.shouldPromote(entry)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME)
+ fun shouldPromote_uiFlagOn_notifDoesNotHaveFlag_false() {
+ val entry = createNotification(flag = null)
+
+ assertThat(underTest.shouldPromote(entry)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME)
+ fun shouldPromote_uiFlagOn_notifHasFlag_true() {
+ val entry = createNotification(FLAG_PROMOTED_ONGOING)
+
+ assertThat(underTest.shouldPromote(entry)).isTrue()
+ }
+
+ private fun createNotification(flag: Int? = null): NotificationEntry {
+ val n = Notification.Builder(context, "a")
+ if (flag != null) {
+ n.setFlag(flag, true)
+ }
+
+ return NotificationEntryBuilder().setNotification(n.build()).build()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
index 4762527..d665b31 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
@@ -24,7 +24,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
import com.android.systemui.coroutines.collectValues
import com.android.systemui.power.data.repository.FakePowerRepository
import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -75,9 +75,9 @@
configurationController,
context,
testScope.backgroundScope,
- mock()
+ mock(),
)
- private val configurationInteractor = ConfigurationInteractor(configurationRepository)
+ private val configurationInteractor = ConfigurationInteractorImpl(configurationRepository)
private val unfoldTransitionRepository =
UnfoldTransitionRepositoryImpl(Optional.of(unfoldTransitionProgressProvider))
@@ -103,7 +103,7 @@
unfoldTransitionInteractor,
configurationInteractor,
animationStatus,
- powerInteractor
+ powerInteractor,
)
}
@@ -140,7 +140,7 @@
updateDisplay(
width = INITIAL_DISPLAY_HEIGHT,
height = INITIAL_DISPLAY_WIDTH,
- rotation = ROTATION_90
+ rotation = ROTATION_90,
)
runCurrent()
@@ -284,7 +284,7 @@
private fun updateDisplay(
width: Int = INITIAL_DISPLAY_WIDTH,
height: Int = INITIAL_DISPLAY_HEIGHT,
- @Surface.Rotation rotation: Int = ROTATION_0
+ @Surface.Rotation rotation: Int = ROTATION_0,
) {
configuration.windowConfiguration.maxBounds.set(Rect(0, 0, width, height))
configuration.windowConfiguration.displayRotation = rotation
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt
index e9d88cc..122cfdd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt
@@ -16,16 +16,22 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.setSceneTransition
import com.android.systemui.scene.data.repository.windowRootViewVisibilityRepository
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
import com.android.systemui.testKosmos
@@ -33,10 +39,12 @@
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
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class NotificationLoggerViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class NotificationLoggerViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos = testKosmos()
@@ -46,9 +54,22 @@
private val powerInteractor = kosmos.powerInteractor
private val windowRootViewVisibilityRepository = kosmos.windowRootViewVisibilityRepository
- private val underTest = kosmos.notificationListLoggerViewModel
+ private val underTest by lazy { kosmos.notificationListLoggerViewModel }
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
@Test
+ @DisableSceneContainer
fun isLockscreenOrShadeInteractive_deviceActiveAndShadeIsInteractive_true() =
testScope.runTest {
powerInteractor.setAwakeForTest()
@@ -60,6 +81,7 @@
}
@Test
+ @DisableSceneContainer
fun isLockscreenOrShadeInteractive_deviceIsAsleepAndShadeIsInteractive_false() =
testScope.runTest {
powerInteractor.setAsleepForTest()
@@ -71,6 +93,7 @@
}
@Test
+ @DisableSceneContainer
fun isLockscreenOrShadeInteractive_deviceActiveAndShadeIsNotInteractive_false() =
testScope.runTest {
powerInteractor.setAwakeForTest()
@@ -82,6 +105,54 @@
}
@Test
+ @EnableSceneContainer
+ fun isLockscreenOrShadeInteractive_deviceAwakeAndShadeIsDisplayed_true() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ kosmos.setSceneTransition(Idle(Scenes.Shade))
+
+ val actual by collectLastValue(underTest.isLockscreenOrShadeInteractive)
+
+ assertThat(actual).isTrue()
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun isLockscreenOrShadeInteractive_deviceAwakeAndLockScreenIsDisplayed_true() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
+
+ val actual by collectLastValue(underTest.isLockscreenOrShadeInteractive)
+
+ assertThat(actual).isTrue()
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun isLockscreenOrShadeInteractive_deviceIsAsleepOnLockscreen_false() =
+ testScope.runTest {
+ powerInteractor.setAsleepForTest()
+ kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
+
+ val actual by collectLastValue(underTest.isLockscreenOrShadeInteractive)
+
+ assertThat(actual).isFalse()
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun isLockscreenOrShadeInteractive_deviceActiveAndShadeIsClosed() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ kosmos.setSceneTransition(Idle(Scenes.Gone))
+
+ val actual by collectLastValue(underTest.isLockscreenOrShadeInteractive)
+
+ assertThat(actual).isFalse()
+ }
+
+ @Test
fun activeNotifications_hasNotifications() =
testScope.runTest {
activeNotificationListRepository.setActiveNotifs(5)
@@ -135,6 +206,7 @@
assertThat(isOnLockScreen).isTrue()
}
+
@Test
fun isOnLockScreen_false() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
deleted file mode 100644
index 157f818..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ /dev/null
@@ -1,845 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-
-import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
-import static android.app.StatusBarManager.DISABLE_SYSTEM_INFO;
-
-import static com.android.systemui.Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND;
-import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
-import static com.android.systemui.statusbar.StatusBarState.SHADE;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
-import android.provider.Settings;
-import android.testing.TestableLooper;
-import android.view.LayoutInflater;
-import android.view.View;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.CarrierTextController;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.keyguard.logging.KeyguardLogger;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.battery.BatteryMeterViewController;
-import com.android.systemui.flags.DisableSceneContainer;
-import com.android.systemui.flags.EnableSceneContainer;
-import com.android.systemui.kosmos.KosmosJavaAdapter;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.res.R;
-import com.android.systemui.shade.ShadeViewStateProvider;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore;
-import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
-import com.android.systemui.statusbar.phone.ui.StatusBarIconController;
-import com.android.systemui.statusbar.phone.ui.TintedIconManager;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.UserInfoController;
-import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.settings.SecureSettings;
-import com.android.systemui.util.time.FakeSystemClock;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-@TestableLooper.RunWithLooper
-public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
- @Mock
- private CarrierTextController mCarrierTextController;
- @Mock
- private ConfigurationController mConfigurationController;
- @Mock
- private SystemStatusAnimationScheduler mAnimationScheduler;
- @Mock
- private BatteryController mBatteryController;
- @Mock
- private UserInfoController mUserInfoController;
- @Mock
- private StatusBarIconController mStatusBarIconController;
- @Mock
- private TintedIconManager.Factory mIconManagerFactory;
- @Mock
- private TintedIconManager mIconManager;
- @Mock
- private BatteryMeterViewController mBatteryMeterViewController;
- @Mock
- private KeyguardStateController mKeyguardStateController;
- @Mock
- private KeyguardBypassController mKeyguardBypassController;
- @Mock
- private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock
- private BiometricUnlockController mBiometricUnlockController;
- @Mock
- private SysuiStatusBarStateController mStatusBarStateController;
- @Mock
- private StatusBarContentInsetsProvider mStatusBarContentInsetsProvider;
- @Mock
- private StatusBarContentInsetsProviderStore mStatusBarContentInsetsProviderStore;
- @Mock
- private UserManager mUserManager;
- @Mock
- private StatusBarUserChipViewModel mStatusBarUserChipViewModel;
- @Captor
- private ArgumentCaptor<ConfigurationListener> mConfigurationListenerCaptor;
- @Captor
- private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardCallbackCaptor;
- @Mock private SecureSettings mSecureSettings;
- @Mock private CommandQueue mCommandQueue;
- @Mock private KeyguardLogger mLogger;
- @Mock private StatusOverlayHoverListenerFactory mStatusOverlayHoverListenerFactory;
-
- private TestShadeViewStateProvider mShadeViewStateProvider;
- private KeyguardStatusBarView mKeyguardStatusBarView;
- private KeyguardStatusBarViewController mController;
- private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
- private final FakeExecutor mBackgroundExecutor = new FakeExecutor(new FakeSystemClock());
- private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
-
- @Before
- public void setup() throws Exception {
- mShadeViewStateProvider = new TestShadeViewStateProvider();
-
- MockitoAnnotations.initMocks(this);
- when(mStatusBarContentInsetsProviderStore.getDefaultDisplay())
- .thenReturn(mStatusBarContentInsetsProvider);
- when(mIconManagerFactory.create(any(), any())).thenReturn(mIconManager);
-
- allowTestableLooperAsMainThread();
- TestableLooper.get(this).runWithLooper(() -> {
- mKeyguardStatusBarView =
- spy((KeyguardStatusBarView) LayoutInflater.from(mContext)
- .inflate(R.layout.keyguard_status_bar, null));
- when(mKeyguardStatusBarView.getDisplay()).thenReturn(mContext.getDisplay());
- });
-
- mController = createController();
- }
-
- private KeyguardStatusBarViewController createController() {
- return new KeyguardStatusBarViewController(
- mKeyguardStatusBarView,
- mCarrierTextController,
- mConfigurationController,
- mAnimationScheduler,
- mBatteryController,
- mUserInfoController,
- mStatusBarIconController,
- mIconManagerFactory,
- mBatteryMeterViewController,
- mShadeViewStateProvider,
- mKeyguardStateController,
- mKeyguardBypassController,
- mKeyguardUpdateMonitor,
- mKosmos.getKeyguardStatusBarViewModel(),
- mBiometricUnlockController,
- mStatusBarStateController,
- mStatusBarContentInsetsProviderStore,
- mUserManager,
- mStatusBarUserChipViewModel,
- mSecureSettings,
- mCommandQueue,
- mFakeExecutor,
- mBackgroundExecutor,
- mLogger,
- mStatusOverlayHoverListenerFactory,
- mKosmos.getCommunalSceneInteractor()
- );
- }
-
- @Test
- @EnableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
- public void onViewAttached_updateUserSwitcherFlagEnabled_callbacksRegistered() {
- mController.onViewAttached();
-
- runAllScheduled();
- verify(mConfigurationController).addCallback(any());
- verify(mAnimationScheduler).addCallback(any());
- verify(mUserInfoController).addCallback(any());
- verify(mCommandQueue).addCallback(any());
- verify(mStatusBarIconController).addIconGroup(any());
- verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
- }
-
- @Test
- @DisableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
- public void onViewAttached_updateUserSwitcherFlagDisabled_callbacksRegistered() {
- mController.onViewAttached();
-
- verify(mConfigurationController).addCallback(any());
- verify(mAnimationScheduler).addCallback(any());
- verify(mUserInfoController).addCallback(any());
- verify(mCommandQueue).addCallback(any());
- verify(mStatusBarIconController).addIconGroup(any());
- verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
- }
-
- @Test
- @EnableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
- public void
- onConfigurationChanged_updateUserSwitcherFlagEnabled_updatesUserSwitcherVisibility() {
- mController.onViewAttached();
- runAllScheduled();
- verify(mConfigurationController).addCallback(mConfigurationListenerCaptor.capture());
- clearInvocations(mUserManager);
- clearInvocations(mKeyguardStatusBarView);
-
- mConfigurationListenerCaptor.getValue().onConfigChanged(null);
-
- runAllScheduled();
- verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
- verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean());
- }
-
- @Test
- @DisableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
- public void
- onConfigurationChanged_updateUserSwitcherFlagDisabled_updatesUserSwitcherVisibility() {
- mController.onViewAttached();
- verify(mConfigurationController).addCallback(mConfigurationListenerCaptor.capture());
- clearInvocations(mUserManager);
- clearInvocations(mKeyguardStatusBarView);
-
- mConfigurationListenerCaptor.getValue().onConfigChanged(null);
- verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
- verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean());
- }
-
- @Test
- @EnableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
- public void
- onKeyguardVisibilityChanged_userSwitcherFlagEnabled_updatesUserSwitcherVisibility() {
- mController.onViewAttached();
- runAllScheduled();
- verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardCallbackCaptor.capture());
- clearInvocations(mUserManager);
- clearInvocations(mKeyguardStatusBarView);
-
- mKeyguardCallbackCaptor.getValue().onKeyguardVisibilityChanged(true);
-
- runAllScheduled();
- verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
- verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean());
- }
-
- @Test
- @DisableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
- public void
- onKeyguardVisibilityChanged_userSwitcherFlagDisabled_updatesUserSwitcherVisibility() {
- mController.onViewAttached();
- verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardCallbackCaptor.capture());
- clearInvocations(mUserManager);
- clearInvocations(mKeyguardStatusBarView);
-
- mKeyguardCallbackCaptor.getValue().onKeyguardVisibilityChanged(true);
- verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
- verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean());
- }
-
- @Test
- public void onViewDetached_callbacksUnregistered() {
- // Set everything up first.
- mController.onViewAttached();
-
- mController.onViewDetached();
-
- verify(mConfigurationController).removeCallback(any());
- verify(mAnimationScheduler).removeCallback(any());
- verify(mUserInfoController).removeCallback(any());
- verify(mCommandQueue).removeCallback(any());
- verify(mStatusBarIconController).removeIconGroup(any());
- }
-
- @Test
- @DisableSceneContainer
- public void onViewReAttached_flagOff_iconManagerNotReRegistered() {
- mController.onViewAttached();
- mController.onViewDetached();
- reset(mStatusBarIconController);
-
- mController.onViewAttached();
-
- verify(mStatusBarIconController, never()).addIconGroup(any());
- }
-
- @Test
- @EnableSceneContainer
- public void onViewReAttached_flagOn_iconManagerReRegistered() {
- mController.onViewAttached();
- mController.onViewDetached();
- reset(mStatusBarIconController);
-
- mController.onViewAttached();
-
- verify(mStatusBarIconController).addIconGroup(any());
- }
-
- @Test
- @DisableSceneContainer
- public void setBatteryListening_true_callbackAdded() {
- mController.setBatteryListening(true);
-
- verify(mBatteryController).addCallback(any());
- }
-
- @Test
- @DisableSceneContainer
- public void setBatteryListening_false_callbackRemoved() {
- // First set to true so that we know setting to false is a change in state.
- mController.setBatteryListening(true);
-
- mController.setBatteryListening(false);
-
- verify(mBatteryController).removeCallback(any());
- }
-
- @Test
- @DisableSceneContainer
- public void setBatteryListening_trueThenTrue_callbackAddedOnce() {
- mController.setBatteryListening(true);
- mController.setBatteryListening(true);
-
- verify(mBatteryController).addCallback(any());
- }
-
- @Test
- @EnableSceneContainer
- public void setBatteryListening_true_flagOn_callbackNotAdded() {
- mController.setBatteryListening(true);
-
- verify(mBatteryController, never()).addCallback(any());
- }
-
- @Test
- public void updateTopClipping_viewClippingUpdated() {
- int viewTop = 20;
- mKeyguardStatusBarView.setTop(viewTop);
- int notificationPanelTop = 30;
-
- mController.updateTopClipping(notificationPanelTop);
-
- assertThat(mKeyguardStatusBarView.getClipBounds().top).isEqualTo(
- notificationPanelTop - viewTop);
- }
-
- @Test
- public void setNotTopClipping_viewClippingUpdatedToZero() {
- // Start out with some amount of top clipping.
- mController.updateTopClipping(50);
- assertThat(mKeyguardStatusBarView.getClipBounds().top).isGreaterThan(0);
-
- mController.setNoTopClipping();
-
- assertThat(mKeyguardStatusBarView.getClipBounds().top).isEqualTo(0);
- }
-
- @Test
- @DisableSceneContainer
- public void updateViewState_alphaAndVisibilityGiven_viewUpdated() {
- // Verify the initial values so we know the method triggers changes.
- assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(1f);
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
-
- float newAlpha = 0.5f;
- int newVisibility = View.INVISIBLE;
- mController.updateViewState(newAlpha, newVisibility);
-
- assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(newAlpha);
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(newVisibility);
- }
-
- @Test
- @DisableSceneContainer
- public void updateViewState_paramVisibleButIsDisabled_viewIsInvisible() {
- mController.onViewAttached();
- setDisableSystemIcons(true);
-
- mController.updateViewState(1f, View.VISIBLE);
-
- // Since we're disabled, we stay invisible
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
- }
-
- @Test
- @DisableSceneContainer
- public void updateViewState_notKeyguardState_nothingUpdated() {
- mController.onViewAttached();
- updateStateToNotKeyguard();
-
- float oldAlpha = mKeyguardStatusBarView.getAlpha();
-
- mController.updateViewState();
-
- assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(oldAlpha);
- }
-
- @Test
- @DisableSceneContainer
- public void updateViewState_bypassEnabledAndShouldListenForFace_viewHidden() {
- mController.onViewAttached();
- updateStateToKeyguard();
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
-
- when(mKeyguardUpdateMonitor.shouldListenForFace()).thenReturn(true);
- when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
- onFinishedGoingToSleep();
-
- mController.updateViewState();
-
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
- }
-
- @Test
- @DisableSceneContainer
- public void updateViewState_bypassNotEnabled_viewShown() {
- mController.onViewAttached();
- updateStateToKeyguard();
-
- when(mKeyguardUpdateMonitor.shouldListenForFace()).thenReturn(true);
- when(mKeyguardBypassController.getBypassEnabled()).thenReturn(false);
- onFinishedGoingToSleep();
-
- mController.updateViewState();
-
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
- }
-
- @Test
- @DisableSceneContainer
- public void updateViewState_shouldNotListenForFace_viewShown() {
- mController.onViewAttached();
- updateStateToKeyguard();
-
- when(mKeyguardUpdateMonitor.shouldListenForFace()).thenReturn(false);
- when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
- onFinishedGoingToSleep();
-
- mController.updateViewState();
-
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
- }
-
- @Test
- @DisableSceneContainer
- public void updateViewState_panelExpandedHeightZero_viewHidden() {
- mController.onViewAttached();
- updateStateToKeyguard();
-
- mShadeViewStateProvider.setPanelViewExpandedHeight(0);
-
- mController.updateViewState();
-
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
- }
-
- @Test
- @DisableSceneContainer
- public void updateViewState_dragProgressOne_viewHidden() {
- mController.onViewAttached();
- updateStateToKeyguard();
-
- mShadeViewStateProvider.setLockscreenShadeDragProgress(1f);
-
- mController.updateViewState();
-
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
- }
-
- @Test
- @DisableSceneContainer
- public void updateViewState_disableSystemInfoFalse_viewShown() {
- mController.onViewAttached();
- updateStateToKeyguard();
- setDisableSystemInfo(false);
-
- mController.updateViewState();
-
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
- }
-
- @Test
- @DisableSceneContainer
- public void updateViewState_disableSystemInfoTrue_viewHidden() {
- mController.onViewAttached();
- updateStateToKeyguard();
- setDisableSystemInfo(true);
-
- mController.updateViewState();
-
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
- }
-
- @Test
- @DisableSceneContainer
- public void updateViewState_disableSystemIconsFalse_viewShown() {
- mController.onViewAttached();
- updateStateToKeyguard();
- setDisableSystemIcons(false);
-
- mController.updateViewState();
-
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
- }
-
- @Test
- @DisableSceneContainer
- public void updateViewState_disableSystemIconsTrue_viewHidden() {
- mController.onViewAttached();
- updateStateToKeyguard();
- setDisableSystemIcons(true);
-
- mController.updateViewState();
-
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
- }
-
- @Test
- @DisableSceneContainer
- public void updateViewState_dozingTrue_flagOff_viewHidden() {
- mController.init();
- mController.onViewAttached();
- updateStateToKeyguard();
-
- mController.setDozing(true);
- mController.updateViewState();
-
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
- }
-
- @Test
- @DisableSceneContainer
- public void updateViewState_dozingFalse_flagOff_viewShown() {
- mController.init();
- mController.onViewAttached();
- updateStateToKeyguard();
-
- mController.setDozing(false);
- mController.updateViewState();
-
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
- }
-
- @Test
- @EnableSceneContainer
- public void updateViewState_flagOn_doesNothing() {
- mController.init();
- mController.onViewAttached();
- updateStateToKeyguard();
-
- mKeyguardStatusBarView.setVisibility(View.GONE);
- mKeyguardStatusBarView.setAlpha(0.456f);
-
- mController.updateViewState();
-
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.GONE);
- assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(0.456f);
- }
-
- @Test
- @EnableSceneContainer
- public void updateViewStateWithAlphaAndVis_flagOn_doesNothing() {
- mController.init();
- mController.onViewAttached();
- updateStateToKeyguard();
-
- mKeyguardStatusBarView.setVisibility(View.GONE);
- mKeyguardStatusBarView.setAlpha(0.456f);
-
- mController.updateViewState(0.789f, View.VISIBLE);
-
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.GONE);
- assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(0.456f);
- }
-
- @Test
- @EnableSceneContainer
- public void setAlpha_flagOn_doesNothing() {
- mController.init();
- mController.onViewAttached();
- updateStateToKeyguard();
-
- mKeyguardStatusBarView.setAlpha(0.456f);
-
- mController.setAlpha(0.123f);
-
- assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(0.456f);
- }
-
- @Test
- @EnableSceneContainer
- public void setDozing_flagOn_doesNothing() {
- mController.init();
- mController.onViewAttached();
- updateStateToKeyguard();
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
-
- mController.setDozing(true);
- mController.updateViewState();
-
- // setDozing(true) should typically cause the view to hide. But since the flag is on, we
- // should ignore these set dozing calls and stay the same visibility.
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
- }
-
- @Test
- @DisableSceneContainer
- public void setAlpha_explicitAlpha_setsExplicitAlpha() {
- mController.onViewAttached();
- updateStateToKeyguard();
-
- mController.setAlpha(0.5f);
-
- assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(0.5f);
- }
-
- @Test
- @DisableSceneContainer
- public void setAlpha_explicitAlpha_thenMinusOneAlpha_setsAlphaBasedOnDefaultCriteria() {
- mController.onViewAttached();
- updateStateToKeyguard();
-
- mController.setAlpha(0.5f);
- mController.setAlpha(-1f);
-
- assertThat(mKeyguardStatusBarView.getAlpha()).isGreaterThan(0);
- assertThat(mKeyguardStatusBarView.getAlpha()).isNotEqualTo(0.5f);
- }
-
- // TODO(b/195442899): Add more tests for #updateViewState once CLs are finalized.
-
- @Test
- @DisableSceneContainer
- public void updateForHeadsUp_headsUpShouldBeVisible_viewHidden() {
- mController.onViewAttached();
- updateStateToKeyguard();
- mKeyguardStatusBarView.setVisibility(View.VISIBLE);
-
- mShadeViewStateProvider.setShouldHeadsUpBeVisible(true);
- mController.updateForHeadsUp(/* animate= */ false);
-
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
- }
-
- @Test
- @DisableSceneContainer
- public void updateForHeadsUp_headsUpShouldNotBeVisible_viewShown() {
- mController.onViewAttached();
- updateStateToKeyguard();
-
- // Start with the opposite state.
- mShadeViewStateProvider.setShouldHeadsUpBeVisible(true);
- mController.updateForHeadsUp(/* animate= */ false);
-
- mShadeViewStateProvider.setShouldHeadsUpBeVisible(false);
- mController.updateForHeadsUp(/* animate= */ false);
-
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
- }
-
- @Test
- public void testNewUserSwitcherDisablesAvatar_newUiOn() {
- // GIVEN the status bar user switcher chip is enabled
- when(mStatusBarUserChipViewModel.getChipEnabled()).thenReturn(true);
-
- // WHEN the controller is created
- mController = createController();
-
- // THEN keyguard status bar view avatar is disabled
- assertThat(mKeyguardStatusBarView.isKeyguardUserAvatarEnabled()).isFalse();
- }
-
- @Test
- public void testNewUserSwitcherDisablesAvatar_newUiOff() {
- // GIVEN the status bar user switcher chip is disabled
- when(mStatusBarUserChipViewModel.getChipEnabled()).thenReturn(false);
-
- // WHEN the controller is created
- mController = createController();
-
- // THEN keyguard status bar view avatar is enabled
- assertThat(mKeyguardStatusBarView.isKeyguardUserAvatarEnabled()).isTrue();
- }
-
- @Test
- public void testBlockedIcons_obeysSettingForVibrateIcon_settingOff() {
- String str = mContext.getString(com.android.internal.R.string.status_bar_volume);
-
- // GIVEN the setting is off
- when(mSecureSettings.getInt(Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON, 0))
- .thenReturn(0);
-
- // WHEN CollapsedStatusBarFragment builds the blocklist
- mController.updateBlockedIcons();
-
- // THEN status_bar_volume SHOULD be present in the list
- boolean contains = mController.getBlockedIcons().contains(str);
- assertTrue(contains);
- }
-
- @Test
- public void testBlockedIcons_obeysSettingForVibrateIcon_settingOn() {
- String str = mContext.getString(com.android.internal.R.string.status_bar_volume);
-
- // GIVEN the setting is ON
- when(mSecureSettings.getIntForUser(Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON, 0,
- UserHandle.USER_CURRENT))
- .thenReturn(1);
-
- // WHEN CollapsedStatusBarFragment builds the blocklist
- mController.updateBlockedIcons();
-
- // THEN status_bar_volume SHOULD NOT be present in the list
- boolean contains = mController.getBlockedIcons().contains(str);
- assertFalse(contains);
- }
-
- private void updateStateToNotKeyguard() {
- updateStatusBarState(SHADE);
- }
-
- private void updateStateToKeyguard() {
- updateStatusBarState(KEYGUARD);
- }
-
- private void updateStatusBarState(int state) {
- ArgumentCaptor<StatusBarStateController.StateListener> statusBarStateListenerCaptor =
- ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
- verify(mStatusBarStateController).addCallback(statusBarStateListenerCaptor.capture());
- StatusBarStateController.StateListener callback = statusBarStateListenerCaptor.getValue();
-
- callback.onStateChanged(state);
- }
-
- @Test
- @DisableSceneContainer
- public void animateKeyguardStatusBarIn_isDisabled_viewStillHidden() {
- mController.onViewAttached();
- updateStateToKeyguard();
- setDisableSystemInfo(true);
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
-
- mController.animateKeyguardStatusBarIn();
-
- // Since we're disabled, we don't actually animate in and stay invisible
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
- }
-
- /**
- * Calls {@link com.android.keyguard.KeyguardUpdateMonitorCallback#onFinishedGoingToSleep(int)}
- * to ensure values are updated properly.
- */
- private void onFinishedGoingToSleep() {
- ArgumentCaptor<KeyguardUpdateMonitorCallback> keyguardUpdateCallbackCaptor =
- ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
- verify(mKeyguardUpdateMonitor).registerCallback(keyguardUpdateCallbackCaptor.capture());
- KeyguardUpdateMonitorCallback callback = keyguardUpdateCallbackCaptor.getValue();
-
- callback.onFinishedGoingToSleep(0);
- }
-
- private void setDisableSystemInfo(boolean disabled) {
- CommandQueue.Callbacks callback = getCommandQueueCallback();
- int disabled1 = disabled ? DISABLE_SYSTEM_INFO : 0;
- callback.disable(mContext.getDisplayId(), disabled1, 0, false);
- }
-
- private void setDisableSystemIcons(boolean disabled) {
- CommandQueue.Callbacks callback = getCommandQueueCallback();
- int disabled2 = disabled ? DISABLE2_SYSTEM_ICONS : 0;
- callback.disable(mContext.getDisplayId(), 0, disabled2, false);
- }
-
- private CommandQueue.Callbacks getCommandQueueCallback() {
- ArgumentCaptor<CommandQueue.Callbacks> captor =
- ArgumentCaptor.forClass(CommandQueue.Callbacks.class);
- verify(mCommandQueue).addCallback(captor.capture());
- return captor.getValue();
- }
-
- private void runAllScheduled() {
- mBackgroundExecutor.runAllReady();
- mFakeExecutor.runAllReady();
- }
-
- private static class TestShadeViewStateProvider
- implements ShadeViewStateProvider {
-
- TestShadeViewStateProvider() {}
-
- private float mPanelViewExpandedHeight = 100f;
- private boolean mShouldHeadsUpBeVisible = false;
- private float mLockscreenShadeDragProgress = 0f;
-
- @Override
- public float getPanelViewExpandedHeight() {
- return mPanelViewExpandedHeight;
- }
-
- @Override
- public boolean shouldHeadsUpBeVisible() {
- return mShouldHeadsUpBeVisible;
- }
-
- @Override
- public float getLockscreenShadeDragProgress() {
- return mLockscreenShadeDragProgress;
- }
-
- public void setPanelViewExpandedHeight(float panelViewExpandedHeight) {
- this.mPanelViewExpandedHeight = panelViewExpandedHeight;
- }
-
- public void setShouldHeadsUpBeVisible(boolean shouldHeadsUpBeVisible) {
- this.mShouldHeadsUpBeVisible = shouldHeadsUpBeVisible;
- }
-
- public void setLockscreenShadeDragProgress(float lockscreenShadeDragProgress) {
- this.mLockscreenShadeDragProgress = lockscreenShadeDragProgress;
- }
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
new file mode 100644
index 0000000..b815c6c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
@@ -0,0 +1,867 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.phone
+
+import android.app.StatusBarManager
+import android.graphics.Insets
+import android.os.UserHandle
+import android.os.UserManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.provider.Settings
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.testing.ViewUtils
+import android.view.LayoutInflater
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.CarrierTextController
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.keyguard.logging.KeyguardLogger
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGlanceableHubTransitionViewModel
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeViewStateProvider
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
+import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler
+import com.android.systemui.statusbar.phone.ui.StatusBarIconController
+import com.android.systemui.statusbar.phone.ui.TintedIconManager
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.UserInfoController
+import com.android.systemui.statusbar.ui.viewmodel.keyguardStatusBarViewModel
+import com.android.systemui.statusbar.ui.viewmodel.statusBarUserChipViewModel
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@RunWithLooper(setAsMainLooper = true)
+class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
+ private lateinit var kosmos: Kosmos
+ private lateinit var testScope: TestScope
+
+ @Mock private lateinit var carrierTextController: CarrierTextController
+
+ @Mock private lateinit var configurationController: ConfigurationController
+
+ @Mock private lateinit var animationScheduler: SystemStatusAnimationScheduler
+
+ @Mock private lateinit var batteryController: BatteryController
+
+ @Mock private lateinit var userInfoController: UserInfoController
+
+ @Mock private lateinit var statusBarIconController: StatusBarIconController
+
+ @Mock private lateinit var iconManagerFactory: TintedIconManager.Factory
+
+ @Mock private lateinit var iconManager: TintedIconManager
+
+ @Mock private lateinit var batteryMeterViewController: BatteryMeterViewController
+
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+
+ @Mock private lateinit var keyguardBypassController: KeyguardBypassController
+
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+
+ @Mock private lateinit var biometricUnlockController: BiometricUnlockController
+
+ @Mock
+ private lateinit var statusBarContentInsetsProviderStore: StatusBarContentInsetsProviderStore
+
+ @Mock private lateinit var userManager: UserManager
+
+ @Captor
+ private lateinit var configurationListenerCaptor:
+ ArgumentCaptor<ConfigurationController.ConfigurationListener>
+
+ @Captor
+ private lateinit var keyguardCallbackCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback>
+
+ @Mock private lateinit var secureSettings: SecureSettings
+
+ @Mock private lateinit var commandQueue: CommandQueue
+
+ @Mock private lateinit var logger: KeyguardLogger
+
+ @Mock private lateinit var statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory
+
+ private lateinit var shadeViewStateProvider: TestShadeViewStateProvider
+
+ private lateinit var keyguardStatusBarView: KeyguardStatusBarView
+ private lateinit var controller: KeyguardStatusBarViewController
+ private val fakeExecutor = FakeExecutor(FakeSystemClock())
+ private val backgroundExecutor = FakeExecutor(FakeSystemClock())
+
+ private lateinit var looper: TestableLooper
+
+ @Before
+ @Throws(Exception::class)
+ fun setup() {
+ looper = TestableLooper.get(this)
+ kosmos = testKosmos()
+ testScope = kosmos.testScope
+ shadeViewStateProvider = TestShadeViewStateProvider()
+
+ Mockito.`when`(
+ kosmos.statusBarContentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()
+ )
+ .thenReturn(Insets.of(0, 0, 0, 0))
+
+ MockitoAnnotations.initMocks(this)
+
+ Mockito.`when`(iconManagerFactory.create(ArgumentMatchers.any(), ArgumentMatchers.any()))
+ .thenReturn(iconManager)
+ Mockito.`when`(statusBarContentInsetsProviderStore.defaultDisplay)
+ .thenReturn(kosmos.statusBarContentInsetsProvider)
+ allowTestableLooperAsMainThread()
+ looper.runWithLooper {
+ keyguardStatusBarView =
+ Mockito.spy(
+ LayoutInflater.from(mContext).inflate(R.layout.keyguard_status_bar, null)
+ as KeyguardStatusBarView
+ )
+ Mockito.`when`(keyguardStatusBarView.getDisplay()).thenReturn(mContext.display)
+ }
+
+ controller = createController()
+ }
+
+ private fun createController(): KeyguardStatusBarViewController {
+ return KeyguardStatusBarViewController(
+ kosmos.testDispatcher,
+ keyguardStatusBarView,
+ carrierTextController,
+ configurationController,
+ animationScheduler,
+ batteryController,
+ userInfoController,
+ statusBarIconController,
+ iconManagerFactory,
+ batteryMeterViewController,
+ shadeViewStateProvider,
+ keyguardStateController,
+ keyguardBypassController,
+ keyguardUpdateMonitor,
+ kosmos.keyguardStatusBarViewModel,
+ biometricUnlockController,
+ kosmos.statusBarStateController,
+ statusBarContentInsetsProviderStore,
+ userManager,
+ kosmos.statusBarUserChipViewModel,
+ secureSettings,
+ commandQueue,
+ fakeExecutor,
+ backgroundExecutor,
+ logger,
+ statusOverlayHoverListenerFactory,
+ kosmos.communalSceneInteractor,
+ kosmos.glanceableHubToLockscreenTransitionViewModel,
+ kosmos.lockscreenToGlanceableHubTransitionViewModel,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+ fun onViewAttached_updateUserSwitcherFlagEnabled_callbacksRegistered() {
+ controller.onViewAttached()
+
+ runAllScheduled()
+ Mockito.verify(configurationController).addCallback(ArgumentMatchers.any())
+ Mockito.verify(animationScheduler).addCallback(ArgumentMatchers.any())
+ Mockito.verify(userInfoController).addCallback(ArgumentMatchers.any())
+ Mockito.verify(commandQueue).addCallback(ArgumentMatchers.any())
+ Mockito.verify(statusBarIconController).addIconGroup(ArgumentMatchers.any())
+ Mockito.verify(userManager).isUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+ fun onViewAttached_updateUserSwitcherFlagDisabled_callbacksRegistered() {
+ controller.onViewAttached()
+
+ Mockito.verify(configurationController).addCallback(ArgumentMatchers.any())
+ Mockito.verify(animationScheduler).addCallback(ArgumentMatchers.any())
+ Mockito.verify(userInfoController).addCallback(ArgumentMatchers.any())
+ Mockito.verify(commandQueue).addCallback(ArgumentMatchers.any())
+ Mockito.verify(statusBarIconController).addIconGroup(ArgumentMatchers.any())
+ Mockito.verify(userManager).isUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+ fun onConfigurationChanged_updateUserSwitcherFlagEnabled_updatesUserSwitcherVisibility() {
+ controller.onViewAttached()
+ runAllScheduled()
+ Mockito.verify(configurationController).addCallback(configurationListenerCaptor.capture())
+ Mockito.clearInvocations(userManager)
+ Mockito.clearInvocations(keyguardStatusBarView)
+
+ configurationListenerCaptor.value.onConfigChanged(null)
+
+ runAllScheduled()
+ Mockito.verify(userManager).isUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+ Mockito.verify(keyguardStatusBarView).setUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+ fun onConfigurationChanged_updateUserSwitcherFlagDisabled_updatesUserSwitcherVisibility() {
+ controller.onViewAttached()
+ Mockito.verify(configurationController).addCallback(configurationListenerCaptor.capture())
+ Mockito.clearInvocations(userManager)
+ Mockito.clearInvocations(keyguardStatusBarView)
+
+ configurationListenerCaptor.value.onConfigChanged(null)
+ Mockito.verify(userManager).isUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+ Mockito.verify(keyguardStatusBarView).setUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+ fun onKeyguardVisibilityChanged_userSwitcherFlagEnabled_updatesUserSwitcherVisibility() {
+ controller.onViewAttached()
+ runAllScheduled()
+ Mockito.verify(keyguardUpdateMonitor).registerCallback(keyguardCallbackCaptor.capture())
+ Mockito.clearInvocations(userManager)
+ Mockito.clearInvocations(keyguardStatusBarView)
+
+ keyguardCallbackCaptor.value.onKeyguardVisibilityChanged(true)
+
+ runAllScheduled()
+ Mockito.verify(userManager).isUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+ Mockito.verify(keyguardStatusBarView).setUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+ fun onKeyguardVisibilityChanged_userSwitcherFlagDisabled_updatesUserSwitcherVisibility() {
+ controller.onViewAttached()
+ Mockito.verify(keyguardUpdateMonitor).registerCallback(keyguardCallbackCaptor.capture())
+ Mockito.clearInvocations(userManager)
+ Mockito.clearInvocations(keyguardStatusBarView)
+
+ keyguardCallbackCaptor.value.onKeyguardVisibilityChanged(true)
+ Mockito.verify(userManager).isUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+ Mockito.verify(keyguardStatusBarView).setUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+ }
+
+ @Test
+ fun onViewDetached_callbacksUnregistered() {
+ // Set everything up first.
+ controller.onViewAttached()
+
+ controller.onViewDetached()
+
+ Mockito.verify(configurationController).removeCallback(ArgumentMatchers.any())
+ Mockito.verify(animationScheduler).removeCallback(ArgumentMatchers.any())
+ Mockito.verify(userInfoController).removeCallback(ArgumentMatchers.any())
+ Mockito.verify(commandQueue).removeCallback(ArgumentMatchers.any())
+ Mockito.verify(statusBarIconController).removeIconGroup(ArgumentMatchers.any())
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun onViewReAttached_flagOff_iconManagerNotReRegistered() {
+ controller.onViewAttached()
+ controller.onViewDetached()
+ Mockito.reset(statusBarIconController)
+
+ controller.onViewAttached()
+
+ Mockito.verify(statusBarIconController, Mockito.never())
+ .addIconGroup(ArgumentMatchers.any())
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun onViewReAttached_flagOn_iconManagerReRegistered() {
+ controller.onViewAttached()
+ controller.onViewDetached()
+ Mockito.reset(statusBarIconController)
+
+ controller.onViewAttached()
+
+ Mockito.verify(statusBarIconController).addIconGroup(ArgumentMatchers.any())
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun setBatteryListening_true_callbackAdded() {
+ controller.setBatteryListening(true)
+
+ Mockito.verify(batteryController).addCallback(ArgumentMatchers.any())
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun setBatteryListening_false_callbackRemoved() {
+ // First set to true so that we know setting to false is a change in state.
+ controller.setBatteryListening(true)
+
+ controller.setBatteryListening(false)
+
+ Mockito.verify(batteryController).removeCallback(ArgumentMatchers.any())
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun setBatteryListening_trueThenTrue_callbackAddedOnce() {
+ controller.setBatteryListening(true)
+ controller.setBatteryListening(true)
+
+ Mockito.verify(batteryController).addCallback(ArgumentMatchers.any())
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun setBatteryListening_true_flagOn_callbackNotAdded() {
+ controller.setBatteryListening(true)
+
+ Mockito.verify(batteryController, Mockito.never()).addCallback(ArgumentMatchers.any())
+ }
+
+ @Test
+ fun updateTopClipping_viewClippingUpdated() {
+ val viewTop = 20
+ keyguardStatusBarView.top = viewTop
+ val notificationPanelTop = 30
+
+ controller.updateTopClipping(notificationPanelTop)
+
+ Truth.assertThat(keyguardStatusBarView.clipBounds.top)
+ .isEqualTo(notificationPanelTop - viewTop)
+ }
+
+ @Test
+ fun setNotTopClipping_viewClippingUpdatedToZero() {
+ // Start out with some amount of top clipping.
+ controller.updateTopClipping(50)
+ Truth.assertThat(keyguardStatusBarView.clipBounds.top).isGreaterThan(0)
+
+ controller.setNoTopClipping()
+
+ Truth.assertThat(keyguardStatusBarView.clipBounds.top).isEqualTo(0)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun updateViewState_alphaAndVisibilityGiven_viewUpdated() {
+ // Verify the initial values so we know the method triggers changes.
+ Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(1f)
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+
+ val newAlpha = 0.5f
+ val newVisibility = View.INVISIBLE
+ controller.updateViewState(newAlpha, newVisibility)
+
+ Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(newAlpha)
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(newVisibility)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun updateViewState_paramVisibleButIsDisabled_viewIsInvisible() {
+ controller.onViewAttached()
+ setDisableSystemIcons(true)
+
+ controller.updateViewState(1f, View.VISIBLE)
+
+ // Since we're disabled, we stay invisible
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun updateViewState_notKeyguardState_nothingUpdated() {
+ controller.onViewAttached()
+ updateStateToNotKeyguard()
+
+ val oldAlpha = keyguardStatusBarView.alpha
+
+ controller.updateViewState()
+
+ Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(oldAlpha)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun updateViewState_bypassEnabledAndShouldListenForFace_viewHidden() {
+ controller.onViewAttached()
+ updateStateToKeyguard()
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+
+ Mockito.`when`(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(true)
+ Mockito.`when`(keyguardBypassController.bypassEnabled).thenReturn(true)
+ onFinishedGoingToSleep()
+
+ controller.updateViewState()
+
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun updateViewState_bypassNotEnabled_viewShown() {
+ controller.onViewAttached()
+ updateStateToKeyguard()
+
+ Mockito.`when`(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(true)
+ Mockito.`when`(keyguardBypassController.bypassEnabled).thenReturn(false)
+ onFinishedGoingToSleep()
+
+ controller.updateViewState()
+
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun updateViewState_shouldNotListenForFace_viewShown() {
+ controller.onViewAttached()
+ updateStateToKeyguard()
+
+ Mockito.`when`(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(false)
+ Mockito.`when`(keyguardBypassController.bypassEnabled).thenReturn(true)
+ onFinishedGoingToSleep()
+
+ controller.updateViewState()
+
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun updateViewState_panelExpandedHeightZero_viewHidden() {
+ controller.onViewAttached()
+ updateStateToKeyguard()
+
+ shadeViewStateProvider.panelViewExpandedHeight = 0f
+
+ controller.updateViewState()
+
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun updateViewState_dragProgressOne_viewHidden() {
+ controller.onViewAttached()
+ updateStateToKeyguard()
+
+ shadeViewStateProvider.lockscreenShadeDragProgress = 1f
+
+ controller.updateViewState()
+
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun updateViewState_disableSystemInfoFalse_viewShown() {
+ controller.onViewAttached()
+ updateStateToKeyguard()
+ setDisableSystemInfo(false)
+
+ controller.updateViewState()
+
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun updateViewState_disableSystemInfoTrue_viewHidden() {
+ controller.onViewAttached()
+ updateStateToKeyguard()
+ setDisableSystemInfo(true)
+
+ controller.updateViewState()
+
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun updateViewState_disableSystemIconsFalse_viewShown() {
+ controller.onViewAttached()
+ updateStateToKeyguard()
+ setDisableSystemIcons(false)
+
+ controller.updateViewState()
+
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun updateViewState_disableSystemIconsTrue_viewHidden() {
+ controller.onViewAttached()
+ updateStateToKeyguard()
+ setDisableSystemIcons(true)
+
+ controller.updateViewState()
+
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun updateViewState_dozingTrue_flagOff_viewHidden() {
+ controller.init()
+ controller.onViewAttached()
+ updateStateToKeyguard()
+
+ controller.setDozing(true)
+ controller.updateViewState()
+
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun updateViewState_dozingFalse_flagOff_viewShown() {
+ controller.init()
+ controller.onViewAttached()
+ updateStateToKeyguard()
+
+ controller.setDozing(false)
+ controller.updateViewState()
+
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun updateViewState_flagOn_doesNothing() {
+ controller.init()
+ controller.onViewAttached()
+ updateStateToKeyguard()
+
+ keyguardStatusBarView.visibility = View.GONE
+ keyguardStatusBarView.alpha = 0.456f
+
+ controller.updateViewState()
+
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.GONE)
+ Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(0.456f)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun updateViewStateWithAlphaAndVis_flagOn_doesNothing() {
+ controller.init()
+ controller.onViewAttached()
+ updateStateToKeyguard()
+
+ keyguardStatusBarView.visibility = View.GONE
+ keyguardStatusBarView.alpha = 0.456f
+
+ controller.updateViewState(0.789f, View.VISIBLE)
+
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.GONE)
+ Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(0.456f)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun setAlpha_flagOn_doesNothing() {
+ controller.init()
+ controller.onViewAttached()
+ updateStateToKeyguard()
+
+ keyguardStatusBarView.alpha = 0.456f
+
+ controller.setAlpha(0.123f)
+
+ Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(0.456f)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun setDozing_flagOn_doesNothing() {
+ controller.init()
+ controller.onViewAttached()
+ updateStateToKeyguard()
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+
+ controller.setDozing(true)
+ controller.updateViewState()
+
+ // setDozing(true) should typically cause the view to hide. But since the flag is on, we
+ // should ignore these set dozing calls and stay the same visibility.
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun setAlpha_explicitAlpha_setsExplicitAlpha() {
+ controller.onViewAttached()
+ updateStateToKeyguard()
+
+ controller.setAlpha(0.5f)
+
+ Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(0.5f)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun setAlpha_explicitAlpha_thenMinusOneAlpha_setsAlphaBasedOnDefaultCriteria() {
+ controller.onViewAttached()
+ updateStateToKeyguard()
+
+ controller.setAlpha(0.5f)
+ controller.setAlpha(-1f)
+
+ Truth.assertThat(keyguardStatusBarView.alpha).isGreaterThan(0)
+ Truth.assertThat(keyguardStatusBarView.alpha).isNotEqualTo(0.5f)
+ }
+
+ // TODO(b/195442899): Add more tests for #updateViewState once CLs are finalized.
+ @Test
+ @DisableSceneContainer
+ fun updateForHeadsUp_headsUpShouldBeVisible_viewHidden() {
+ controller.onViewAttached()
+ updateStateToKeyguard()
+ keyguardStatusBarView.visibility = View.VISIBLE
+
+ shadeViewStateProvider.setShouldHeadsUpBeVisible(true)
+ controller.updateForHeadsUp(/* animate= */ false)
+
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun updateForHeadsUp_headsUpShouldNotBeVisible_viewShown() {
+ controller.onViewAttached()
+ updateStateToKeyguard()
+
+ // Start with the opposite state.
+ shadeViewStateProvider.setShouldHeadsUpBeVisible(true)
+ controller.updateForHeadsUp(/* animate= */ false)
+
+ shadeViewStateProvider.setShouldHeadsUpBeVisible(false)
+ controller.updateForHeadsUp(/* animate= */ false)
+
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ fun testNewUserSwitcherDisablesAvatar_newUiOn() =
+ testScope.runTest {
+ // GIVEN the status bar user switcher chip is enabled
+ kosmos.fakeUserRepository.isStatusBarUserChipEnabled = true
+
+ // WHEN the controller is created
+ controller = createController()
+
+ // THEN keyguard status bar view avatar is disabled
+ Truth.assertThat(keyguardStatusBarView.isKeyguardUserAvatarEnabled).isFalse()
+ }
+
+ @Test
+ fun testNewUserSwitcherDisablesAvatar_newUiOff() {
+ // GIVEN the status bar user switcher chip is disabled
+ kosmos.fakeUserRepository.isStatusBarUserChipEnabled = false
+
+ // WHEN the controller is created
+ controller = createController()
+
+ // THEN keyguard status bar view avatar is enabled
+ Truth.assertThat(keyguardStatusBarView.isKeyguardUserAvatarEnabled).isTrue()
+ }
+
+ @Test
+ fun testBlockedIcons_obeysSettingForVibrateIcon_settingOff() {
+ val str = mContext.getString(com.android.internal.R.string.status_bar_volume)
+
+ // GIVEN the setting is off
+ Mockito.`when`(secureSettings.getInt(Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON, 0))
+ .thenReturn(0)
+
+ // WHEN CollapsedStatusBarFragment builds the blocklist
+ controller.updateBlockedIcons()
+
+ // THEN status_bar_volume SHOULD be present in the list
+ val contains = controller.blockedIcons.contains(str)
+ Assert.assertTrue(contains)
+ }
+
+ @Test
+ fun testBlockedIcons_obeysSettingForVibrateIcon_settingOn() {
+ val str = mContext.getString(com.android.internal.R.string.status_bar_volume)
+
+ // GIVEN the setting is ON
+ Mockito.`when`(
+ secureSettings.getIntForUser(
+ Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
+ 0,
+ UserHandle.USER_CURRENT,
+ )
+ )
+ .thenReturn(1)
+
+ // WHEN CollapsedStatusBarFragment builds the blocklist
+ controller.updateBlockedIcons()
+
+ // THEN status_bar_volume SHOULD NOT be present in the list
+ val contains = controller.blockedIcons.contains(str)
+ Assert.assertFalse(contains)
+ }
+
+ private fun updateStateToNotKeyguard() {
+ updateStatusBarState(StatusBarState.SHADE)
+ }
+
+ private fun updateStateToKeyguard() {
+ updateStatusBarState(StatusBarState.KEYGUARD)
+ }
+
+ private fun updateStatusBarState(state: Int) {
+ kosmos.statusBarStateController.setState(state)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun animateKeyguardStatusBarIn_isDisabled_viewStillHidden() {
+ controller.onViewAttached()
+ updateStateToKeyguard()
+ setDisableSystemInfo(true)
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+
+ controller.animateKeyguardStatusBarIn()
+
+ // Since we're disabled, we don't actually animate in and stay invisible
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ }
+
+ @Test
+ fun animateToGlanceableHub_affectsAlpha() =
+ testScope.runTest {
+ controller.init()
+ val transitionAlphaAmount = .5f
+ ViewUtils.attachView(keyguardStatusBarView)
+ looper.processAllMessages()
+ updateStateToKeyguard()
+ kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Communal)
+ runCurrent()
+ controller.updateCommunalAlphaTransition(transitionAlphaAmount)
+ Truth.assertThat(keyguardStatusBarView.getAlpha()).isEqualTo(transitionAlphaAmount)
+ }
+
+ @Test
+ fun animateToGlanceableHub_alphaResetOnCommunalNotShowing() =
+ testScope.runTest {
+ controller.init()
+ val transitionAlphaAmount = .5f
+ ViewUtils.attachView(keyguardStatusBarView)
+ looper.processAllMessages()
+ updateStateToKeyguard()
+ kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Communal)
+ runCurrent()
+ controller.updateCommunalAlphaTransition(transitionAlphaAmount)
+ kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Blank)
+ runCurrent()
+ Truth.assertThat(keyguardStatusBarView.getAlpha()).isNotEqualTo(transitionAlphaAmount)
+ }
+
+ /**
+ * Calls [com.android.keyguard.KeyguardUpdateMonitorCallback.onFinishedGoingToSleep] to ensure
+ * values are updated properly.
+ */
+ private fun onFinishedGoingToSleep() {
+ val keyguardUpdateCallbackCaptor =
+ ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
+ Mockito.verify(keyguardUpdateMonitor)
+ .registerCallback(keyguardUpdateCallbackCaptor.capture())
+ val callback = keyguardUpdateCallbackCaptor.value
+
+ callback.onFinishedGoingToSleep(0)
+ }
+
+ private fun setDisableSystemInfo(disabled: Boolean) {
+ val callback = commandQueueCallback
+ val disabled1 = if (disabled) StatusBarManager.DISABLE_SYSTEM_INFO else 0
+ callback.disable(mContext.displayId, disabled1, 0, false)
+ }
+
+ private fun setDisableSystemIcons(disabled: Boolean) {
+ val callback = commandQueueCallback
+ val disabled2 = if (disabled) StatusBarManager.DISABLE2_SYSTEM_ICONS else 0
+ callback.disable(mContext.displayId, 0, disabled2, false)
+ }
+
+ private val commandQueueCallback: CommandQueue.Callbacks
+ get() {
+ val captor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
+ Mockito.verify(commandQueue).addCallback(captor.capture())
+ return captor.value
+ }
+
+ private fun runAllScheduled() {
+ backgroundExecutor.runAllReady()
+ fakeExecutor.runAllReady()
+ }
+
+ private class TestShadeViewStateProvider : ShadeViewStateProvider {
+ override var panelViewExpandedHeight: Float = 100f
+ private var mShouldHeadsUpBeVisible = false
+ override var lockscreenShadeDragProgress: Float = 0f
+
+ override fun shouldHeadsUpBeVisible(): Boolean {
+ return mShouldHeadsUpBeVisible
+ }
+
+ fun setShouldHeadsUpBeVisible(shouldHeadsUpBeVisible: Boolean) {
+ this.mShouldHeadsUpBeVisible = shouldHeadsUpBeVisible
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
index 88ec18d..9099334 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
@@ -48,12 +48,10 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.statusbar.data.model.StatusBarAppearance;
import com.android.systemui.statusbar.data.model.StatusBarMode;
-import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository;
+import com.android.systemui.statusbar.data.repository.FakeStatusBarModePerDisplayRepository;
import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.util.kotlin.JavaAdapter;
import kotlinx.coroutines.test.TestScope;
@@ -81,8 +79,8 @@
private SysuiDarkIconDispatcher mStatusBarIconController;
private LightBarController mLightBarController;
private final TestScope mTestScope = TestScopeProvider.getTestScope();
- private final FakeStatusBarModeRepository mStatusBarModeRepository =
- new FakeStatusBarModeRepository();
+ private final FakeStatusBarModePerDisplayRepository mStatusBarModeRepository =
+ new FakeStatusBarModePerDisplayRepository();
@Before
public void setup() {
@@ -92,15 +90,16 @@
mLightBarTransitionsController = mock(LightBarTransitionsController.class);
when(mStatusBarIconController.getTransitionsController()).thenReturn(
mLightBarTransitionsController);
- mLightBarController = new LightBarController(
- mContext,
- new JavaAdapter(mTestScope),
+ mLightBarController = new LightBarControllerImpl(
+ mContext.getDisplayId(),
+ mTestScope,
mStatusBarIconController,
mock(BatteryController.class),
mock(NavigationModeController.class),
mStatusBarModeRepository,
mock(DumpManager.class),
- new FakeDisplayTracker(mContext));
+ mTestScope.getCoroutineContext(),
+ mock(BiometricUnlockController.class));
mLightBarController.start();
}
@@ -121,7 +120,7 @@
new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, secondBounds)
);
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -142,7 +141,7 @@
new AppearanceRegion(0 /* appearance */, secondBounds)
);
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -165,7 +164,7 @@
new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, secondBounds)
);
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -190,7 +189,7 @@
new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, thirdBounds)
);
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -214,7 +213,7 @@
new AppearanceRegion(0 /* appearance */, secondBounds)
);
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -231,7 +230,7 @@
new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, new Rect(0, 0, 1, 1))
);
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -249,7 +248,7 @@
new AppearanceRegion(0, new Rect(0, 0, 1, 1))
);
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -266,7 +265,7 @@
new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, new Rect(0, 0, 1, 1))
);
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -276,7 +275,7 @@
reset(mStatusBarIconController);
// WHEN the same appearance regions but different status bar mode is sent
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.LIGHTS_OUT_TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -298,7 +297,7 @@
/* start= */ new Rect(0, 0, 10, 10),
/* end= */ new Rect(0, 0, 20, 20));
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
startingBounds,
@@ -311,7 +310,7 @@
BoundsPair newBounds = new BoundsPair(
/* start= */ new Rect(0, 0, 30, 30),
/* end= */ new Rect(0, 0, 40, 40));
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
newBounds,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
index e0d9fac..110dec6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
@@ -36,7 +36,6 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
@@ -58,7 +57,6 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
-import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -144,6 +142,7 @@
controller.start()
controller.addCallback(mockOngoingCallListener)
controller.setChipView(chipView)
+ onTeardown { controller.tearDownChipView() }
val collectionListenerCaptor = ArgumentCaptor.forClass(NotifCollectionListener::class.java)
verify(notificationCollection).addCollectionListener(collectionListenerCaptor.capture())
@@ -153,11 +152,6 @@
.thenReturn(PROC_STATE_INVISIBLE)
}
- @After
- fun tearDown() {
- controller.tearDownChipView()
- }
-
@Test
fun onEntryUpdated_isOngoingCallNotif_listenerAndRepoNotified() {
val notification = NotificationEntryBuilder(createOngoingCallNotifEntry())
@@ -224,7 +218,7 @@
notifCollectionListener.onEntryUpdated(notification.build())
chipView.measure(
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
)
assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
@@ -241,7 +235,7 @@
notifCollectionListener.onEntryUpdated(notification.build())
chipView.measure(
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
)
assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
@@ -257,7 +251,7 @@
notifCollectionListener.onEntryUpdated(notification.build())
chipView.measure(
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
)
assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
@@ -668,7 +662,7 @@
private fun createCallNotifEntry(
callStyle: Notification.CallStyle,
- nullContentIntent: Boolean = false
+ nullContentIntent: Boolean = false,
): NotificationEntry {
val notificationEntryBuilder = NotificationEntryBuilder()
notificationEntryBuilder.modifyNotification(context).style = callStyle
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt
index fc1ea22..19d5a16 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt
@@ -66,6 +66,7 @@
logBuffer = FakeLogBuffer.Factory.create(),
verboseLogBuffer = FakeLogBuffer.Factory.create(),
systemClock,
+ context.resources,
)
private val demoDataSource =
mock<DemoDeviceBasedSatelliteDataSource>().also {
@@ -80,7 +81,11 @@
)
}
private val demoImpl =
- DemoDeviceBasedSatelliteRepository(demoDataSource, testScope.backgroundScope)
+ DemoDeviceBasedSatelliteRepository(
+ demoDataSource,
+ testScope.backgroundScope,
+ context.resources,
+ )
private val underTest =
DeviceBasedSatelliteRepositorySwitcher(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt
index 8769389..a70881a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt
@@ -58,7 +58,12 @@
whenever(it.satelliteEvents).thenReturn(fakeSatelliteEvents)
}
- underTest = DemoDeviceBasedSatelliteRepository(dataSource, testScope.backgroundScope)
+ underTest =
+ DemoDeviceBasedSatelliteRepository(
+ dataSource,
+ testScope.backgroundScope,
+ context.resources,
+ )
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt
index 55460bd..41fa9e7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt
@@ -28,4 +28,6 @@
override val signalStrength = MutableStateFlow(0)
override val isSatelliteAllowedForCurrentLocation = MutableStateFlow(false)
+
+ override var isOpportunisticSatelliteIconEnabled: Boolean = true
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
index e7e4969..509aa7a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
@@ -172,6 +172,26 @@
}
@Test
+ fun icon_null_allOosAndConfigIsFalse() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ // GIVEN config for opportunistic icon is false
+ repo.isOpportunisticSatelliteIconEnabled = false
+
+ // GIVEN all icons are OOS
+ val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+ i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
+
+ // GIVEN apm is disabled
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ // THEN icon is null because it is not allowed
+ assertThat(latest).isNull()
+ }
+
+ @Test
fun icon_null_isEmergencyOnly() =
testScope.runTest {
val latest by collectLastValue(underTest.icon)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 53e033e..e7ca1dd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -51,6 +51,8 @@
import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.provider.Settings;
import androidx.annotation.VisibleForTesting;
@@ -561,6 +563,113 @@
}
@Test
+ @EnableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+ public void onWallpaperColorsChanged_homeWallpaper_shouldUpdateTheme() {
+ // Should ask for a new theme when the colors of the last applied wallpaper change
+ WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+ Color.valueOf(Color.BLUE), null);
+
+ String jsonString =
+ "{\"android.theme.customization.color_source\":\"home_wallpaper\","
+ + "\"android.theme.customization.system_palette\":\"A16B00\","
+ + "\"android.theme.customization.accent_color\":\"A16B00\","
+ + "\"android.theme.customization.color_index\":\"2\"}";
+
+ when(mSecureSettings.getStringForUser(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+ .thenReturn(jsonString);
+ when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, USER_SYSTEM))
+ .thenReturn(1);
+ // Set LOCK wallpaper as the last applied one to verify that theme is no longer based on
+ // latest wallpaper
+ when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM))
+ .thenReturn(2);
+
+ mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
+ USER_SYSTEM);
+
+ ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
+ verify(mSecureSettings).putStringForUser(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture(),
+ anyInt());
+
+ verify(mThemeOverlayApplier)
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+ }
+
+
+
+ @Test
+ @EnableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+ public void onWallpaperColorsChanged_homeWallpaperWithSameColor_shouldKeepThemeAndReapply() {
+ // Shouldn't ask for a new theme when the colors of the last applied wallpaper change
+ // with the same specified system palette one.
+ WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+ Color.valueOf(0xffa16b00), null);
+
+ String jsonString =
+ "{\"android.theme.customization.color_source\":\"home_wallpaper\","
+ + "\"android.theme.customization.system_palette\":\"A16B00\","
+ + "\"android.theme.customization.accent_color\":\"A16B00\","
+ + "\"android.theme.customization.color_index\":\"2\"}";
+
+ when(mSecureSettings.getStringForUser(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+ .thenReturn(jsonString);
+ when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, USER_SYSTEM))
+ .thenReturn(1);
+ // Set LOCK wallpaper as the last applied one to verify that theme is no longer based on
+ // latest wallpaper
+ when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM))
+ .thenReturn(2);
+
+ mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
+ USER_SYSTEM);
+
+ ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
+ verify(mSecureSettings, never()).putString(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture());
+
+ // Apply overlay by existing theme from secure setting
+ verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+ }
+
+ @Test
+ @EnableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+ public void onWallpaperColorsChanged_lockWallpaper_shouldKeepTheme() {
+ // Should ask for a new theme when the colors of the last applied wallpaper change
+ WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+ Color.valueOf(Color.BLUE), null);
+
+ String jsonString =
+ "{\"android.theme.customization.color_source\":\"home_wallpaper\","
+ + "\"android.theme.customization.system_palette\":\"A16B00\","
+ + "\"android.theme.customization.accent_color\":\"A16B00\","
+ + "\"android.theme.customization.color_index\":\"2\"}";
+
+ when(mSecureSettings.getStringForUser(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+ .thenReturn(jsonString);
+ when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, USER_SYSTEM))
+ .thenReturn(1);
+ // Set LOCK wallpaper as the last applied one to verify that theme is no longer based on
+ // latest wallpaper
+ when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM))
+ .thenReturn(2);
+
+ mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_LOCK,
+ USER_SYSTEM);
+
+ ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
+ verify(mSecureSettings, never()).putString(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture());
+
+ verify(mThemeOverlayApplier, never())
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+ }
+
+ @Test
+ @DisableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
public void onWallpaperColorsChanged_resetThemeWhenFromLatestWallpaper() {
// Should ask for a new theme when the colors of the last applied wallpaper change
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
@@ -594,6 +703,7 @@
}
@Test
+ @DisableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
public void onWallpaperColorsChanged_keepThemeWhenFromLatestWallpaperAndSpecifiedColor() {
// Shouldn't ask for a new theme when the colors of the last applied wallpaper change
// with the same specified system palette one.
@@ -627,6 +737,7 @@
}
@Test
+ @DisableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
public void onWallpaperColorsChanged_keepThemeIfNotLatestWallpaper() {
// Shouldn't ask for a new theme when the colors of the wallpaper that is not the last
// applied one change
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
index f101539..09be93d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
@@ -24,7 +24,7 @@
import com.android.internal.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
import com.android.systemui.defaultDeviceState
import com.android.systemui.deviceStateManager
import com.android.systemui.display.data.repository.DeviceStateRepository
@@ -107,9 +107,9 @@
configurationController,
context,
testScope.backgroundScope,
- mock()
+ mock(),
)
- private val configurationInteractor = ConfigurationInteractor(configurationRepository)
+ private val configurationInteractor = ConfigurationInteractorImpl(configurationRepository)
private val unfoldTransitionProgressProvider = FakeUnfoldTransitionProvider()
private val unfoldTransitionRepository =
UnfoldTransitionRepositoryImpl(Optional.of(unfoldTransitionProgressProvider))
@@ -145,7 +145,7 @@
testScope.backgroundScope,
displaySwitchLatencyLogger,
systemClock,
- deviceStateManager
+ deviceStateManager,
)
}
@@ -174,7 +174,7 @@
DisplaySwitchLatencyEvent(
latencyMs = 250,
fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED,
- toFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN
+ toFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN,
)
assertThat(loggedEvent).isEqualTo(expectedLoggedEvent)
}
@@ -200,7 +200,7 @@
testScope.backgroundScope,
displaySwitchLatencyLogger,
systemClock,
- deviceStateManager
+ deviceStateManager,
)
areAnimationEnabled.emit(true)
@@ -226,7 +226,7 @@
DisplaySwitchLatencyEvent(
latencyMs = 50,
fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED,
- toFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN
+ toFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN,
)
assertThat(loggedEvent).isEqualTo(expectedLoggedEvent)
}
@@ -259,7 +259,7 @@
DisplaySwitchLatencyEvent(
latencyMs = 50,
fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED,
- toFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN
+ toFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN,
)
assertThat(loggedEvent).isEqualTo(expectedLoggedEvent)
}
@@ -289,7 +289,7 @@
DisplaySwitchLatencyEvent(
latencyMs = 200,
fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN,
- toFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED
+ toFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED,
)
assertThat(loggedEvent).isEqualTo(expectedLoggedEvent)
}
@@ -310,7 +310,7 @@
lastWakefulnessEvent.emit(
WakefulnessModel(
internalWakefulnessState = WakefulnessState.ASLEEP,
- lastSleepReason = WakeSleepReason.FOLD
+ lastSleepReason = WakeSleepReason.FOLD,
)
)
screenPowerState.emit(ScreenPowerState.SCREEN_OFF)
@@ -326,7 +326,7 @@
latencyMs = 200,
fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN,
toFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED,
- toState = SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__AOD
+ toState = SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__AOD,
)
assertThat(loggedEvent).isEqualTo(expectedLoggedEvent)
}
@@ -372,7 +372,7 @@
lastWakefulnessEvent.emit(
WakefulnessModel(
internalWakefulnessState = WakefulnessState.ASLEEP,
- lastSleepReason = WakeSleepReason.FOLD
+ lastSleepReason = WakeSleepReason.FOLD,
)
)
screenPowerState.emit(ScreenPowerState.SCREEN_OFF)
@@ -385,7 +385,7 @@
latencyMs = 0,
fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN,
toFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED,
- toState = SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__SCREEN_OFF
+ toState = SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__SCREEN_OFF,
)
assertThat(loggedEvent).isEqualTo(expectedLoggedEvent)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
index a0cfab4d..e88dbd2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
@@ -22,11 +22,10 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository
import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -42,70 +41,127 @@
class UserAwareSecureSettingsRepositoryTest : SysuiTestCase() {
private val kosmos = testKosmos()
- private val dispatcher = kosmos.testDispatcher
private val testScope = kosmos.testScope
private val secureSettings = kosmos.fakeSettings
- private val userRepository = Kosmos().fakeUserRepository
- private lateinit var repository: UserAwareSecureSettingsRepository
+ private val userRepository = kosmos.fakeUserRepository
+ private lateinit var underTest: UserAwareSecureSettingsRepository
@Before
fun setup() {
- repository =
- UserAwareSecureSettingsRepositoryImpl(
- secureSettings,
- userRepository,
- dispatcher,
- )
+ underTest = kosmos.userAwareSecureSettingsRepository
+
userRepository.setUserInfos(USER_INFOS)
- setSettingValueForUser(enabled = true, userInfo = SETTING_ENABLED_USER)
- setSettingValueForUser(enabled = false, userInfo = SETTING_DISABLED_USER)
+
+ secureSettings.putBoolForUser(BOOL_SETTING_NAME, true, USER_1.id)
+ secureSettings.putBoolForUser(BOOL_SETTING_NAME, false, USER_2.id)
+ secureSettings.putIntForUser(INT_SETTING_NAME, 1337, USER_1.id)
+ secureSettings.putIntForUser(INT_SETTING_NAME, 818, USER_2.id)
}
@Test
- fun settingEnabledEmitsValueForCurrentUser() {
+ fun boolSetting_emitsInitialValue() {
testScope.runTest {
- userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
+ userRepository.setSelectedUserInfo(USER_1)
- val enabled by collectLastValue(repository.boolSettingForActiveUser(SETTING_NAME))
+ val enabled by collectLastValue(underTest.boolSetting(BOOL_SETTING_NAME, false))
assertThat(enabled).isTrue()
}
}
@Test
- fun settingEnabledEmitsNewValueWhenSettingChanges() {
+ fun boolSetting_whenSettingChanges_emitsNewValue() {
testScope.runTest {
- userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
- val enabled by collectValues(repository.boolSettingForActiveUser(SETTING_NAME))
+ userRepository.setSelectedUserInfo(USER_1)
+ val enabled by collectValues(underTest.boolSetting(BOOL_SETTING_NAME, false))
runCurrent()
- setSettingValueForUser(enabled = false, userInfo = SETTING_ENABLED_USER)
+ secureSettings.putBoolForUser(BOOL_SETTING_NAME, false, USER_1.id)
assertThat(enabled).containsExactly(true, false).inOrder()
}
}
@Test
- fun settingEnabledEmitsValueForNewUserWhenUserChanges() {
+ fun boolSetting_whenWhenUserChanges_emitsNewValue() {
testScope.runTest {
- userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
- val enabled by collectLastValue(repository.boolSettingForActiveUser(SETTING_NAME))
+ userRepository.setSelectedUserInfo(USER_1)
+ val enabled by collectLastValue(underTest.boolSetting(BOOL_SETTING_NAME, false))
runCurrent()
- userRepository.setSelectedUserInfo(SETTING_DISABLED_USER)
+ userRepository.setSelectedUserInfo(USER_2)
assertThat(enabled).isFalse()
}
}
- private fun setSettingValueForUser(enabled: Boolean, userInfo: UserInfo) {
- secureSettings.putBoolForUser(SETTING_NAME, enabled, userInfo.id)
+ @Test
+ fun intSetting_emitsInitialValue() {
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(USER_1)
+
+ val number by collectLastValue(underTest.intSetting(INT_SETTING_NAME, 0))
+
+ assertThat(number).isEqualTo(1337)
+ }
}
+ @Test
+ fun intSetting_whenSettingChanges_emitsNewValue() {
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(USER_1)
+ val number by collectValues(underTest.intSetting(INT_SETTING_NAME, 0))
+ runCurrent()
+
+ secureSettings.putIntForUser(INT_SETTING_NAME, 1338, USER_1.id)
+
+ assertThat(number).containsExactly(1337, 1338).inOrder()
+ }
+ }
+
+ @Test
+ fun intSetting_whenWhenUserChanges_emitsNewValue() {
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(USER_1)
+ val number by collectLastValue(underTest.intSetting(INT_SETTING_NAME, 0))
+ runCurrent()
+
+ userRepository.setSelectedUserInfo(USER_2)
+
+ assertThat(number).isEqualTo(818)
+ }
+ }
+
+ @Test
+ fun getInt_returnsInitialValue() =
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(USER_1)
+
+ assertThat(underTest.getInt(INT_SETTING_NAME, 0)).isEqualTo(1337)
+ }
+
+ @Test
+ fun getInt_whenSettingChanges_returnsNewValue() =
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(USER_1)
+ secureSettings.putIntForUser(INT_SETTING_NAME, 999, USER_1.id)
+
+ assertThat(underTest.getInt(INT_SETTING_NAME, 0)).isEqualTo(999)
+ }
+
+ @Test
+ fun getInt_whenUserChanges_returnsThatUserValue() =
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(USER_2)
+
+ assertThat(underTest.getInt(INT_SETTING_NAME, 0)).isEqualTo(818)
+ }
+
private companion object {
- const val SETTING_NAME = "SETTING_NAME"
- val SETTING_ENABLED_USER = UserInfo(/* id= */ 0, "user1", /* flags= */ 0)
- val SETTING_DISABLED_USER = UserInfo(/* id= */ 1, "user2", /* flags= */ 0)
- val USER_INFOS = listOf(SETTING_ENABLED_USER, SETTING_DISABLED_USER)
+ const val BOOL_SETTING_NAME = "BOOL_SETTING_NAME"
+ const val INT_SETTING_NAME = "INT_SETTING_NAME"
+ val USER_1 = UserInfo(/* id= */ 0, "user1", /* flags= */ 0)
+ val USER_2 = UserInfo(/* id= */ 1, "user2", /* flags= */ 0)
+ val USER_INFOS = listOf(USER_1, USER_2)
}
}
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index d4a52c3..c8ef093 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -383,6 +383,10 @@
<!-- Whether to show activity indicators in the status bar -->
<bool name="config_showActivity">false</bool>
+ <!-- Whether to show the opportunistic satellite icon. When true, an icon will show to indicate
+ satellite capabilities when all other connections are out of service. -->
+ <bool name="config_showOpportunisticSatelliteIcon">true</bool>
+
<!-- Whether or not to show the notification shelf that houses the icons of notifications that
have been scrolled off-screen. -->
<bool name="config_showNotificationShelf">true</bool>
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 46e45aa..66b3e189 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -905,7 +905,18 @@
return lp;
}
- private WindowManager.LayoutParams getWindowLayoutBaseParams() {
+ public static WindowManager.LayoutParams getWindowLayoutBaseParams() {
+ return getWindowLayoutBaseParams(/* excludeFromScreenshots= */ true);
+ }
+
+ /**
+ * Creates the base {@link WindowManager.LayoutParams} that are used for all decoration windows.
+ *
+ * @param excludeFromScreenshots whether to set the {@link
+ * WindowManager.LayoutParams#PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY} flag.
+ */
+ public static WindowManager.LayoutParams getWindowLayoutBaseParams(
+ boolean excludeFromScreenshots) {
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
@@ -921,7 +932,7 @@
// FLAG_SLIPPERY can only be set by trusted overlays
lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
- if (!DEBUG_SCREENSHOT_ROUNDED_CORNERS) {
+ if (!DEBUG_SCREENSHOT_ROUNDED_CORNERS && excludeFromScreenshots) {
lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 12b5fc0..b491c94 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -27,6 +27,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AlertDialog;
+import android.app.KeyguardManager;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
@@ -315,6 +316,16 @@
mBiometricCallback = new BiometricCallback();
mMSDLPlayer = msdlPlayer;
+ // Listener for when device locks from adaptive auth, dismiss prompt
+ getContext().getSystemService(KeyguardManager.class).addKeyguardLockedStateListener(
+ getContext().getMainExecutor(),
+ isKeyguardLocked -> {
+ if (isKeyguardLocked) {
+ onStartedGoingToSleep();
+ }
+ }
+ );
+
final BiometricModalities biometricModalities = new BiometricModalities(
Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds),
Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds));
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
index ba51d02..68ec0f2d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
@@ -25,6 +25,7 @@
import android.hardware.fingerprint.FingerprintSensorProperties
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
+import android.util.Log
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.biometrics.shared.model.toSensorStrength
@@ -91,13 +92,14 @@
trySendWithFailureLogging(
DEFAULT_PROPS,
TAG,
- "no registered sensors, use default props"
+ "no registered sensors, use default props",
)
} else {
+ Log.d(TAG, "onAllAuthenticatorsRegistered $sensors")
trySendWithFailureLogging(
sensors[0],
TAG,
- "update properties on authenticators registered"
+ "update properties on authenticators registered",
)
}
}
@@ -160,7 +162,7 @@
FingerprintSensorProperties.TYPE_UNKNOWN,
false /* halControlsIllumination */,
true /* resetLockoutRequiresHardwareAuthToken */,
- listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT)
+ listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT),
)
private val DEFAULT_PROPS =
FingerprintSensorPropertiesInternal(
@@ -171,7 +173,7 @@
FingerprintSensorProperties.TYPE_UNKNOWN,
false /* halControlsIllumination */,
true /* resetLockoutRequiresHardwareAuthToken */,
- listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT)
+ listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT),
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
index 18a7739..abbbd73 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
@@ -48,14 +48,14 @@
private val authController: AuthController,
private val selectedUserInteractor: SelectedUserInteractor,
private val fingerprintManager: FingerprintManager?,
- @Application scope: CoroutineScope
+ @Application scope: CoroutineScope,
) {
private fun calculateIconSize(): Int {
val pixelPitch = context.resources.getFloat(R.dimen.pixel_pitch)
if (pixelPitch <= 0) {
Log.e(
"UdfpsOverlayInteractor",
- "invalid pixelPitch: $pixelPitch. Pixel pitch must be updated per device."
+ "invalid pixelPitch: $pixelPitch. Pixel pitch must be updated per device.",
)
}
return (context.resources.getFloat(R.dimen.udfps_icon_size) / pixelPitch).toInt()
@@ -83,12 +83,11 @@
/** Sets whether Udfps overlay should handle touches */
fun setHandleTouches(shouldHandle: Boolean = true) {
- if (authController.isUdfpsSupported
- && shouldHandle != _shouldHandleTouches.value) {
+ if (authController.isUdfpsSupported && shouldHandle != _shouldHandleTouches.value) {
fingerprintManager?.setIgnoreDisplayTouches(
requestId.value,
authController.udfpsProps!!.get(0).sensorId,
- !shouldHandle
+ !shouldHandle,
)
}
_shouldHandleTouches.value = shouldHandle
@@ -107,10 +106,11 @@
override fun onUdfpsLocationChanged(
udfpsOverlayParams: UdfpsOverlayParams
) {
+ Log.d(TAG, "udfpsOverlayParams updated $udfpsOverlayParams")
trySendWithFailureLogging(
udfpsOverlayParams,
TAG,
- "update udfpsOverlayParams"
+ "update udfpsOverlayParams",
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
index d1c728c..1923880 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
@@ -18,19 +18,12 @@
import com.android.systemui.common.data.repository.PackageChangeRepository
import com.android.systemui.common.data.repository.PackageChangeRepositoryImpl
-import com.android.systemui.common.ui.data.repository.ConfigurationRepository
-import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
import dagger.Binds
import dagger.Module
@Module
abstract class CommonDataLayerModule {
@Binds
- abstract fun bindConfigurationRepository(
- impl: ConfigurationRepositoryImpl
- ): ConfigurationRepository
-
- @Binds
abstract fun bindPackageChangeRepository(
impl: PackageChangeRepositoryImpl
): PackageChangeRepository
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationModule.kt
similarity index 67%
rename from packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt
rename to packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationModule.kt
index b36da3b..7f50e4a 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationModule.kt
@@ -17,6 +17,9 @@
package com.android.systemui.common.ui
import android.content.Context
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -28,24 +31,31 @@
/**
* Annotates elements that provide information from the global configuration.
*
- * The global configuration is the one associted with the main display. Secondary displays will
+ * The global configuration is the one associated with the main display. Secondary displays will
* apply override to the global configuration. Elements annotated with this shouldn't be used for
* secondary displays.
*/
@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class GlobalConfig
@Module
-interface ConfigurationStateModule {
+interface ConfigurationModule {
/**
* Deprecated: [ConfigurationState] should be injected only with the correct annotation. For
* now, without annotation the global config associated state is provided.
*/
@Binds
+ @Deprecated("Use the @GlobalConfig annotated one instead of this.")
fun provideGlobalConfigurationState(
@GlobalConfig configurationState: ConfigurationState
): ConfigurationState
+ @Binds
+ @Deprecated("Use the @GlobalConfig annotated one instead of this.")
+ fun provideDefaultConfigurationState(
+ @GlobalConfig configurationState: ConfigurationInteractor
+ ): ConfigurationInteractor
+
companion object {
@SysUISingleton
@Provides
@@ -57,5 +67,14 @@
): ConfigurationState {
return configStateFactory.create(context, configurationController)
}
+
+ @SysUISingleton
+ @Provides
+ @GlobalConfig
+ fun provideGlobalConfigurationInteractor(
+ configurationRepository: ConfigurationRepository
+ ): ConfigurationInteractor {
+ return ConfigurationInteractorImpl(configurationRepository)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
index 2052c70..df89152 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
@@ -23,13 +23,17 @@
import androidx.annotation.DimenRes
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.common.ui.GlobalConfig
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.wrapper.DisplayUtilsWrapper
import dagger.Binds
import dagger.Module
-import javax.inject.Inject
+import dagger.Provides
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
@@ -57,66 +61,62 @@
fun getDimensionPixelSize(id: Int): Int
}
-@SysUISingleton
class ConfigurationRepositoryImpl
-@Inject
+@AssistedInject
constructor(
- private val configurationController: ConfigurationController,
- private val context: Context,
+ @Assisted private val configurationController: ConfigurationController,
+ @Assisted private val context: Context,
@Application private val scope: CoroutineScope,
private val displayUtils: DisplayUtilsWrapper,
) : ConfigurationRepository {
private val displayInfo = MutableStateFlow(DisplayInfo())
- override val onAnyConfigurationChange: Flow<Unit> =
- conflatedCallbackFlow {
- val callback =
- object : ConfigurationController.ConfigurationListener {
- override fun onUiModeChanged() {
- sendUpdate("ConfigurationRepository#onUiModeChanged")
- }
-
- override fun onThemeChanged() {
- sendUpdate("ConfigurationRepository#onThemeChanged")
- }
-
- override fun onConfigChanged(newConfig: Configuration) {
- sendUpdate("ConfigurationRepository#onConfigChanged")
- }
-
- fun sendUpdate(reason: String) {
- trySendWithFailureLogging(Unit, reason)
- }
+ override val onAnyConfigurationChange: Flow<Unit> = conflatedCallbackFlow {
+ val callback =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onUiModeChanged() {
+ sendUpdate("ConfigurationRepository#onUiModeChanged")
}
- configurationController.addCallback(callback)
- awaitClose { configurationController.removeCallback(callback) }
- }
- override val onConfigurationChange: Flow<Unit> =
- conflatedCallbackFlow {
- val callback =
- object : ConfigurationController.ConfigurationListener {
- override fun onConfigChanged(newConfig: Configuration) {
- trySendWithFailureLogging(Unit, "ConfigurationRepository#onConfigChanged")
- }
+ override fun onThemeChanged() {
+ sendUpdate("ConfigurationRepository#onThemeChanged")
}
- configurationController.addCallback(callback)
- awaitClose { configurationController.removeCallback(callback) }
- }
- override val configurationValues: Flow<Configuration> =
- conflatedCallbackFlow {
- val callback =
- object : ConfigurationController.ConfigurationListener {
- override fun onConfigChanged(newConfig: Configuration) {
- trySend(newConfig)
- }
- }
+ override fun onConfigChanged(newConfig: Configuration) {
+ sendUpdate("ConfigurationRepository#onConfigChanged")
+ }
- trySend(context.resources.configuration)
- configurationController.addCallback(callback)
- awaitClose { configurationController.removeCallback(callback) }
+ fun sendUpdate(reason: String) {
+ trySendWithFailureLogging(Unit, reason)
+ }
}
+ configurationController.addCallback(callback)
+ awaitClose { configurationController.removeCallback(callback) }
+ }
+
+ override val onConfigurationChange: Flow<Unit> = conflatedCallbackFlow {
+ val callback =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onConfigChanged(newConfig: Configuration) {
+ trySendWithFailureLogging(Unit, "ConfigurationRepository#onConfigChanged")
+ }
+ }
+ configurationController.addCallback(callback)
+ awaitClose { configurationController.removeCallback(callback) }
+ }
+
+ override val configurationValues: Flow<Configuration> = conflatedCallbackFlow {
+ val callback =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onConfigChanged(newConfig: Configuration) {
+ trySend(newConfig)
+ }
+ }
+
+ trySend(context.resources.configuration)
+ configurationController.addCallback(callback)
+ awaitClose { configurationController.removeCallback(callback) }
+ }
override val scaleForResolution: StateFlow<Float> =
onConfigurationChange
@@ -134,7 +134,7 @@
maxDisplayMode.physicalWidth,
maxDisplayMode.physicalHeight,
displayInfo.value.naturalWidth,
- displayInfo.value.naturalHeight
+ displayInfo.value.naturalHeight,
)
return if (scaleFactor == Float.POSITIVE_INFINITY) 1f else scaleFactor
}
@@ -144,9 +144,40 @@
override fun getDimensionPixelSize(@DimenRes id: Int): Int {
return context.resources.getDimensionPixelSize(id)
}
+
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ context: Context,
+ configurationController: ConfigurationController,
+ ): ConfigurationRepositoryImpl
+ }
}
@Module
-interface ConfigurationRepositoryModule {
- @Binds fun bindImpl(impl: ConfigurationRepositoryImpl): ConfigurationRepository
+abstract class ConfigurationRepositoryModule {
+
+ /**
+ * For compatibility reasons. Ideally, only the an annotated [ConfigurationRepository] should be
+ * injected.
+ */
+ @Binds
+ @Deprecated("Use the ConfigurationRepository annotated with @GlobalConfig instead.")
+ @SysUISingleton
+ abstract fun provideDefaultConfigRepository(
+ @GlobalConfig configurationRepository: ConfigurationRepository
+ ): ConfigurationRepository
+
+ companion object {
+ @Provides
+ @GlobalConfig
+ @SysUISingleton
+ fun provideGlobalConfigRepository(
+ context: Context,
+ @GlobalConfig configurationController: ConfigurationController,
+ factory: ConfigurationRepositoryImpl.Factory,
+ ): ConfigurationRepository {
+ return factory.create(context, configurationController)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
index adb1ee2..97a23e1 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
@@ -21,8 +21,6 @@
import android.graphics.Rect
import android.view.Surface
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
-import com.android.systemui.dagger.SysUISingleton
-import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -32,13 +30,52 @@
import kotlinx.coroutines.flow.onStart
/** Business logic related to configuration changes. */
-@SysUISingleton
-class ConfigurationInteractor @Inject constructor(private val repository: ConfigurationRepository) {
+interface ConfigurationInteractor {
/**
* Returns screen size adjusted to rotation, so returned screen size is stable across all
* rotations
*/
- private val Configuration.naturalScreenBounds: Rect
+ val Configuration.naturalScreenBounds: Rect
+
+ /** Returns the unadjusted screen size. */
+ val maxBounds: Flow<Rect>
+
+ /**
+ * Returns screen size adjusted to rotation, so returned screen sizes are stable across all
+ * rotations, could be useful if you need to react to screen resize (e.g. fold/unfold on
+ * foldable devices)
+ */
+ val naturalMaxBounds: Flow<Rect>
+
+ /**
+ * The layout direction. Will be either `View#LAYOUT_DIRECTION_LTR` or
+ * `View#LAYOUT_DIRECTION_RTL`.
+ */
+ val layoutDirection: Flow<Int>
+
+ /** Emit an event on any config change */
+ val onAnyConfigurationChange: Flow<Unit>
+
+ /** Emits the new configuration on any configuration change */
+ val configurationValues: Flow<Configuration>
+
+ /** Emits the current resolution scaling factor */
+ val scaleForResolution: Flow<Float>
+
+ /** Given [resourceId], emit the dimension pixel size on config change */
+ fun dimensionPixelSize(resourceId: Int): Flow<Int>
+
+ /** Emits the dimensional pixel size of the given resource, inverting it for RTL if necessary */
+ fun directionalDimensionPixelSize(originLayoutDirection: Int, resourceId: Int): Flow<Int>
+
+ /** Given a set of [resourceId]s, emit Map<ResourceId, DimensionPixelSize> on config change */
+ fun dimensionPixelSize(resourceIds: Set<Int>): Flow<Map<Int, Int>>
+}
+
+class ConfigurationInteractorImpl(private val repository: ConfigurationRepository) :
+ ConfigurationInteractor {
+
+ override val Configuration.naturalScreenBounds: Rect
get() {
val rotation = windowConfiguration.displayRotation
val maxBounds = windowConfiguration.maxBounds
@@ -49,53 +86,40 @@
}
}
- /** Returns the unadjusted screen size. */
- val maxBounds: Flow<Rect> =
+ override val maxBounds: Flow<Rect> =
repository.configurationValues
.map { Rect(it.windowConfiguration.maxBounds) }
.distinctUntilChanged()
- /**
- * Returns screen size adjusted to rotation, so returned screen sizes are stable across all
- * rotations, could be useful if you need to react to screen resize (e.g. fold/unfold on
- * foldable devices)
- */
- val naturalMaxBounds: Flow<Rect> =
+ override val naturalMaxBounds: Flow<Rect> =
repository.configurationValues.map { it.naturalScreenBounds }.distinctUntilChanged()
- /**
- * The layout direction. Will be either `View#LAYOUT_DIRECTION_LTR` or
- * `View#LAYOUT_DIRECTION_RTL`.
- */
- val layoutDirection: Flow<Int> =
+ override val layoutDirection: Flow<Int> =
repository.configurationValues.map { it.layoutDirection }.distinctUntilChanged()
- /** Given [resourceId], emit the dimension pixel size on config change */
- fun dimensionPixelSize(resourceId: Int): Flow<Int> {
+ override fun dimensionPixelSize(resourceId: Int): Flow<Int> {
return onAnyConfigurationChange.mapLatest { repository.getDimensionPixelSize(resourceId) }
}
- /** Emits the dimensional pixel size of the given resource, inverting it for RTL if necessary */
- fun directionalDimensionPixelSize(originLayoutDirection: Int, resourceId: Int): Flow<Int> {
+ override fun directionalDimensionPixelSize(
+ originLayoutDirection: Int,
+ resourceId: Int,
+ ): Flow<Int> {
return dimensionPixelSize(resourceId).combine(layoutDirection) { size, direction ->
if (originLayoutDirection == direction) size else -size
}
}
- /** Given a set of [resourceId]s, emit Map<ResourceId, DimensionPixelSize> on config change */
- fun dimensionPixelSize(resourceIds: Set<Int>): Flow<Map<Int, Int>> {
+ override fun dimensionPixelSize(resourceIds: Set<Int>): Flow<Map<Int, Int>> {
return onAnyConfigurationChange.mapLatest {
resourceIds.associateWith { repository.getDimensionPixelSize(it) }
}
}
- /** Emit an event on any config change */
- val onAnyConfigurationChange: Flow<Unit> =
+ override val onAnyConfigurationChange: Flow<Unit> =
repository.onAnyConfigurationChange.onStart { emit(Unit) }
- /** Emits the new configuration on any configuration change */
- val configurationValues: Flow<Configuration> = repository.configurationValues
+ override val configurationValues: Flow<Configuration> = repository.configurationValues
- /** Emits the current resolution scaling factor */
- val scaleForResolution: Flow<Float> = repository.scaleForResolution
+ override val scaleForResolution: Flow<Float> = repository.scaleForResolution
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
index 012c844..b80e77c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
@@ -19,13 +19,13 @@
import android.app.smartspace.SmartspaceConfig
import android.app.smartspace.SmartspaceManager
import android.app.smartspace.SmartspaceSession
-import android.content.Context
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
import com.android.systemui.plugins.BcSmartspaceDataPlugin.UI_SURFACE_GLANCEABLE_HUB
+import com.android.systemui.settings.UserTracker
import com.android.systemui.smartspace.SmartspacePrecondition
import com.android.systemui.smartspace.SmartspaceTargetFilter
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.GLANCEABLE_HUB_SMARTSPACE_DATA_PLUGIN
@@ -42,8 +42,7 @@
class CommunalSmartspaceController
@Inject
constructor(
- private val context: Context,
- private val smartspaceManager: SmartspaceManager?,
+ private val userTracker: UserTracker,
private val execution: Execution,
@Main private val uiExecutor: Executor,
@Named(LOCKSCREEN_SMARTSPACE_PRECONDITION) private val precondition: SmartspacePrecondition,
@@ -55,6 +54,7 @@
private const val TAG = "CommunalSmartspaceCtrlr"
}
+ private var userSmartspaceManager: SmartspaceManager? = null
private var session: SmartspaceSession? = null
private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
private var targetFilter: SmartspaceTargetFilter? = optionalTargetFilter.orElse(null)
@@ -104,7 +104,11 @@
}
private fun connectSession() {
- if (smartspaceManager == null) {
+ if (userSmartspaceManager == null) {
+ userSmartspaceManager =
+ userTracker.userContext.getSystemService(SmartspaceManager::class.java)
+ }
+ if (userSmartspaceManager == null) {
return
}
if (plugin == null) {
@@ -119,11 +123,11 @@
}
val newSession =
- smartspaceManager.createSmartspaceSession(
- SmartspaceConfig.Builder(context, UI_SURFACE_GLANCEABLE_HUB).build()
+ userSmartspaceManager?.createSmartspaceSession(
+ SmartspaceConfig.Builder(userTracker.userContext, UI_SURFACE_GLANCEABLE_HUB).build()
)
Log.d(TAG, "Starting smartspace session for communal")
- newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
+ newSession?.addOnTargetsAvailableListener(uiExecutor, sessionListener)
this.session = newSession
plugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
@@ -163,7 +167,7 @@
private fun addAndRegisterListener(
listener: SmartspaceTargetListener,
- smartspaceDataPlugin: BcSmartspaceDataPlugin?
+ smartspaceDataPlugin: BcSmartspaceDataPlugin?,
) {
execution.assertIsMainThread()
smartspaceDataPlugin?.registerListener(listener)
@@ -174,7 +178,7 @@
private fun removeAndUnregisterListener(
listener: SmartspaceTargetListener,
- smartspaceDataPlugin: BcSmartspaceDataPlugin?
+ smartspaceDataPlugin: BcSmartspaceDataPlugin?,
) {
execution.assertIsMainThread()
smartspaceDataPlugin?.unregisterListener(listener)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManager.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManager.kt
index 202edf7..2f686fd 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManager.kt
@@ -19,7 +19,10 @@
import android.appwidget.AppWidgetHost.AppWidgetHostListener
import android.appwidget.AppWidgetProviderInfo
import android.content.ComponentName
+import android.content.IntentSender
import android.os.IBinder
+import android.os.OutcomeReceiver
+import android.os.RemoteException
import android.os.UserHandle
import android.widget.RemoteViews
import com.android.server.servicewatcher.ServiceWatcher
@@ -27,14 +30,19 @@
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IAppWidgetHostListener
+import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IConfigureWidgetCallback
import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IGlanceableHubWidgetsListener
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import java.util.concurrent.Executor
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.launch
/**
* Manages updates to Glanceable Hub widgets and requests to edit them from the headless system
@@ -47,6 +55,8 @@
class GlanceableHubWidgetManager
@Inject
constructor(
+ @Background private val bgExecutor: Executor,
+ @Background private val bgScope: CoroutineScope,
glanceableHubMultiUserHelper: GlanceableHubMultiUserHelper,
@CommunalLog logBuffer: LogBuffer,
serviceWatcherFactory: ServiceWatcherFactory<GlanceableHubWidgetManagerServiceInfo?>,
@@ -101,8 +111,7 @@
rank: Int?,
configurator: WidgetConfigurator?,
) = runOnService { service ->
- // TODO(b/375036327): Add support for widget configuration
- service.addWidget(provider, user, rank ?: -1)
+ service.addWidget(provider, user, rank ?: -1, createIConfigureWidgetCallback(configurator))
}
/** Requests the foreground user to delete a widget. */
@@ -129,18 +138,54 @@
)
}
- private fun runOnService(block: (IGlanceableHubWidgetManagerService) -> Unit) {
- serviceWatcher.runOnBinder(
- object : ServiceWatcher.BinderOperation {
- override fun run(binder: IBinder?) {
- block(IGlanceableHubWidgetManagerService.Stub.asInterface(binder))
- }
+ /**
+ * Requests the foreground user for the [IntentSender] to start a configuration activity for the
+ * widget.
+ *
+ * @param appWidgetId Id of the widget to configure.
+ * @param outcomeReceiver Callback for receiving the result or error.
+ * @param executor Executor to run the callback on.
+ */
+ fun getIntentSenderForConfigureActivity(
+ appWidgetId: Int,
+ outcomeReceiver: OutcomeReceiver<IntentSender?, Throwable>,
+ executor: Executor,
+ ) {
+ bgExecutor.execute {
+ serviceWatcher.runOnBinder(
+ object : ServiceWatcher.BinderOperation {
+ override fun run(binder: IBinder?) {
+ val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder)
+ try {
+ val result = service.getIntentSenderForConfigureActivity(appWidgetId)
+ executor.execute { outcomeReceiver.onResult(result) }
+ } catch (e: RemoteException) {
+ executor.execute { outcomeReceiver.onError(e) }
+ }
+ }
- override fun onError(t: Throwable?) {
- // TODO(b/375236794): handle failure in case service is unbound
+ override fun onError(t: Throwable?) {
+ t?.let { executor.execute { outcomeReceiver.onError(t) } }
+ }
}
- }
- )
+ )
+ }
+ }
+
+ private fun runOnService(block: (IGlanceableHubWidgetManagerService) -> Unit) {
+ bgExecutor.execute {
+ serviceWatcher.runOnBinder(
+ object : ServiceWatcher.BinderOperation {
+ override fun run(binder: IBinder?) {
+ block(IGlanceableHubWidgetManagerService.Stub.asInterface(binder))
+ }
+
+ override fun onError(t: Throwable?) {
+ // TODO(b/375236794): handle failure in case service is unbound
+ }
+ }
+ )
+ }
}
private fun createIAppWidgetHostListener(
@@ -165,6 +210,30 @@
}
}
+ private fun createIConfigureWidgetCallback(
+ configurator: WidgetConfigurator?
+ ): IConfigureWidgetCallback? {
+ return configurator?.let {
+ object : IConfigureWidgetCallback.Stub() {
+ override fun onConfigureWidget(
+ appWidgetId: Int,
+ resultReceiver: IConfigureWidgetCallback.IResultReceiver?,
+ ) {
+ bgScope.launch {
+ val success = configurator.configureWidget(appWidgetId)
+ try {
+ resultReceiver?.onResult(success)
+ } catch (e: RemoteException) {
+ logger.e({ "Error reporting widget configuration result: $str1" }) {
+ str1 = e.localizedMessage
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
companion object {
private const val TAG = "GlanceableHubWidgetManager"
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerService.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerService.kt
index 4d042fc..0e43e2a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerService.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerService.kt
@@ -20,8 +20,10 @@
import android.appwidget.AppWidgetProviderInfo
import android.content.ComponentName
import android.content.Intent
+import android.content.IntentSender
import android.os.IBinder
import android.os.RemoteCallbackList
+import android.os.RemoteException
import android.os.UserHandle
import android.widget.RemoteViews
import androidx.lifecycle.LifecycleService
@@ -29,11 +31,13 @@
import com.android.systemui.communal.data.repository.CommunalWidgetRepository
import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IAppWidgetHostListener
+import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IConfigureWidgetCallback
import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IGlanceableHubWidgetsListener
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
import javax.inject.Inject
+import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -98,7 +102,15 @@
val job =
widgetRepository.communalWidgets
- .onEach { widgets -> listener.onWidgetsUpdated(widgets) }
+ .onEach { widgets ->
+ try {
+ listener.onWidgetsUpdated(widgets)
+ } catch (e: RemoteException) {
+ logger.e({ "Error pushing widget update: $str1" }) {
+ str1 = e.localizedMessage
+ }
+ }
+ }
.launchIn(lifecycleScope)
widgetListenersRegistry.register(listener, job)
}
@@ -122,7 +134,12 @@
appWidgetHost.setListener(appWidgetId, createListener(listener))
}
- private fun addWidgetInternal(provider: ComponentName?, user: UserHandle?, rank: Int) {
+ private fun addWidgetInternal(
+ provider: ComponentName?,
+ user: UserHandle?,
+ rank: Int,
+ callback: IConfigureWidgetCallback?,
+ ) {
if (provider == null) {
throw IllegalStateException("Provider cannot be null")
}
@@ -131,8 +148,29 @@
throw IllegalStateException("User cannot be null")
}
- // TODO(b/375036327): Add support for widget configuration
- widgetRepository.addWidget(provider, user, rank, configurator = null)
+ val configurator =
+ callback?.let {
+ WidgetConfigurator { appWidgetId ->
+ try {
+ val result = CompletableDeferred<Boolean>()
+ val resultReceiver =
+ object : IConfigureWidgetCallback.IResultReceiver.Stub() {
+ override fun onResult(success: Boolean) {
+ result.complete(success)
+ }
+ }
+
+ callback.onConfigureWidget(appWidgetId, resultReceiver)
+ result.await()
+ } catch (e: RemoteException) {
+ logger.e({ "Error configuring widget: $str1" }) {
+ str1 = e.localizedMessage
+ }
+ false
+ }
+ }
+ }
+ widgetRepository.addWidget(provider, user, rank, configurator)
}
private fun deleteWidgetInternal(appWidgetId: Int) {
@@ -168,22 +206,55 @@
widgetRepository.resizeWidget(appWidgetId, spanY, appWidgetIds.zip(ranks).toMap())
}
+ private fun getIntentSenderForConfigureActivityInternal(appWidgetId: Int): IntentSender? {
+ return try {
+ appWidgetHost.getIntentSenderForConfigureActivity(appWidgetId, /* intentFlags= */ 0)
+ } catch (e: IntentSender.SendIntentException) {
+ logger.e({ "Error getting intent sender for configure activity" }) {
+ str1 = e.localizedMessage
+ }
+ null
+ }
+ }
+
private fun createListener(listener: IAppWidgetHostListener): AppWidgetHostListener {
return object : AppWidgetHostListener {
override fun onUpdateProviderInfo(appWidget: AppWidgetProviderInfo?) {
- listener.onUpdateProviderInfo(appWidget)
+ try {
+ listener.onUpdateProviderInfo(appWidget)
+ } catch (e: RemoteException) {
+ logger.e({ "Error pushing on update provider info: $str1" }) {
+ str1 = e.localizedMessage
+ }
+ }
}
override fun updateAppWidget(views: RemoteViews?) {
- listener.updateAppWidget(views)
+ try {
+ listener.updateAppWidget(views)
+ } catch (e: RemoteException) {
+ logger.e({ "Error updating app widget: $str1" }) { str1 = e.localizedMessage }
+ }
}
override fun updateAppWidgetDeferred(packageName: String?, appWidgetId: Int) {
- listener.updateAppWidgetDeferred(packageName, appWidgetId)
+ try {
+ listener.updateAppWidgetDeferred(packageName, appWidgetId)
+ } catch (e: RemoteException) {
+ logger.e({ "Error updating app widget deferred: $str1" }) {
+ str1 = e.localizedMessage
+ }
+ }
}
override fun onViewDataChanged(viewId: Int) {
- listener.onViewDataChanged(viewId)
+ try {
+ listener.onViewDataChanged(viewId)
+ } catch (e: RemoteException) {
+ logger.e({ "Error pushing on view data changed: $str1" }) {
+ str1 = e.localizedMessage
+ }
+ }
}
}
}
@@ -219,11 +290,16 @@
}
}
- override fun addWidget(provider: ComponentName?, user: UserHandle?, rank: Int) {
+ override fun addWidget(
+ provider: ComponentName?,
+ user: UserHandle?,
+ rank: Int,
+ callback: IConfigureWidgetCallback?,
+ ) {
val iden = clearCallingIdentity()
try {
- addWidgetInternal(provider, user, rank)
+ addWidgetInternal(provider, user, rank, callback)
} finally {
restoreCallingIdentity(iden)
}
@@ -263,6 +339,16 @@
restoreCallingIdentity(iden)
}
}
+
+ override fun getIntentSenderForConfigureActivity(appWidgetId: Int): IntentSender? {
+ val iden = clearCallingIdentity()
+
+ try {
+ return getIntentSenderForConfigureActivityInternal(appWidgetId)
+ } finally {
+ restoreCallingIdentity(iden)
+ }
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/IGlanceableHubWidgetManagerService.aidl b/packages/SystemUI/src/com/android/systemui/communal/widgets/IGlanceableHubWidgetManagerService.aidl
index e556472..d71b230 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/IGlanceableHubWidgetManagerService.aidl
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/IGlanceableHubWidgetManagerService.aidl
@@ -2,6 +2,7 @@
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
+import android.content.IntentSender;
import android.os.UserHandle;
import android.widget.RemoteViews;
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel;
@@ -21,7 +22,8 @@
oneway void setAppWidgetHostListener(int appWidgetId, in IAppWidgetHostListener listener);
// Requests to add a widget in the Glanceable Hub.
- oneway void addWidget(in ComponentName provider, in UserHandle user, int rank);
+ oneway void addWidget(in ComponentName provider, in UserHandle user, int rank,
+ in IConfigureWidgetCallback callback);
// Requests to delete a widget from the Glanceable Hub.
oneway void deleteWidget(int appWidgetId);
@@ -32,6 +34,9 @@
// Requests to resize a widget in the Glanceable Hub.
oneway void resizeWidget(int appWidgetId, int spanY, in int[] appWidgetIds, in int[] ranks);
+ // Returns the [IntentSender] for launching the configuration activity of the given widget.
+ IntentSender getIntentSenderForConfigureActivity(int appWidgetId);
+
// Listener for Glanceable Hub widget updates
oneway interface IGlanceableHubWidgetsListener {
// Called when widgets have updated.
@@ -48,4 +53,15 @@
void onViewDataChanged(int viewId);
}
+
+ oneway interface IConfigureWidgetCallback {
+ // Called when the given widget should launch its configuration activity. The caller should
+ // report the result through the [IResultReceiver].
+ void onConfigureWidget(int appWidgetId, in IResultReceiver resultReceiver);
+
+ interface IResultReceiver {
+ // Called when the widget configuration operation returns a result.
+ void onResult(boolean success);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetConfigurationController.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetConfigurationController.kt
index d157cd7..fddec56 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetConfigurationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetConfigurationController.kt
@@ -18,16 +18,19 @@
import android.app.Activity
import android.app.ActivityOptions
-import android.content.ActivityNotFoundException
+import android.content.IntentSender
+import android.os.OutcomeReceiver
import android.window.SplashScreen
import androidx.activity.ComponentActivity
import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.util.nullableAtomicReference
import dagger.Lazy
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import java.util.concurrent.Executor
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
@@ -43,12 +46,22 @@
private val appWidgetHostLazy: Lazy<CommunalAppWidgetHost>,
@Background private val bgDispatcher: CoroutineDispatcher,
private val glanceableHubMultiUserHelper: GlanceableHubMultiUserHelper,
-) : WidgetConfigurator {
+ private val glanceableHubWidgetManagerLazy: Lazy<GlanceableHubWidgetManager>,
+ @Main private val mainExecutor: Executor,
+) : WidgetConfigurator, OutcomeReceiver<IntentSender?, Throwable> {
@AssistedFactory
fun interface Factory {
fun create(activity: ComponentActivity): WidgetConfigurationController
}
+ private val activityOptions: ActivityOptions
+ get() =
+ ActivityOptions.makeBasic().apply {
+ pendingIntentBackgroundActivityStartMode =
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ splashScreenStyle = SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR
+ }
+
private var result: CompletableDeferred<Boolean>? by nullableAtomicReference()
override suspend fun configureWidget(appWidgetId: Int): Boolean =
@@ -57,37 +70,64 @@
throw IllegalStateException("There is already a pending configuration")
}
result = CompletableDeferred()
- val options =
- ActivityOptions.makeBasic().apply {
- pendingIntentBackgroundActivityStartMode =
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
- splashScreenStyle = SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR
- }
try {
- // TODO(b/375036327): Add support for widget configuration
if (
!glanceableHubMultiUserHelper.glanceableHubHsumFlagEnabled ||
!glanceableHubMultiUserHelper.isInHeadlessSystemUser()
) {
+ // Start configuration activity directly if we're running in a foreground user
with(appWidgetHostLazy.get()) {
startAppWidgetConfigureActivityForResult(
activity,
appWidgetId,
0,
REQUEST_CODE,
- options.toBundle(),
+ activityOptions.toBundle(),
+ )
+ }
+ } else {
+ with(glanceableHubWidgetManagerLazy.get()) {
+ // Use service to get intent sender and start configuration activity
+ // locally if running in a headless system user
+ getIntentSenderForConfigureActivity(
+ appWidgetId,
+ outcomeReceiver = this@WidgetConfigurationController,
+ mainExecutor,
)
}
}
- } catch (e: ActivityNotFoundException) {
+ } catch (_: Exception) {
setConfigurationResult(Activity.RESULT_CANCELED)
}
- val value = result?.await() ?: false
+ val value = result?.await() == true
result = null
return@withContext value
}
+ // Called when an intent sender is returned, and the configuration activity should be started.
+ override fun onResult(intentSender: IntentSender?) {
+ if (intentSender == null) {
+ setConfigurationResult(Activity.RESULT_CANCELED)
+ return
+ }
+
+ activity.startIntentSenderForResult(
+ intentSender,
+ REQUEST_CODE,
+ /* fillInIntent = */ null,
+ /* flagsMask = */ 0,
+ /* flagsValues = */ 0,
+ /* extraFlags = */ 0,
+ activityOptions.toBundle(),
+ )
+ }
+
+ // Called when there is an error getting the intent sender.
+ override fun onError(e: Throwable) {
+ setConfigurationResult(Activity.RESULT_CANCELED)
+ }
+
fun setConfigurationResult(resultCode: Int) {
result?.complete(resultCode == Activity.RESULT_OK)
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index cb649f2..4447dff 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -48,7 +48,8 @@
import com.android.systemui.classifier.FalsingModule;
import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
import com.android.systemui.common.data.CommonDataLayerModule;
-import com.android.systemui.common.ui.ConfigurationStateModule;
+import com.android.systemui.common.ui.ConfigurationModule;
+import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryModule;
import com.android.systemui.common.usagestats.data.CommonUsageStatsDataLayerModule;
import com.android.systemui.communal.dagger.CommunalModule;
import com.android.systemui.complication.dagger.ComplicationComponent;
@@ -211,7 +212,8 @@
ClockRegistryModule.class,
CommunalModule.class,
CommonDataLayerModule.class,
- ConfigurationStateModule.class,
+ ConfigurationModule.class,
+ ConfigurationRepositoryModule.class,
CommonUsageStatsDataLayerModule.class,
ConfigurationControllerModule.class,
ConnectivityModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
index 9aa7fd1..78d8d8f 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
@@ -81,7 +81,7 @@
override val viewId: Int,
@DisplayCutout.BoundsPosition override val alignedBound1: Int,
@DisplayCutout.BoundsPosition override val alignedBound2: Int,
- private val layoutId: Int,
+ val layoutId: Int,
) : CornerDecorProvider() {
override fun onReloadResAndMeasure(
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxy.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxy.kt
index 2bcfea8..45a5901 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxy.kt
@@ -16,20 +16,16 @@
package com.android.systemui.dreams.homecontrols.service
-import android.content.ComponentName
-import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dreams.homecontrols.shared.IHomeControlsRemoteProxy
-import com.android.systemui.dreams.homecontrols.shared.IOnControlsSettingsChangeListener
+import com.android.systemui.dreams.homecontrols.shared.controlsSettings
import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsComponentInfo
import com.android.systemui.dump.DumpManager
import com.android.systemui.util.kotlin.FlowDumperImpl
-import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -45,30 +41,8 @@
@Assisted private val proxy: IHomeControlsRemoteProxy,
) : FlowDumperImpl(dumpManager) {
- private companion object {
- const val TAG = "HomeControlsRemoteProxy"
- }
-
val componentInfo: Flow<HomeControlsComponentInfo> =
- conflatedCallbackFlow {
- val listener =
- object : IOnControlsSettingsChangeListener.Stub() {
- override fun onControlsSettingsChanged(
- panelComponent: ComponentName?,
- allowTrivialControlsOnLockscreen: Boolean,
- ) {
- trySendWithFailureLogging(
- HomeControlsComponentInfo(
- panelComponent,
- allowTrivialControlsOnLockscreen,
- ),
- TAG,
- )
- }
- }
- proxy.registerListenerForCurrentUser(listener)
- awaitClose { proxy.unregisterListenerForCurrentUser(listener) }
- }
+ proxy.controlsSettings
.distinctUntilChanged()
.stateIn(bgScope, SharingStarted.WhileSubscribed(), null)
.dumpValue("componentInfo")
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/IHomeControlsRemoteProxyExt.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/IHomeControlsRemoteProxyExt.kt
new file mode 100644
index 0000000..2993ab5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/IHomeControlsRemoteProxyExt.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.dreams.homecontrols.shared
+
+import android.content.ComponentName
+import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsComponentInfo
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+val IHomeControlsRemoteProxy.controlsSettings: Flow<HomeControlsComponentInfo>
+ get() = conflatedCallbackFlow {
+ val listener =
+ object : IOnControlsSettingsChangeListener.Stub() {
+ override fun onControlsSettingsChanged(
+ panelComponent: ComponentName?,
+ allowTrivialControlsOnLockscreen: Boolean,
+ ) {
+ trySend(
+ HomeControlsComponentInfo(panelComponent, allowTrivialControlsOnLockscreen)
+ )
+ }
+ }
+ registerListenerForCurrentUser(listener)
+ awaitClose { unregisterListenerForCurrentUser(listener) }
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteService.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteService.kt
index a65d216..6d1fd4d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteService.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteService.kt
@@ -57,6 +57,11 @@
super.onBind(intent)
return binder
}
+
+ override fun onDestroy() {
+ super.onDestroy()
+ binder.onDestroy()
+ }
}
class HomeControlsRemoteServiceBinder
@@ -148,6 +153,14 @@
}
}
+ fun onDestroy() {
+ logger.d("Service destroyed")
+ callbacks.kill()
+ callbackCount.set(0)
+ collectionJob?.cancel()
+ collectionJob = null
+ }
+
@AssistedFactory
interface Factory {
fun create(lifecycleOwner: LifecycleOwner): HomeControlsRemoteServiceBinder
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
index 4a8c040..fd91389 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
@@ -20,7 +20,6 @@
import android.app.smartspace.SmartspaceManager
import android.app.smartspace.SmartspaceSession
import android.app.smartspace.SmartspaceTarget
-import android.content.Context
import android.graphics.Color
import android.util.Log
import android.view.View
@@ -31,6 +30,7 @@
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
import com.android.systemui.plugins.BcSmartspaceDataPlugin.UI_SURFACE_DREAM
+import com.android.systemui.settings.UserTracker
import com.android.systemui.smartspace.SmartspacePrecondition
import com.android.systemui.smartspace.SmartspaceTargetFilter
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_DATA_PLUGIN
@@ -44,13 +44,12 @@
import javax.inject.Inject
import javax.inject.Named
-/**
- * Controller for managing the smartspace view on the dream
- */
+/** Controller for managing the smartspace view on the dream */
@SysUISingleton
-class DreamSmartspaceController @Inject constructor(
- private val context: Context,
- private val smartspaceManager: SmartspaceManager?,
+class DreamSmartspaceController
+@Inject
+constructor(
+ private val userTracker: UserTracker,
private val execution: Execution,
@Main private val uiExecutor: Executor,
private val smartspaceViewComponentFactory: SmartspaceViewComponent.Factory,
@@ -65,6 +64,7 @@
private const val TAG = "DreamSmartspaceCtrlr"
}
+ private var userSmartspaceManager: SmartspaceManager? = null
private var session: SmartspaceSession? = null
private val weatherPlugin: BcSmartspaceDataPlugin? = optionalWeatherPlugin.orElse(null)
private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
@@ -78,66 +78,68 @@
// Smartspace can be used on multiple displays, such as when the user casts their screen
private var smartspaceViews = mutableSetOf<SmartspaceView>()
- var preconditionListener = object : SmartspacePrecondition.Listener {
- override fun onCriteriaChanged() {
- reloadSmartspace()
+ var preconditionListener =
+ object : SmartspacePrecondition.Listener {
+ override fun onCriteriaChanged() {
+ reloadSmartspace()
+ }
}
- }
init {
precondition.addListener(preconditionListener)
}
- var filterListener = object : SmartspaceTargetFilter.Listener {
- override fun onCriteriaChanged() {
- reloadSmartspace()
+ var filterListener =
+ object : SmartspaceTargetFilter.Listener {
+ override fun onCriteriaChanged() {
+ reloadSmartspace()
+ }
}
- }
init {
targetFilter?.addListener(filterListener)
}
- var stateChangeListener = object : View.OnAttachStateChangeListener {
- override fun onViewAttachedToWindow(v: View) {
- val view = v as SmartspaceView
- // Until there is dream color matching
- view.setPrimaryTextColor(Color.WHITE)
- smartspaceViews.add(view)
- connectSession()
- view.setDozeAmount(0f)
- }
+ var stateChangeListener =
+ object : View.OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(v: View) {
+ val view = v as SmartspaceView
+ // Until there is dream color matching
+ view.setPrimaryTextColor(Color.WHITE)
+ smartspaceViews.add(view)
+ connectSession()
+ view.setDozeAmount(0f)
+ }
- override fun onViewDetachedFromWindow(v: View) {
- smartspaceViews.remove(v as SmartspaceView)
+ override fun onViewDetachedFromWindow(v: View) {
+ smartspaceViews.remove(v as SmartspaceView)
- if (smartspaceViews.isEmpty()) {
- disconnect()
+ if (smartspaceViews.isEmpty()) {
+ disconnect()
+ }
}
}
- }
- private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
- execution.assertIsMainThread()
+ private val sessionListener =
+ SmartspaceSession.OnTargetsAvailableListener { targets ->
+ execution.assertIsMainThread()
- // The weather data plugin takes unfiltered targets and performs the filtering internally.
- weatherPlugin?.onTargetsAvailable(targets)
+ // The weather data plugin takes unfiltered targets and performs the filtering
+ // internally.
+ weatherPlugin?.onTargetsAvailable(targets)
- onTargetsAvailableUnfiltered(targets)
- val filteredTargets = targets.filter { targetFilter?.filterSmartspaceTarget(it) ?: true }
- plugin?.onTargetsAvailable(filteredTargets)
- }
+ onTargetsAvailableUnfiltered(targets)
+ val filteredTargets =
+ targets.filter { targetFilter?.filterSmartspaceTarget(it) ?: true }
+ plugin?.onTargetsAvailable(filteredTargets)
+ }
- /**
- * Constructs the weather view with custom layout and connects it to the weather plugin.
- */
+ /** Constructs the weather view with custom layout and connects it to the weather plugin. */
fun buildAndConnectWeatherView(parent: ViewGroup, customView: View?): View? {
return buildAndConnectViewWithPlugin(parent, weatherPlugin, customView)
}
- /**
- * Constructs the smartspace view and connects it to the smartspace service.
- */
+ /** Constructs the smartspace view and connects it to the smartspace service. */
fun buildAndConnectView(parent: ViewGroup): View? {
return buildAndConnectViewWithPlugin(parent, plugin, null)
}
@@ -145,7 +147,7 @@
private fun buildAndConnectViewWithPlugin(
parent: ViewGroup,
smartspaceDataPlugin: BcSmartspaceDataPlugin?,
- customView: View?
+ customView: View?,
): View? {
execution.assertIsMainThread()
@@ -163,12 +165,13 @@
private fun buildView(
parent: ViewGroup,
smartspaceDataPlugin: BcSmartspaceDataPlugin?,
- customView: View?
+ customView: View?,
): View? {
return if (smartspaceDataPlugin != null) {
- val view = smartspaceViewComponentFactory.create(parent, smartspaceDataPlugin,
- stateChangeListener, customView)
- .getView()
+ val view =
+ smartspaceViewComponentFactory
+ .create(parent, smartspaceDataPlugin, stateChangeListener, customView)
+ .getView()
if (view !is View) {
return null
}
@@ -179,12 +182,17 @@
}
private fun hasActiveSessionListeners(): Boolean {
- return smartspaceViews.isNotEmpty() || listeners.isNotEmpty() ||
+ return smartspaceViews.isNotEmpty() ||
+ listeners.isNotEmpty() ||
unfilteredListeners.isNotEmpty()
}
private fun connectSession() {
- if (smartspaceManager == null) {
+ if (userSmartspaceManager == null) {
+ userSmartspaceManager =
+ userTracker.userContext.getSystemService(SmartspaceManager::class.java)
+ }
+ if (userSmartspaceManager == null) {
return
}
if (plugin == null && weatherPlugin == null) {
@@ -198,25 +206,21 @@
return
}
- val newSession = smartspaceManager.createSmartspaceSession(
- SmartspaceConfig.Builder(context, UI_SURFACE_DREAM).build()
- )
+ val newSession =
+ userSmartspaceManager?.createSmartspaceSession(
+ SmartspaceConfig.Builder(userTracker.userContext, UI_SURFACE_DREAM).build()
+ )
Log.d(TAG, "Starting smartspace session for dream")
- newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
+ newSession?.addOnTargetsAvailableListener(uiExecutor, sessionListener)
this.session = newSession
weatherPlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
- plugin?.registerSmartspaceEventNotifier {
- e ->
- session?.notifySmartspaceEvent(e)
- }
+ plugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
reloadSmartspace()
}
- /**
- * Disconnects the smartspace view from the smartspace service and cleans up any resources.
- */
+ /** Disconnects the smartspace view from the smartspace service and cleans up any resources. */
private fun disconnect() {
if (hasActiveSessionListeners()) return
@@ -259,7 +263,7 @@
private fun addAndRegisterListener(
listener: SmartspaceTargetListener,
- smartspaceDataPlugin: BcSmartspaceDataPlugin?
+ smartspaceDataPlugin: BcSmartspaceDataPlugin?,
) {
execution.assertIsMainThread()
smartspaceDataPlugin?.registerListener(listener)
@@ -270,7 +274,7 @@
private fun removeAndUnregisterListener(
listener: SmartspaceTargetListener,
- smartspaceDataPlugin: BcSmartspaceDataPlugin?
+ smartspaceDataPlugin: BcSmartspaceDataPlugin?,
) {
execution.assertIsMainThread()
smartspaceDataPlugin?.unregisterListener(listener)
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
index 3492365..b2fcc43 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -45,7 +45,7 @@
/** See [registerCriticalDumpable]. */
fun registerCriticalDumpable(module: Dumpable) {
- registerCriticalDumpable(module::class.java.canonicalName, module)
+ registerCriticalDumpable(module::class.java.name, module)
}
/**
@@ -62,7 +62,7 @@
/** See [registerNormalDumpable]. */
fun registerNormalDumpable(module: Dumpable) {
- registerNormalDumpable(module::class.java.canonicalName, module)
+ registerNormalDumpable(module::class.java.name, module)
}
/**
@@ -104,13 +104,10 @@
dumpables[name] = DumpableEntry(module, name, priority)
}
- /**
- * Same as the above override, but automatically uses the canonical class name as the dumpable
- * name.
- */
+ /** Same as the above override, but automatically uses the class name as the dumpable name. */
@Synchronized
fun registerDumpable(module: Dumpable) {
- registerDumpable(module::class.java.canonicalName, module)
+ registerDumpable(module::class.java.name, module)
}
/** Unregisters a previously-registered dumpable. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
index 89cdd25..585f7ed 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
@@ -33,13 +33,13 @@
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT
import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
+import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
-import javax.inject.Inject
interface StickyKeysRepository {
val stickyKeys: Flow<LinkedHashMap<ModifierKey, Locked>>
@@ -71,7 +71,7 @@
override val settingEnabled: Flow<Boolean> =
secureSettingsRepository
- .boolSettingForActiveUser(SETTING_KEY, defaultValue = false)
+ .boolSetting(SETTING_KEY, defaultValue = false)
.onEach { stickyKeysLogger.logNewSettingValue(it) }
.flowOn(backgroundDispatcher)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
index 032af94..2914cb9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
@@ -44,7 +44,7 @@
private val keyguardStateController: KeyguardStateController,
private val keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier,
private val keyguardDismissTransitionInteractor: KeyguardDismissTransitionInteractor,
- private val keyguardTransitions: KeyguardTransitions
+ private val keyguardTransitions: KeyguardTransitions,
) {
/**
@@ -108,27 +108,28 @@
* Manager to effect the change.
*/
fun setSurfaceBehindVisibility(visible: Boolean) {
- if (isKeyguardGoingAway == visible) {
- Log.d(TAG, "WmLockscreenVisibilityManager#setVisibility -> already visible=$visible")
+ if (isKeyguardGoingAway && visible) {
+ Log.d(TAG, "#setSurfaceBehindVisibility: already visible, ignoring")
return
}
// The surface behind is always visible if the lockscreen is not showing, so we're already
// visible.
if (visible && isLockscreenShowing != true) {
- Log.d(TAG, "#setVisibility -> already visible since the lockscreen isn't showing")
+ Log.d(TAG, "#setSurfaceBehindVisibility: ignoring since the lockscreen isn't showing")
return
}
-
-
if (visible) {
if (enableNewKeyguardShellTransitions) {
- keyguardTransitions.startKeyguardTransition(false /* keyguardShowing */, false /* aodShowing */)
+ keyguardTransitions.startKeyguardTransition(
+ false /* keyguardShowing */,
+ false, /* aodShowing */
+ )
isKeyguardGoingAway = true
return
}
- // Make the surface visible behind the keyguard by calling keyguardGoingAway. The
+ // Make the surface behind the keyguard visible by calling keyguardGoingAway. The
// lockscreen is still showing as well, allowing us to animate unlocked.
Log.d(TAG, "ActivityTaskManagerService#keyguardGoingAway()")
activityTaskManagerService.keyguardGoingAway(0)
@@ -153,7 +154,7 @@
apps: Array<RemoteAnimationTarget>,
wallpapers: Array<RemoteAnimationTarget>,
nonApps: Array<RemoteAnimationTarget>,
- finishedCallback: IRemoteAnimationFinishedCallback
+ finishedCallback: IRemoteAnimationFinishedCallback,
) {
// Ensure that we've started a dismiss keyguard transition. WindowManager can start the
// going away animation on its own, if an activity launches and then requests dismissing the
@@ -203,27 +204,25 @@
*/
private fun setWmLockscreenState(
lockscreenShowing: Boolean? = this.isLockscreenShowing,
- aodVisible: Boolean = this.isAodVisible
+ aodVisible: Boolean = this.isAodVisible,
) {
- Log.d(
- TAG,
- "#setWmLockscreenState(" +
- "isLockscreenShowing=$lockscreenShowing, " +
- "aodVisible=$aodVisible)."
- )
-
if (lockscreenShowing == null) {
Log.d(
TAG,
"isAodVisible=$aodVisible, but lockscreenShowing=null. Waiting for" +
"non-null lockscreenShowing before calling ATMS#setLockScreenShown, which" +
- "will happen once KeyguardTransitionBootInteractor starts the boot transition."
+ "will happen once KeyguardTransitionBootInteractor starts the boot transition.",
)
this.isAodVisible = aodVisible
return
}
if (this.isLockscreenShowing == lockscreenShowing && this.isAodVisible == aodVisible) {
+ Log.d(
+ TAG,
+ "#setWmLockscreenState: lockscreenShowing=$lockscreenShowing and " +
+ "isAodVisible=$aodVisible were both unchanged, not forwarding to ATMS.",
+ )
return
}
@@ -231,7 +230,7 @@
TAG,
"ATMS#setLockScreenShown(" +
"isLockscreenShowing=$lockscreenShowing, " +
- "aodVisible=$aodVisible)."
+ "aodVisible=$aodVisible).",
)
if (enableNewKeyguardShellTransitions) {
keyguardTransitions.startKeyguardTransition(lockscreenShowing, aodVisible)
@@ -247,7 +246,7 @@
Log.d(
TAG,
"#endKeyguardGoingAwayAnimation() called when isKeyguardGoingAway=false. " +
- "Short-circuiting."
+ "Short-circuiting.",
)
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
index a1f6067..2c9884a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -18,7 +18,8 @@
package com.android.systemui.keyguard.domain.interactor
-import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.ObservableTransitionState.Idle
+import com.android.compose.animation.scene.ObservableTransitionState.Transition
import com.android.systemui.Flags.transitionRaceCondition
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
@@ -30,6 +31,7 @@
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
@@ -110,11 +112,8 @@
}
.distinctUntilChanged()
- private val isDeviceEntered: Flow<Boolean> by lazy {
- deviceEntryInteractor.get().isDeviceEntered
- }
-
- private val isDeviceNotEntered: Flow<Boolean> by lazy { isDeviceEntered.map { !it } }
+ private val isDeviceEntered by lazy { deviceEntryInteractor.get().isDeviceEntered }
+ private val isDeviceNotEntered by lazy { isDeviceEntered.map { !it } }
/**
* Surface visibility, which is either determined by the default visibility when not
@@ -124,32 +123,24 @@
@OptIn(ExperimentalCoroutinesApi::class)
val surfaceBehindVisibility: Flow<Boolean> =
if (SceneContainerFlag.isEnabled) {
- sceneInteractor.get().transitionState.flatMapLatestConflated { transitionState ->
- when (transitionState) {
- is ObservableTransitionState.Transition ->
- when {
- transitionState.fromContent == Scenes.Lockscreen &&
- transitionState.toContent == Scenes.Gone ->
- sceneInteractor
- .get()
- .isTransitionUserInputOngoing
- .flatMapLatestConflated { isUserInputOngoing ->
- if (isUserInputOngoing) {
- isDeviceEntered
- } else {
- flowOf(true)
- }
- }
- transitionState.fromContent == Scenes.Bouncer &&
- transitionState.toContent == Scenes.Gone ->
- transitionState.progress.map { progress ->
- progress >
- FromPrimaryBouncerTransitionInteractor
- .TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD
- }
- else -> isDeviceEntered
+ sceneInteractor.get().transitionState.flatMapLatestConflated { state ->
+ when {
+ state.isTransitioning(from = Scenes.Lockscreen, to = Scenes.Gone) ->
+ (state as Transition).isUserInputOngoing.flatMapLatestConflated {
+ isUserInputOngoing ->
+ if (isUserInputOngoing) {
+ isDeviceEntered
+ } else {
+ flowOf(true)
+ }
}
- is ObservableTransitionState.Idle -> isDeviceEntered
+ state.isTransitioning(from = Scenes.Bouncer, to = Scenes.Gone) ->
+ (state as Transition).progress.map { progress ->
+ progress >
+ FromPrimaryBouncerTransitionInteractor
+ .TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD
+ }
+ else -> lockscreenVisibilityWithScenes.map { !it }
}
}
} else {
@@ -219,6 +210,123 @@
}
/**
+ * Scenes that are part of the keyguard and are shown when the device is locked or when the
+ * keyguard still needs to be dismissed.
+ */
+ private val keyguardScenes = setOf(Scenes.Lockscreen, Scenes.Bouncer, Scenes.Communal)
+
+ /**
+ * Scenes that don't belong in the keyguard family and cannot show when the device is locked or
+ * when the keyguard still needs to be dismissed.
+ */
+ private val nonKeyguardScenes = setOf(Scenes.Gone)
+
+ /**
+ * Scenes that can show regardless of device lock or keyguard dismissal states. Other sources of
+ * state need to be consulted to know whether the device has been entered or not.
+ */
+ private val keyguardAgnosticScenes =
+ setOf(
+ Scenes.Shade,
+ Scenes.QuickSettings,
+ Overlays.NotificationsShade,
+ Overlays.QuickSettingsShade,
+ )
+
+ private val lockscreenVisibilityWithScenes =
+ combine(
+ sceneInteractor.get().transitionState.flatMapLatestConflated {
+ when (it) {
+ is Idle -> {
+ when (it.currentScene) {
+ in keyguardScenes -> flowOf(true)
+ in nonKeyguardScenes -> flowOf(false)
+ in keyguardAgnosticScenes -> isDeviceNotEntered
+ else ->
+ throw IllegalStateException("Unknown scene: ${it.currentScene}")
+ }
+ }
+ is Transition -> {
+ when {
+ it.isTransitioningSets(from = keyguardScenes) -> flowOf(true)
+ it.isTransitioningSets(from = nonKeyguardScenes) -> flowOf(false)
+ it.isTransitioningSets(from = keyguardAgnosticScenes) ->
+ isDeviceNotEntered
+ else ->
+ throw IllegalStateException("Unknown scene: ${it.fromContent}")
+ }
+ }
+ }
+ },
+ wakeToGoneInteractor.canWakeDirectlyToGone,
+ ::Pair,
+ )
+ .map { (lockscreenVisibilityByTransitionState, canWakeDirectlyToGone) ->
+ lockscreenVisibilityByTransitionState && !canWakeDirectlyToGone
+ }
+
+ private val lockscreenVisibilityLegacy =
+ combine(
+ transitionInteractor.currentKeyguardState,
+ wakeToGoneInteractor.canWakeDirectlyToGone,
+ ::Pair,
+ )
+ .sample(transitionInteractor.startedStepWithPrecedingStep, ::toTriple)
+ .map { (currentState, canWakeDirectlyToGone, startedWithPrev) ->
+ val startedFromStep = startedWithPrev.previousValue
+ val startedStep = startedWithPrev.newValue
+ val returningToGoneAfterCancellation =
+ startedStep.to == KeyguardState.GONE &&
+ startedFromStep.transitionState == TransitionState.CANCELED &&
+ startedFromStep.from == KeyguardState.GONE
+
+ val transitionInfo =
+ if (transitionRaceCondition()) {
+ transitionRepository.currentTransitionInfo
+ } else {
+ transitionRepository.currentTransitionInfoInternal.value
+ }
+ val wakingDirectlyToGone =
+ deviceIsAsleepInState(transitionInfo.from) &&
+ transitionInfo.to == KeyguardState.GONE
+
+ if (returningToGoneAfterCancellation || wakingDirectlyToGone) {
+ // GONE -> AOD/DOZING (cancel) -> GONE is the camera launch transition,
+ // which means we never want to show the lockscreen throughout the
+ // transition. Same for waking directly to gone, due to the lockscreen being
+ // disabled or because the device was woken back up before the lock timeout
+ // duration elapsed.
+ false
+ } else if (canWakeDirectlyToGone) {
+ // Never show the lockscreen if we can wake directly to GONE. This means
+ // that the lock timeout has not yet elapsed, or the keyguard is disabled.
+ // In either case, we don't show the activity lock screen until one of those
+ // conditions changes.
+ false
+ } else if (
+ currentState == KeyguardState.DREAMING &&
+ deviceEntryInteractor.get().isUnlocked.value
+ ) {
+ // Dreams dismiss keyguard and return to GONE if they can.
+ false
+ } else if (
+ startedWithPrev.newValue.from == KeyguardState.OCCLUDED &&
+ startedWithPrev.newValue.to == KeyguardState.GONE
+ ) {
+ // OCCLUDED -> GONE directly, without transiting a *_BOUNCER state, occurs
+ // when an app uses intent flags to launch over an insecure keyguard without
+ // dismissing it, and then manually requests keyguard dismissal while
+ // OCCLUDED. This transition is not user-visible; the device unlocks in the
+ // background and the app remains on top, while we're now GONE. In this case
+ // we should simply tell WM that the lockscreen is no longer visible, and
+ // *not* play the going away animation or related animations.
+ false
+ } else {
+ currentState != KeyguardState.GONE
+ }
+ }
+
+ /**
* Whether the lockscreen is visible, from the Window Manager (WM) perspective.
*
* Note: This may briefly be true even if the lockscreen UI has animated out (alpha = 0f), as we
@@ -227,69 +335,11 @@
*/
val lockscreenVisibility: Flow<Boolean> =
if (SceneContainerFlag.isEnabled) {
- isDeviceNotEntered
- } else {
- combine(
- transitionInteractor.currentKeyguardState,
- wakeToGoneInteractor.canWakeDirectlyToGone,
- ::Pair,
- )
- .sample(transitionInteractor.startedStepWithPrecedingStep, ::toTriple)
- .map { (currentState, canWakeDirectlyToGone, startedWithPrev) ->
- val startedFromStep = startedWithPrev.previousValue
- val startedStep = startedWithPrev.newValue
- val returningToGoneAfterCancellation =
- startedStep.to == KeyguardState.GONE &&
- startedFromStep.transitionState == TransitionState.CANCELED &&
- startedFromStep.from == KeyguardState.GONE
-
- val transitionInfo =
- if (transitionRaceCondition()) {
- transitionRepository.currentTransitionInfo
- } else {
- transitionRepository.currentTransitionInfoInternal.value
- }
- val wakingDirectlyToGone =
- deviceIsAsleepInState(transitionInfo.from) &&
- transitionInfo.to == KeyguardState.GONE
-
- if (returningToGoneAfterCancellation || wakingDirectlyToGone) {
- // GONE -> AOD/DOZING (cancel) -> GONE is the camera launch transition,
- // which means we never want to show the lockscreen throughout the
- // transition. Same for waking directly to gone, due to the lockscreen being
- // disabled or because the device was woken back up before the lock timeout
- // duration elapsed.
- false
- } else if (canWakeDirectlyToGone) {
- // Never show the lockscreen if we can wake directly to GONE. This means
- // that the lock timeout has not yet elapsed, or the keyguard is disabled.
- // In either case, we don't show the activity lock screen until one of those
- // conditions changes.
- false
- } else if (
- currentState == KeyguardState.DREAMING &&
- deviceEntryInteractor.get().isUnlocked.value
- ) {
- // Dreams dismiss keyguard and return to GONE if they can.
- false
- } else if (
- startedWithPrev.newValue.from == KeyguardState.OCCLUDED &&
- startedWithPrev.newValue.to == KeyguardState.GONE
- ) {
- // OCCLUDED -> GONE directly, without transiting a *_BOUNCER state, occurs
- // when an app uses intent flags to launch over an insecure keyguard without
- // dismissing it, and then manually requests keyguard dismissal while
- // OCCLUDED. This transition is not user-visible; the device unlocks in the
- // background and the app remains on top, while we're now GONE. In this case
- // we should simply tell WM that the lockscreen is no longer visible, and
- // *not* play the going away animation or related animations.
- false
- } else {
- currentState != KeyguardState.GONE
- }
- }
- .distinctUntilChanged()
- }
+ lockscreenVisibilityWithScenes
+ } else {
+ lockscreenVisibilityLegacy
+ }
+ .distinctUntilChanged()
/**
* Whether always-on-display (AOD) is visible when the lockscreen is visible, from window
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
index be4bc23..6985615 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -182,9 +182,11 @@
fgIconView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
// Start with an empty state
+ Log.d(TAG, "Initializing device entry fgIconView")
fgIconView.setImageState(StateSet.NOTHING, /* merge */ false)
launch("$TAG#fpIconView.viewModel") {
fgViewModel.viewModel.collect { viewModel ->
+ Log.d(TAG, "Updating device entry icon image state $viewModel")
fgIconView.setImageState(
view.getIconState(viewModel.type, viewModel.useAodVariant),
/* merge */ false,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
index 67b009e..7f3ef61 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
@@ -54,9 +54,7 @@
duration = TO_LOCKSCREEN_DURATION,
edge = Edge.create(from = Scenes.Communal, to = LOCKSCREEN),
)
- .setupWithoutSceneContainer(
- edge = Edge.create(from = GLANCEABLE_HUB, to = LOCKSCREEN),
- )
+ .setupWithoutSceneContainer(edge = Edge.create(from = GLANCEABLE_HUB, to = LOCKSCREEN))
val keyguardAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
@@ -75,7 +73,7 @@
configurationInteractor
.directionalDimensionPixelSize(
LayoutDirection.LTR,
- R.dimen.hub_to_lockscreen_transition_lockscreen_translation_x
+ R.dimen.hub_to_lockscreen_transition_lockscreen_translation_x,
)
.flatMapLatest { translatePx: Int ->
transitionAnimation.sharedFlowWithState(
@@ -87,7 +85,7 @@
// is cancelled.
onFinish = { 0f },
onCancel = { 0f },
- name = "GLANCEABLE_HUB->LOCKSCREEN: keyguardTranslationX"
+ name = "GLANCEABLE_HUB->LOCKSCREEN: keyguardTranslationX",
)
}
@@ -95,6 +93,8 @@
val shortcutsAlpha: Flow<Float> = keyguardAlpha
+ val statusBarAlpha: Flow<Float> = keyguardAlpha
+
val notificationTranslationX: Flow<Float> =
keyguardTranslationX.map { it.value }.filterNotNull()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
index 378374e..dd8ff8c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
@@ -54,9 +54,7 @@
duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
edge = Edge.create(from = LOCKSCREEN, to = Scenes.Communal),
)
- .setupWithoutSceneContainer(
- edge = Edge.create(from = LOCKSCREEN, to = GLANCEABLE_HUB),
- )
+ .setupWithoutSceneContainer(edge = Edge.create(from = LOCKSCREEN, to = GLANCEABLE_HUB))
val keyguardAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
@@ -74,7 +72,7 @@
configurationInteractor
.directionalDimensionPixelSize(
LayoutDirection.LTR,
- R.dimen.lockscreen_to_hub_transition_lockscreen_translation_x
+ R.dimen.lockscreen_to_hub_transition_lockscreen_translation_x,
)
.flatMapLatest { translatePx: Int ->
transitionAnimation.sharedFlowWithState(
@@ -86,7 +84,7 @@
onFinish = { 0f },
onCancel = { 0f },
interpolator = EMPHASIZED,
- name = "LOCKSCREEN->GLANCEABLE_HUB: keyguardTranslationX"
+ name = "LOCKSCREEN->GLANCEABLE_HUB: keyguardTranslationX",
)
}
@@ -94,6 +92,8 @@
val shortcutsAlpha: Flow<Float> = keyguardAlpha
+ val statusBarAlpha: Flow<Float> = keyguardAlpha
+
val notificationTranslationX: Flow<Float> =
keyguardTranslationX.map { it.value }.filterNotNull()
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt
new file mode 100644
index 0000000..a33685b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt
@@ -0,0 +1,341 @@
+/*
+ * 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.media.controls.domain.pipeline
+
+import android.content.Context
+import android.graphics.drawable.Animatable
+import android.graphics.drawable.Drawable
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.os.Handler
+import android.os.Looper
+import android.util.Log
+import androidx.annotation.WorkerThread
+import androidx.media.utils.MediaConstants
+import androidx.media3.common.Player
+import androidx.media3.session.CommandButton
+import androidx.media3.session.SessionCommand
+import androidx.media3.session.SessionToken
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.graphics.ImageLoader
+import com.android.systemui.media.controls.shared.MediaControlDrawables
+import com.android.systemui.media.controls.shared.MediaLogger
+import com.android.systemui.media.controls.shared.model.MediaAction
+import com.android.systemui.media.controls.shared.model.MediaButton
+import com.android.systemui.media.controls.util.MediaControllerFactory
+import com.android.systemui.media.controls.util.SessionTokenFactory
+import com.android.systemui.res.R
+import com.android.systemui.util.Assert
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+private const val TAG = "Media3ActionFactory"
+
+@SysUISingleton
+class Media3ActionFactory
+@Inject
+constructor(
+ @Application val context: Context,
+ private val imageLoader: ImageLoader,
+ private val controllerFactory: MediaControllerFactory,
+ private val tokenFactory: SessionTokenFactory,
+ private val logger: MediaLogger,
+ @Background private val looper: Looper,
+ @Background private val handler: Handler,
+ @Background private val bgScope: CoroutineScope,
+) {
+
+ /**
+ * Generates action button info for this media session based on the Media3 session info
+ *
+ * @param packageName Package name for the media app
+ * @param controller The framework [MediaController] for the session
+ * @return The media action buttons, or null if the session token is null
+ */
+ suspend fun createActionsFromSession(
+ packageName: String,
+ sessionToken: MediaSession.Token,
+ ): MediaButton? {
+ // Get the Media3 controller using the legacy token
+ val token = tokenFactory.createTokenFromLegacy(sessionToken)
+ val m3controller = controllerFactory.create(token, looper)
+
+ // Build button info
+ val buttons = suspendCancellableCoroutine { continuation ->
+ // Media3Controller methods must always be called from a specific looper
+ handler.post {
+ val result = getMedia3Actions(packageName, m3controller, token)
+ m3controller.release()
+ continuation.resumeWith(Result.success(result))
+ }
+ }
+ return buttons
+ }
+
+ /** This method must be called on the Media3 looper! */
+ @WorkerThread
+ private fun getMedia3Actions(
+ packageName: String,
+ m3controller: androidx.media3.session.MediaController,
+ token: SessionToken,
+ ): MediaButton? {
+ Assert.isNotMainThread()
+
+ // First, get standard actions
+ val playOrPause =
+ if (m3controller.playbackState == Player.STATE_BUFFERING) {
+ // Spinner needs to be animating to render anything. Start it here.
+ val drawable =
+ context.getDrawable(com.android.internal.R.drawable.progress_small_material)
+ (drawable as Animatable).start()
+ MediaAction(
+ drawable,
+ null, // no action to perform when clicked
+ context.getString(R.string.controls_media_button_connecting),
+ context.getDrawable(R.drawable.ic_media_connecting_container),
+ // Specify a rebind id to prevent the spinner from restarting on later binds.
+ com.android.internal.R.drawable.progress_small_material,
+ )
+ } else {
+ getStandardAction(m3controller, token, Player.COMMAND_PLAY_PAUSE)
+ }
+
+ val prevButton =
+ getStandardAction(
+ m3controller,
+ token,
+ Player.COMMAND_SEEK_TO_PREVIOUS,
+ Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM,
+ )
+ val nextButton =
+ getStandardAction(
+ m3controller,
+ token,
+ Player.COMMAND_SEEK_TO_NEXT,
+ Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM,
+ )
+
+ // Then, get custom actions
+ var customActions =
+ m3controller.customLayout
+ .asSequence()
+ .filter {
+ it.isEnabled &&
+ it.sessionCommand?.commandCode == SessionCommand.COMMAND_CODE_CUSTOM &&
+ m3controller.isSessionCommandAvailable(it.sessionCommand!!)
+ }
+ .map { getCustomAction(packageName, token, it) }
+ .iterator()
+ fun nextCustomAction() = if (customActions.hasNext()) customActions.next() else null
+
+ // Finally, assign the remaining button slots: play/pause A B C D
+ // A = previous, else custom action (if not reserved)
+ // B = next, else custom action (if not reserved)
+ // C and D are always custom actions
+ val reservePrev =
+ m3controller.sessionExtras.getBoolean(
+ MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV,
+ false,
+ )
+ val reserveNext =
+ m3controller.sessionExtras.getBoolean(
+ MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT,
+ false,
+ )
+
+ val prevOrCustom =
+ prevButton
+ ?: if (reservePrev) {
+ null
+ } else {
+ nextCustomAction()
+ }
+
+ val nextOrCustom =
+ nextButton
+ ?: if (reserveNext) {
+ null
+ } else {
+ nextCustomAction()
+ }
+
+ return MediaButton(
+ playOrPause = playOrPause,
+ nextOrCustom = nextOrCustom,
+ prevOrCustom = prevOrCustom,
+ custom0 = nextCustomAction(),
+ custom1 = nextCustomAction(),
+ reserveNext = reserveNext,
+ reservePrev = reservePrev,
+ )
+ }
+
+ /**
+ * Create a [MediaAction] for a given command, if supported
+ *
+ * @param controller Media3 controller for the session
+ * @param commands Commands to check, in priority order
+ * @return A [MediaAction] representing the first supported command, or null if not supported
+ */
+ private fun getStandardAction(
+ controller: androidx.media3.session.MediaController,
+ token: SessionToken,
+ vararg commands: @Player.Command Int,
+ ): MediaAction? {
+ for (command in commands) {
+ if (!controller.isCommandAvailable(command)) {
+ continue
+ }
+
+ return when (command) {
+ Player.COMMAND_PLAY_PAUSE -> {
+ if (!controller.isPlaying) {
+ MediaAction(
+ context.getDrawable(R.drawable.ic_media_play),
+ { executeAction(token, Player.COMMAND_PLAY_PAUSE) },
+ context.getString(R.string.controls_media_button_play),
+ context.getDrawable(R.drawable.ic_media_play_container),
+ )
+ } else {
+ MediaAction(
+ context.getDrawable(R.drawable.ic_media_pause),
+ { executeAction(token, Player.COMMAND_PLAY_PAUSE) },
+ context.getString(R.string.controls_media_button_pause),
+ context.getDrawable(R.drawable.ic_media_pause_container),
+ )
+ }
+ }
+ else -> {
+ MediaAction(
+ icon = getIconForAction(command),
+ action = { executeAction(token, command) },
+ contentDescription = getDescriptionForAction(command),
+ background = null,
+ )
+ }
+ }
+ }
+ return null
+ }
+
+ /** Get a [MediaAction] representing a [CommandButton] */
+ private fun getCustomAction(
+ packageName: String,
+ token: SessionToken,
+ customAction: CommandButton,
+ ): MediaAction {
+ return MediaAction(
+ getIconForAction(customAction, packageName),
+ { executeAction(token, Player.COMMAND_INVALID, customAction) },
+ customAction.displayName,
+ null,
+ )
+ }
+
+ private fun getIconForAction(command: @Player.Command Int): Drawable? {
+ return when (command) {
+ Player.COMMAND_SEEK_TO_PREVIOUS -> MediaControlDrawables.getPrevIcon(context)
+ Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM -> MediaControlDrawables.getPrevIcon(context)
+ Player.COMMAND_SEEK_TO_NEXT -> MediaControlDrawables.getNextIcon(context)
+ Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM -> MediaControlDrawables.getNextIcon(context)
+ else -> {
+ Log.e(TAG, "Unknown icon for $command")
+ null
+ }
+ }
+ }
+
+ private fun getIconForAction(customAction: CommandButton, packageName: String): Drawable? {
+ val size = context.resources.getDimensionPixelSize(R.dimen.min_clickable_item_size)
+ // TODO(b/360196209): check customAction.icon field to use platform icons
+ if (customAction.iconResId != 0) {
+ val packageContext = context.createPackageContext(packageName, 0)
+ val source = ImageLoader.Res(customAction.iconResId, packageContext)
+ return runBlocking { imageLoader.loadDrawable(source, size, size) }
+ }
+
+ if (customAction.iconUri != null) {
+ val source = ImageLoader.Uri(customAction.iconUri!!)
+ return runBlocking { imageLoader.loadDrawable(source, size, size) }
+ }
+ return null
+ }
+
+ private fun getDescriptionForAction(command: @Player.Command Int): String? {
+ return when (command) {
+ Player.COMMAND_SEEK_TO_PREVIOUS ->
+ context.getString(R.string.controls_media_button_prev)
+ Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM ->
+ context.getString(R.string.controls_media_button_prev)
+ Player.COMMAND_SEEK_TO_NEXT -> context.getString(R.string.controls_media_button_next)
+ Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM ->
+ context.getString(R.string.controls_media_button_next)
+ else -> {
+ Log.e(TAG, "Unknown content description for $command")
+ null
+ }
+ }
+ }
+
+ private fun executeAction(
+ token: SessionToken,
+ command: Int,
+ customAction: CommandButton? = null,
+ ) {
+ bgScope.launch {
+ val controller = controllerFactory.create(token, looper)
+ handler.post {
+ when (command) {
+ Player.COMMAND_PLAY_PAUSE -> {
+ if (controller.isPlaying) controller.pause() else controller.play()
+ }
+
+ Player.COMMAND_SEEK_TO_PREVIOUS -> controller.seekToPrevious()
+ Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM ->
+ controller.seekToPreviousMediaItem()
+
+ Player.COMMAND_SEEK_TO_NEXT -> controller.seekToNext()
+ Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM -> controller.seekToNextMediaItem()
+ Player.COMMAND_INVALID -> {
+ if (
+ customAction != null &&
+ customAction!!.sessionCommand != null &&
+ controller.isSessionCommandAvailable(
+ customAction!!.sessionCommand!!
+ )
+ ) {
+ controller.sendCustomCommand(
+ customAction!!.sessionCommand!!,
+ customAction!!.extras,
+ )
+ } else {
+ logger.logMedia3UnsupportedCommand("$command, action $customAction")
+ }
+ }
+
+ else -> logger.logMedia3UnsupportedCommand(command.toString())
+ }
+ controller.release()
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt
index 591a9cc..a176e0c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt
@@ -84,6 +84,7 @@
private val mediaFlags: MediaFlags,
private val imageLoader: ImageLoader,
private val statusBarManager: StatusBarManager,
+ private val media3ActionFactory: Media3ActionFactory,
) {
private val mediaProcessingJobs = ConcurrentHashMap<String, Job>()
@@ -364,7 +365,7 @@
)
}
- private fun createActionsFromState(
+ private suspend fun createActionsFromState(
packageName: String,
controller: MediaController,
user: UserHandle,
@@ -373,6 +374,12 @@
return null
}
+ if (mediaFlags.areMedia3ActionsEnabled(packageName, user)) {
+ return media3ActionFactory.createActionsFromSession(
+ packageName,
+ controller.sessionToken,
+ )
+ }
return createActionsFromState(context, packageName, controller)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaProcessingHelper.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaProcessingHelper.kt
index 2bdee67..beb4d41 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaProcessingHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaProcessingHelper.kt
@@ -141,6 +141,7 @@
new: MediaData,
old: MediaData,
): Boolean {
+ // TODO(b/360196209): account for actions generated from media3
val oldState = MediaController(context, old.token!!).playbackState
return if (
new.semanticActions == null &&
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt
index 88c47ba..0b598c1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt
@@ -140,6 +140,10 @@
)
}
+ fun logMedia3UnsupportedCommand(command: String) {
+ buffer.log(TAG, LogLevel.DEBUG, { str1 = command }, { "Unsupported media3 command $str1" })
+ }
+
companion object {
private const val TAG = "MediaLog"
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.java b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.java
deleted file mode 100644
index 6caf5c2..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2020 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.media.controls.util;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-
-import javax.inject.Inject;
-
-/**
- * Testable wrapper around {@link MediaController} constructor.
- */
-public class MediaControllerFactory {
-
- private final Context mContext;
-
- @Inject
- public MediaControllerFactory(Context context) {
- mContext = context;
- }
-
- /**
- * Creates a new MediaController from a session's token.
- *
- * @param token The token for the session. This value must never be null.
- */
- public MediaController create(@NonNull MediaSession.Token token) {
- return new MediaController(mContext, token);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.kt
new file mode 100644
index 0000000..741f529
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 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.media.controls.util
+
+import android.content.Context
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.os.Looper
+import androidx.concurrent.futures.await
+import androidx.media3.session.MediaController as Media3Controller
+import androidx.media3.session.SessionToken
+import javax.inject.Inject
+
+/** Testable wrapper for media controller construction */
+open class MediaControllerFactory @Inject constructor(private val context: Context) {
+ /**
+ * Creates a new [MediaController] from the framework session token.
+ *
+ * @param token The token for the session. This value must never be null.
+ */
+ open fun create(token: MediaSession.Token): MediaController {
+ return MediaController(context, token)
+ }
+
+ /** Creates a new [Media3Controller] from a [SessionToken] */
+ open suspend fun create(token: SessionToken, looper: Looper): Media3Controller {
+ return Media3Controller.Builder(context, token)
+ .setApplicationLooper(looper)
+ .buildAsync()
+ .await()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index d4af1b5..ac60c47 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -18,9 +18,10 @@
import android.app.StatusBarManager
import android.os.UserHandle
+import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
+import com.android.systemui.flags.Flags as FlagsClassic
import javax.inject.Inject
@SysUISingleton
@@ -29,22 +30,29 @@
* Check whether media control actions should be based on PlaybackState instead of notification
*/
fun areMediaSessionActionsEnabled(packageName: String, user: UserHandle): Boolean {
- // Allow global override with flag
return StatusBarManager.useMediaSessionActionsForApp(packageName, user)
}
+ /** Check whether media control actions should be derived from Media3 controller */
+ fun areMedia3ActionsEnabled(packageName: String, user: UserHandle): Boolean {
+ val compatFlag = StatusBarManager.useMedia3ControllerForApp(packageName, user)
+ val featureFlag = Flags.mediaControlsButtonMedia3()
+ return featureFlag && compatFlag
+ }
+
/**
* If true, keep active media controls for the lifetime of the MediaSession, regardless of
* whether the underlying notification was dismissed
*/
- fun isRetainingPlayersEnabled() = featureFlags.isEnabled(Flags.MEDIA_RETAIN_SESSIONS)
+ fun isRetainingPlayersEnabled() = featureFlags.isEnabled(FlagsClassic.MEDIA_RETAIN_SESSIONS)
/** Check whether to get progress information for resume players */
- fun isResumeProgressEnabled() = featureFlags.isEnabled(Flags.MEDIA_RESUME_PROGRESS)
+ fun isResumeProgressEnabled() = featureFlags.isEnabled(FlagsClassic.MEDIA_RESUME_PROGRESS)
/** If true, do not automatically dismiss the recommendation card */
- fun isPersistentSsCardEnabled() = featureFlags.isEnabled(Flags.MEDIA_RETAIN_RECOMMENDATIONS)
+ fun isPersistentSsCardEnabled() =
+ featureFlags.isEnabled(FlagsClassic.MEDIA_RETAIN_RECOMMENDATIONS)
/** Check whether we allow remote media to generate resume controls */
- fun isRemoteResumeAllowed() = featureFlags.isEnabled(Flags.MEDIA_REMOTE_RESUME)
+ fun isRemoteResumeAllowed() = featureFlags.isEnabled(FlagsClassic.MEDIA_REMOTE_RESUME)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/SessionTokenFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/SessionTokenFactory.kt
new file mode 100644
index 0000000..b289fd4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/SessionTokenFactory.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.media.controls.util
+
+import android.content.Context
+import android.media.session.MediaSession
+import androidx.concurrent.futures.await
+import androidx.media3.session.SessionToken
+import javax.inject.Inject
+
+/** Testable wrapper for [SessionToken] creation */
+open class SessionTokenFactory @Inject constructor(private val context: Context) {
+ /** Create a new [SessionToken] from the framework [MediaSession.Token] */
+ open suspend fun createTokenFromLegacy(token: MediaSession.Token): SessionToken {
+ return SessionToken.createSessionToken(context, token).await()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
index 96c0cac..40613c0 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
@@ -149,6 +149,7 @@
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.data.repository.LightBarControllerStore;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -258,8 +259,7 @@
private boolean mTransientShownFromGestureOnSystemBar;
private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
private LightBarController mLightBarController;
- private final LightBarController mMainLightBarController;
- private final LightBarController.Factory mLightBarControllerFactory;
+ private final LightBarControllerStore mLightBarControllerStore;
private AutoHideController mAutoHideController;
private final AutoHideController mMainAutoHideController;
private final AutoHideController.Factory mAutoHideControllerFactory;
@@ -580,8 +580,7 @@
@Background Executor bgExecutor,
UiEventLogger uiEventLogger,
NavBarHelper navBarHelper,
- LightBarController mainLightBarController,
- LightBarController.Factory lightBarControllerFactory,
+ LightBarControllerStore lightBarControllerStore,
AutoHideController mainAutoHideController,
AutoHideController.Factory autoHideControllerFactory,
Optional<TelecomManager> telecomManagerOptional,
@@ -628,8 +627,7 @@
mUiEventLogger = uiEventLogger;
mNavBarHelper = navBarHelper;
mNotificationShadeDepthController = notificationShadeDepthController;
- mMainLightBarController = mainLightBarController;
- mLightBarControllerFactory = lightBarControllerFactory;
+ mLightBarControllerStore = lightBarControllerStore;
mMainAutoHideController = mainAutoHideController;
mAutoHideControllerFactory = autoHideControllerFactory;
mTelecomManagerOptional = telecomManagerOptional;
@@ -842,8 +840,7 @@
// Unfortunately, we still need it because status bar needs LightBarController
// before notifications creation. We cannot directly use getLightBarController()
// from NavigationBarFragment directly.
- LightBarController lightBarController = mIsOnDefaultDisplay
- ? mMainLightBarController : mLightBarControllerFactory.create(mContext);
+ LightBarController lightBarController = mLightBarControllerStore.forDisplay(mDisplayId);
setLightBarController(lightBarController);
// TODO(b/118592525): to support multi-display, we start to add something which is
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
index 1c9cb3d..fef5a74 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
@@ -25,6 +25,7 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.coroutineScope
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.logging.MetricsLogger
import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.qualifiers.Background
@@ -48,7 +49,6 @@
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
import javax.inject.Inject
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.runBlocking
class ModesTile
@@ -120,8 +120,7 @@
tileState = tileMapper.map(config, model)
state?.apply {
this.state = tileState.activationState.legacyState
- val tileStateIcon = tileState.icon()
- icon = tileStateIcon?.asQSTileIcon() ?: ResourceIcon.get(ICON_RES_ID)
+ icon = tileState.icon?.asQSTileIcon() ?: ResourceIcon.get(ICON_RES_ID)
label = tileLabel
secondaryLabel = tileState.secondaryLabel
contentDescription = tileState.contentDescription
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
index 9fb1d46..d67057a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
@@ -30,10 +30,8 @@
/** Maps [AirplaneModeTileModel] to [QSTileState]. */
class AirplaneModeMapper
@Inject
-constructor(
- @Main private val resources: Resources,
- val theme: Theme,
-) : QSTileDataToStateMapper<AirplaneModeTileModel> {
+constructor(@Main private val resources: Resources, val theme: Theme) :
+ QSTileDataToStateMapper<AirplaneModeTileModel> {
override fun map(config: QSTileConfig, data: AirplaneModeTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
@@ -43,16 +41,7 @@
} else {
R.drawable.qs_airplane_icon_off
}
-
- icon = {
- Icon.Loaded(
- resources.getDrawable(
- iconRes!!,
- theme,
- ),
- contentDescription = null
- )
- }
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
if (data.isEnabled) {
activationState = QSTileState.ActivationState.ACTIVE
secondaryLabel = resources.getStringArray(R.array.tile_states_airplane)[2]
@@ -62,9 +51,6 @@
}
contentDescription = label
supportedActions =
- setOf(
- QSTileState.UserAction.CLICK,
- QSTileState.UserAction.LONG_CLICK,
- )
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
index f088943..7322b8d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
@@ -45,6 +45,7 @@
val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("E HH:mm")
val formatterDateOnly: DateTimeFormatter = DateTimeFormatter.ofPattern("E MMM d")
}
+
override fun map(config: QSTileConfig, data: AlarmTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
when (data) {
@@ -54,13 +55,13 @@
val alarmDateTime =
LocalDateTime.ofInstant(
Instant.ofEpochMilli(data.alarmClockInfo.triggerTime),
- TimeZone.getDefault().toZoneId()
+ TimeZone.getDefault().toZoneId(),
)
val nowDateTime =
LocalDateTime.ofInstant(
Instant.ofEpochMilli(clock.currentTimeMillis()),
- TimeZone.getDefault().toZoneId()
+ TimeZone.getDefault().toZoneId(),
)
// Edge case: If it's 8:00:30 right now and alarm is requested for next week at
@@ -84,7 +85,7 @@
}
}
iconRes = R.drawable.ic_alarm
- icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) }
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
sideViewIcon = QSTileState.SideViewIcon.Chevron
contentDescription = label
supportedActions = setOf(QSTileState.UserAction.CLICK)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
index bcf0935..5b30e8d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
@@ -29,10 +29,8 @@
/** Maps [BatterySaverTileModel] to [QSTileState]. */
open class BatterySaverTileMapper
@Inject
-constructor(
- @Main protected val resources: Resources,
- private val theme: Resources.Theme,
-) : QSTileDataToStateMapper<BatterySaverTileModel> {
+constructor(@Main protected val resources: Resources, private val theme: Resources.Theme) :
+ QSTileDataToStateMapper<BatterySaverTileModel> {
override fun map(config: QSTileConfig, data: BatterySaverTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
@@ -41,8 +39,7 @@
iconRes =
if (data.isPowerSaving) R.drawable.qs_battery_saver_icon_on
else R.drawable.qs_battery_saver_icon_off
- icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) }
-
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
sideViewIcon = QSTileState.SideViewIcon.None
if (data.isPluggedIn) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt
index cad7c65..7c90b3d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.tiles.impl.colorcorrection.domain
import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel
@@ -28,17 +29,14 @@
/** Maps [ColorCorrectionTileModel] to [QSTileState]. */
class ColorCorrectionTileMapper
@Inject
-constructor(
- @Main private val resources: Resources,
- private val theme: Resources.Theme,
-) : QSTileDataToStateMapper<ColorCorrectionTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
+ QSTileDataToStateMapper<ColorCorrectionTileModel> {
override fun map(config: QSTileConfig, data: ColorCorrectionTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
val subtitleArray = resources.getStringArray(R.array.tile_states_color_correction)
-
iconRes = R.drawable.ic_qs_color_correction
-
+ icon = Icon.Loaded(resources.getDrawable(R.drawable.ic_qs_color_correction)!!, null)
if (data.isEnabled) {
activationState = QSTileState.ActivationState.ACTIVE
secondaryLabel = subtitleArray[2]
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt
index 984228d..60aa4ea 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt
@@ -35,10 +35,8 @@
@SysUISingleton
class CustomTileMapper
@Inject
-constructor(
- private val context: Context,
- private val uriGrantsManager: IUriGrantsManager,
-) : QSTileDataToStateMapper<CustomTileDataModel> {
+constructor(private val context: Context, private val uriGrantsManager: IUriGrantsManager) :
+ QSTileDataToStateMapper<CustomTileDataModel> {
override fun map(config: QSTileConfig, data: CustomTileDataModel): QSTileState {
val userContext =
@@ -50,7 +48,7 @@
val iconResult =
if (userContext != null) {
- getIconProvider(
+ getIcon(
userContext = userContext,
icon = data.tile.icon,
callingAppUid = data.callingAppUid,
@@ -58,16 +56,16 @@
defaultIcon = data.defaultTileIcon,
)
} else {
- IconResult({ null }, true)
+ IconResult(null, true)
}
- return QSTileState.build(iconResult.iconProvider, data.tile.label) {
+ return QSTileState.build(iconResult.icon, data.tile.label) {
var tileState: Int = data.tile.state
if (data.hasPendingBind) {
tileState = Tile.STATE_UNAVAILABLE
}
- icon = iconResult.iconProvider
+ icon = iconResult.icon
activationState =
if (iconResult.failedToLoad) {
QSTileState.ActivationState.UNAVAILABLE
@@ -102,7 +100,7 @@
}
@SuppressLint("MissingPermission") // android.permission.INTERACT_ACROSS_USERS_FULL
- private fun getIconProvider(
+ private fun getIcon(
userContext: Context,
icon: android.graphics.drawable.Icon?,
callingAppUid: Int,
@@ -123,17 +121,12 @@
null
} ?: defaultIcon?.loadDrawable(userContext)
return IconResult(
- {
- drawable?.constantState?.newDrawable()?.let {
- Icon.Loaded(it, contentDescription = null)
- }
+ drawable?.constantState?.newDrawable()?.let {
+ Icon.Loaded(it, contentDescription = null)
},
failedToLoad,
)
}
- class IconResult(
- val iconProvider: () -> Icon?,
- val failedToLoad: Boolean,
- )
+ class IconResult(val icon: Icon?, val failedToLoad: Boolean)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
index d7d6124..7e557eb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
@@ -30,10 +30,8 @@
/** Maps [FlashlightTileModel] to [QSTileState]. */
class FlashlightMapper
@Inject
-constructor(
- @Main private val resources: Resources,
- private val theme: Theme,
-) : QSTileDataToStateMapper<FlashlightTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Theme) :
+ QSTileDataToStateMapper<FlashlightTileModel> {
override fun map(config: QSTileConfig, data: FlashlightTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
@@ -43,15 +41,8 @@
} else {
R.drawable.qs_flashlight_icon_off
}
- val icon =
- Icon.Loaded(
- resources.getDrawable(
- iconRes!!,
- theme,
- ),
- contentDescription = null
- )
- this.icon = { icon }
+
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
contentDescription = label
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
index 6b4dda1..9d44fc6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
@@ -29,23 +29,13 @@
/** Maps [FontScalingTileModel] to [QSTileState]. */
class FontScalingTileMapper
@Inject
-constructor(
- @Main private val resources: Resources,
- private val theme: Resources.Theme,
-) : QSTileDataToStateMapper<FontScalingTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
+ QSTileDataToStateMapper<FontScalingTileModel> {
override fun map(config: QSTileConfig, data: FontScalingTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
iconRes = R.drawable.ic_qs_font_scaling
- val icon =
- Icon.Loaded(
- resources.getDrawable(
- iconRes!!,
- theme,
- ),
- contentDescription = null
- )
- this.icon = { icon }
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
contentDescription = label
activationState = QSTileState.ActivationState.ACTIVE
sideViewIcon = QSTileState.SideViewIcon.Chevron
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt
index 8dd611f..c3ac1f8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt
@@ -36,9 +36,7 @@
QSTileState.build(resources, theme, config.uiConfig) {
label = resources.getString(R.string.quick_settings_hearing_devices_label)
iconRes = R.drawable.qs_hearing_devices_icon
- val loadedIcon =
- Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
- icon = { loadedIcon }
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
sideViewIcon = QSTileState.SideViewIcon.Chevron
contentDescription = label
if (data.isAnyActiveHearingDevice) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
index bb0b9b7..fc94585 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
@@ -61,28 +61,26 @@
when (val dataIcon = data.icon) {
is InternetTileIconModel.ResourceId -> {
iconRes = dataIcon.resId
- icon = {
+ icon =
Icon.Loaded(
resources.getDrawable(dataIcon.resId, theme),
contentDescription = null,
)
- }
}
is InternetTileIconModel.Cellular -> {
val signalDrawable = SignalDrawable(context, handler)
signalDrawable.setLevel(dataIcon.level)
- icon = { Icon.Loaded(signalDrawable, contentDescription = null) }
+ icon = Icon.Loaded(signalDrawable, contentDescription = null)
}
is InternetTileIconModel.Satellite -> {
iconRes = dataIcon.resourceIcon.res // level is inferred from res
- icon = {
+ icon =
Icon.Loaded(
resources.getDrawable(dataIcon.resourceIcon.res, theme),
contentDescription = null,
)
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt
index 40aee65..3692c35 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt
@@ -30,10 +30,8 @@
/** Maps [ColorInversionTileModel] to [QSTileState]. */
class ColorInversionTileMapper
@Inject
-constructor(
- @Main private val resources: Resources,
- private val theme: Theme,
-) : QSTileDataToStateMapper<ColorInversionTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Theme) :
+ QSTileDataToStateMapper<ColorInversionTileModel> {
override fun map(config: QSTileConfig, data: ColorInversionTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
val subtitleArray = resources.getStringArray(R.array.tile_states_inversion)
@@ -47,7 +45,7 @@
secondaryLabel = subtitleArray[1]
iconRes = R.drawable.qs_invert_colors_icon_off
}
- icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) }
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
contentDescription = label
supportedActions =
setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt
index ff931b3..3fe2a77 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt
@@ -28,21 +28,26 @@
class IssueRecordingMapper
@Inject
-constructor(
- @Main private val resources: Resources,
- private val theme: Theme,
-) : QSTileDataToStateMapper<IssueRecordingModel> {
+constructor(@Main private val resources: Resources, private val theme: Theme) :
+ QSTileDataToStateMapper<IssueRecordingModel> {
override fun map(config: QSTileConfig, data: IssueRecordingModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
- if (data.isRecording) {
- activationState = QSTileState.ActivationState.ACTIVE
- secondaryLabel = resources.getString(R.string.qs_record_issue_stop)
- icon = { Icon.Resource(R.drawable.qs_record_issue_icon_on, null) }
- } else {
- icon = { Icon.Resource(R.drawable.qs_record_issue_icon_off, null) }
- activationState = QSTileState.ActivationState.INACTIVE
- secondaryLabel = resources.getString(R.string.qs_record_issue_start)
- }
+ icon =
+ if (data.isRecording) {
+ activationState = QSTileState.ActivationState.ACTIVE
+ secondaryLabel = resources.getString(R.string.qs_record_issue_stop)
+ Icon.Loaded(
+ resources.getDrawable(R.drawable.qs_record_issue_icon_on, theme),
+ null,
+ )
+ } else {
+ activationState = QSTileState.ActivationState.INACTIVE
+ secondaryLabel = resources.getString(R.string.qs_record_issue_start)
+ Icon.Loaded(
+ resources.getDrawable(R.drawable.qs_record_issue_icon_off, theme),
+ null,
+ )
+ }
supportedActions = setOf(QSTileState.UserAction.CLICK)
contentDescription = "$label, $secondaryLabel"
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
index d58f5ab..08432f6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
@@ -30,10 +30,8 @@
/** Maps [LocationTileModel] to [QSTileState]. */
class LocationTileMapper
@Inject
-constructor(
- @Main private val resources: Resources,
- private val theme: Theme,
-) : QSTileDataToStateMapper<LocationTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Theme) :
+ QSTileDataToStateMapper<LocationTileModel> {
override fun map(config: QSTileConfig, data: LocationTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
@@ -43,17 +41,9 @@
} else {
R.drawable.qs_location_icon_off
}
- val icon =
- Icon.Loaded(
- resources.getDrawable(
- iconRes!!,
- theme,
- ),
- contentDescription = null
- )
- this.icon = { icon }
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
- this.label = resources.getString(R.string.quick_settings_location_label)
+ label = resources.getString(R.string.quick_settings_location_label)
if (data.isEnabled) {
activationState = QSTileState.ActivationState.ACTIVE
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
index 69da313..4a64313 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
@@ -30,14 +30,12 @@
class ModesTileMapper
@Inject
-constructor(
- @Main private val resources: Resources,
- val theme: Resources.Theme,
-) : QSTileDataToStateMapper<ModesTileModel> {
+constructor(@Main private val resources: Resources, val theme: Resources.Theme) :
+ QSTileDataToStateMapper<ModesTileModel> {
override fun map(config: QSTileConfig, data: ModesTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
iconRes = data.iconResId
- icon = { data.icon }
+ icon = data.icon
activationState =
if (data.isActivated) {
QSTileState.ActivationState.ACTIVE
@@ -47,10 +45,7 @@
secondaryLabel = getModesStatus(data, resources)
contentDescription = "$label. $secondaryLabel"
supportedActions =
- setOf(
- QSTileState.UserAction.CLICK,
- QSTileState.UserAction.LONG_CLICK,
- )
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
sideViewIcon = QSTileState.SideViewIcon.Chevron
expandedAccessibilityClass = Button::class
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
index bcf7cc7..081a03c7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
@@ -57,9 +57,8 @@
activationState = QSTileState.ActivationState.INACTIVE
iconRes = R.drawable.qs_nightlight_icon_off
}
- val loadedIcon =
- Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
- icon = { loadedIcon }
+
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
secondaryLabel = getSecondaryLabel(data, resources)
@@ -70,7 +69,7 @@
private fun getSecondaryLabel(
data: NightDisplayTileModel,
- resources: Resources
+ resources: Resources,
): CharSequence? {
when (data) {
is NightDisplayTileModel.AutoModeTwilight -> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt
index 4080996..8e5d0d4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt
@@ -29,17 +29,15 @@
/** Maps [OneHandedModeTileModel] to [QSTileState]. */
class OneHandedModeTileMapper
@Inject
-constructor(
- @Main private val resources: Resources,
- private val theme: Resources.Theme,
-) : QSTileDataToStateMapper<OneHandedModeTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
+ QSTileDataToStateMapper<OneHandedModeTileModel> {
override fun map(config: QSTileConfig, data: OneHandedModeTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
val subtitleArray = resources.getStringArray(R.array.tile_states_onehanded)
label = resources.getString(R.string.quick_settings_onehanded_label)
iconRes = com.android.internal.R.drawable.ic_qs_one_handed_mode
- icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) }
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
if (data.isEnabled) {
activationState = QSTileState.ActivationState.ACTIVE
secondaryLabel = subtitleArray[2]
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
index 8231742..5c6351e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
@@ -29,17 +29,15 @@
/** Maps [QRCodeScannerTileModel] to [QSTileState]. */
class QRCodeScannerTileMapper
@Inject
-constructor(
- @Main private val resources: Resources,
- private val theme: Resources.Theme,
-) : QSTileDataToStateMapper<QRCodeScannerTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
+ QSTileDataToStateMapper<QRCodeScannerTileModel> {
override fun map(config: QSTileConfig, data: QRCodeScannerTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
label = resources.getString(R.string.qr_code_scanner_title)
contentDescription = label
iconRes = R.drawable.ic_qr_code_scanner
- icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) }
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
sideViewIcon = QSTileState.SideViewIcon.Chevron
supportedActions = setOf(QSTileState.UserAction.CLICK)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
index 85ee022..fe77fe6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
@@ -30,10 +30,8 @@
/** Maps [ReduceBrightColorsTileModel] to [QSTileState]. */
class ReduceBrightColorsTileMapper
@Inject
-constructor(
- @Main private val resources: Resources,
- private val theme: Resources.Theme,
-) : QSTileDataToStateMapper<ReduceBrightColorsTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
+ QSTileDataToStateMapper<ReduceBrightColorsTileModel> {
override fun map(config: QSTileConfig, data: ReduceBrightColorsTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
@@ -50,12 +48,7 @@
resources
.getStringArray(R.array.tile_states_reduce_brightness)[Tile.STATE_INACTIVE]
}
- icon = {
- Icon.Loaded(
- drawable = resources.getDrawable(iconRes!!, theme),
- contentDescription = null
- )
- }
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
label =
resources.getString(com.android.internal.R.string.reduce_bright_colors_feature_name)
contentDescription = label
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt
index 33dc6ed..9a003ff 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt
@@ -36,36 +36,33 @@
@Main private val resources: Resources,
private val theme: Resources.Theme,
private val devicePostureController: DevicePostureController,
- private val deviceStateManager: DeviceStateManager
+ private val deviceStateManager: DeviceStateManager,
) : QSTileDataToStateMapper<RotationLockTileModel> {
override fun map(config: QSTileConfig, data: RotationLockTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
- this.label = resources.getString(R.string.quick_settings_rotation_unlocked_label)
- this.contentDescription =
- resources.getString(R.string.accessibility_quick_settings_rotation)
+ label = resources.getString(R.string.quick_settings_rotation_unlocked_label)
+ contentDescription = resources.getString(R.string.accessibility_quick_settings_rotation)
if (data.isRotationLocked) {
activationState = QSTileState.ActivationState.INACTIVE
- this.secondaryLabel = EMPTY_SECONDARY_STRING
+ secondaryLabel = EMPTY_SECONDARY_STRING
iconRes = R.drawable.qs_auto_rotate_icon_off
} else {
activationState = QSTileState.ActivationState.ACTIVE
- this.secondaryLabel =
+ secondaryLabel =
if (data.isCameraRotationEnabled) {
resources.getString(R.string.rotation_lock_camera_rotation_on)
} else {
EMPTY_SECONDARY_STRING
}
- this.iconRes = R.drawable.qs_auto_rotate_icon_on
+ iconRes = R.drawable.qs_auto_rotate_icon_on
}
- this.icon = {
- Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
- }
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
if (isDeviceFoldable(resources, deviceStateManager)) {
- this.secondaryLabel = getSecondaryLabelWithPosture(this.activationState)
+ secondaryLabel = getSecondaryLabelWithPosture(activationState)
}
- this.stateDescription = this.secondaryLabel
- this.sideViewIcon = QSTileState.SideViewIcon.None
+ stateDescription = secondaryLabel
+ sideViewIcon = QSTileState.SideViewIcon.None
supportedActions =
setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
}
@@ -86,7 +83,7 @@
return resources.getString(
R.string.rotation_tile_with_posture_secondary_label_template,
stateName,
- posture
+ posture,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
index 888bba87..08196bb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
@@ -29,10 +29,8 @@
/** Maps [DataSaverTileModel] to [QSTileState]. */
class DataSaverTileMapper
@Inject
-constructor(
- @Main private val resources: Resources,
- private val theme: Resources.Theme,
-) : QSTileDataToStateMapper<DataSaverTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
+ QSTileDataToStateMapper<DataSaverTileModel> {
override fun map(config: QSTileConfig, data: DataSaverTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
with(data) {
@@ -45,9 +43,7 @@
iconRes = R.drawable.qs_data_saver_icon_off
secondaryLabel = resources.getStringArray(R.array.tile_states_saver)[1]
}
- val loadedIcon =
- Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
- icon = { loadedIcon }
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
contentDescription = label
supportedActions =
setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
index e74e77f..ba06de9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
@@ -30,10 +30,8 @@
/** Maps [ScreenRecordModel] to [QSTileState]. */
class ScreenRecordTileMapper
@Inject
-constructor(
- @Main private val resources: Resources,
- private val theme: Resources.Theme,
-) : QSTileDataToStateMapper<ScreenRecordModel> {
+constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
+ QSTileDataToStateMapper<ScreenRecordModel> {
override fun map(config: QSTileConfig, data: ScreenRecordModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
label = resources.getString(R.string.quick_settings_screen_record_label)
@@ -43,24 +41,12 @@
is ScreenRecordModel.Recording -> {
activationState = QSTileState.ActivationState.ACTIVE
iconRes = R.drawable.qs_screen_record_icon_on
- val loadedIcon =
- Icon.Loaded(
- resources.getDrawable(iconRes!!, theme),
- contentDescription = null
- )
- icon = { loadedIcon }
sideViewIcon = QSTileState.SideViewIcon.None
secondaryLabel = resources.getString(R.string.quick_settings_screen_record_stop)
}
is ScreenRecordModel.Starting -> {
activationState = QSTileState.ActivationState.ACTIVE
iconRes = R.drawable.qs_screen_record_icon_on
- val loadedIcon =
- Icon.Loaded(
- resources.getDrawable(iconRes!!, theme),
- contentDescription = null
- )
- icon = { loadedIcon }
val countDown = data.countdownSeconds
sideViewIcon = QSTileState.SideViewIcon.None
secondaryLabel = String.format("%d...", countDown)
@@ -68,17 +54,13 @@
is ScreenRecordModel.DoingNothing -> {
activationState = QSTileState.ActivationState.INACTIVE
iconRes = R.drawable.qs_screen_record_icon_off
- val loadedIcon =
- Icon.Loaded(
- resources.getDrawable(iconRes!!, theme),
- contentDescription = null
- )
- icon = { loadedIcon }
sideViewIcon = QSTileState.SideViewIcon.Chevron // tapping will open dialog
secondaryLabel =
resources.getString(R.string.quick_settings_screen_record_start)
}
}
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+
contentDescription =
if (TextUtils.isEmpty(secondaryLabel)) label
else TextUtils.concat(label, ", ", secondaryLabel)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt
index 597cf27..b4cfec4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt
@@ -51,8 +51,7 @@
supportedActions =
setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
iconRes = sensorPrivacyTileResources.getIconRes(data.isBlocked)
- icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) }
-
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
sideViewIcon = QSTileState.SideViewIcon.None
if (data.isBlocked) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
index f29c745d..eda8e5c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
@@ -34,14 +34,13 @@
/** Maps [UiModeNightTileModel] to [QSTileState]. */
class UiModeNightTileMapper
@Inject
-constructor(
- @Main private val resources: Resources,
- private val theme: Theme,
-) : QSTileDataToStateMapper<UiModeNightTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Theme) :
+ QSTileDataToStateMapper<UiModeNightTileModel> {
companion object {
val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm a")
val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm")
}
+
override fun map(config: QSTileConfig, data: UiModeNightTileModel): QSTileState =
with(data) {
QSTileState.build(resources, theme, config.uiConfig) {
@@ -76,7 +75,7 @@
if (isNightMode)
R.string.quick_settings_dark_mode_secondary_label_until
else R.string.quick_settings_dark_mode_secondary_label_on_at,
- formatter.format(time)
+ formatter.format(time),
)
} else if (
nightModeCustomType == UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME
@@ -121,9 +120,7 @@
if (activationState == QSTileState.ActivationState.ACTIVE)
R.drawable.qs_light_dark_theme_icon_on
else R.drawable.qs_light_dark_theme_icon_off
- val loadedIcon =
- Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
- icon = { loadedIcon }
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
supportedActions =
if (activationState == QSTileState.ActivationState.UNAVAILABLE)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt
index eee95b7..a1bc8a8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt
@@ -42,9 +42,7 @@
label = getTileLabel()!!
contentDescription = label
iconRes = com.android.internal.R.drawable.stat_sys_managed_profile_status
- icon = {
- Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
- }
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
when (data) {
is WorkModeTileModel.HasActiveProfile -> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
index 549f0a7..8394be5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
@@ -35,7 +35,7 @@
* // TODO(b/http://b/299909989): Clean up legacy mappings after the transition
*/
data class QSTileState(
- val icon: () -> Icon?,
+ val icon: Icon?,
val iconRes: Int?,
val label: CharSequence,
val activationState: ActivationState,
@@ -54,21 +54,18 @@
resources: Resources,
theme: Theme,
config: QSTileUIConfig,
- builder: Builder.() -> Unit
+ builder: Builder.() -> Unit,
): QSTileState {
val iconDrawable = resources.getDrawable(config.iconRes, theme)
return build(
- { Icon.Loaded(iconDrawable, null) },
+ Icon.Loaded(iconDrawable, null),
resources.getString(config.labelRes),
builder,
)
}
- fun build(
- icon: () -> Icon?,
- label: CharSequence,
- builder: Builder.() -> Unit
- ): QSTileState = Builder(icon, label).apply { builder() }.build()
+ fun build(icon: Icon?, label: CharSequence, builder: Builder.() -> Unit): QSTileState =
+ Builder(icon, label).apply { builder() }.build()
}
enum class ActivationState(val legacyState: Int) {
@@ -117,10 +114,7 @@
data object None : SideViewIcon
}
- class Builder(
- var icon: () -> Icon?,
- var label: CharSequence,
- ) {
+ class Builder(var icon: Icon?, var label: CharSequence) {
var iconRes: Int? = null
var activationState: ActivationState = ActivationState.INACTIVE
var secondaryLabel: CharSequence? = null
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
index f89745f..35b1b96 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.os.UserHandle
import android.util.Log
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.logging.InstanceId
import com.android.systemui.Dumpable
import com.android.systemui.animation.Expandable
@@ -42,7 +43,6 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.takeWhile
-import com.android.app.tracing.coroutines.launchTraced as launch
// TODO(b/http://b/299909989): Use QSTileViewModel directly after the rollout
class QSTileViewModelAdapter
@@ -223,7 +223,7 @@
fun mapState(
context: Context,
viewModelState: QSTileState,
- config: QSTileConfig
+ config: QSTileConfig,
): QSTile.State =
// we have to use QSTile.BooleanState to support different side icons
// which are bound to instanceof QSTile.BooleanState in QSTileView.
@@ -241,7 +241,7 @@
viewModelState.supportedActions.contains(QSTileState.UserAction.TOGGLE_CLICK)
icon =
- when (val stateIcon = viewModelState.icon()) {
+ when (val stateIcon = viewModelState.icon) {
is Icon.Loaded ->
if (viewModelState.iconRes == null) DrawableIcon(stateIcon.drawable)
else DrawableIconWithRes(stateIcon.drawable, viewModelState.iconRes)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
index 6d5bf32..d4adcdd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
@@ -22,6 +22,7 @@
import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.asynclayoutinflater.view.AsyncLayoutInflater
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.settingslib.applications.InterestingConfigChanges
import com.android.systemui.Dumpable
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
@@ -36,6 +37,7 @@
import com.android.systemui.qs.dagger.QSSceneComponent
import com.android.systemui.res.R
import com.android.systemui.settings.brightness.MirrorController
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.util.kotlin.sample
@@ -57,7 +59,6 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.withContext
// TODO(307945185) Split View concerns into a ViewBinder
@@ -206,7 +207,7 @@
dumpManager: DumpManager,
@Main private val mainDispatcher: CoroutineDispatcher,
@Application applicationScope: CoroutineScope,
- private val configurationInteractor: ConfigurationInteractor,
+ @ShadeDisplayAware private val configurationInteractor: ConfigurationInteractor,
private val asyncLayoutInflaterFactory: (Context) -> AsyncLayoutInflater,
) : QSContainerController, QSSceneAdapter, Dumpable {
@@ -219,7 +220,7 @@
dumpManager: DumpManager,
@Main dispatcher: CoroutineDispatcher,
@Application scope: CoroutineScope,
- configurationInteractor: ConfigurationInteractor,
+ @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
) : this(
qsSceneComponentFactory,
qsImplProvider,
@@ -256,7 +257,7 @@
.stateIn(
applicationScope,
SharingStarted.WhileSubscribed(),
- customizerState.value.isShowing
+ customizerState.value.isShowing,
)
override val customizerAnimationDuration: StateFlow<Int> =
customizerState
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index f3c6190..1fbe8e2 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -213,7 +213,6 @@
/** Updates the visibility of the scene container. */
private fun hydrateVisibility() {
applicationScope.launch {
- // TODO(b/296114544): Combine with some global hun state to make it visible!
deviceProvisioningInteractor.isDeviceProvisioned
.flatMapLatest { isAllowedToBeVisible ->
if (isAllowedToBeVisible) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt
index 2048b7c..137b4fd 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.screenshot.data.model
import android.app.ActivityTaskManager.RootTaskInfo
+import com.android.systemui.screenshot.policy.childTasksTopDown
/** Information about the tasks on a display. */
data class DisplayContentModel(
@@ -27,3 +28,5 @@
/** A list of root tasks on the display, ordered from top to bottom along the z-axis */
val rootTasks: List<RootTaskInfo>,
)
+
+fun DisplayContentModel.allTasks() = rootTasks.asSequence().flatMap { it.childTasksTopDown() }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt
index 5e2b576..2a4fe3e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt
@@ -16,15 +16,17 @@
package com.android.systemui.screenshot.policy
-import android.content.ComponentName
import android.os.UserHandle
-/** The parameters dictated by a [CapturePolicy], used to adjust alter screenshot request. */
data class CaptureParameters(
- /** How should the content be captured? */
+ /** Describes how the image should be obtained. */
val type: CaptureType,
- /** The focused or top component at the time of the screenshot. */
- val component: ComponentName?,
- /** Which user should receive the screenshot file? */
+ /** Which user to receive the image. */
val owner: UserHandle,
+ /**
+ * The task which represents the main content or focal point of the screenshot. This is the task
+ * used for retrieval of [AssistContent][android.app.assist.AssistContent] as well as
+ * [Scroll Capture][android.view.IWindowManager.requestScrollCapture].
+ */
+ val contentTask: TaskReference,
)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt
index 0fb5366..73ff566 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt
@@ -22,7 +22,7 @@
fun interface CapturePolicy {
/**
* Test the policy against the current display task state. If the policy applies, Returns a
- * [PolicyResult.Matched] containing [CaptureParameters] used to alter the request.
+ * [PolicyResult.Matched] containing [LegacyCaptureParameters] used to alter the request.
*/
suspend fun check(content: DisplayContentModel): PolicyResult
@@ -35,7 +35,7 @@
/** Why the policy matched. */
val reason: String,
/** Details on how to modify the screen capture request. */
- val parameters: CaptureParameters,
+ val parameters: LegacyCaptureParameters,
) : PolicyResult
/** The policy rules do not match the given display content and do not apply. */
@@ -43,7 +43,7 @@
/** The name of the policy rule which matched. */
val policy: String,
/** Why the policy did not match. */
- val reason: String
+ val reason: String,
) : PolicyResult
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt
index 9455201..34c85110 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt
@@ -20,12 +20,10 @@
/** What to capture */
sealed interface CaptureType {
+
/** Capture the entire screen contents. */
data class FullScreen(val displayId: Int) : CaptureType
/** Capture the contents of the task only. */
data class IsolatedTask(val taskId: Int, val taskBounds: Rect?) : CaptureType
-
- data class RootTask(val parentTaskId: Int, val taskBounds: Rect?, val childTaskIds: List<Int>) :
- CaptureType
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/LegacyCaptureParameters.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/LegacyCaptureParameters.kt
new file mode 100644
index 0000000..4b697b2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/LegacyCaptureParameters.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.screenshot.policy
+
+import android.content.ComponentName
+import android.os.UserHandle
+
+/** The parameters dictated by a [CapturePolicy], used to adjust alter screenshot request. */
+data class LegacyCaptureParameters(
+ /** How should the content be captured? */
+ val type: CaptureType,
+ /** The focused or top component at the time of the screenshot. */
+ val component: ComponentName?,
+ /** Which user should receive the screenshot file? */
+ val owner: UserHandle,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
index e840668..a84cdf40 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
@@ -80,6 +80,7 @@
Log.i(TAG, "$result")
return modify(original, result.parameters)
}
+
is NotMatched -> Log.i(TAG, "$result")
}
}
@@ -89,7 +90,45 @@
}
/** Produce a new [ScreenshotData] using [CaptureParameters] */
- suspend fun modify(original: ScreenshotData, updates: CaptureParameters): ScreenshotData {
+ suspend fun modify(original: ScreenshotData, params: CaptureParameters): ScreenshotData {
+ Log.d(TAG, "[modify] CaptureParameters = $params")
+ // Update and apply bitmap capture depending on the parameters.
+ when (val type = params.type) {
+ is IsolatedTask -> {
+ Log.i(TAG, "Capturing task snapshot: $params")
+
+ val taskSnapshot =
+ capture.captureTask(type.taskId) ?: error("Failed to capture task")
+
+ return original.copy(
+ type = TAKE_SCREENSHOT_PROVIDED_IMAGE,
+ bitmap = taskSnapshot,
+ userHandle = params.owner,
+ taskId = params.contentTask.taskId,
+ topComponent = params.contentTask.component,
+ originalScreenBounds = type.taskBounds,
+ )
+ }
+
+ is FullScreen -> {
+ Log.i(TAG, "Capturing screenshot: $params")
+
+ val screenshot =
+ captureDisplay(type.displayId) ?: error("Failed to capture screenshot")
+ return original.copy(
+ type = TAKE_SCREENSHOT_FULLSCREEN,
+ bitmap = screenshot,
+ userHandle = params.owner,
+ topComponent = params.contentTask.component,
+ originalScreenBounds = Rect(0, 0, screenshot.width, screenshot.height),
+ taskId = params.contentTask.taskId,
+ )
+ }
+ }
+ }
+
+ /** Produce a new [ScreenshotData] using [LegacyCaptureParameters] */
+ suspend fun modify(original: ScreenshotData, updates: LegacyCaptureParameters): ScreenshotData {
Log.d(TAG, "[modify] CaptureParameters = $updates")
// Update and apply bitmap capture depending on the parameters.
val updated =
@@ -102,14 +141,7 @@
type.taskId,
type.taskBounds,
)
- is CaptureType.RootTask ->
- replaceWithTaskSnapshot(
- original,
- updates.component,
- updates.owner,
- type.parentTaskId,
- type.taskBounds,
- )
+
is FullScreen ->
replaceWithScreenshot(
original,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt
index 1945c25..3857ba3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt
@@ -31,11 +31,8 @@
*
* Parameters: Capture the whole screen, owned by the private user.
*/
-class PrivateProfilePolicy
-@Inject
-constructor(
- private val profileTypes: ProfileTypeRepository,
-) : CapturePolicy {
+class PrivateProfilePolicy @Inject constructor(private val profileTypes: ProfileTypeRepository) :
+ CapturePolicy {
override suspend fun check(content: DisplayContentModel): PolicyResult {
// The systemUI notification shade isn't a private profile app, skip.
if (content.systemUiState.shadeExpanded) {
@@ -47,25 +44,23 @@
content.rootTasks
.filter { it.isVisible }
.firstNotNullOfOrNull { root ->
- root
- .childTasksTopDown()
- .firstOrNull {
- profileTypes.getProfileType(it.userId) == ProfileType.PRIVATE
- }
- }
- ?: return NotMatched(policy = NAME, reason = NO_VISIBLE_TASKS)
+ root.childTasksTopDown().firstOrNull {
+ profileTypes.getProfileType(it.userId) == ProfileType.PRIVATE
+ }
+ } ?: return NotMatched(policy = NAME, reason = NO_VISIBLE_TASKS)
// If matched, return parameters needed to modify the request.
return Matched(
policy = NAME,
reason = PRIVATE_TASK_VISIBLE,
- CaptureParameters(
+ LegacyCaptureParameters(
type = FullScreen(content.displayId),
component = content.rootTasks.first { it.isVisible }.topActivity,
owner = UserHandle.of(childTask.userId),
- )
+ ),
)
}
+
companion object {
const val NAME = "PrivateProfile"
const val SHADE_EXPANDED = "Notification shade is expanded"
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt
index dd39f92..c43e929 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt
@@ -20,7 +20,7 @@
import com.android.systemui.screenshot.data.model.ChildTaskModel
/** The child tasks of A RootTaskInfo as [ChildTaskModel] in top-down (z-index ascending) order. */
-internal fun RootTaskInfo.childTasksTopDown(): Sequence<ChildTaskModel> {
+fun RootTaskInfo.childTasksTopDown(): Sequence<ChildTaskModel> {
return ((childTaskIds.size - 1) downTo 0).asSequence().map { index ->
ChildTaskModel(
childTaskIds[index],
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicy.kt
index 9967aff..5213579 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicy.kt
@@ -20,18 +20,16 @@
import android.app.WindowConfiguration
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
-import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
import android.content.ComponentName
+import android.graphics.Rect
import android.os.UserHandle
import android.util.Log
import com.android.systemui.screenshot.data.model.DisplayContentModel
-import com.android.systemui.screenshot.data.model.ProfileType
import com.android.systemui.screenshot.data.model.ProfileType.PRIVATE
import com.android.systemui.screenshot.data.model.ProfileType.WORK
import com.android.systemui.screenshot.data.repository.ProfileTypeRepository
import com.android.systemui.screenshot.policy.CaptureType.FullScreen
import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask
-import com.android.systemui.screenshot.policy.CaptureType.RootTask
import javax.inject.Inject
private const val TAG = "ScreenshotPolicy"
@@ -39,7 +37,7 @@
/** Determines what to capture and which user owns the output. */
class ScreenshotPolicy @Inject constructor(private val profileTypes: ProfileTypeRepository) {
/**
- * Apply the policy to the content, resulting in [CaptureParameters].
+ * Apply the policy to the content, resulting in [LegacyCaptureParameters].
*
* @param content the content of the display
* @param defaultComponent the component associated with the screenshot by default
@@ -53,7 +51,7 @@
val defaultFullScreen by lazy {
CaptureParameters(
type = FullScreen(displayId = content.displayId),
- component = defaultComponent,
+ contentTask = TaskReference(-1, defaultComponent, defaultOwner, Rect()),
owner = defaultOwner,
)
}
@@ -70,32 +68,47 @@
} ?: return defaultFullScreen
Log.d(TAG, "topRootTask: $topRootTask")
- val rootTaskOwners = topRootTask.childTaskUserIds.distinct()
- // Special case: Only WORK in top root task which is full-screen or maximized freeform
+ // When:
+ // * there is one or more child task
+ // * all owned by the same user
+ // * this user is a work profile
+ // * the root task is fullscreen or freeform-maximized
+ //
+ // Then:
+ // the result will be a task snapshot instead of a full screen capture. If there is more
+ // than one child task, the root task will be snapshot to include any/all child tasks. This
+ // is intended to cover split-screen mode.
+ val rootTaskOwners = topRootTask.childTaskUserIds.distinct()
if (
rootTaskOwners.size == 1 &&
profileTypes.getProfileType(rootTaskOwners.single()) == WORK &&
(topRootTask.isFullScreen() || topRootTask.isMaximizedFreeform())
) {
+ val topChildTask = topRootTask.childTasksTopDown().first()
+
+ // If there is more than one task, capture the parent to include both.
val type =
if (topRootTask.childTaskCount() > 1) {
- RootTask(
- parentTaskId = topRootTask.taskId,
- taskBounds = topRootTask.bounds,
- childTaskIds = topRootTask.childTasksTopDown().map { it.id }.toList(),
- )
+ IsolatedTask(taskId = topRootTask.taskId, taskBounds = topRootTask.bounds)
} else {
- IsolatedTask(
- taskId = topRootTask.childTasksTopDown().first().id,
- taskBounds = topRootTask.bounds,
- )
+ // Otherwise capture the single task, and use its bounds.
+ IsolatedTask(taskId = topChildTask.id, taskBounds = topChildTask.bounds)
}
- // Capture the RootTask (and all children)
+
+ // The content task (the focus of the screenshot) must represent a single task
+ // containing an activity, so always reference the top child task here. The owner
+ // of the screenshot here is always the same as well.
return CaptureParameters(
type = type,
- component = topRootTask.topActivity,
- owner = UserHandle.of(rootTaskOwners.single()),
+ contentTask =
+ TaskReference(
+ taskId = topChildTask.id,
+ component = topRootTask.topActivity ?: defaultComponent,
+ owner = UserHandle.of(topChildTask.userId),
+ bounds = topChildTask.bounds,
+ ),
+ owner = UserHandle.of(topChildTask.userId),
)
}
@@ -105,26 +118,36 @@
val visibleChildTasks =
content.rootTasks.filter { it.isVisible }.flatMap { it.childTasksTopDown() }
+ // Don't target a PIP window as the screenshot "content", it should only be used
+ // to determine ownership (above).
+ val contentTask =
+ content.rootTasks
+ .filter {
+ it.isVisible && it.windowingMode != WindowConfiguration.WINDOWING_MODE_PINNED
+ }
+ .flatMap { it.childTasksTopDown() }
+ .first()
+
val allVisibleProfileTypes =
visibleChildTasks
.map { it.userId }
.distinct()
.associate { profileTypes.getProfileType(it) to UserHandle.of(it) }
- // If any visible content belongs to the private profile user -> private profile
- // otherwise the personal user (including partial screen work content).
- val ownerHandle =
- allVisibleProfileTypes[PRIVATE]
- ?: allVisibleProfileTypes[ProfileType.NONE]
- ?: defaultOwner
-
- // Attribute to the component of top-most task owned by this user (or fallback to default)
- val topComponent =
- visibleChildTasks.firstOrNull { it.userId == ownerHandle.identifier }?.componentName
+ // If any task is visible and owned by a PRIVATE profile user, the screenshot is assigned
+ // to that user. Work profile has been handled above so it is not considered here. Fallback
+ // to the default user which is the primary "current" user ('aka' personal "profile").
+ val ownerHandle = allVisibleProfileTypes[PRIVATE] ?: defaultOwner
return CaptureParameters(
type = FullScreen(content.displayId),
- component = topComponent ?: topRootTask.topActivity ?: defaultComponent,
+ contentTask =
+ TaskReference(
+ taskId = contentTask.id,
+ component = contentTask.componentName,
+ owner = UserHandle.of(contentTask.userId),
+ bounds = contentTask.bounds,
+ ),
owner = ownerHandle,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/TaskReference.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/TaskReference.kt
new file mode 100644
index 0000000..04f5b1e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/TaskReference.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.screenshot.policy
+
+import android.content.ComponentName
+import android.graphics.Rect
+import android.os.UserHandle
+
+data class TaskReference(
+ /** The id of the task. */
+ val taskId: Int,
+ /** The component name of the task. */
+ val component: ComponentName?,
+ /** The owner of the task. */
+ val owner: UserHandle,
+ /** The bounds of the task. */
+ val bounds: Rect,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
index cf90c0a..109c1cb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
@@ -68,7 +68,7 @@
return PolicyResult.Matched(
policy = NAME,
reason = WORK_TASK_IS_TOP,
- CaptureParameters(
+ LegacyCaptureParameters(
type = IsolatedTask(taskId = childTask.id, taskBounds = childTask.bounds),
component = childTask.componentName ?: rootTask.topActivity,
owner = UserHandle.of(childTask.userId),
diff --git a/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt
deleted file mode 100644
index 6199a83..0000000
--- a/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.settings
-
-import android.content.ContentResolver
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
-import com.android.systemui.shared.settings.data.repository.SecureSettingsRepositoryImpl
-import dagger.Module
-import dagger.Provides
-import kotlinx.coroutines.CoroutineDispatcher
-
-@Module
-object SecureSettingsRepositoryModule {
- @JvmStatic
- @Provides
- @SysUISingleton
- fun provideSecureSettingsRepository(
- contentResolver: ContentResolver,
- @Background backgroundDispatcher: CoroutineDispatcher,
- ): SecureSettingsRepository =
- SecureSettingsRepositoryImpl(contentResolver, backgroundDispatcher)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/SystemSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/settings/SystemSettingsRepositoryModule.kt
deleted file mode 100644
index 02ce74a..0000000
--- a/packages/SystemUI/src/com/android/systemui/settings/SystemSettingsRepositoryModule.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.settings
-
-import android.content.ContentResolver
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.shared.settings.data.repository.SystemSettingsRepository
-import com.android.systemui.shared.settings.data.repository.SystemSettingsRepositoryImpl
-import dagger.Module
-import dagger.Provides
-import kotlinx.coroutines.CoroutineDispatcher
-
-@Module
-object SystemSettingsRepositoryModule {
- @JvmStatic
- @Provides
- @SysUISingleton
- fun provideSystemSettingsRepository(
- contentResolver: ContentResolver,
- @Background backgroundDispatcher: CoroutineDispatcher,
- ): SystemSettingsRepository =
- SystemSettingsRepositoryImpl(contentResolver, backgroundDispatcher)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/settings/UserSettingsRepositoryModule.kt
new file mode 100644
index 0000000..3d7b2ea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserSettingsRepositoryModule.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.settings
+
+import android.content.ContentResolver
+import com.android.systemui.Flags
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepositoryImpl
+import com.android.systemui.shared.settings.data.repository.SystemSettingsRepository
+import com.android.systemui.shared.settings.data.repository.SystemSettingsRepositoryImpl
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SystemSettings
+import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
+import com.android.systemui.util.settings.repository.UserAwareSystemSettingsRepository
+import dagger.Lazy
+import dagger.Module
+import dagger.Provides
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineDispatcher
+
+@Module
+object UserSettingsRepositoryModule {
+ @JvmStatic
+ @Provides
+ @SysUISingleton
+ fun provideSecureSettingsRepository(
+ secureSettings: Lazy<SecureSettings>,
+ userRepository: Lazy<UserRepository>,
+ contentResolver: Lazy<ContentResolver>,
+ @Background backgroundDispatcher: CoroutineDispatcher,
+ @Background backgroundContext: CoroutineContext,
+ ): SecureSettingsRepository {
+ return if (Flags.userAwareSettingsRepositories()) {
+ UserAwareSecureSettingsRepository(
+ secureSettings.get(),
+ userRepository.get(),
+ backgroundDispatcher,
+ backgroundContext,
+ )
+ } else {
+ SecureSettingsRepositoryImpl(contentResolver.get(), backgroundDispatcher)
+ }
+ }
+
+ @JvmStatic
+ @Provides
+ @SysUISingleton
+ fun provideSystemSettingsRepository(
+ systemSettings: Lazy<SystemSettings>,
+ userRepository: Lazy<UserRepository>,
+ contentResolver: Lazy<ContentResolver>,
+ @Background backgroundDispatcher: CoroutineDispatcher,
+ @Background backgroundContext: CoroutineContext,
+ ): SystemSettingsRepository {
+ return if (Flags.userAwareSettingsRepositories()) {
+ UserAwareSystemSettingsRepository(
+ systemSettings.get(),
+ userRepository.get(),
+ backgroundDispatcher,
+ backgroundContext,
+ )
+ } else {
+ SystemSettingsRepositoryImpl(contentResolver.get(), backgroundDispatcher)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
index 6e63446..1776a2c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
@@ -18,14 +18,14 @@
import android.content.Context
import android.view.ViewGroup
-import com.android.systemui.res.R
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.StatusBarState.SHADE
-import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
+import com.android.systemui.res.R
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.END
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.START
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
+import com.android.systemui.statusbar.StatusBarState.SHADE
+import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
import com.android.systemui.unfold.SysUIUnfoldScope
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
import javax.inject.Inject
@@ -39,8 +39,10 @@
progressProvider: NaturalRotationUnfoldProgressProvider,
) {
- private val filterShade: () -> Boolean = { statusBarStateController.getState() == SHADE ||
- statusBarStateController.getState() == SHADE_LOCKED }
+ private val filterShade: () -> Boolean = {
+ statusBarStateController.getState() == SHADE ||
+ statusBarStateController.getState() == SHADE_LOCKED
+ }
private val translateAnimator by lazy {
UnfoldConstantTranslateAnimator(
@@ -48,21 +50,23 @@
setOf(
ViewIdToTranslate(R.id.quick_settings_panel, START, filterShade),
ViewIdToTranslate(R.id.qs_footer_actions, START, filterShade),
- ViewIdToTranslate(R.id.notification_stack_scroller, END, filterShade)),
- progressProvider = progressProvider)
+ ViewIdToTranslate(R.id.notification_stack_scroller, END, filterShade),
+ ),
+ progressProvider = progressProvider,
+ )
}
private val translateAnimatorStatusBar by lazy {
UnfoldConstantTranslateAnimator(
viewsIdToTranslate =
- setOf(
- ViewIdToTranslate(R.id.shade_header_system_icons, END, filterShade),
- ViewIdToTranslate(R.id.privacy_container, END, filterShade),
- ViewIdToTranslate(R.id.carrier_group, END, filterShade),
- ViewIdToTranslate(R.id.clock, START, filterShade),
- ViewIdToTranslate(R.id.date, START, filterShade)
- ),
- progressProvider = progressProvider
+ setOf(
+ ViewIdToTranslate(R.id.shade_header_system_icons, END, filterShade),
+ ViewIdToTranslate(R.id.privacy_container, END, filterShade),
+ ViewIdToTranslate(R.id.carrier_group, END, filterShade),
+ ViewIdToTranslate(R.id.clock, START, filterShade),
+ ViewIdToTranslate(R.id.date, START, filterShade),
+ ),
+ progressProvider = progressProvider,
)
}
@@ -73,10 +77,7 @@
val splitShadeStatusBarViewGroup: ViewGroup? =
root.findViewById(R.id.split_shade_status_bar)
if (splitShadeStatusBarViewGroup != null) {
- translateAnimatorStatusBar.init(
- splitShadeStatusBarViewGroup,
- translationMax
- )
+ translateAnimatorStatusBar.init(splitShadeStatusBarViewGroup, translationMax)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 0e82bf8..c15c8f9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2053,6 +2053,9 @@
}
if (mQsController.getExpanded()) {
mQsController.flingQs(0, FLING_COLLAPSE);
+ } else if (mBarState == KEYGUARD) {
+ mLockscreenShadeTransitionController.goToLockedShade(
+ /* expandedView= */null, /* needsQSAnimation= */false);
} else {
expand(true /* animate */);
}
@@ -3109,7 +3112,7 @@
if (isTracking()) {
onTrackingStopped(true);
}
- if (isExpanded() && !mQsController.getExpanded()) {
+ if (isExpanded() && mBarState != KEYGUARD && !mQsController.getExpanded()) {
mShadeLog.d("Status Bar was long pressed. Expanding to QS.");
expandToQs();
} else {
@@ -5091,13 +5094,6 @@
}
boolean handled = mHeadsUpTouchHelper.onTouchEvent(event);
- if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && mQsController.handleTouch(
- event, isFullyCollapsed(), isShadeOrQsHeightAnimationRunning())) {
- if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
- mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event");
- }
- return true;
- }
// This touch session has already resulted in shade expansion. Ignore everything else.
if (ShadeExpandsOnStatusBarLongPress.isEnabled()
&& event.getActionMasked() != MotionEvent.ACTION_DOWN
@@ -5105,6 +5101,13 @@
mShadeLog.d("Touch has same down time as Status Bar long press. Ignoring.");
return false;
}
+ if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && mQsController.handleTouch(
+ event, isFullyCollapsed(), isShadeOrQsHeightAnimationRunning())) {
+ if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
+ mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event");
+ }
+ return true;
+ }
if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
handled = true;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index be2bf82..f2c3906 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -88,6 +88,7 @@
import java.util.function.Consumer;
import javax.inject.Inject;
+import javax.inject.Provider;
/**
* Controller for {@link NotificationShadeWindowView}.
@@ -193,7 +194,7 @@
PrimaryBouncerInteractor primaryBouncerInteractor,
AlternateBouncerInteractor alternateBouncerInteractor,
BouncerViewBinder bouncerViewBinder,
- @ShadeDisplayAware ConfigurationForwarder configurationForwarder,
+ @ShadeDisplayAware Provider<ConfigurationForwarder> configurationForwarder,
BrightnessMirrorShowingInteractor brightnessMirrorShowingInteractor) {
mLockscreenShadeTransitionController = transitionController;
mFalsingCollector = falsingCollector;
@@ -257,7 +258,7 @@
}
if (ShadeWindowGoesAround.isEnabled()) {
- mView.setConfigurationForwarder(configurationForwarder);
+ mView.setConfigurationForwarder(configurationForwarder.get());
}
dumpManager.registerDumpable(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
index 51f1f81..42d4eff 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
@@ -20,7 +20,13 @@
import android.content.res.Resources
import android.view.LayoutInflater
import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
+import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.common.ui.ConfigurationStateImpl
import com.android.systemui.common.ui.GlobalConfig
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.res.R
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
@@ -79,12 +85,12 @@
fun provideShadeWindowConfigurationController(
@ShadeDisplayAware shadeContext: Context,
factory: ConfigurationControllerImpl.Factory,
- @GlobalConfig globalConfigConfigController: ConfigurationController,
+ @GlobalConfig globalConfigController: ConfigurationController,
): ConfigurationController {
return if (ShadeWindowGoesAround.isEnabled) {
factory.create(shadeContext)
} else {
- globalConfigConfigController
+ globalConfigController
}
}
@@ -92,13 +98,55 @@
@ShadeDisplayAware
@SysUISingleton
fun provideShadeWindowConfigurationForwarder(
- @ShadeDisplayAware shadeConfigurationController: ConfigurationController,
- @GlobalConfig globalConfigController: ConfigurationController,
+ @ShadeDisplayAware shadeConfigurationController: ConfigurationController
): ConfigurationForwarder {
+ ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
+ return shadeConfigurationController
+ }
+
+ @SysUISingleton
+ @Provides
+ @ShadeDisplayAware
+ fun provideShadeDisplayAwareConfigurationState(
+ factory: ConfigurationStateImpl.Factory,
+ @ShadeDisplayAware configurationController: ConfigurationController,
+ @ShadeDisplayAware context: Context,
+ @GlobalConfig configurationState: ConfigurationState,
+ ): ConfigurationState {
return if (ShadeWindowGoesAround.isEnabled) {
- shadeConfigurationController
+ factory.create(context, configurationController)
} else {
- globalConfigController
+ configurationState
+ }
+ }
+
+ @SysUISingleton
+ @Provides
+ @ShadeDisplayAware
+ fun provideShadeDisplayAwareConfigurationRepository(
+ factory: ConfigurationRepositoryImpl.Factory,
+ @ShadeDisplayAware configurationController: ConfigurationController,
+ @ShadeDisplayAware context: Context,
+ @GlobalConfig globalConfigurationRepository: ConfigurationRepository,
+ ): ConfigurationRepository {
+ return if (ShadeWindowGoesAround.isEnabled) {
+ factory.create(context, configurationController)
+ } else {
+ globalConfigurationRepository
+ }
+ }
+
+ @SysUISingleton
+ @Provides
+ @ShadeDisplayAware
+ fun provideShadeAwareConfigurationInteractor(
+ @ShadeDisplayAware configurationRepository: ConfigurationRepository,
+ @GlobalConfig configurationInteractor: ConfigurationInteractor,
+ ): ConfigurationInteractor {
+ return if (ShadeWindowGoesAround.isEnabled) {
+ ConfigurationInteractorImpl(configurationRepository)
+ } else {
+ configurationInteractor
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index 72a4650..2348a11 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -49,10 +49,7 @@
import javax.inject.Provider
/** Module for classes related to the notification shade. */
-@Module(
- includes =
- [StartShadeModule::class, ShadeViewProviderModule::class]
-)
+@Module(includes = [StartShadeModule::class, ShadeViewProviderModule::class])
abstract class ShadeModule {
companion object {
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt b/packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt
rename to packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt
index 6fb3ca5..ae36e81 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt
@@ -25,7 +25,7 @@
/** Accepts touch events, detects long press, and calls ShadeViewController#onStatusBarLongPress. */
@SysUISingleton
-class LongPressGestureDetector
+class StatusBarLongPressGestureDetector
@Inject
constructor(context: Context, val shadeViewController: ShadeViewController) {
val gestureDetector =
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index 5629938..ef62d2d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -15,9 +15,7 @@
*/
package com.android.systemui.shade.data.repository
-import android.content.Context
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -182,8 +180,7 @@
/** Business logic for shade interactions */
@SysUISingleton
-class ShadeRepositoryImpl @Inject constructor() :
- ShadeRepository {
+class ShadeRepositoryImpl @Inject constructor() : ShadeRepository {
private val _qsExpansion = MutableStateFlow(0f)
@Deprecated("Use ShadeInteractor.qsExpansion instead")
override val qsExpansion: StateFlow<Float> = _qsExpansion.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
index e5d08a0..44f2911 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
@@ -17,6 +17,7 @@
package com.android.systemui.shade.domain.startable
import android.content.Context
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.CoreStartable
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
@@ -42,7 +43,6 @@
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onStart
-import com.android.app.tracing.coroutines.launchTraced as launch
@SysUISingleton
class ShadeStartable
@@ -51,7 +51,7 @@
@Application private val applicationScope: CoroutineScope,
@ShadeDisplayAware private val context: Context,
@ShadeTouchLog private val touchLog: LogBuffer,
- private val configurationRepository: ConfigurationRepository,
+ @ShadeDisplayAware private val configurationRepository: ConfigurationRepository,
private val shadeRepository: ShadeRepository,
private val splitShadeStateController: SplitShadeStateController,
private val scrimShadeTransitionController: ScrimShadeTransitionController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
index e115922..9b3513e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
@@ -17,11 +17,13 @@
package com.android.systemui.statusbar.core
import android.view.Display
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayScopeRepository
+import com.android.systemui.statusbar.data.repository.PrivacyDotWindowControllerStore
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStore
@@ -29,7 +31,6 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.onStart
-import com.android.app.tracing.coroutines.launchTraced as launch
/**
* Responsible for creating and starting the status bar components for each display. Also does it
@@ -48,6 +49,7 @@
private val initializerStore: StatusBarInitializerStore,
private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
private val statusBarInitializerStore: StatusBarInitializerStore,
+ private val privacyDotWindowControllerStore: PrivacyDotWindowControllerStore,
) : CoreStartable {
init {
@@ -71,6 +73,7 @@
val displayId = display.displayId
createAndStartOrchestratorForDisplay(displayId)
createAndStartInitializerForDisplay(displayId)
+ startPrivacyDotForDisplay(displayId)
}
private fun createAndStartOrchestratorForDisplay(displayId: Int) {
@@ -89,4 +92,12 @@
private fun createAndStartInitializerForDisplay(displayId: Int) {
statusBarInitializerStore.forDisplay(displayId).start()
}
+
+ private fun startPrivacyDotForDisplay(displayId: Int) {
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ // For the default display, privacy dot is started via ScreenDecorations
+ return
+ }
+ privacyDotWindowControllerStore.forDisplay(displayId).start()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
index 3abbc6e..f441fd6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
@@ -23,6 +23,7 @@
import com.android.systemui.res.R
import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener
import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewUpdatedListener
+import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository
import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions
import com.android.systemui.statusbar.phone.PhoneStatusBarView
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
@@ -71,7 +72,10 @@
}
interface Factory {
- fun create(statusBarWindowController: StatusBarWindowController): StatusBarInitializer
+ fun create(
+ statusBarWindowController: StatusBarWindowController,
+ statusBarModePerDisplayRepository: StatusBarModePerDisplayRepository,
+ ): StatusBarInitializer
}
}
@@ -79,6 +83,7 @@
@AssistedInject
constructor(
@Assisted private val statusBarWindowController: StatusBarWindowController,
+ @Assisted private val statusBarModePerDisplayRepository: StatusBarModePerDisplayRepository,
private val collapsedStatusBarFragmentProvider: Provider<CollapsedStatusBarFragment>,
private val statusBarRootFactory: StatusBarRootFactory,
private val componentFactory: HomeStatusBarComponent.Factory,
@@ -127,7 +132,7 @@
val phoneStatusBarView = cv.findViewById<PhoneStatusBarView>(R.id.status_bar)
component =
componentFactory.create(phoneStatusBarView).also { component ->
- // CollapsedStatusBarFragment used to be responsible initializting
+ // CollapsedStatusBarFragment used to be responsible initializing
component.init()
statusBarViewUpdatedListener?.onStatusBarViewUpdated(
@@ -135,8 +140,12 @@
component.phoneStatusBarTransitions,
)
- creationListeners.forEach { listener ->
- listener.onStatusBarViewInitialized(component)
+ if (StatusBarConnectedDisplays.isEnabled) {
+ statusBarModePerDisplayRepository.onStatusBarViewInitialized(component)
+ } else {
+ creationListeners.forEach { listener ->
+ listener.onStatusBarViewInitialized(component)
+ }
}
}
}
@@ -184,7 +193,8 @@
@AssistedFactory
interface Factory : StatusBarInitializer.Factory {
override fun create(
- statusBarWindowController: StatusBarWindowController
+ statusBarWindowController: StatusBarWindowController,
+ statusBarModePerDisplayRepository: StatusBarModePerDisplayRepository,
): StatusBarInitializerImpl
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt
index 041f0b0..4f815c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt
@@ -22,6 +22,7 @@
import com.android.systemui.display.data.repository.PerDisplayStore
import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.display.data.repository.SingleDisplayStore
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -37,6 +38,7 @@
displayRepository: DisplayRepository,
private val factory: StatusBarInitializer.Factory,
private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
+ private val statusBarModeRepositoryStore: StatusBarModeRepositoryStore,
) :
StatusBarInitializerStore,
PerDisplayStoreImpl<StatusBarInitializer>(backgroundApplicationScope, displayRepository) {
@@ -47,7 +49,8 @@
override fun createInstanceForDisplay(displayId: Int): StatusBarInitializer {
return factory.create(
- statusBarWindowController = statusBarWindowControllerStore.forDisplay(displayId)
+ statusBarWindowController = statusBarWindowControllerStore.forDisplay(displayId),
+ statusBarModePerDisplayRepository = statusBarModeRepositoryStore.forDisplay(displayId),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index f65ae67..4341200 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -26,6 +26,7 @@
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.data.StatusBarDataLayerModule
+import com.android.systemui.statusbar.data.repository.LightBarControllerStore
import com.android.systemui.statusbar.phone.LightBarController
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProviderImpl
@@ -55,26 +56,26 @@
* [com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule], etc.).
*/
@Module(includes = [StatusBarDataLayerModule::class, SystemBarUtilsProxyImpl.Module::class])
-abstract class StatusBarModule {
+interface StatusBarModule {
@Binds
@IntoMap
@ClassKey(OngoingCallController::class)
- abstract fun bindOngoingCallController(impl: OngoingCallController): CoreStartable
+ fun bindOngoingCallController(impl: OngoingCallController): CoreStartable
@Binds
@IntoMap
@ClassKey(LightBarController::class)
- abstract fun bindLightBarController(impl: LightBarController): CoreStartable
+ fun lightBarControllerAsCoreStartable(controller: LightBarController): CoreStartable
@Binds
@IntoMap
@ClassKey(StatusBarSignalPolicy::class)
- abstract fun bindStatusBarSignalPolicy(impl: StatusBarSignalPolicy): CoreStartable
+ fun bindStatusBarSignalPolicy(impl: StatusBarSignalPolicy): CoreStartable
@Binds
@SysUISingleton
- abstract fun statusBarWindowControllerFactory(
+ fun statusBarWindowControllerFactory(
implFactory: StatusBarWindowControllerImpl.Factory
): StatusBarWindowController.Factory
@@ -82,6 +83,12 @@
@Provides
@SysUISingleton
+ fun lightBarController(store: LightBarControllerStore): LightBarController {
+ return store.defaultDisplay
+ }
+
+ @Provides
+ @SysUISingleton
fun windowControllerStore(
multiDisplayImplLazy: Lazy<MultiDisplayStatusBarWindowControllerStore>,
singleDisplayImplLazy: Lazy<SingleDisplayStatusBarWindowControllerStore>,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
index c416bf7..39de28e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
@@ -16,10 +16,12 @@
package com.android.systemui.statusbar.data
import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepositoryModule
+import com.android.systemui.statusbar.data.repository.LightBarControllerStoreModule
import com.android.systemui.statusbar.data.repository.RemoteInputRepositoryModule
import com.android.systemui.statusbar.data.repository.StatusBarConfigurationControllerModule
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStoreModule
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryModule
+import com.android.systemui.statusbar.data.repository.SystemEventChipAnimationControllerStoreModule
import com.android.systemui.statusbar.phone.data.StatusBarPhoneDataLayerModule
import dagger.Module
@@ -27,11 +29,13 @@
includes =
[
KeyguardStatusBarRepositoryModule::class,
+ LightBarControllerStoreModule::class,
RemoteInputRepositoryModule::class,
StatusBarConfigurationControllerModule::class,
StatusBarContentInsetsProviderStoreModule::class,
StatusBarModeRepositoryModule::class,
StatusBarPhoneDataLayerModule::class,
+ SystemEventChipAnimationControllerStoreModule::class,
]
)
object StatusBarDataLayerModule
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt
new file mode 100644
index 0000000..ff50e31
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.data.repository
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.DisplayScopeRepository
+import com.android.systemui.display.data.repository.PerDisplayStore
+import com.android.systemui.display.data.repository.PerDisplayStoreImpl
+import com.android.systemui.statusbar.phone.LightBarController
+import com.android.systemui.statusbar.phone.LightBarControllerImpl
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+
+/** Provides per display instances of [LightBarController]. */
+interface LightBarControllerStore : PerDisplayStore<LightBarController>
+
+@SysUISingleton
+class LightBarControllerStoreImpl
+@Inject
+constructor(
+ @Background backgroundApplicationScope: CoroutineScope,
+ displayRepository: DisplayRepository,
+ private val factory: LightBarControllerImpl.Factory,
+ private val displayScopeRepository: DisplayScopeRepository,
+ private val statusBarModeRepositoryStore: StatusBarModeRepositoryStore,
+) :
+ LightBarControllerStore,
+ PerDisplayStoreImpl<LightBarController>(backgroundApplicationScope, displayRepository) {
+
+ override fun createInstanceForDisplay(displayId: Int): LightBarController {
+ return factory
+ .create(
+ displayId,
+ displayScopeRepository.scopeForDisplay(displayId),
+ statusBarModeRepositoryStore.forDisplay(displayId),
+ )
+ .also { it.start() }
+ }
+
+ override suspend fun onDisplayRemovalAction(instance: LightBarController) {
+ instance.stop()
+ }
+
+ override val instanceClass = LightBarController::class.java
+}
+
+@Module
+interface LightBarControllerStoreModule {
+
+ @Binds fun store(impl: LightBarControllerStoreImpl): LightBarControllerStore
+
+ @Binds
+ @IntoMap
+ @ClassKey(LightBarControllerStore::class)
+ fun storeAsCoreStartable(impl: LightBarControllerStoreImpl): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt
new file mode 100644
index 0000000..a1f5655
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.data.repository
+
+import android.view.Display
+import android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
+import com.android.systemui.display.data.repository.PerDisplayStore
+import com.android.systemui.display.data.repository.PerDisplayStoreImpl
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.events.PrivacyDotWindowController
+import dagger.Binds
+import dagger.Lazy
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+
+/** Providers per display instances of [PrivacyDotWindowController]. */
+interface PrivacyDotWindowControllerStore : PerDisplayStore<PrivacyDotWindowController>
+
+@SysUISingleton
+class PrivacyDotWindowControllerStoreImpl
+@Inject
+constructor(
+ @Background backgroundApplicationScope: CoroutineScope,
+ displayRepository: DisplayRepository,
+ private val windowControllerFactory: PrivacyDotWindowController.Factory,
+ private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository,
+ private val privacyDotViewControllerStore: PrivacyDotViewControllerStore,
+ private val viewCaptureAwareWindowManagerFactory: ViewCaptureAwareWindowManager.Factory,
+) :
+ PrivacyDotWindowControllerStore,
+ PerDisplayStoreImpl<PrivacyDotWindowController>(backgroundApplicationScope, displayRepository) {
+
+ init {
+ StatusBarConnectedDisplays.assertInNewMode()
+ }
+
+ override fun createInstanceForDisplay(displayId: Int): PrivacyDotWindowController {
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ throw IllegalArgumentException("This class should only be used for connected displays")
+ }
+ val displayWindowProperties =
+ displayWindowPropertiesRepository.get(displayId, TYPE_NAVIGATION_BAR_PANEL)
+ return windowControllerFactory.create(
+ displayId = displayId,
+ privacyDotViewController = privacyDotViewControllerStore.forDisplay(displayId),
+ viewCaptureAwareWindowManager =
+ viewCaptureAwareWindowManagerFactory.create(displayWindowProperties.windowManager),
+ inflater = displayWindowProperties.layoutInflater,
+ )
+ }
+
+ override val instanceClass = PrivacyDotWindowController::class.java
+}
+
+@Module
+interface PrivacyDotWindowControllerStoreModule {
+
+ @Binds fun store(impl: PrivacyDotWindowControllerStoreImpl): PrivacyDotWindowControllerStore
+
+ companion object {
+ @Provides
+ @SysUISingleton
+ @IntoMap
+ @ClassKey(PrivacyDotWindowControllerStore::class)
+ fun storeAsCoreStartable(
+ storeLazy: Lazy<PrivacyDotWindowControllerStoreImpl>
+ ): CoreStartable {
+ return if (StatusBarConnectedDisplays.isEnabled) {
+ storeLazy.get()
+ } else {
+ CoreStartable.NOP
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
index 44bee1d..cc91e2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
@@ -27,7 +27,7 @@
import android.view.WindowInsetsController.Appearance
import com.android.internal.statusbar.LetterboxDetails
import com.android.internal.view.AppearanceRegion
-import com.android.systemui.Dumpable
+import com.android.systemui.CoreStartable
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener
@@ -59,7 +59,7 @@
* Note: These status bar modes are status bar *window* states that are sent to us from
* WindowManager, not determined internally.
*/
-interface StatusBarModePerDisplayRepository {
+interface StatusBarModePerDisplayRepository : OnStatusBarViewInitializedListener, CoreStartable {
/**
* True if the status bar window is showing transiently and will disappear soon, and false
* otherwise. ("Otherwise" in this case means the status bar is persistently hidden OR
@@ -104,6 +104,12 @@
* determined internally instead.
*/
fun clearTransient()
+
+ /**
+ * Called when the [StatusBarModePerDisplayRepository] should stop doing any work and clean up
+ * if needed.
+ */
+ fun stop()
}
class StatusBarModePerDisplayRepositoryImpl
@@ -114,7 +120,7 @@
private val commandQueue: CommandQueue,
private val letterboxAppearanceCalculator: LetterboxAppearanceCalculator,
ongoingCallRepository: OngoingCallRepository,
-) : StatusBarModePerDisplayRepository, OnStatusBarViewInitializedListener, Dumpable {
+) : StatusBarModePerDisplayRepository {
private val commandQueueCallback =
object : CommandQueue.Callbacks {
@@ -163,10 +169,14 @@
}
}
- fun start() {
+ override fun start() {
commandQueue.addCallback(commandQueueCallback)
}
+ override fun stop() {
+ commandQueue.removeCallback(commandQueueCallback)
+ }
+
private val _isTransientShown = MutableStateFlow(false)
override val isTransientShown: StateFlow<Boolean> = _isTransientShown.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
index 2c9fa25..143e998 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
@@ -18,21 +18,54 @@
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.DisplayId
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.PerDisplayStore
+import com.android.systemui.display.data.repository.PerDisplayStoreImpl
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.core.StatusBarInitializer
import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent
import dagger.Binds
+import dagger.Lazy
import dagger.Module
+import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
import dagger.multibindings.IntoSet
import java.io.PrintWriter
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
-interface StatusBarModeRepositoryStore {
- val defaultDisplay: StatusBarModePerDisplayRepository
+interface StatusBarModeRepositoryStore : PerDisplayStore<StatusBarModePerDisplayRepository>
- fun forDisplay(displayId: Int): StatusBarModePerDisplayRepository
+@SysUISingleton
+class MultiDisplayStatusBarModeRepositoryStore
+@Inject
+constructor(
+ @Background backgroundApplicationScope: CoroutineScope,
+ private val factory: StatusBarModePerDisplayRepositoryFactory,
+ displayRepository: DisplayRepository,
+) :
+ StatusBarModeRepositoryStore,
+ PerDisplayStoreImpl<StatusBarModePerDisplayRepository>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
+
+ init {
+ StatusBarConnectedDisplays.assertInNewMode()
+ }
+
+ override fun createInstanceForDisplay(displayId: Int): StatusBarModePerDisplayRepository {
+ return factory.create(displayId).also { it.start() }
+ }
+
+ override suspend fun onDisplayRemovalAction(instance: StatusBarModePerDisplayRepository) {
+ instance.stop()
+ }
+
+ override val instanceClass = StatusBarModePerDisplayRepository::class.java
}
@SysUISingleton
@@ -47,10 +80,7 @@
StatusBarInitializer.OnStatusBarViewInitializedListener {
override val defaultDisplay = factory.create(displayId)
- override fun forDisplay(displayId: Int) =
- // TODO(b/369337087): implement per display status bar modes.
- // For now just use default display instance.
- defaultDisplay
+ override fun forDisplay(displayId: Int) = defaultDisplay
override fun start() {
defaultDisplay.start()
@@ -66,17 +96,40 @@
}
@Module
-interface StatusBarModeRepositoryModule {
- @Binds fun bindImpl(impl: StatusBarModeRepositoryImpl): StatusBarModeRepositoryStore
-
- @Binds
- @IntoMap
- @ClassKey(StatusBarModeRepositoryStore::class)
- fun bindCoreStartable(impl: StatusBarModeRepositoryImpl): CoreStartable
-
+abstract class StatusBarModeRepositoryModule {
@Binds
@IntoSet
- fun bindViewInitListener(
+ abstract fun bindViewInitListener(
impl: StatusBarModeRepositoryImpl
): StatusBarInitializer.OnStatusBarViewInitializedListener
+
+ companion object {
+ @Provides
+ @SysUISingleton
+ @IntoMap
+ @ClassKey(StatusBarModeRepositoryStore::class)
+ fun storeAsCoreStartable(
+ singleDisplayLazy: Lazy<StatusBarModeRepositoryImpl>,
+ multiDisplayLazy: Lazy<MultiDisplayStatusBarModeRepositoryStore>,
+ ): CoreStartable {
+ return if (StatusBarConnectedDisplays.isEnabled) {
+ multiDisplayLazy.get()
+ } else {
+ singleDisplayLazy.get()
+ }
+ }
+
+ @Provides
+ @SysUISingleton
+ fun store(
+ singleDisplayLazy: Lazy<StatusBarModeRepositoryImpl>,
+ multiDisplayLazy: Lazy<MultiDisplayStatusBarModeRepositoryStore>,
+ ): StatusBarModeRepositoryStore {
+ return if (StatusBarConnectedDisplays.isEnabled) {
+ multiDisplayLazy.get()
+ } else {
+ singleDisplayLazy.get()
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt
new file mode 100644
index 0000000..7760f58
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt
@@ -0,0 +1,104 @@
+/*
+ * 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.data.repository
+
+import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
+import com.android.systemui.display.data.repository.PerDisplayStore
+import com.android.systemui.display.data.repository.PerDisplayStoreImpl
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.events.SystemEventChipAnimationController
+import com.android.systemui.statusbar.events.SystemEventChipAnimationControllerImpl
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
+import dagger.Binds
+import dagger.Lazy
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+
+/** Provides per display instances of [SystemEventChipAnimationController]. */
+interface SystemEventChipAnimationControllerStore :
+ PerDisplayStore<SystemEventChipAnimationController>
+
+@SysUISingleton
+class SystemEventChipAnimationControllerStoreImpl
+@Inject
+constructor(
+ @Background backgroundApplicationScope: CoroutineScope,
+ displayRepository: DisplayRepository,
+ private val factory: SystemEventChipAnimationControllerImpl.Factory,
+ private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository,
+ private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
+ private val statusBarContentInsetsProviderStore: StatusBarContentInsetsProviderStore,
+) :
+ SystemEventChipAnimationControllerStore,
+ PerDisplayStoreImpl<SystemEventChipAnimationController>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
+
+ init {
+ StatusBarConnectedDisplays.assertInNewMode()
+ }
+
+ override fun createInstanceForDisplay(displayId: Int): SystemEventChipAnimationController {
+ return factory.create(
+ displayWindowPropertiesRepository.get(displayId, TYPE_STATUS_BAR).context,
+ statusBarWindowControllerStore.forDisplay(displayId),
+ statusBarContentInsetsProviderStore.forDisplay(displayId),
+ )
+ }
+
+ override suspend fun onDisplayRemovalAction(instance: SystemEventChipAnimationController) {
+ instance.stop()
+ }
+
+ override val instanceClass = SystemEventChipAnimationController::class.java
+}
+
+@Module
+interface SystemEventChipAnimationControllerStoreModule {
+
+ @Binds
+ @SysUISingleton
+ fun store(
+ impl: SystemEventChipAnimationControllerStoreImpl
+ ): SystemEventChipAnimationControllerStore
+
+ companion object {
+ @Provides
+ @SysUISingleton
+ @IntoMap
+ @ClassKey(SystemEventChipAnimationControllerStore::class)
+ fun storeAsCoreStartable(
+ implLazy: Lazy<SystemEventChipAnimationControllerStoreImpl>
+ ): CoreStartable {
+ return if (StatusBarConnectedDisplays.isEnabled) {
+ implLazy.get()
+ } else {
+ CoreStartable.NOP
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationController.kt
new file mode 100644
index 0000000..f2bb7b1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationController.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.events
+
+import androidx.core.animation.Animator
+import androidx.core.animation.AnimatorSet
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.data.repository.SystemEventChipAnimationControllerStore
+import javax.inject.Inject
+
+/**
+ * A [SystemEventChipAnimationController] that handles animations for multiple displays. It
+ * delegates the animation tasks to individual controllers for each display.
+ */
+@SysUISingleton
+class MultiDisplaySystemEventChipAnimationController
+@Inject
+constructor(
+ private val displayRepository: DisplayRepository,
+ private val controllerStore: SystemEventChipAnimationControllerStore,
+) : SystemEventChipAnimationController {
+
+ init {
+ StatusBarConnectedDisplays.assertInNewMode()
+ }
+
+ override fun prepareChipAnimation(viewCreator: ViewCreator) {
+ forEachController { it.prepareChipAnimation(viewCreator) }
+ }
+
+ override fun init() {
+ forEachController { it.init() }
+ }
+
+ override fun stop() {
+ forEachController { it.stop() }
+ }
+
+ override fun announceForAccessibility(contentDescriptions: String) {
+ forEachController { it.announceForAccessibility(contentDescriptions) }
+ }
+
+ override fun onSystemEventAnimationBegin(): Animator {
+ val animators = controllersForAllDisplays().map { it.onSystemEventAnimationBegin() }
+ return AnimatorSet().apply { playTogether(animators) }
+ }
+
+ override fun onSystemEventAnimationFinish(hasPersistentDot: Boolean): Animator {
+ val animators =
+ controllersForAllDisplays().map { it.onSystemEventAnimationFinish(hasPersistentDot) }
+ return AnimatorSet().apply { playTogether(animators) }
+ }
+
+ private fun forEachController(consumer: (SystemEventChipAnimationController) -> Unit) {
+ controllersForAllDisplays().forEach { consumer(it) }
+ }
+
+ private fun controllersForAllDisplays() =
+ displayRepository.displays.value.map { controllerStore.forDisplay(it.displayId) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotWindowController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotWindowController.kt
new file mode 100644
index 0000000..9928ac6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotWindowController.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.
+ */
+
+package com.android.systemui.statusbar.events
+
+import android.view.Display
+import android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM
+import android.view.DisplayCutout.BOUNDS_POSITION_LEFT
+import android.view.DisplayCutout.BOUNDS_POSITION_RIGHT
+import android.view.DisplayCutout.BOUNDS_POSITION_TOP
+import android.view.LayoutInflater
+import android.view.View
+import android.view.WindowManager.LayoutParams.WRAP_CONTENT
+import android.widget.FrameLayout
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
+import com.android.systemui.ScreenDecorations
+import com.android.systemui.ScreenDecorationsThread
+import com.android.systemui.decor.DecorProvider
+import com.android.systemui.decor.PrivacyDotCornerDecorProviderImpl
+import com.android.systemui.decor.PrivacyDotDecorProviderFactory
+import com.android.systemui.statusbar.events.PrivacyDotCorner.BottomLeft
+import com.android.systemui.statusbar.events.PrivacyDotCorner.BottomRight
+import com.android.systemui.statusbar.events.PrivacyDotCorner.TopLeft
+import com.android.systemui.statusbar.events.PrivacyDotCorner.TopRight
+import com.android.systemui.util.containsExactly
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.concurrent.Executor
+
+/**
+ * Responsible for adding the privacy dot to a window.
+ *
+ * It will create one window per corner (top left, top right, bottom left, bottom right), which are
+ * used dependant on the display's rotation.
+ */
+class PrivacyDotWindowController
+@AssistedInject
+constructor(
+ @Assisted private val displayId: Int,
+ @Assisted private val privacyDotViewController: PrivacyDotViewController,
+ @Assisted private val viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
+ @Assisted private val inflater: LayoutInflater,
+ @ScreenDecorationsThread private val uiExecutor: Executor,
+ private val dotFactory: PrivacyDotDecorProviderFactory,
+) {
+
+ fun start() {
+ uiExecutor.execute { startOnUiThread() }
+ }
+
+ private fun startOnUiThread() {
+ val providers = dotFactory.providers
+
+ val topLeft = providers.inflate(BOUNDS_POSITION_TOP, BOUNDS_POSITION_LEFT)
+ val topRight = providers.inflate(BOUNDS_POSITION_TOP, BOUNDS_POSITION_RIGHT)
+ val bottomLeft = providers.inflate(BOUNDS_POSITION_BOTTOM, BOUNDS_POSITION_LEFT)
+ val bottomRight = providers.inflate(BOUNDS_POSITION_BOTTOM, BOUNDS_POSITION_RIGHT)
+
+ topLeft.addToWindow(TopLeft)
+ topRight.addToWindow(TopRight)
+ bottomLeft.addToWindow(BottomLeft)
+ bottomRight.addToWindow(BottomRight)
+
+ privacyDotViewController.initialize(topLeft, topRight, bottomLeft, bottomRight)
+ }
+
+ private fun List<DecorProvider>.inflate(alignedBound1: Int, alignedBound2: Int): View {
+ val provider =
+ first { it.alignedBounds.containsExactly(alignedBound1, alignedBound2) }
+ as PrivacyDotCornerDecorProviderImpl
+ return inflater.inflate(/* resource= */ provider.layoutId, /* root= */ null)
+ }
+
+ private fun View.addToWindow(corner: PrivacyDotCorner) {
+ val excludeFromScreenshots = displayId == Display.DEFAULT_DISPLAY
+ val params =
+ ScreenDecorations.getWindowLayoutBaseParams(excludeFromScreenshots).apply {
+ width = WRAP_CONTENT
+ height = WRAP_CONTENT
+ gravity = corner.rotatedCorner(context.display.rotation).gravity
+ title = "PrivacyDot${corner.title}$displayId"
+ }
+ // PrivacyDotViewController expects the dot view to have a FrameLayout parent.
+ val rootView = FrameLayout(context)
+ rootView.addView(this)
+ viewCaptureAwareWindowManager.addView(rootView, params)
+ }
+
+ @AssistedFactory
+ fun interface Factory {
+ fun create(
+ displayId: Int,
+ privacyDotViewController: PrivacyDotViewController,
+ viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
+ inflater: LayoutInflater,
+ ): PrivacyDotWindowController
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
index b286605..1038ad4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
@@ -32,13 +32,16 @@
import androidx.core.animation.ValueAnimator
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Default
import com.android.systemui.res.R
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
import com.android.systemui.statusbar.phone.StatusBarContentInsetsChangedListener
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
import com.android.systemui.util.animation.AnimationUtil.Companion.frames
+import dagger.Lazy
import dagger.Module
import dagger.Provides
import dagger.assisted.Assisted
@@ -57,6 +60,8 @@
fun init()
+ fun stop()
+
/** Announces [contentDescriptions] for accessibility. */
fun announceForAccessibility(contentDescriptions: String)
@@ -287,6 +292,26 @@
return animSet
}
+ private val statusBarContentInsetsChangedListener =
+ object : StatusBarContentInsetsChangedListener {
+ override fun onStatusBarContentInsetsChanged() {
+ val newContentArea =
+ contentInsetsProvider.getStatusBarContentAreaForCurrentRotation()
+ updateDimens(newContentArea)
+
+ // If we are currently animating, we have to re-solve for the chip bounds. If
+ // we're not animating then [prepareChipAnimation] will take care of it for us.
+ currentAnimatedView?.let {
+ updateChipBounds(it, newContentArea)
+ // Since updateCurrentAnimatedView can only be called during an animation,
+ // we have to create a no-op animator here to apply the new chip bounds.
+ val animator = ValueAnimator.ofInt(0, 1).setDuration(0)
+ animator.addUpdateListener { updateCurrentAnimatedView() }
+ animator.start()
+ }
+ }
+ }
+
override fun init() {
initialized = true
themedContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
@@ -303,28 +328,11 @@
// Use contentInsetsProvider rather than configuration controller, since we only care
// about status bar dimens
- contentInsetsProvider.addCallback(
- object : StatusBarContentInsetsChangedListener {
- override fun onStatusBarContentInsetsChanged() {
- val newContentArea =
- contentInsetsProvider.getStatusBarContentAreaForCurrentRotation()
- updateDimens(newContentArea)
+ contentInsetsProvider.addCallback(statusBarContentInsetsChangedListener)
+ }
- // If we are currently animating, we have to re-solve for the chip bounds. If
- // we're
- // not animating then [prepareChipAnimation] will take care of it for us
- currentAnimatedView?.let {
- updateChipBounds(it, newContentArea)
- // Since updateCurrentAnimatedView can only be called during an animation,
- // we
- // have to create a dummy animator here to apply the new chip bounds
- val animator = ValueAnimator.ofInt(0, 1).setDuration(0)
- animator.addUpdateListener { updateCurrentAnimatedView() }
- animator.start()
- }
- }
- }
- )
+ override fun stop() {
+ contentInsetsProvider.removeCallback(statusBarContentInsetsChangedListener)
}
override fun announceForAccessibility(contentDescriptions: String) {
@@ -418,7 +426,7 @@
}
@AssistedFactory
- interface Factory {
+ fun interface Factory {
fun create(
context: Context,
statusBarWindowController: StatusBarWindowController,
@@ -446,20 +454,36 @@
private const val RIGHT = 2
@Module
-object SystemEventChipAnimationControllerModule {
+interface SystemEventChipAnimationControllerModule {
- @Provides
- @SysUISingleton
- fun controller(
- factory: SystemEventChipAnimationControllerImpl.Factory,
- context: Context,
- statusBarWindowControllerStore: StatusBarWindowControllerStore,
- contentInsetsProviderStore: StatusBarContentInsetsProviderStore,
- ): SystemEventChipAnimationController {
- return factory.create(
- context,
- statusBarWindowControllerStore.defaultDisplay,
- contentInsetsProviderStore.defaultDisplay,
- )
+ companion object {
+ @Provides
+ @Default
+ @SysUISingleton
+ fun defaultController(
+ factory: SystemEventChipAnimationControllerImpl.Factory,
+ context: Context,
+ statusBarWindowControllerStore: StatusBarWindowControllerStore,
+ contentInsetsProviderStore: StatusBarContentInsetsProviderStore,
+ ): SystemEventChipAnimationController {
+ return factory.create(
+ context,
+ statusBarWindowControllerStore.defaultDisplay,
+ contentInsetsProviderStore.defaultDisplay,
+ )
+ }
+
+ @Provides
+ @SysUISingleton
+ fun controller(
+ @Default defaultLazy: Lazy<SystemEventChipAnimationController>,
+ multiDisplayLazy: Lazy<MultiDisplaySystemEventChipAnimationController>,
+ ): SystemEventChipAnimationController {
+ return if (StatusBarConnectedDisplays.isEnabled) {
+ multiDisplayLazy.get()
+ } else {
+ defaultLazy.get()
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
index 77ec65b..9a779300 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -49,12 +49,12 @@
@Inject
constructor(
private val launcherApps: LauncherApps,
- private val conversationNotificationManager: ConversationNotificationManager
+ private val conversationNotificationManager: ConversationNotificationManager,
) {
fun processNotification(
entry: NotificationEntry,
recoveredBuilder: Notification.Builder,
- logger: NotificationRowContentBinderLogger
+ logger: NotificationRowContentBinderLogger,
): Notification.MessagingStyle? {
val messagingStyle = recoveredBuilder.style as? Notification.MessagingStyle ?: return null
messagingStyle.conversationType =
@@ -83,7 +83,7 @@
private val notifCollection: CommonNotifCollection,
private val bindEventManager: BindEventManager,
private val headsUpManager: HeadsUpManager,
- private val statusBarStateController: StatusBarStateController
+ private val statusBarStateController: StatusBarStateController,
) {
private var isStatusBarExpanded = false
@@ -118,7 +118,8 @@
.flatMap { layout -> layout.allViews.asSequence() }
.flatMap { view ->
(view as? ConversationLayout)?.messagingGroups?.asSequence()
- ?: (view as? MessagingLayout)?.messagingGroups?.asSequence() ?: emptySequence()
+ ?: (view as? MessagingLayout)?.messagingGroups?.asSequence()
+ ?: emptySequence()
}
.flatMap { messagingGroup -> messagingGroup.messageContainer.children }
.mapNotNull { view ->
@@ -144,7 +145,7 @@
bindEventManager: BindEventManager,
@ShadeDisplayAware private val context: Context,
private val notifCollection: CommonNotifCollection,
- @Main private val mainHandler: Handler
+ @Main private val mainHandler: Handler,
) {
// Need this state to be thread safe, since it's accessed from the ui thread
// (NotificationEntryListener) and a bg thread (NotificationRowContentBinder)
@@ -175,7 +176,7 @@
// the notif has been moved in the shade
mainHandler.postDelayed(
{ layout.setIsImportantConversation(important, true) },
- IMPORTANCE_ANIMATION_DELAY.toLong()
+ IMPORTANCE_ANIMATION_DELAY.toLong(),
)
} else {
layout.setIsImportantConversation(important, false)
@@ -233,8 +234,7 @@
state?.run {
if (shouldIncrementUnread(recoveredBuilder)) unreadCount + 1
else unreadCount
- }
- ?: 1
+ } ?: 1
ConversationState(newCount, entry.sbn.notification)
}!!
.unreadCount
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
index de868d4..df8e56e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
@@ -33,29 +33,30 @@
* they are fully attached.
*/
@CoordinatorScope
-class RowAppearanceCoordinator @Inject internal constructor(
+class RowAppearanceCoordinator
+@Inject
+internal constructor(
@ShadeDisplayAware context: Context,
private var mAssistantFeedbackController: AssistantFeedbackController,
- private var mSectionStyleProvider: SectionStyleProvider
+ private var mSectionStyleProvider: SectionStyleProvider,
) : Coordinator {
private var entryToExpand: NotificationEntry? = null
/**
- * `true` if notifications not part of a group should by default be rendered in their
- * expanded state. If `false`, then only the first notification will be expanded if
- * possible.
+ * `true` if notifications not part of a group should by default be rendered in their expanded
+ * state. If `false`, then only the first notification will be expanded if possible.
*/
private val mAlwaysExpandNonGroupedNotification =
context.resources.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications)
/**
- * `true` if the first non-group expandable notification should be expanded automatically
- * when possible. If `false`, then the first non-group expandable notification should not
- * be expanded.
+ * `true` if the first non-group expandable notification should be expanded automatically when
+ * possible. If `false`, then the first non-group expandable notification should not be
+ * expanded.
*/
private val mAutoExpandFirstNotification =
- context.resources.getBoolean(R.bool.config_autoExpandFirstNotification)
+ context.resources.getBoolean(R.bool.config_autoExpandFirstNotification)
override fun attach(pipeline: NotifPipeline) {
pipeline.addOnBeforeRenderListListener(::onBeforeRenderList)
@@ -63,17 +64,20 @@
}
private fun onBeforeRenderList(list: List<ListEntry>) {
- entryToExpand = list.firstOrNull()?.representativeEntry?.takeIf { entry ->
- !mSectionStyleProvider.isMinimizedSection(entry.section!!)
- }
+ entryToExpand =
+ list.firstOrNull()?.representativeEntry?.takeIf { entry ->
+ !mSectionStyleProvider.isMinimizedSection(entry.section!!)
+ }
}
private fun onAfterRenderEntry(entry: NotificationEntry, controller: NotifRowController) {
// If mAlwaysExpandNonGroupedNotification is false, then only expand the
// very first notification if it's not a child of grouped notifications and when
// mAutoExpandFirstNotification is true.
- controller.setSystemExpanded(mAlwaysExpandNonGroupedNotification ||
- (mAutoExpandFirstNotification && entry == entryToExpand))
+ controller.setSystemExpanded(
+ mAlwaysExpandNonGroupedNotification ||
+ (mAutoExpandFirstNotification && entry == entryToExpand)
+ )
// Show/hide the feedback icon
controller.setFeedbackIcon(mAssistantFeedbackController.getFeedbackIcon(entry))
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
index db778b80..2d1eccd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
@@ -17,10 +17,12 @@
package com.android.systemui.statusbar.notification.collection.coordinator
import android.util.Log
+import com.android.app.tracing.traceSection
import com.android.internal.widget.MessagingGroup
import com.android.internal.widget.MessagingMessage
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener
import com.android.systemui.statusbar.notification.ColorUpdateLogger
@@ -29,17 +31,17 @@
import com.android.systemui.statusbar.notification.row.NotificationGutsManager
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.Compile
-import com.android.app.tracing.traceSection
-import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
/**
- * A coordinator which ensures that notifications within the new pipeline are correctly inflated
- * for the current uiMode and screen properties; additionally deferring those changes when a user
- * change is in progress until that process has completed.
+ * A coordinator which ensures that notifications within the new pipeline are correctly inflated for
+ * the current uiMode and screen properties; additionally deferring those changes when a user change
+ * is in progress until that process has completed.
*/
@CoordinatorScope
-class ViewConfigCoordinator @Inject internal constructor(
+class ViewConfigCoordinator
+@Inject
+internal constructor(
@ShadeDisplayAware private val mConfigurationController: ConfigurationController,
private val mLockscreenUserManager: NotificationLockscreenUserManager,
private val mGutsManager: NotificationGutsManager,
@@ -52,28 +54,32 @@
private var mDispatchUiModeChangeOnUserSwitched = false
private var mPipeline: NotifPipeline? = null
- private val mKeyguardUpdateCallback = object : KeyguardUpdateMonitorCallback() {
- override fun onUserSwitching(userId: Int) {
- colorUpdateLogger.logTriggerEvent("VCC.mKeyguardUpdateCallback.onUserSwitching()")
- log { "ViewConfigCoordinator.onUserSwitching(userId=$userId)" }
- mIsSwitchingUser = true
+ private val mKeyguardUpdateCallback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onUserSwitching(userId: Int) {
+ colorUpdateLogger.logTriggerEvent("VCC.mKeyguardUpdateCallback.onUserSwitching()")
+ log { "ViewConfigCoordinator.onUserSwitching(userId=$userId)" }
+ mIsSwitchingUser = true
+ }
+
+ override fun onUserSwitchComplete(userId: Int) {
+ colorUpdateLogger.logTriggerEvent(
+ "VCC.mKeyguardUpdateCallback.onUserSwitchComplete()"
+ )
+ log { "ViewConfigCoordinator.onUserSwitchComplete(userId=$userId)" }
+ mIsSwitchingUser = false
+ applyChangesOnUserSwitched()
+ }
}
- override fun onUserSwitchComplete(userId: Int) {
- colorUpdateLogger.logTriggerEvent("VCC.mKeyguardUpdateCallback.onUserSwitchComplete()")
- log { "ViewConfigCoordinator.onUserSwitchComplete(userId=$userId)" }
- mIsSwitchingUser = false
- applyChangesOnUserSwitched()
+ private val mUserChangedListener =
+ object : UserChangedListener {
+ override fun onUserChanged(userId: Int) {
+ colorUpdateLogger.logTriggerEvent("VCC.mUserChangedListener.onUserChanged()")
+ log { "ViewConfigCoordinator.onUserChanged(userId=$userId)" }
+ applyChangesOnUserSwitched()
+ }
}
- }
-
- private val mUserChangedListener = object : UserChangedListener {
- override fun onUserChanged(userId: Int) {
- colorUpdateLogger.logTriggerEvent("VCC.mUserChangedListener.onUserChanged()")
- log { "ViewConfigCoordinator.onUserChanged(userId=$userId)" }
- applyChangesOnUserSwitched()
- }
- }
override fun attach(pipeline: NotifPipeline) {
mPipeline = pipeline
@@ -87,8 +93,8 @@
log {
val keyguardIsSwitchingUser = mKeyguardUpdateMonitor.isSwitchingUser
"ViewConfigCoordinator.onDensityOrFontScaleChanged()" +
- " isSwitchingUser=$mIsSwitchingUser" +
- " keyguardUpdateMonitor.isSwitchingUser=$keyguardIsSwitchingUser"
+ " isSwitchingUser=$mIsSwitchingUser" +
+ " keyguardUpdateMonitor.isSwitchingUser=$keyguardIsSwitchingUser"
}
MessagingMessage.dropCache()
MessagingGroup.dropCache()
@@ -104,8 +110,8 @@
log {
val keyguardIsSwitchingUser = mKeyguardUpdateMonitor.isSwitchingUser
"ViewConfigCoordinator.onUiModeChanged()" +
- " isSwitchingUser=$mIsSwitchingUser" +
- " keyguardUpdateMonitor.isSwitchingUser=$keyguardIsSwitchingUser"
+ " isSwitchingUser=$mIsSwitchingUser" +
+ " keyguardUpdateMonitor.isSwitchingUser=$keyguardIsSwitchingUser"
}
if (!mIsSwitchingUser) {
updateNotificationsOnUiModeChanged()
@@ -132,13 +138,13 @@
}
private fun updateNotificationsOnUiModeChanged() {
- colorUpdateLogger.logEvent("VCC.updateNotificationsOnUiModeChanged()",
- "mode=" + mConfigurationController.nightModeName)
+ colorUpdateLogger.logEvent(
+ "VCC.updateNotificationsOnUiModeChanged()",
+ "mode=" + mConfigurationController.nightModeName,
+ )
log { "ViewConfigCoordinator.updateNotificationsOnUiModeChanged()" }
traceSection("updateNotifOnUiModeChanged") {
- mPipeline?.allNotifs?.forEach { entry ->
- entry.row?.onUiModeChanged()
- }
+ mPipeline?.allNotifs?.forEach { entry -> entry.row?.onUiModeChanged() }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
index cab4c1c..3c838e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
@@ -18,6 +18,8 @@
import android.content.Context
import android.view.View
+import com.android.app.tracing.traceSection
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
@@ -26,8 +28,6 @@
import com.android.systemui.statusbar.notification.collection.PipelineDumper
import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
-import com.android.app.tracing.traceSection
-import com.android.systemui.shade.ShadeDisplayAware
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -36,7 +36,9 @@
* Responsible for building and applying the "shade node spec": the list (tree) of things that
* currently populate the notification shade.
*/
-class ShadeViewManager @AssistedInject constructor(
+class ShadeViewManager
+@AssistedInject
+constructor(
@ShadeDisplayAware context: Context,
@Assisted listContainer: NotificationListContainer,
@Assisted private val stackController: NotifStackController,
@@ -45,13 +47,19 @@
sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
nodeSpecBuilderLogger: NodeSpecBuilderLogger,
shadeViewDifferLogger: ShadeViewDifferLogger,
- private val viewBarn: NotifViewBarn
+ private val viewBarn: NotifViewBarn,
) : PipelineDumpable {
// We pass a shim view here because the listContainer may not actually have a view associated
// with it and the differ never actually cares about the root node's view.
private val rootController = RootNodeController(listContainer, View(context))
- private val specBuilder = NodeSpecBuilder(mediaContainerController, featureManager,
- sectionHeaderVisibilityProvider, viewBarn, nodeSpecBuilderLogger)
+ private val specBuilder =
+ NodeSpecBuilder(
+ mediaContainerController,
+ featureManager,
+ sectionHeaderVisibilityProvider,
+ viewBarn,
+ nodeSpecBuilderLogger,
+ )
private val viewDiffer = ShadeViewDiffer(rootController, shadeViewDifferLogger)
/** Method for attaching this manager to the pipeline. */
@@ -59,34 +67,36 @@
renderStageManager.setViewRenderer(viewRenderer)
}
- override fun dumpPipeline(d: PipelineDumper) = with(d) {
- dump("rootController", rootController)
- dump("specBuilder", specBuilder)
- dump("viewDiffer", viewDiffer)
- }
-
- private val viewRenderer = object : NotifViewRenderer {
-
- override fun onRenderList(notifList: List<ListEntry>) {
- traceSection("ShadeViewManager.onRenderList") {
- viewDiffer.applySpec(specBuilder.buildNodeSpec(rootController, notifList))
- }
+ override fun dumpPipeline(d: PipelineDumper) =
+ with(d) {
+ dump("rootController", rootController)
+ dump("specBuilder", specBuilder)
+ dump("viewDiffer", viewDiffer)
}
- override fun getStackController(): NotifStackController = stackController
+ private val viewRenderer =
+ object : NotifViewRenderer {
- override fun getGroupController(group: GroupEntry): NotifGroupController =
- viewBarn.requireGroupController(group.requireSummary)
+ override fun onRenderList(notifList: List<ListEntry>) {
+ traceSection("ShadeViewManager.onRenderList") {
+ viewDiffer.applySpec(specBuilder.buildNodeSpec(rootController, notifList))
+ }
+ }
- override fun getRowController(entry: NotificationEntry): NotifRowController =
- viewBarn.requireRowController(entry)
- }
+ override fun getStackController(): NotifStackController = stackController
+
+ override fun getGroupController(group: GroupEntry): NotifGroupController =
+ viewBarn.requireGroupController(group.requireSummary)
+
+ override fun getRowController(entry: NotificationEntry): NotifRowController =
+ viewBarn.requireRowController(entry)
+ }
}
@AssistedFactory
interface ShadeViewManagerFactory {
fun create(
listContainer: NotificationListContainer,
- stackController: NotifStackController
+ stackController: NotifStackController,
): ShadeViewManager
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/ReferenceNotificationsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/ReferenceNotificationsModule.kt
index 925d4a5..4c25129 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/ReferenceNotificationsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/ReferenceNotificationsModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.dagger
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationsModule
import com.android.systemui.statusbar.notification.row.NotificationRowModule
import dagger.Module
@@ -23,5 +24,12 @@
* A module that includes the standard notifications classes that most SysUI variants need. Variants
* are free to not include this module and instead write a custom notifications module.
*/
-@Module(includes = [NotificationsModule::class, NotificationRowModule::class])
+@Module(
+ includes =
+ [
+ NotificationsModule::class,
+ NotificationRowModule::class,
+ PromotedNotificationsModule::class,
+ ]
+)
object ReferenceNotificationsModule
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt
index 7d374b0..dbe5833 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt
@@ -16,12 +16,12 @@
package com.android.systemui.statusbar.notification.data
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.settings.SecureSettingsRepositoryModule
-import com.android.systemui.settings.SystemSettingsRepositoryModule
+import com.android.systemui.settings.UserSettingsRepositoryModule
import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
import com.android.systemui.shared.settings.data.repository.SystemSettingsRepository
@@ -32,9 +32,8 @@
import dagger.multibindings.IntoMap
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import com.android.app.tracing.coroutines.launchTraced as launch
-@Module(includes = [SecureSettingsRepositoryModule::class, SystemSettingsRepositoryModule::class])
+@Module(includes = [UserSettingsRepositoryModule::class])
object NotificationSettingsRepositoryModule {
@Provides
@SysUISingleton
@@ -48,7 +47,8 @@
backgroundScope,
backgroundDispatcher,
secureSettingsRepository,
- systemSettingsRepository)
+ systemSettingsRepository,
+ )
@Provides
@IntoMap
@@ -57,7 +57,7 @@
fun provideCoreStartable(
@Application applicationScope: CoroutineScope,
repository: NotificationSettingsRepository,
- logger: VisualInterruptionDecisionLogger
+ logger: VisualInterruptionDecisionLogger,
) = CoreStartable {
applicationScope.launch {
repository.isCooldownEnabled.collect { value -> logger.logCooldownSetting(value) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
index 697a6ce..cff5bef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
@@ -83,6 +83,9 @@
// TODO(b/364653005): [ongoingCallNotification] should be incorporated into this flow
// instead of being separate.
topLevelRepresentativeNotifications
+ .map { notifs -> notifs.filter { it.isPromoted } }
+ .distinctUntilChanged()
+ .flowOn(backgroundDispatcher)
} else {
flowOf(emptyList())
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
index 1008451..23da90d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
@@ -33,6 +33,7 @@
import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationsProvider
import com.android.systemui.statusbar.notification.shared.ActiveNotificationEntryModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
@@ -50,6 +51,7 @@
constructor(
private val repository: ActiveNotificationListRepository,
private val sectionStyleProvider: SectionStyleProvider,
+ private val promotedNotificationsProvider: PromotedNotificationsProvider,
) {
/**
* Sets the current list of rendered notification entries as displayed in the notification list.
@@ -57,7 +59,11 @@
fun setRenderedList(entries: List<ListEntry>) {
traceSection("RenderNotificationListInteractor.setRenderedList") {
repository.activeNotifications.update { existingModels ->
- buildActiveNotificationsStore(existingModels, sectionStyleProvider) {
+ buildActiveNotificationsStore(
+ existingModels,
+ sectionStyleProvider,
+ promotedNotificationsProvider,
+ ) {
entries.forEach(::addListEntry)
setRankingsMap(entries)
}
@@ -69,13 +75,21 @@
private fun buildActiveNotificationsStore(
existingModels: ActiveNotificationsStore,
sectionStyleProvider: SectionStyleProvider,
- block: ActiveNotificationsStoreBuilder.() -> Unit
+ promotedNotificationsProvider: PromotedNotificationsProvider,
+ block: ActiveNotificationsStoreBuilder.() -> Unit,
): ActiveNotificationsStore =
- ActiveNotificationsStoreBuilder(existingModels, sectionStyleProvider).apply(block).build()
+ ActiveNotificationsStoreBuilder(
+ existingModels,
+ sectionStyleProvider,
+ promotedNotificationsProvider,
+ )
+ .apply(block)
+ .build()
private class ActiveNotificationsStoreBuilder(
private val existingModels: ActiveNotificationsStore,
private val sectionStyleProvider: SectionStyleProvider,
+ private val promotedNotificationsProvider: PromotedNotificationsProvider,
) {
private val builder = ActiveNotificationsStore.Builder()
@@ -96,7 +110,7 @@
existingModels.createOrReuse(
key = entry.key,
summary = summaryModel,
- children = childModels
+ children = childModels,
)
)
}
@@ -141,6 +155,7 @@
key = key,
groupKey = sbn.groupKey,
whenTime = sbn.notification.`when`,
+ isPromoted = promotedNotificationsProvider.shouldPromote(this),
isAmbient = sectionStyleProvider.isMinimized(this),
isRowDismissed = isRowDismissed,
isSilent = sectionStyleProvider.isSilent(this),
@@ -166,6 +181,7 @@
key: String,
groupKey: String?,
whenTime: Long,
+ isPromoted: Boolean,
isAmbient: Boolean,
isRowDismissed: Boolean,
isSilent: Boolean,
@@ -189,6 +205,7 @@
key = key,
groupKey = groupKey,
whenTime = whenTime,
+ isPromoted = isPromoted,
isAmbient = isAmbient,
isRowDismissed = isRowDismissed,
isSilent = isSilent,
@@ -212,6 +229,7 @@
key = key,
groupKey = groupKey,
whenTime = whenTime,
+ isPromoted = isPromoted,
isAmbient = isAmbient,
isRowDismissed = isRowDismissed,
isSilent = isSilent,
@@ -236,6 +254,7 @@
key: String,
groupKey: String?,
whenTime: Long,
+ isPromoted: Boolean,
isAmbient: Boolean,
isRowDismissed: Boolean,
isSilent: Boolean,
@@ -258,6 +277,7 @@
key != this.key -> false
groupKey != this.groupKey -> false
whenTime != this.whenTime -> false
+ isPromoted != this.isPromoted -> false
isAmbient != this.isAmbient -> false
isRowDismissed != this.isRowDismissed -> false
isSilent != this.isSilent -> false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index 96f47e5..a0515ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -459,8 +459,12 @@
Resources.Theme theme = mContext.getTheme();
final @ColorInt int onSurface = Utils.getColorAttrDefaultColor(mContext,
com.android.internal.R.attr.materialColorOnSurface);
+ // Same resource, separate drawables to prevent touch effects from showing on the wrong
+ // button.
final Drawable clearAllBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
- final Drawable manageBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
+ final Drawable settingsBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
+ final Drawable historyBg = NotifRedesignFooter.isEnabled()
+ ? theme.getDrawable(R.drawable.notif_footer_btn_background) : null;
final @ColorInt int scHigh;
if (!notificationFooterBackgroundTintOptimization()) {
scHigh = Utils.getColorAttrDefaultColor(mContext,
@@ -468,7 +472,10 @@
if (scHigh != 0) {
final ColorFilter bgColorFilter = new PorterDuffColorFilter(scHigh, SRC_ATOP);
clearAllBg.setColorFilter(bgColorFilter);
- manageBg.setColorFilter(bgColorFilter);
+ settingsBg.setColorFilter(bgColorFilter);
+ if (NotifRedesignFooter.isEnabled()) {
+ historyBg.setColorFilter(bgColorFilter);
+ }
}
} else {
scHigh = 0;
@@ -476,13 +483,13 @@
mClearAllButton.setBackground(clearAllBg);
mClearAllButton.setTextColor(onSurface);
if (NotifRedesignFooter.isEnabled()) {
- mSettingsButton.setBackground(manageBg);
+ mSettingsButton.setBackground(settingsBg);
mSettingsButton.setCompoundDrawableTintList(ColorStateList.valueOf(onSurface));
- mHistoryButton.setBackground(manageBg);
+ mHistoryButton.setBackground(historyBg);
mHistoryButton.setCompoundDrawableTintList(ColorStateList.valueOf(onSurface));
} else {
- mManageOrHistoryButton.setBackground(manageBg);
+ mManageOrHistoryButton.setBackground(settingsBg);
mManageOrHistoryButton.setTextColor(onSurface);
}
mSeenNotifsFooterTextView.setTextColor(onSurface);
@@ -492,7 +499,7 @@
colorUpdateLogger.logEvent("Footer.updateColors()",
"textColor(onSurface)=" + hexColorString(onSurface)
+ " backgroundTint(surfaceContainerHigh)=" + hexColorString(scHigh)
- + " background=" + DrawableDumpKt.dumpToString(manageBg));
+ + " background=" + DrawableDumpKt.dumpToString(settingsBg));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
index 71cddc9..52336be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -75,7 +75,7 @@
private val bubbles: Optional<Bubbles>,
@ShadeDisplayAware private val context: Context,
private val notificationManager: NotificationManager,
- private val settingsInteractor: NotificationSettingsInteractor
+ private val settingsInteractor: NotificationSettingsInteractor,
) : VisualInterruptionDecisionProvider {
init {
@@ -89,7 +89,7 @@
private class DecisionImpl(
override val shouldInterrupt: Boolean,
- override val logReason: String
+ override val logReason: String,
) : Decision
private data class LoggableDecision
@@ -107,7 +107,7 @@
LoggableDecision(
DecisionImpl(
shouldInterrupt = false,
- logReason = "${legacySuppressor.name}.$methodName"
+ logReason = "${legacySuppressor.name}.$methodName",
)
)
@@ -123,7 +123,7 @@
private class FullScreenIntentDecisionImpl(
val entry: NotificationEntry,
- private val fsiDecision: FullScreenIntentDecisionProvider.Decision
+ private val fsiDecision: FullScreenIntentDecisionProvider.Decision,
) : FullScreenIntentDecision, Loggable {
var hasBeenLogged = false
@@ -154,7 +154,7 @@
deviceProvisionedController,
keyguardStateController,
powerManager,
- statusBarStateController
+ statusBarStateController,
)
private val legacySuppressors = mutableSetOf<NotificationInterruptSuppressor>()
@@ -197,7 +197,7 @@
context,
notificationManager,
logger,
- systemSettings
+ systemSettings,
)
)
avalancheProvider.register()
@@ -290,7 +290,7 @@
private fun logDecision(
type: VisualInterruptionType,
entry: NotificationEntry,
- loggableDecision: LoggableDecision
+ loggableDecision: LoggableDecision,
) {
if (!loggableDecision.isSpammy || logger.spew) {
logger.logDecision(type.name, entry, loggableDecision.decision)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUi.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUi.kt
new file mode 100644
index 0000000..6324219
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUi.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.promoted
+
+import android.app.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the promoted ongoing notifications UI flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object PromotedNotificationUi {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_UI_RICH_ONGOING
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.uiRichOngoing()
+
+ /**
+ * 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/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsModule.kt
similarity index 61%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsModule.kt
index 94b2bdf..4be12bd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsModule.kt
@@ -14,9 +14,17 @@
* limitations under the License.
*/
-package com.android.systemui.util.settings
+package com.android.systemui.statusbar.notification.promoted
-import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
-val Kosmos.userAwareSecureSettingsRepository by
- Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+@Module
+abstract class PromotedNotificationsModule {
+ @Binds
+ @SysUISingleton
+ abstract fun bindPromotedNotificationsProvider(
+ impl: PromotedNotificationsProviderImpl
+ ): PromotedNotificationsProvider
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProvider.kt
new file mode 100644
index 0000000..691dc6f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProvider.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.promoted
+
+import android.app.Notification.FLAG_PROMOTED_ONGOING
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import javax.inject.Inject
+
+/** A provider for making decisions on which notifications should be promoted. */
+interface PromotedNotificationsProvider {
+ /** Returns true if the given notification should be promoted and false otherwise. */
+ fun shouldPromote(entry: NotificationEntry): Boolean
+}
+
+@SysUISingleton
+open class PromotedNotificationsProviderImpl @Inject constructor() : PromotedNotificationsProvider {
+ override fun shouldPromote(entry: NotificationEntry): Boolean {
+ if (!PromotedNotificationUi.isEnabled) {
+ return false
+ }
+ return (entry.sbn.notification.flags and FLAG_PROMOTED_ONGOING) != 0
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt
index e233def..9164145 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt
@@ -25,6 +25,7 @@
import android.util.Log
import android.util.Size
import androidx.annotation.MainThread
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.R
import com.android.internal.widget.NotificationDrawableConsumer
import com.android.internal.widget.NotificationIconManager
@@ -45,7 +46,6 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.withContext
private const val TAG = "BigPicImageLoader"
@@ -67,7 +67,7 @@
private val statsManager: BigPictureStatsManager,
@Application private val scope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
- @Background private val bgDispatcher: CoroutineDispatcher
+ @Background private val bgDispatcher: CoroutineDispatcher,
) : NotificationIconManager, Dumpable {
private var lastLoadingJob: Job? = null
@@ -153,7 +153,7 @@
private fun checkPlaceHolderSizeForDrawable(
displayedState: DrawableState,
- newDrawable: Drawable
+ newDrawable: Drawable,
) {
if (displayedState is PlaceHolder) {
val (oldWidth, oldHeight) = displayedState.drawableSize
@@ -163,7 +163,7 @@
Log.e(
TAG,
"Mismatch in dimensions, when replacing PlaceHolder " +
- "$oldWidth X $oldHeight with Drawable $newWidth X $newHeight."
+ "$oldWidth X $oldHeight with Drawable $newWidth X $newHeight.",
)
}
}
@@ -184,9 +184,8 @@
displayedState = drawableAndState?.second ?: Empty
}
- private fun startLoadingJob(icon: Icon): Job = scope.launch {
- statsManager.measure { loadImage(icon) }
- }
+ private fun startLoadingJob(icon: Icon): Job =
+ scope.launch { statsManager.measure { loadImage(icon) } }
private suspend fun loadImage(icon: Icon) {
val drawableAndState = withContext(bgDispatcher) { loadImageSync(icon) }
@@ -254,9 +253,12 @@
private sealed class DrawableState(open val icon: Icon?) {
data object Initial : DrawableState(null)
+
data object Empty : DrawableState(null)
+
data class PlaceHolder(override val icon: Icon, val drawableSize: Size) :
DrawableState(icon)
+
data class FullImage(override val icon: Icon, val drawableSize: Size) : DrawableState(icon)
}
}
@@ -298,7 +300,7 @@
}
private val Drawable.intrinsicSize
- get() = Size(/*width=*/ intrinsicWidth, /*height=*/ intrinsicHeight)
+ get() = Size(/* width= */ intrinsicWidth, /* height= */ intrinsicHeight)
private operator fun Size.component1() = width
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
index cf19938..19a92a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
@@ -36,6 +36,8 @@
val groupKey: String?,
/** When this notification was posted. */
val whenTime: Long,
+ /** True if this notification should be promoted and false otherwise. */
+ val isPromoted: Boolean,
/** Is this entry in the ambient / minimized section (lowest priority)? */
val isAmbient: Boolean,
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
index c9a0010..31e4d2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -26,7 +26,14 @@
import com.android.systemui.statusbar.notification.collection.NotificationClassificationFlag
import com.android.systemui.statusbar.notification.collection.render.MediaContainerController
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController
-import com.android.systemui.statusbar.notification.dagger.*
+import com.android.systemui.statusbar.notification.dagger.AlertingHeader
+import com.android.systemui.statusbar.notification.dagger.IncomingHeader
+import com.android.systemui.statusbar.notification.dagger.NewsHeader
+import com.android.systemui.statusbar.notification.dagger.PeopleHeader
+import com.android.systemui.statusbar.notification.dagger.PromoHeader
+import com.android.systemui.statusbar.notification.dagger.RecsHeader
+import com.android.systemui.statusbar.notification.dagger.SilentHeader
+import com.android.systemui.statusbar.notification.dagger.SocialHeader
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider
@@ -54,7 +61,7 @@
@NewsHeader private val newsHeaderController: SectionHeaderController,
@SocialHeader private val socialHeaderController: SectionHeaderController,
@RecsHeader private val recsHeaderController: SectionHeaderController,
- @PromoHeader private val promoHeaderController: SectionHeaderController
+ @PromoHeader private val promoHeaderController: SectionHeaderController,
) : SectionProvider {
private val configurationListener =
@@ -136,14 +143,16 @@
override fun beginsSection(view: View, previous: View?): Boolean =
view === silentHeaderView ||
- view === mediaControlsView ||
- view === peopleHeaderView ||
- view === alertingHeaderView ||
- view === incomingHeaderView ||
- (NotificationClassificationFlag.isEnabled && (view === newsHeaderView
- || view === socialHeaderView || view === recsHeaderView
- || view === promoHeaderView)) ||
- getBucket(view) != getBucket(previous)
+ view === mediaControlsView ||
+ view === peopleHeaderView ||
+ view === alertingHeaderView ||
+ view === incomingHeaderView ||
+ (NotificationClassificationFlag.isEnabled &&
+ (view === newsHeaderView ||
+ view === socialHeaderView ||
+ view === recsHeaderView ||
+ view === promoHeaderView)) ||
+ getBucket(view) != getBucket(previous)
private fun getBucket(view: View?): Int? =
when {
@@ -165,6 +174,7 @@
data class Many(val first: ExpandableView, val last: ExpandableView) : SectionBounds()
data class One(val lone: ExpandableView) : SectionBounds()
+
object None : SectionBounds()
fun addNotif(notif: ExpandableView): SectionBounds =
@@ -183,7 +193,7 @@
private fun NotificationSection.setFirstAndLastVisibleChildren(
first: ExpandableView?,
- last: ExpandableView?
+ last: ExpandableView?,
): Boolean {
val firstChanged = setFirstVisibleChild(first)
val lastChanged = setLastVisibleChild(last)
@@ -198,7 +208,7 @@
*/
fun updateFirstAndLastViewsForAllSections(
sections: Array<NotificationSection>,
- children: List<ExpandableView>
+ children: List<ExpandableView>,
): Boolean {
// Create mapping of bucket to section
val sectionBounds =
@@ -213,7 +223,7 @@
.foldToSparseArray(
SectionBounds.None,
size = sections.size,
- operation = SectionBounds::addNotif
+ operation = SectionBounds::addNotif,
)
// Build a set of the old first/last Views of the sections
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 99efba4..7389086 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -171,12 +171,14 @@
import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.shade.ShadeExpansionListener;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shade.ShadeLogger;
import com.android.systemui.shade.ShadeSurface;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.StatusBarLongPressGestureDetector;
import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.statusbar.phone.BarTransitions;
import com.android.systemui.statusbar.AutoHideUiElement;
@@ -295,7 +297,7 @@
};
void onStatusBarWindowStateChanged(@WindowVisibleState int state) {
- StatusBarSimpleFragment.assertInLegacyMode();
+ StatusBarConnectedDisplays.assertInLegacyMode();
mStatusBarWindowState = state;
updateBubblesVisibility();
}
@@ -366,6 +368,7 @@
private PhoneStatusBarViewController mPhoneStatusBarViewController;
private PhoneStatusBarTransitions mStatusBarTransitions;
+ private final Provider<StatusBarLongPressGestureDetector> mStatusBarLongPressGestureDetector;
private final AuthRippleController mAuthRippleController;
@WindowVisibleState private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
private final NotificationShadeWindowController mNotificationShadeWindowController;
@@ -671,6 +674,7 @@
ShadeController shadeController,
WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ Provider<StatusBarLongPressGestureDetector> statusBarLongPressGestureDetector,
ViewMediatorCallback viewMediatorCallback,
InitController initController,
@Named(TIME_TICK_HANDLER_NAME) Handler timeTickHandler,
@@ -778,6 +782,7 @@
mShadeController = shadeController;
mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mStatusBarLongPressGestureDetector = statusBarLongPressGestureDetector;
mKeyguardViewMediatorCallback = viewMediatorCallback;
mInitController = initController;
mPluginDependencyProvider = pluginDependencyProvider;
@@ -1527,6 +1532,11 @@
// to touch outside the customizer to close it, such as on the status or nav bar.
mShadeController.onStatusBarTouch(event);
}
+ if (ShadeExpandsOnStatusBarLongPress.isEnabled()
+ && mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
+ mStatusBarLongPressGestureDetector.get().handleTouch(event);
+ }
+
return getNotificationShadeWindowView().onTouchEvent(event);
};
}
@@ -1589,8 +1599,6 @@
.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
mRemoteInputManager.addControllerCallback(mStatusBarKeyguardViewManager);
-
- mLightBarController.setBiometricUnlockController(mBiometricUnlockController);
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index be2fb68..2433b78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -50,6 +50,8 @@
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGlanceableHubTransitionViewModel;
import com.android.systemui.log.core.LogLevel;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
@@ -82,6 +84,8 @@
import kotlin.Unit;
+import kotlinx.coroutines.CoroutineDispatcher;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -108,6 +112,7 @@
R.id.keyguard_hun_animator_end_tag,
R.id.keyguard_hun_animator_start_tag);
+ private final CoroutineDispatcher mCoroutineDispatcher;
private final CarrierTextController mCarrierTextController;
private final ConfigurationController mConfigurationController;
private final SystemStatusAnimationScheduler mAnimationScheduler;
@@ -133,6 +138,8 @@
private final Object mLock = new Object();
private final KeyguardLogger mLogger;
private final CommunalSceneInteractor mCommunalSceneInteractor;
+ private final GlanceableHubToLockscreenTransitionViewModel mHubToLockscreenTransitionViewModel;
+ private final LockscreenToGlanceableHubTransitionViewModel mLockscreenToHubTransitionViewModel;
private View mSystemIconsContainer;
private final StatusOverlayHoverListenerFactory mStatusOverlayHoverListenerFactory;
@@ -249,10 +256,21 @@
private boolean mCommunalShowing;
private final Consumer<Boolean> mCommunalConsumer = (communalShowing) -> {
- mCommunalShowing = communalShowing;
- updateViewState();
+ updateCommunalShowing(communalShowing);
};
+ @VisibleForTesting
+ void updateCommunalShowing(boolean communalShowing) {
+ mCommunalShowing = communalShowing;
+
+ // When communal is hidden (either by transition or state change), set alpha to fully
+ // visible.
+ if (!mCommunalShowing) {
+ setAlpha(-1f);
+ }
+ updateViewState();
+ }
+
private final DisableStateTracker mDisableStateTracker;
private final List<String> mBlockedIcons = new ArrayList<>();
@@ -277,6 +295,15 @@
private boolean mShowingKeyguardHeadsUp;
private StatusBarSystemEventDefaultAnimator mSystemEventAnimator;
private float mSystemEventAnimatorAlpha = 1;
+ private final Consumer<Float> mToGlanceableHubStatusBarAlphaConsumer = (alpha) ->
+ updateCommunalAlphaTransition(alpha);
+
+ private final Consumer<Float> mFromGlanceableHubStatusBarAlphaConsumer = (alpha) ->
+ updateCommunalAlphaTransition(alpha);
+
+ @VisibleForTesting void updateCommunalAlphaTransition(float alpha) {
+ setAlpha(!mCommunalShowing || alpha == 0 ? -1 : alpha);
+ }
/**
* The alpha value to be set on the View. If -1, this value is to be ignored.
@@ -285,6 +312,7 @@
@Inject
public KeyguardStatusBarViewController(
+ @Main CoroutineDispatcher dispatcher,
KeyguardStatusBarView view,
CarrierTextController carrierTextController,
ConfigurationController configurationController,
@@ -310,9 +338,14 @@
@Background Executor backgroundExecutor,
KeyguardLogger logger,
StatusOverlayHoverListenerFactory statusOverlayHoverListenerFactory,
- CommunalSceneInteractor communalSceneInteractor
+ CommunalSceneInteractor communalSceneInteractor,
+ GlanceableHubToLockscreenTransitionViewModel
+ glanceableHubToLockscreenTransitionViewModel,
+ LockscreenToGlanceableHubTransitionViewModel
+ lockscreenToGlanceableHubTransitionViewModel
) {
super(view);
+ mCoroutineDispatcher = dispatcher;
mCarrierTextController = carrierTextController;
mConfigurationController = configurationController;
mAnimationScheduler = animationScheduler;
@@ -337,6 +370,8 @@
mBackgroundExecutor = backgroundExecutor;
mLogger = logger;
mCommunalSceneInteractor = communalSceneInteractor;
+ mHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel;
+ mLockscreenToHubTransitionViewModel = lockscreenToGlanceableHubTransitionViewModel;
mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
mKeyguardStateController.addCallback(
@@ -418,7 +453,12 @@
UserHandle.USER_ALL);
updateUserSwitcher();
onThemeChanged();
- collectFlow(mView, mCommunalSceneInteractor.isCommunalVisible(), mCommunalConsumer);
+ collectFlow(mView, mCommunalSceneInteractor.isCommunalVisible(), mCommunalConsumer,
+ mCoroutineDispatcher);
+ collectFlow(mView, mLockscreenToHubTransitionViewModel.getStatusBarAlpha(),
+ mToGlanceableHubStatusBarAlphaConsumer, mCoroutineDispatcher);
+ collectFlow(mView, mHubToLockscreenTransitionViewModel.getStatusBarAlpha(),
+ mFromGlanceableHubStatusBarAlphaConsumer, mCoroutineDispatcher);
}
@Override
@@ -573,7 +613,7 @@
&& !mDozing
&& !hideForBypass
&& !mDisableStateTracker.isDisabled()
- && !mCommunalShowing
+ && (!mCommunalShowing || mExplicitAlpha != -1)
? View.VISIBLE : View.INVISIBLE;
updateViewState(newAlpha, newVisibility);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.kt
new file mode 100644
index 0000000..a6374a6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.phone
+
+import android.view.WindowInsetsController
+import com.android.internal.colorextraction.ColorExtractor
+import com.android.internal.view.AppearanceRegion
+import com.android.systemui.CoreStartable
+
+/** Controls how light status bar flag applies to the icons. */
+interface LightBarController : CoreStartable {
+
+ fun stop()
+
+ fun setNavigationBar(navigationBar: LightBarTransitionsController)
+
+ fun onNavigationBarAppearanceChanged(
+ @WindowInsetsController.Appearance appearance: Int,
+ nbModeChanged: Boolean,
+ navigationBarMode: Int,
+ navbarColorManagedByIme: Boolean,
+ )
+
+ fun onNavigationBarModeChanged(newBarMode: Int)
+
+ fun setQsCustomizing(customizing: Boolean)
+
+ /** Set if Quick Settings is fully expanded, which affects notification scrim visibility. */
+ fun setQsExpanded(expanded: Boolean)
+
+ /** Set if Global Actions dialog is visible, which requires dark mode (light buttons). */
+ fun setGlobalActionsVisible(visible: Boolean)
+
+ /**
+ * Controls the light status bar temporarily for back navigation.
+ *
+ * @param appearance the customized appearance.
+ */
+ fun customizeStatusBarAppearance(appearance: AppearanceRegion)
+
+ /**
+ * Sets whether the direct-reply is in use or not.
+ *
+ * @param directReplying `true` when the direct-reply is in-use.
+ */
+ fun setDirectReplying(directReplying: Boolean)
+
+ fun setScrimState(
+ scrimState: ScrimState,
+ scrimBehindAlpha: Float,
+ scrimInFrontColor: ColorExtractor.GradientColors,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java
similarity index 81%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java
index a33996b..edc1f88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java
@@ -11,7 +11,7 @@
* 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
+ * limitations under the License.
*/
package com.android.systemui.statusbar.phone;
@@ -22,9 +22,9 @@
import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
-import android.content.Context;
import android.graphics.Rect;
import android.util.Log;
+import android.view.Display;
import android.view.InsetsFlags;
import android.view.ViewDebug;
import android.view.WindowInsetsController.Appearance;
@@ -34,30 +34,32 @@
import com.android.internal.colorextraction.ColorExtractor.GradientColors;
import com.android.internal.view.AppearanceRegion;
-import com.android.systemui.CoreStartable;
-import com.android.systemui.Dumpable;
-import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.statusbar.data.model.StatusBarAppearance;
-import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore;
+import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.Compile;
-import com.android.systemui.util.kotlin.JavaAdapter;
+import com.android.systemui.util.kotlin.JavaAdapterKt;
+
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+
+import kotlin.coroutines.CoroutineContext;
+
+import kotlinx.coroutines.CoroutineScope;
import java.io.PrintWriter;
import java.util.ArrayList;
-import javax.inject.Inject;
-
/**
* Controls how light status bar flag applies to the icons.
*/
-@SysUISingleton
-public class LightBarController implements
- BatteryController.BatteryStateChangeCallback, Dumpable, CoreStartable {
+public class LightBarControllerImpl implements
+ BatteryController.BatteryStateChangeCallback, LightBarController {
private static final String TAG = "LightBarController";
private static final boolean DEBUG_NAVBAR = Compile.IS_DEBUG;
@@ -65,11 +67,14 @@
private static final float NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD = 0.1f;
- private final JavaAdapter mJavaAdapter;
+ private final CoroutineScope mCoroutineScope;
private final SysuiDarkIconDispatcher mStatusBarIconController;
private final BatteryController mBatteryController;
- private final StatusBarModeRepositoryStore mStatusBarModeRepository;
- private BiometricUnlockController mBiometricUnlockController;
+ private final NavigationModeController mNavModeController;
+ private final DumpManager mDumpManager;
+ private final StatusBarModePerDisplayRepository mStatusBarModeRepository;
+ private final CoroutineContext mMainContext;
+ private final BiometricUnlockController mBiometricUnlockController;
private LightBarTransitionsController mNavigationBarController;
private @Appearance int mAppearance;
@@ -119,47 +124,60 @@
private String mLastNavigationBarAppearanceChangedLog;
private StringBuilder mLogStringBuilder = null;
- @Inject
- public LightBarController(
- Context ctx,
- JavaAdapter javaAdapter,
+ private final String mDumpableName;
+
+ private final NavigationModeController.ModeChangedListener mNavigationModeListener =
+ (mode) -> mNavigationMode = mode;
+
+ @AssistedInject
+ public LightBarControllerImpl(
+ @Assisted int displayId,
+ @Assisted CoroutineScope coroutineScope,
DarkIconDispatcher darkIconDispatcher,
BatteryController batteryController,
NavigationModeController navModeController,
- StatusBarModeRepositoryStore statusBarModeRepository,
+ @Assisted StatusBarModePerDisplayRepository statusBarModeRepository,
DumpManager dumpManager,
- DisplayTracker displayTracker) {
- mJavaAdapter = javaAdapter;
+ @Main CoroutineContext mainContext,
+ BiometricUnlockController biometricUnlockController) {
+ mCoroutineScope = coroutineScope;
mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher;
mBatteryController = batteryController;
- mBatteryController.addCallback(this);
+ mNavModeController = navModeController;
+ mDumpManager = dumpManager;
mStatusBarModeRepository = statusBarModeRepository;
- mNavigationMode = navModeController.addListener((mode) -> {
- mNavigationMode = mode;
- });
-
- if (ctx.getDisplayId() == displayTracker.getDefaultDisplayId()) {
- dumpManager.registerDumpable(getClass().getSimpleName(), this);
- }
+ mMainContext = mainContext;
+ mBiometricUnlockController = biometricUnlockController;
+ String dumpableNameSuffix =
+ displayId == Display.DEFAULT_DISPLAY ? "" : String.valueOf(displayId);
+ mDumpableName = getClass().getSimpleName() + dumpableNameSuffix;
}
@Override
public void start() {
- mJavaAdapter.alwaysCollectFlow(
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance(),
+ mDumpManager.registerCriticalDumpable(mDumpableName, this);
+ mBatteryController.addCallback(this);
+ mNavigationMode = mNavModeController.addListener(mNavigationModeListener);
+ JavaAdapterKt.collectFlow(
+ mCoroutineScope,
+ mMainContext,
+ mStatusBarModeRepository.getStatusBarAppearance(),
this::onStatusBarAppearanceChanged);
}
+ @Override
+ public void stop() {
+ mDumpManager.unregisterDumpable(mDumpableName);
+ mBatteryController.removeCallback(this);
+ mNavModeController.removeListener(mNavigationModeListener);
+ }
+
+ @Override
public void setNavigationBar(LightBarTransitionsController navigationBar) {
mNavigationBarController = navigationBar;
updateNavigation();
}
- public void setBiometricUnlockController(
- BiometricUnlockController biometricUnlockController) {
- mBiometricUnlockController = biometricUnlockController;
- }
-
private void onStatusBarAppearanceChanged(@Nullable StatusBarAppearance params) {
if (params == null) {
return;
@@ -202,6 +220,7 @@
mNavbarColorManagedByIme = navbarColorManagedByIme;
}
+ @Override
public void onNavigationBarAppearanceChanged(@Appearance int appearance, boolean nbModeChanged,
int navigationBarMode, boolean navbarColorManagedByIme) {
int diff = appearance ^ mAppearance;
@@ -244,6 +263,7 @@
mNavbarColorManagedByIme = navbarColorManagedByIme;
}
+ @Override
public void onNavigationBarModeChanged(int newBarMode) {
mHasLightNavigationBar = isLight(mAppearance, newBarMode, APPEARANCE_LIGHT_NAVIGATION_BARS);
}
@@ -258,30 +278,28 @@
mNavigationBarMode, mNavbarColorManagedByIme);
}
+ @Override
public void setQsCustomizing(boolean customizing) {
if (mQsCustomizing == customizing) return;
mQsCustomizing = customizing;
reevaluate();
}
- /** Set if Quick Settings is fully expanded, which affects notification scrim visibility */
+ @Override
public void setQsExpanded(boolean expanded) {
if (mQsExpanded == expanded) return;
mQsExpanded = expanded;
reevaluate();
}
- /** Set if Global Actions dialog is visible, which requires dark mode (light buttons) */
+ @Override
public void setGlobalActionsVisible(boolean visible) {
if (mGlobalActionsVisible == visible) return;
mGlobalActionsVisible = visible;
reevaluate();
}
- /**
- * Controls the light status bar temporarily for back navigation.
- * @param appearance the custmoized appearance.
- */
+ @Override
public void customizeStatusBarAppearance(AppearanceRegion appearance) {
if (appearance != null) {
final ArrayList<AppearanceRegion> appearancesList = new ArrayList<>();
@@ -303,16 +321,14 @@
}
}
- /**
- * Sets whether the direct-reply is in use or not.
- * @param directReplying {@code true} when the direct-reply is in-use.
- */
+ @Override
public void setDirectReplying(boolean directReplying) {
if (mDirectReplying == directReplying) return;
mDirectReplying = directReplying;
reevaluate();
}
+ @Override
public void setScrimState(ScrimState scrimState, float scrimBehindAlpha,
GradientColors scrimInFrontColor) {
boolean bouncerVisibleLast = mBouncerVisible;
@@ -368,9 +384,6 @@
}
private boolean animateChange() {
- if (mBiometricUnlockController == null) {
- return false;
- }
int unlockMode = mBiometricUnlockController.getMode();
return unlockMode != BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
&& unlockMode != BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
@@ -387,20 +400,17 @@
}
}
- // If no one is light, all icons become white.
if (lightBarBounds.isEmpty()) {
- mStatusBarIconController.getTransitionsController().setIconsDark(
- false, animateChange());
- }
-
- // If all stacks are light, all icons get dark.
- else if (lightBarBounds.size() == numStacks) {
+ // If no one is light, all icons become white.
+ mStatusBarIconController
+ .getTransitionsController()
+ .setIconsDark(false, animateChange());
+ } else if (lightBarBounds.size() == numStacks) {
+ // If all stacks are light, all icons get dark.
mStatusBarIconController.setIconsDarkArea(null);
mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange());
- }
-
- // Not the same for every stack, magic!
- else {
+ } else {
+ // Not the same for every stack, magic!
mStatusBarIconController.setIconsDarkArea(lightBarBounds);
mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange());
}
@@ -468,47 +478,15 @@
}
}
- /**
- * Injectable factory for creating a {@link LightBarController}.
- */
- public static class Factory {
- private final JavaAdapter mJavaAdapter;
- private final DarkIconDispatcher mDarkIconDispatcher;
- private final BatteryController mBatteryController;
- private final NavigationModeController mNavModeController;
- private final StatusBarModeRepositoryStore mStatusBarModeRepository;
- private final DumpManager mDumpManager;
- private final DisplayTracker mDisplayTracker;
+ /** Injectable factory for creating a {@link LightBarControllerImpl}. */
+ @AssistedFactory
+ @FunctionalInterface
+ public interface Factory {
- @Inject
- public Factory(
- JavaAdapter javaAdapter,
- DarkIconDispatcher darkIconDispatcher,
- BatteryController batteryController,
- NavigationModeController navModeController,
- StatusBarModeRepositoryStore statusBarModeRepository,
- DumpManager dumpManager,
- DisplayTracker displayTracker) {
- mJavaAdapter = javaAdapter;
- mDarkIconDispatcher = darkIconDispatcher;
- mBatteryController = batteryController;
- mNavModeController = navModeController;
- mStatusBarModeRepository = statusBarModeRepository;
- mDumpManager = dumpManager;
- mDisplayTracker = displayTracker;
- }
-
- /** Create an {@link LightBarController} */
- public LightBarController create(Context context) {
- return new LightBarController(
- context,
- mJavaAdapter,
- mDarkIconDispatcher,
- mBatteryController,
- mNavModeController,
- mStatusBarModeRepository,
- mDumpManager,
- mDisplayTracker);
- }
+ /** Creates a {@link LightBarControllerImpl}. */
+ LightBarControllerImpl create(
+ int displayId,
+ CoroutineScope coroutineScope,
+ StatusBarModePerDisplayRepository statusBarModePerDisplayRepository);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 91c43dd..176dd8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -39,8 +39,8 @@
import com.android.systemui.Flags;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.res.R;
-import com.android.systemui.shade.LongPressGestureDetector;
import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress;
+import com.android.systemui.shade.StatusBarLongPressGestureDetector;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder;
@@ -69,7 +69,7 @@
private InsetsFetcher mInsetsFetcher;
private int mDensity;
private float mFontScale;
- private LongPressGestureDetector mLongPressGestureDetector;
+ private StatusBarLongPressGestureDetector mStatusBarLongPressGestureDetector;
/**
* Draw this many pixels into the left/right side of the cutout to optimally use the space
@@ -81,9 +81,10 @@
mStatusBarWindowControllerStore = Dependency.get(StatusBarWindowControllerStore.class);
}
- void setLongPressGestureDetector(LongPressGestureDetector longPressGestureDetector) {
+ void setLongPressGestureDetector(
+ StatusBarLongPressGestureDetector statusBarLongPressGestureDetector) {
if (ShadeExpandsOnStatusBarLongPress.isEnabled()) {
- mLongPressGestureDetector = longPressGestureDetector;
+ mStatusBarLongPressGestureDetector = statusBarLongPressGestureDetector;
}
}
@@ -207,8 +208,9 @@
@Override
public boolean onTouchEvent(MotionEvent event) {
- if (ShadeExpandsOnStatusBarLongPress.isEnabled() && mLongPressGestureDetector != null) {
- mLongPressGestureDetector.handleTouch(event);
+ if (ShadeExpandsOnStatusBarLongPress.isEnabled()
+ && mStatusBarLongPressGestureDetector != null) {
+ mStatusBarLongPressGestureDetector.handleTouch(event);
}
if (mTouchEventHandler == null) {
Log.w(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index c24f432..4245494 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -33,11 +33,11 @@
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.ui.view.WindowRootView
-import com.android.systemui.shade.LongPressGestureDetector
import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress
import com.android.systemui.shade.ShadeLogger
import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.StatusBarLongPressGestureDetector
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
@@ -68,7 +68,7 @@
private val shadeController: ShadeController,
private val shadeViewController: ShadeViewController,
private val panelExpansionInteractor: PanelExpansionInteractor,
- private val longPressGestureDetector: Provider<LongPressGestureDetector>,
+ private val statusBarLongPressGestureDetector: Provider<StatusBarLongPressGestureDetector>,
private val windowRootView: Provider<WindowRootView>,
private val shadeLogger: ShadeLogger,
private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
@@ -118,7 +118,7 @@
addCursorSupportToIconContainers()
if (ShadeExpandsOnStatusBarLongPress.isEnabled) {
- mView.setLongPressGestureDetector(longPressGestureDetector.get())
+ mView.setLongPressGestureDetector(statusBarLongPressGestureDetector.get())
}
progressProvider?.setReadyToHandleTransition(true)
@@ -335,7 +335,7 @@
private val shadeController: ShadeController,
private val shadeViewController: ShadeViewController,
private val panelExpansionInteractor: PanelExpansionInteractor,
- private val longPressGestureDetector: Provider<LongPressGestureDetector>,
+ private val statusBarLongPressGestureDetector: Provider<StatusBarLongPressGestureDetector>,
private val windowRootView: Provider<WindowRootView>,
private val shadeLogger: ShadeLogger,
private val viewUtil: ViewUtil,
@@ -360,7 +360,7 @@
shadeController,
shadeViewController,
panelExpansionInteractor,
- longPressGestureDetector,
+ statusBarLongPressGestureDetector,
windowRootView,
shadeLogger,
statusBarMoveFromCenterAnimationController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
index 23f3482..ba878ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
@@ -32,6 +32,7 @@
import com.android.systemui.statusbar.core.StatusBarOrchestrator
import com.android.systemui.statusbar.core.StatusBarSimpleFragment
import com.android.systemui.statusbar.data.repository.PrivacyDotViewControllerStoreModule
+import com.android.systemui.statusbar.data.repository.PrivacyDotWindowControllerStoreModule
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
import com.android.systemui.statusbar.events.PrivacyDotViewControllerModule
import com.android.systemui.statusbar.phone.CentralSurfacesCommandQueueCallbacks
@@ -48,7 +49,12 @@
/** Similar in purpose to [StatusBarModule], but scoped only to phones */
@Module(
- includes = [PrivacyDotViewControllerModule::class, PrivacyDotViewControllerStoreModule::class]
+ includes =
+ [
+ PrivacyDotViewControllerModule::class,
+ PrivacyDotWindowControllerStoreModule::class,
+ PrivacyDotViewControllerStoreModule::class,
+ ]
)
interface StatusBarPhoneModule {
@@ -97,8 +103,12 @@
fun statusBarInitializerImpl(
implFactory: StatusBarInitializerImpl.Factory,
statusBarWindowControllerStore: StatusBarWindowControllerStore,
+ statusBarModeRepositoryStore: StatusBarModeRepositoryStore,
): StatusBarInitializerImpl {
- return implFactory.create(statusBarWindowControllerStore.defaultDisplay)
+ return implFactory.create(
+ statusBarWindowControllerStore.defaultDisplay,
+ statusBarModeRepositoryStore.defaultDisplay,
+ )
}
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt
index 1d08f2b..98eed84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt
@@ -37,6 +37,9 @@
/** Clients must observe this property, as device-based satellite is location-dependent */
val isSatelliteAllowedForCurrentLocation: StateFlow<Boolean>
+
+ /** When enabled, a satellite icon will display when all other connections are OOS */
+ val isOpportunisticSatelliteIconEnabled: Boolean
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt
index 58c30e0..de42b92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt
@@ -97,6 +97,9 @@
}
.stateIn(scope, SharingStarted.WhileSubscribed(), realImpl)
+ override val isOpportunisticSatelliteIconEnabled: Boolean
+ get() = activeRepo.value.isOpportunisticSatelliteIconEnabled
+
override val isSatelliteProvisioned: StateFlow<Boolean> =
activeRepo
.flatMapLatest { it.isSatelliteProvisioned }
@@ -118,6 +121,6 @@
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
- realImpl.isSatelliteAllowedForCurrentLocation.value
+ realImpl.isSatelliteAllowedForCurrentLocation.value,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt
index d557bbf..755899f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt
@@ -16,15 +16,18 @@
package com.android.systemui.statusbar.pipeline.satellite.data.demo
+import android.content.res.Resources
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository
import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
-import com.android.app.tracing.coroutines.launchTraced as launch
/** A satellite repository that represents the latest satellite values sent via demo mode. */
@SysUISingleton
@@ -33,9 +36,13 @@
constructor(
private val dataSource: DemoDeviceBasedSatelliteDataSource,
@Application private val scope: CoroutineScope,
+ @Main resources: Resources,
) : DeviceBasedSatelliteRepository {
private var demoCommandJob: Job? = null
+ override val isOpportunisticSatelliteIconEnabled =
+ resources.getBoolean(R.bool.config_showOpportunisticSatelliteIcon)
+
override val isSatelliteProvisioned = MutableStateFlow(true)
override val connectionState = MutableStateFlow(SatelliteConnectionState.Unknown)
override val signalStrength = MutableStateFlow(0)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
index 7686338..a36ef56 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.satellite.data.prod
+import android.content.res.Resources
import android.os.OutcomeReceiver
import android.telephony.TelephonyCallback
import android.telephony.TelephonyManager
@@ -27,14 +28,17 @@
import android.telephony.satellite.SatelliteProvisionStateCallback
import android.telephony.satellite.SatelliteSupportedStateCallback
import androidx.annotation.VisibleForTesting
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
import com.android.systemui.log.core.MessageInitializer
import com.android.systemui.log.core.MessagePrinter
+import com.android.systemui.res.R
import com.android.systemui.statusbar.pipeline.dagger.DeviceBasedSatelliteInputLog
import com.android.systemui.statusbar.pipeline.dagger.VerboseDeviceBasedSatelliteInputLog
import com.android.systemui.statusbar.pipeline.satellite.data.RealDeviceBasedSatelliteRepository
@@ -66,7 +70,6 @@
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
@@ -146,10 +149,14 @@
@DeviceBasedSatelliteInputLog private val logBuffer: LogBuffer,
@VerboseDeviceBasedSatelliteInputLog private val verboseLogBuffer: LogBuffer,
private val systemClock: SystemClock,
+ @Main resources: Resources,
) : RealDeviceBasedSatelliteRepository {
private val satelliteManager: SatelliteManager?
+ override val isOpportunisticSatelliteIconEnabled: Boolean =
+ resources.getBoolean(R.bool.config_showOpportunisticSatelliteIcon)
+
// Some calls into satellite manager will throw exceptions if it is not supported.
// This is never expected to change after boot, but may need to be retried in some cases
@get:VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
index f1a444f..08a98c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
@@ -53,6 +53,9 @@
@DeviceBasedSatelliteInputLog private val logBuffer: LogBuffer,
@DeviceBasedSatelliteTableLog private val tableLog: TableLogBuffer,
) {
+ /** Whether or not we should show the satellite icon when all connections are OOS */
+ val isOpportunisticSatelliteIconEnabled = repo.isOpportunisticSatelliteIconEnabled
+
/** Must be observed by any UI showing Satellite iconography */
val isSatelliteAllowed =
if (Flags.oemEnabledSatelliteFlag()) {
@@ -93,12 +96,7 @@
flowOf(0)
}
.distinctUntilChanged()
- .logDiffsForTable(
- tableLog,
- columnPrefix = "",
- columnName = COL_LEVEL,
- initialValue = 0,
- )
+ .logDiffsForTable(tableLog, columnPrefix = "", columnName = COL_LEVEL, initialValue = 0)
.stateIn(scope, SharingStarted.WhileSubscribed(), 0)
val isSatelliteProvisioned = repo.isSatelliteProvisioned
@@ -132,10 +130,9 @@
/** When all connections are considered OOS, satellite connectivity is potentially valid */
val areAllConnectionsOutOfService =
if (Flags.oemEnabledSatelliteFlag()) {
- combine(
- allConnectionsOos,
- iconsInteractor.isDeviceInEmergencyCallsOnlyMode,
- ) { connectionsOos, deviceEmergencyOnly ->
+ combine(allConnectionsOos, iconsInteractor.isDeviceInEmergencyCallsOnlyMode) {
+ connectionsOos,
+ deviceEmergencyOnly ->
logBuffer.log(
TAG,
LogLevel.INFO,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
index 37f2f19..13ac321 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
@@ -77,35 +77,38 @@
// This adds a 10 seconds delay before showing the icon
private val shouldShowIconForOosAfterHysteresis: StateFlow<Boolean> =
- interactor.areAllConnectionsOutOfService
- .flatMapLatest { shouldShow ->
- if (shouldShow) {
- logBuffer.log(
- TAG,
- LogLevel.INFO,
- { long1 = DELAY_DURATION.inWholeSeconds },
- { "Waiting $long1 seconds before showing the satellite icon" }
+ if (interactor.isOpportunisticSatelliteIconEnabled) {
+ interactor.areAllConnectionsOutOfService
+ .flatMapLatest { shouldShow ->
+ if (shouldShow) {
+ logBuffer.log(
+ TAG,
+ LogLevel.INFO,
+ { long1 = DELAY_DURATION.inWholeSeconds },
+ { "Waiting $long1 seconds before showing the satellite icon" },
+ )
+ delay(DELAY_DURATION)
+ flowOf(true)
+ } else {
+ flowOf(false)
+ }
+ }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLog,
+ columnPrefix = "vm",
+ columnName = COL_VISIBLE_FOR_OOS,
+ initialValue = false,
)
- delay(DELAY_DURATION)
- flowOf(true)
- } else {
- flowOf(false)
- }
+ } else {
+ flowOf(false)
}
- .distinctUntilChanged()
- .logDiffsForTable(
- tableLog,
- columnPrefix = "vm",
- columnName = COL_VISIBLE_FOR_OOS,
- initialValue = false,
- )
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
private val canShowIcon =
- combine(
- interactor.isSatelliteAllowed,
- interactor.isSatelliteProvisioned,
- ) { allowed, provisioned ->
+ combine(interactor.isSatelliteAllowed, interactor.isSatelliteProvisioned) {
+ allowed,
+ provisioned ->
allowed && provisioned
}
@@ -141,11 +144,10 @@
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val icon: StateFlow<Icon?> =
- combine(
- showIcon,
- interactor.connectionState,
- interactor.signalStrength,
- ) { shouldShow, state, signalStrength ->
+ combine(showIcon, interactor.connectionState, interactor.signalStrength) {
+ shouldShow,
+ state,
+ signalStrength ->
if (shouldShow) {
SatelliteIconModel.fromConnectionState(state, signalStrength)
} else {
@@ -155,10 +157,7 @@
.stateIn(scope, SharingStarted.WhileSubscribed(), null)
override val carrierText: StateFlow<String?> =
- combine(
- showIcon,
- interactor.connectionState,
- ) { shouldShow, connectionState ->
+ combine(showIcon, interactor.connectionState) { shouldShow, connectionState ->
logBuffer.log(
TAG,
LogLevel.INFO,
@@ -166,7 +165,7 @@
bool1 = shouldShow
str1 = connectionState.name
},
- { "Updating carrier text. shouldShow=$bool1 connectionState=$str1" }
+ { "Updating carrier text. shouldShow=$bool1 connectionState=$str1" },
)
if (shouldShow) {
when (connectionState) {
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 9c8ef04..1c3fece 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -242,10 +242,16 @@
}
};
- private int getLatestWallpaperType(int userId) {
- return mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, userId)
- > mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, userId)
- ? WallpaperManager.FLAG_LOCK : WallpaperManager.FLAG_SYSTEM;
+ private int getDefaultWallpaperColorsSource(int userId) {
+ if (com.android.systemui.shared.Flags.newCustomizationPickerUi()) {
+ // The wallpaper colors source is always the home wallpaper.
+ return WallpaperManager.FLAG_SYSTEM;
+ } else {
+ // The wallpaper colors source is based on the last set wallpaper.
+ return mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, userId)
+ > mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, userId)
+ ? WallpaperManager.FLAG_LOCK : WallpaperManager.FLAG_SYSTEM;
+ }
}
private boolean isSeedColorSet(JSONObject jsonObject, WallpaperColors newWallpaperColors) {
@@ -279,9 +285,9 @@
private void handleWallpaperColors(WallpaperColors wallpaperColors, int flags, int userId) {
final int currentUser = mUserTracker.getUserId();
final boolean hadWallpaperColors = mCurrentColors.get(userId) != null;
- int latestWallpaperType = getLatestWallpaperType(userId);
- boolean eventForLatestWallpaper = (flags & latestWallpaperType) != 0;
- if (eventForLatestWallpaper) {
+ int wallpaperColorsSource = getDefaultWallpaperColorsSource(userId);
+ boolean wallpaperColorsNeedUpdate = (flags & wallpaperColorsSource) != 0;
+ if (wallpaperColorsNeedUpdate) {
mCurrentColors.put(userId, wallpaperColors);
if (DEBUG) Log.d(TAG, "got new colors: " + wallpaperColors + " where: " + flags);
}
@@ -328,7 +334,7 @@
boolean userChoseLockScreenColor = COLOR_SOURCE_LOCK.equals(wallpaperPickerColorSource);
boolean preserveLockScreenColor = isDestinationHomeOnly && userChoseLockScreenColor;
- if (!userChosePresetColor && !preserveLockScreenColor && eventForLatestWallpaper
+ if (!userChosePresetColor && !preserveLockScreenColor && wallpaperColorsNeedUpdate
&& !isSeedColorSet(jsonObject, wallpaperColors)) {
mSkipSettingChange = true;
if (jsonObject.has(OVERLAY_CATEGORY_ACCENT_COLOR) || jsonObject.has(
@@ -494,7 +500,7 @@
// Upon boot, make sure we have the most up to date colors
Runnable updateColors = () -> {
WallpaperColors systemColor = mWallpaperManager.getWallpaperColors(
- getLatestWallpaperType(mUserTracker.getUserId()));
+ getDefaultWallpaperColorsSource(mUserTracker.getUserId()));
Runnable applyColors = () -> {
if (DEBUG) Log.d(TAG, "Boot colors: " + systemColor);
mCurrentColors.put(mUserTracker.getUserId(), systemColor);
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
index 3662c78..163288b 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
@@ -32,6 +32,7 @@
import android.os.UserManager
import android.provider.Settings
import android.util.Log
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.logging.UiEventLogger
import com.android.internal.util.UserIcons
import com.android.keyguard.KeyguardUpdateMonitor
@@ -81,7 +82,6 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
@@ -109,7 +109,7 @@
private val guestUserInteractor: GuestUserInteractor,
private val uiEventLogger: UiEventLogger,
private val userRestrictionChecker: UserRestrictionChecker,
- private val processWrapper: ProcessWrapper
+ private val processWrapper: ProcessWrapper,
) {
/**
* Defines interface for classes that can be notified when the state of users on the device is
@@ -137,11 +137,10 @@
/** List of current on-device users to select from. */
val users: Flow<List<UserModel>>
get() =
- combine(
+ combine(userInfos, repository.selectedUserInfo, repository.userSwitcherSettings) {
userInfos,
- repository.selectedUserInfo,
- repository.userSwitcherSettings,
- ) { userInfos, selectedUserInfo, settings ->
+ selectedUserInfo,
+ settings ->
toUserModels(
userInfos = userInfos,
selectedUserId = selectedUserInfo.id,
@@ -157,7 +156,7 @@
toUserModel(
userInfo = selectedUserInfo,
selectedUserId = selectedUserId,
- canSwitchUsers = canSwitchUsers(selectedUserId)
+ canSwitchUsers = canSwitchUsers(selectedUserId),
)
}
@@ -211,7 +210,7 @@
manager,
repository,
settings.isUserSwitcherEnabled,
- canAccessUserSwitcher
+ canAccessUserSwitcher,
)
if (canCreateUsers) {
@@ -238,7 +237,7 @@
if (
UserActionsUtil.canManageUsers(
repository,
- settings.isUserSwitcherEnabled
+ settings.isUserSwitcherEnabled,
)
) {
add(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
@@ -248,18 +247,14 @@
.flowOn(backgroundDispatcher)
val userRecords: StateFlow<ArrayList<UserRecord>> =
- combine(
+ combine(userInfos, repository.selectedUserInfo, actions, repository.userSwitcherSettings) {
userInfos,
- repository.selectedUserInfo,
- actions,
- repository.userSwitcherSettings,
- ) { userInfos, selectedUserInfo, actionModels, settings ->
+ selectedUserInfo,
+ actionModels,
+ settings ->
ArrayList(
userInfos.map {
- toRecord(
- userInfo = it,
- selectedUserId = selectedUserInfo.id,
- )
+ toRecord(userInfo = it, selectedUserId = selectedUserInfo.id)
} +
actionModels.map {
toRecord(
@@ -298,7 +293,8 @@
val isGuestUserResetting: Boolean = guestUserInteractor.isGuestUserResetting
/** Whether to enable the user chip in the status bar */
- val isStatusBarUserChipEnabled: Boolean = repository.isStatusBarUserChipEnabled
+ val isStatusBarUserChipEnabled: Boolean
+ get() = repository.isStatusBarUserChipEnabled
private val _dialogShowRequests = MutableStateFlow<ShowDialogRequestModel?>(null)
val dialogShowRequests: Flow<ShowDialogRequestModel?> = _dialogShowRequests.asStateFlow()
@@ -467,10 +463,8 @@
when (action) {
UserActionModel.ENTER_GUEST_MODE -> {
uiEventLogger.log(MultiUserActionsEvent.CREATE_GUEST_FROM_USER_SWITCHER)
- guestUserInteractor.createAndSwitchTo(
- this::showDialog,
- this::dismissDialog,
- ) { userId ->
+ guestUserInteractor.createAndSwitchTo(this::showDialog, this::dismissDialog) {
+ userId ->
selectUser(userId, dialogShower)
}
}
@@ -481,7 +475,7 @@
activityStarter.startActivity(
CreateUserActivity.createIntentForStart(
applicationContext,
- keyguardInteractor.isKeyguardShowing()
+ keyguardInteractor.isKeyguardShowing(),
),
/* dismissShade= */ true,
/* animationController */ null,
@@ -523,17 +517,14 @@
)
}
- fun removeGuestUser(
- @UserIdInt guestUserId: Int,
- @UserIdInt targetUserId: Int,
- ) {
+ fun removeGuestUser(@UserIdInt guestUserId: Int, @UserIdInt targetUserId: Int) {
applicationScope.launch {
guestUserInteractor.remove(
guestUserId = guestUserId,
targetUserId = targetUserId,
::showDialog,
::dismissDialog,
- ::switchUser
+ ::switchUser,
)
}
}
@@ -570,10 +561,7 @@
}
}
- private suspend fun toRecord(
- userInfo: UserInfo,
- selectedUserId: Int,
- ): UserRecord {
+ private suspend fun toRecord(userInfo: UserInfo, selectedUserId: Int): UserRecord {
return LegacyUserDataHelper.createRecord(
context = applicationContext,
manager = manager,
@@ -595,10 +583,7 @@
actionType = action,
isRestricted = isRestricted,
isSwitchToEnabled =
- canSwitchUsers(
- selectedUserId = selectedUserId,
- isAction = true,
- ) &&
+ canSwitchUsers(selectedUserId = selectedUserId, isAction = true) &&
// If the user is auto-created is must not be currently resetting.
!(isGuestUserAutoCreated && isGuestUserResetting),
userRestrictionChecker = userRestrictionChecker,
@@ -623,10 +608,7 @@
}
}
- private suspend fun onBroadcastReceived(
- intent: Intent,
- previousUserInfo: UserInfo?,
- ) {
+ private suspend fun onBroadcastReceived(intent: Intent, previousUserInfo: UserInfo?) {
val shouldRefreshAllUsers =
when (intent.action) {
Intent.ACTION_LOCALE_CHANGED -> true
@@ -645,10 +627,8 @@
Intent.ACTION_USER_INFO_CHANGED -> true
Intent.ACTION_USER_UNLOCKED -> {
// If we unlocked the system user, we should refresh all users.
- intent.getIntExtra(
- Intent.EXTRA_USER_HANDLE,
- UserHandle.USER_NULL,
- ) == UserHandle.USER_SYSTEM
+ intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL) ==
+ UserHandle.USER_SYSTEM
}
else -> true
}
@@ -668,20 +648,14 @@
// Disconnect from the old secondary user's service
val secondaryUserId = repository.secondaryUserId
if (secondaryUserId != UserHandle.USER_NULL) {
- applicationContext.stopServiceAsUser(
- intent,
- UserHandle.of(secondaryUserId),
- )
+ applicationContext.stopServiceAsUser(intent, UserHandle.of(secondaryUserId))
repository.secondaryUserId = UserHandle.USER_NULL
}
// Connect to the new secondary user's service (purely to ensure that a persistent
// SystemUI application is created for that user)
if (userId != processWrapper.myUserHandle().identifier && !processWrapper.isSystemUser) {
- applicationContext.startServiceAsUser(
- intent,
- UserHandle.of(userId),
- )
+ applicationContext.startServiceAsUser(intent, UserHandle.of(userId))
repository.secondaryUserId = userId
}
}
@@ -732,7 +706,7 @@
private suspend fun toUserModel(
userInfo: UserInfo,
selectedUserId: Int,
- canSwitchUsers: Boolean
+ canSwitchUsers: Boolean,
): UserModel {
val userId = userInfo.id
val isSelected = userId == selectedUserId
@@ -740,11 +714,7 @@
UserModel(
id = userId,
name = Text.Loaded(userInfo.name),
- image =
- getUserImage(
- isGuest = true,
- userId = userId,
- ),
+ image = getUserImage(isGuest = true, userId = userId),
isSelected = isSelected,
isSelectable = canSwitchUsers,
isGuest = true,
@@ -753,11 +723,7 @@
UserModel(
id = userId,
name = Text.Loaded(userInfo.name),
- image =
- getUserImage(
- isGuest = false,
- userId = userId,
- ),
+ image = getUserImage(isGuest = false, userId = userId),
isSelected = isSelected,
isSelectable = canSwitchUsers || isSelected,
isGuest = false,
@@ -765,10 +731,7 @@
}
}
- private suspend fun canSwitchUsers(
- selectedUserId: Int,
- isAction: Boolean = false,
- ): Boolean {
+ private suspend fun canSwitchUsers(selectedUserId: Int, isAction: Boolean = false): Boolean {
val isHeadlessSystemUserMode =
withContext(backgroundDispatcher) { headlessSystemUserMode.isHeadlessSystemUserMode() }
// Whether menu item should be active. True if item is a user or if any user has
@@ -785,7 +748,7 @@
.getUsers(
/* excludePartial= */ true,
/* excludeDying= */ true,
- /* excludePreCreated= */ true
+ /* excludePreCreated= */ true,
)
.any { user ->
user.id != UserHandle.USER_SYSTEM &&
@@ -794,10 +757,7 @@
}
@SuppressLint("UseCompatLoadingForDrawables")
- private suspend fun getUserImage(
- isGuest: Boolean,
- userId: Int,
- ): Drawable {
+ private suspend fun getUserImage(isGuest: Boolean, userId: Int): Drawable {
if (isGuest) {
return checkNotNull(
applicationContext.getDrawable(com.android.settingslib.R.drawable.ic_account_circle)
@@ -823,13 +783,13 @@
return UserIcons.getDefaultUserIcon(
applicationContext.resources,
userId,
- /* light= */ false
+ /* light= */ false,
)
}
private fun canCreateGuestUser(
settings: UserSwitcherSettingsModel,
- canAccessUserSwitcher: Boolean
+ canAccessUserSwitcher: Boolean,
): Boolean {
return guestUserInteractor.isGuestUserAutoCreated ||
UserActionsUtil.canCreateGuest(
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
index 2c425b19..53c2d88 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
@@ -30,11 +30,10 @@
@OptIn(ExperimentalCoroutinesApi::class)
class StatusBarUserChipViewModel
@Inject
-constructor(
- interactor: UserSwitcherInteractor,
-) {
+constructor(private val interactor: UserSwitcherInteractor) {
/** Whether the status bar chip ui should be available */
- val chipEnabled: Boolean = interactor.isStatusBarUserChipEnabled
+ val chipEnabled: Boolean
+ get() = interactor.isStatusBarUserChipEnabled
/** Whether or not the chip should be showing, based on the number of users */
val isChipVisible: Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
index 3159124..63a5b3f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -20,6 +20,7 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.coroutineScope
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -35,7 +36,7 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
+import kotlinx.coroutines.plus
/** A class allowing Java classes to collect on Kotlin flows. */
@SysUISingleton
@@ -102,6 +103,22 @@
}
}
+/**
+ * Collect information for the given [flow], calling [consumer] for each emitted event on the
+ * specified [collectContext].
+ *
+ * Collection will continue until the given [scope] is cancelled.
+ */
+@JvmOverloads
+fun <T> collectFlow(
+ scope: CoroutineScope,
+ collectContext: CoroutineContext = scope.coroutineContext,
+ flow: Flow<T>,
+ consumer: Consumer<T>,
+): Job {
+ return scope.plus(collectContext).launch { flow.collect { consumer.accept(it) } }
+}
+
fun <A, B, R> combineFlows(flow1: Flow<A>, flow2: Flow<B>, bifunction: (A, B) -> R): Flow<R> {
return combine(flow1, flow2, bifunction)
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java
index d509b2d..f36c335e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java
@@ -16,9 +16,6 @@
package com.android.systemui.util.settings;
-import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository;
-import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepositoryImpl;
-
import dagger.Binds;
import dagger.Module;
@@ -39,9 +36,4 @@
/** Bind GlobalSettingsImpl to GlobalSettings. */
@Binds
GlobalSettings bindsGlobalSettings(GlobalSettingsImpl impl);
-
- /** Bind UserAwareSecureSettingsRepositoryImpl to UserAwareSecureSettingsRepository. */
- @Binds
- UserAwareSecureSettingsRepository bindsUserAwareSecureSettingsRepository(
- UserAwareSecureSettingsRepositoryImpl impl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt
index d3e5080..71335ec 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt
@@ -19,52 +19,25 @@
import android.provider.Settings
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.settings.SettingsProxy
-import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineDispatcher
/**
* Repository for observing values of [Settings.Secure] for the currently active user. That means
* when user is switched and the new user has different value, flow will emit new value.
*/
-interface UserAwareSecureSettingsRepository {
-
- /**
- * Emits boolean value of the setting for active user. Also emits starting value when
- * subscribed.
- * See: [SettingsProxy.getBool].
- */
- fun boolSettingForActiveUser(name: String, defaultValue: Boolean = false): Flow<Boolean>
-}
-
@SysUISingleton
-@OptIn(ExperimentalCoroutinesApi::class)
-class UserAwareSecureSettingsRepositoryImpl @Inject constructor(
- private val secureSettings: SecureSettings,
- private val userRepository: UserRepository,
- @Background private val backgroundDispatcher: CoroutineDispatcher,
-) : UserAwareSecureSettingsRepository {
-
- override fun boolSettingForActiveUser(name: String, defaultValue: Boolean): Flow<Boolean> =
- userRepository.selectedUserInfo
- .flatMapLatest { userInfo -> settingObserver(name, defaultValue, userInfo.id) }
- .distinctUntilChanged()
- .flowOn(backgroundDispatcher)
-
- private fun settingObserver(name: String, defaultValue: Boolean, userId: Int): Flow<Boolean> {
- return secureSettings
- .observerFlow(userId, name)
- .onStart { emit(Unit) }
- .map { secureSettings.getBoolForUser(name, defaultValue, userId) }
- }
-}
\ No newline at end of file
+class UserAwareSecureSettingsRepository
+@Inject
+constructor(
+ secureSettings: SecureSettings,
+ userRepository: UserRepository,
+ @Background backgroundDispatcher: CoroutineDispatcher,
+ @Background bgContext: CoroutineContext,
+) :
+ UserAwareSettingsRepository(secureSettings, userRepository, backgroundDispatcher, bgContext),
+ SecureSettingsRepository
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt
new file mode 100644
index 0000000..a31b8d9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.settings.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import com.android.systemui.util.settings.UserSettingsProxy
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.withContext
+
+/**
+ * Repository for observing values of a [UserSettingsProxy], for the currently active user. That
+ * means that when user is switched and the new user has a different value, the flow will emit the
+ * new value.
+ */
+@SysUISingleton
+@OptIn(ExperimentalCoroutinesApi::class)
+abstract class UserAwareSettingsRepository(
+ private val userSettings: UserSettingsProxy,
+ private val userRepository: UserRepository,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ @Background private val bgContext: CoroutineContext,
+) {
+
+ fun boolSetting(name: String, defaultValue: Boolean): Flow<Boolean> =
+ userRepository.selectedUserInfo
+ .flatMapLatest { userInfo ->
+ settingObserver(name, userInfo.id) {
+ userSettings.getBoolForUser(name, defaultValue, userInfo.id)
+ }
+ }
+ .distinctUntilChanged()
+ .flowOn(backgroundDispatcher)
+
+ fun intSetting(name: String, defaultValue: Int): Flow<Int> {
+ return userRepository.selectedUserInfo
+ .flatMapLatest { userInfo ->
+ settingObserver(name, userInfo.id) {
+ userSettings.getIntForUser(name, defaultValue, userInfo.id)
+ }
+ }
+ .distinctUntilChanged()
+ .flowOn(backgroundDispatcher)
+ }
+
+ private fun <T> settingObserver(name: String, userId: Int, settingsReader: () -> T): Flow<T> {
+ return userSettings
+ .observerFlow(userId, name)
+ .onStart { emit(Unit) }
+ .map { settingsReader.invoke() }
+ }
+
+ suspend fun setInt(name: String, value: Int) {
+ withContext(bgContext) {
+ userSettings.putIntForUser(name, value, userRepository.getSelectedUserInfo().id)
+ }
+ }
+
+ suspend fun getInt(name: String, defaultValue: Int): Int {
+ return withContext(bgContext) {
+ userSettings.getIntForUser(name, defaultValue, userRepository.getSelectedUserInfo().id)
+ }
+ }
+
+ suspend fun getString(name: String): String? {
+ return withContext(bgContext) {
+ userSettings.getStringForUser(name, userRepository.getSelectedUserInfo().id)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepository.kt
new file mode 100644
index 0000000..8b1fca5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepository.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.settings.repository
+
+import android.provider.Settings
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shared.settings.data.repository.SystemSettingsRepository
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.settings.SystemSettings
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineDispatcher
+
+/**
+ * Repository for observing values of [Settings.Secure] for the currently active user. That means
+ * when user is switched and the new user has different value, flow will emit new value.
+ */
+@SysUISingleton
+class UserAwareSystemSettingsRepository
+@Inject
+constructor(
+ systemSettings: SystemSettings,
+ userRepository: UserRepository,
+ @Background backgroundDispatcher: CoroutineDispatcher,
+ @Background bgContext: CoroutineContext,
+) :
+ UserAwareSettingsRepository(systemSettings, userRepository, backgroundDispatcher, bgContext),
+ SystemSettingsRepository
diff --git a/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java b/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
index a95735e..82cfab6 100644
--- a/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
+++ b/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
@@ -56,7 +56,6 @@
@RunWith(AndroidTestingRunner.class)
@SmallTest
public class AAAPlusPlusVerifySysuiRequiredTestPropertiesTest extends SysuiTestCase {
-
private static final String TAG = "AAA++VerifyTest";
private static final Class[] BASE_CLS_TO_INCLUDE = {
@@ -149,6 +148,9 @@
*/
private boolean isTestClass(Class<?> loadedClass) {
try {
+ if (loadedClass.getAnnotation(SkipSysuiVerification.class) != null) {
+ return false;
+ }
if (Modifier.isAbstract(loadedClass.getModifiers())) {
logDebug(String.format("Skipping abstract class %s: not a test",
loadedClass.getName()));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
index 071acfa..288ed4d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
@@ -77,7 +77,10 @@
@get:Rule(order = 2) val animatorTestRule = android.animation.AnimatorTestRule(this)
@get:Rule(order = 3)
val motionRule =
- MotionTestRule(AnimatorTestRuleToolkit(animatorTestRule, kosmos.testScope), pathManager)
+ MotionTestRule(
+ AnimatorTestRuleToolkit(animatorTestRule, kosmos.testScope) { activityRule.scenario },
+ pathManager,
+ )
@Test
fun backgroundAnimation_whenLaunching() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index e1b8a1d..91f9cce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -212,6 +212,20 @@
}
@Test
+ fun testDimissOnLock() {
+ val container = initializeFingerprintContainer(addToView = true)
+ assertThat(container.parent).isNotNull()
+ val root = container.rootView
+
+ // Simulate sleep/lock invocation
+ container.onStartedGoingToSleep()
+ waitForIdleSync()
+
+ assertThat(container.parent).isNull()
+ assertThat(root.isAttachedToWindow).isFalse()
+ }
+
+ @Test
fun testCredentialPasswordDismissesOnBack() {
val container = initializeCredentialPasswordContainer(addToView = true)
assertThat(container.parent).isNotNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt
index 9ace8e9..387cc08 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt
@@ -42,6 +42,7 @@
import com.android.systemui.res.R
import com.android.systemui.scene.domain.startable.sceneContainerStartable
import com.android.systemui.testKosmos
+import kotlin.time.Duration.Companion.seconds
import org.junit.After
import org.junit.Before
import org.junit.Rule
@@ -109,7 +110,7 @@
@Test
fun doubleClick_swapSide() =
- motionTestRule.runTest {
+ motionTestRule.runTest(timeout = 30.seconds) {
val motion =
recordMotion(
content = { BouncerContentUnderTest() },
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
index 088bb02..768f1dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.motion.createSysUiComposeMotionTestRule
import com.android.systemui.testKosmos
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.takeWhile
@@ -71,7 +72,7 @@
@Test
fun entryAnimation() =
- motionTestRule.runTest {
+ motionTestRule.runTest(timeout = 30.seconds) {
val motion =
recordMotion(
content = { play -> if (play) PatternBouncerUnderTest() },
@@ -89,7 +90,7 @@
@Test
fun animateFailure() =
- motionTestRule.runTest {
+ motionTestRule.runTest(timeout = 30.seconds) {
val failureAnimationMotionControl =
MotionControl(
delayReadyToPlay = {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
index 7709a65..0ab4cd0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
@@ -21,7 +21,6 @@
import android.graphics.Rect
import android.os.UserHandle
import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.view.Display.DEFAULT_DISPLAY
import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
@@ -76,9 +75,8 @@
/** Tests applying CaptureParameters with 'IsolatedTask' CaptureType */
@Test
- @EnableFlags(Flags.FLAG_SCREENSHOT_POLICY_SPLIT_AND_DESKTOP_MODE)
fun testProcess_newPolicy_isolatedTask() = runTest {
- val taskImage = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+ val taskImage = Bitmap.createBitmap(200, 100, Bitmap.Config.ARGB_8888)
/* Create a policy request processor with no capture policies */
val requestProcessor =
@@ -96,9 +94,15 @@
requestProcessor.modify(
screenshotRequest,
CaptureParameters(
- IsolatedTask(taskId = TASK_ID, taskBounds = null),
- ComponentName.unflattenFromString(FILES),
- UserHandle.of(WORK),
+ type = IsolatedTask(taskId = 100, taskBounds = Rect(0, 100, 200, 200)),
+ contentTask =
+ TaskReference(
+ taskId = 1001,
+ component = ComponentName.unflattenFromString(FILES)!!,
+ owner = UserHandle.CURRENT,
+ bounds = Rect(100, 100, 200, 200),
+ ),
+ owner = UserHandle.of(WORK),
),
)
@@ -112,14 +116,13 @@
.that(result.topComponent)
.isEqualTo(ComponentName.unflattenFromString(FILES))
- assertWithMessage("Task ID").that(result.taskId).isEqualTo(TASK_ID)
+ assertWithMessage("Task ID").that(result.taskId).isEqualTo(1001)
}
/** Tests applying CaptureParameters with 'FullScreen' CaptureType */
@Test
- @EnableFlags(Flags.FLAG_SCREENSHOT_POLICY_SPLIT_AND_DESKTOP_MODE)
fun testProcess_newPolicy_fullScreen() = runTest {
- val screenImage = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+ val screenImage = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)
/* Create a policy request processor with no capture policies */
val requestProcessor =
@@ -136,7 +139,17 @@
val result =
requestProcessor.modify(
screenshotRequest,
- CaptureParameters(FullScreen(displayId = 0), defaultComponent, defaultOwner),
+ CaptureParameters(
+ type = FullScreen(displayId = 0),
+ contentTask =
+ TaskReference(
+ taskId = 1234,
+ component = defaultComponent,
+ owner = UserHandle.CURRENT,
+ bounds = Rect(1, 2, 3, 4),
+ ),
+ owner = defaultOwner,
+ ),
)
assertWithMessage("The result bitmap").that(result.bitmap).isSameInstanceAs(screenImage)
@@ -149,7 +162,11 @@
.that(result.topComponent)
.isEqualTo(defaultComponent)
- assertWithMessage("Task ID").that(result.taskId).isEqualTo(-1)
+ assertWithMessage("The bounds of the screenshot")
+ .that(result.originalScreenBounds)
+ .isEqualTo(Rect(0, 0, 100, 100))
+
+ assertWithMessage("Task ID").that(result.taskId).isEqualTo(1234)
}
/** Tests behavior when no policies are applied */
@@ -230,7 +247,7 @@
policy = "",
reason = "",
parameters =
- CaptureParameters(
+ LegacyCaptureParameters(
IsolatedTask(taskId = 0, taskBounds = null),
null,
UserHandle.CURRENT,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index a8929a6..f870200 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.notification.logging;
-import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
-
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -59,8 +57,6 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
-import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
import com.android.systemui.statusbar.notification.logging.nano.Notifications;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
@@ -118,11 +114,6 @@
private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
private final PowerInteractor mPowerInteractor =
PowerInteractorFactory.create().getPowerInteractor();
- private final ActiveNotificationListRepository mActiveNotificationListRepository =
- new ActiveNotificationListRepository();
- private final ActiveNotificationsInteractor mActiveNotificationsInteractor =
- new ActiveNotificationsInteractor(mActiveNotificationListRepository,
- StandardTestDispatcher(null, null));
private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
@@ -137,7 +128,7 @@
mKeyguardRepository,
mHeadsUpManager,
mPowerInteractor,
- mActiveNotificationsInteractor,
+ mKosmos.getActiveNotificationsInteractor(),
() -> mKosmos.getSceneInteractor());
mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 625963f..0427011 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -28,8 +28,6 @@
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
-import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
@@ -87,8 +85,6 @@
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
-import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -159,12 +155,6 @@
@Mock private UserManager mUserManager;
- private final ActiveNotificationListRepository mActiveNotificationListRepository =
- new ActiveNotificationListRepository();
- private final ActiveNotificationsInteractor mActiveNotificationsInteractor =
- new ActiveNotificationsInteractor(mActiveNotificationListRepository,
- StandardTestDispatcher(null, null));
-
private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
@Before
@@ -179,7 +169,7 @@
new FakeKeyguardRepository(),
mHeadsUpManager,
PowerInteractorFactory.create().getPowerInteractor(),
- mActiveNotificationsInteractor,
+ mKosmos.getActiveNotificationsInteractor(),
() -> mKosmos.getSceneInteractor()
);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 6069b44..d2350bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -19,6 +19,7 @@
import static android.view.View.GONE;
import static android.view.WindowInsets.Type.ime;
+import static com.android.systemui.flags.SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.RUBBER_BAND_FACTOR_NORMAL;
@@ -54,6 +55,7 @@
import android.os.SystemClock;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
import android.testing.TestableLooper;
import android.testing.TestableResources;
import android.util.MathUtils;
@@ -64,13 +66,13 @@
import android.view.WindowInsetsAnimation;
import android.widget.TextView;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.ExpandHelper;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.BrokenWithSceneContainer;
import com.android.systemui.flags.DisableSceneContainer;
import com.android.systemui.flags.EnableSceneContainer;
import com.android.systemui.flags.FakeFeatureFlags;
@@ -118,16 +120,25 @@
import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
+import java.util.List;
import java.util.function.Consumer;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
/**
* Tests for {@link NotificationStackScrollLayout}.
*/
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(ParameterizedAndroidJunit4.class)
@TestableLooper.RunWithLooper
public class NotificationStackScrollLayoutTest extends SysuiTestCase {
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return parameterizeSceneContainerFlag();
+ }
+
private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
private NotificationStackScrollLayout mStackScroller; // Normally test this
private NotificationStackScrollLayout mStackScrollerInternal; // See explanation below
@@ -154,6 +165,11 @@
@Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
@Mock private AvalancheController mAvalancheController;
+ public NotificationStackScrollLayoutTest(FlagsParameterization flags) {
+ super();
+ mSetFlagsRule.setFlagsParameterization(flags);
+ }
+
@Before
public void setUp() throws Exception {
allowTestableLooperAsMainThread();
@@ -353,6 +369,7 @@
}
@Test
+ @DisableSceneContainer
public void updateStackEndHeightAndStackHeight_onlyUpdatesStackHeightDuringSwipeUp() {
final float expansionFraction = 0.5f;
mAmbientState.setStatusBarState(StatusBarState.KEYGUARD);
@@ -366,6 +383,7 @@
}
@Test
+ @DisableSceneContainer
public void setPanelFlinging_updatesStackEndHeightOnlyOnFinish() {
final float expansionFraction = 0.5f;
mAmbientState.setStatusBarState(StatusBarState.KEYGUARD);
@@ -729,7 +747,9 @@
}
@Test
- @DisableFlags({FooterViewRefactor.FLAG_NAME, ModesEmptyShadeFix.FLAG_NAME})
+ @DisableFlags({FooterViewRefactor.FLAG_NAME,
+ ModesEmptyShadeFix.FLAG_NAME,
+ NotifRedesignFooter.FLAG_NAME})
public void testReInflatesFooterViews() {
when(mEmptyShadeView.getTextResource()).thenReturn(R.string.empty_shade_text);
clearInvocations(mStackScroller);
@@ -1429,6 +1449,7 @@
@Test
@EnableFlags(NotificationThrottleHun.FLAG_NAME)
+ @BrokenWithSceneContainer(bugId = 332732878) // because NSSL#mAnimationsEnabled is always true
public void testGenerateHeadsUpAnimation_isSeenInShade_noAnimation() {
// GIVEN NSSL is ready for HUN animations
Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index f472fd1..7d019bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -155,6 +155,7 @@
import com.android.systemui.shade.ShadeControllerImpl;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shade.ShadeLogger;
+import com.android.systemui.shade.StatusBarLongPressGestureDetector;
import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyboardShortcutListSearch;
@@ -174,8 +175,8 @@
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays;
import com.android.systemui.statusbar.core.StatusBarInitializerImpl;
-import com.android.systemui.statusbar.core.StatusBarOrchestrator;
import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository;
+import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
@@ -371,7 +372,7 @@
@Mock private EmergencyGestureIntentFactory mEmergencyGestureIntentFactory;
@Mock private NotificationSettingsInteractor mNotificationSettingsInteractor;
@Mock private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
- @Mock private StatusBarOrchestrator mStatusBarOrchestrator;
+ @Mock private StatusBarLongPressGestureDetector mStatusBarLongPressGestureDetector;
private ShadeController mShadeController;
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private final FakeGlobalSettings mFakeGlobalSettings = new FakeGlobalSettings();
@@ -387,6 +388,9 @@
private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor =
mKosmos.getBrightnessMirrorShowingInteractor();
+
+ private final StatusBarModePerDisplayRepository mStatusBarModePerDisplayRepository =
+ mKosmos.getStatusBarModePerDisplayRepository();
private ScrimController mScrimController;
@Before
@@ -537,6 +541,7 @@
mAutoHideController,
new StatusBarInitializerImpl(
mStatusBarWindowController,
+ mStatusBarModePerDisplayRepository,
mCollapsedStatusBarFragmentProvider,
mock(StatusBarRootFactory.class),
mock(HomeStatusBarComponent.Factory.class),
@@ -602,6 +607,7 @@
mShadeController,
mWindowRootViewVisibilityInteractor,
mStatusBarKeyguardViewManager,
+ () -> mStatusBarLongPressGestureDetector,
mViewMediatorCallback,
mInitController,
new Handler(TestableLooper.get(this).getLooper()),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 638f195..69efa87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -40,14 +40,13 @@
import com.android.systemui.plugins.fakeDarkIconDispatcher
import com.android.systemui.res.R
import com.android.systemui.scene.ui.view.WindowRootView
-import com.android.systemui.shade.LongPressGestureDetector
import com.android.systemui.shade.ShadeControllerImpl
import com.android.systemui.shade.ShadeLogger
import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.StatusBarLongPressGestureDetector
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.data.repository.fakeStatusBarContentInsetsProviderStore
-import com.android.systemui.statusbar.data.repository.statusBarContentInsetsProviderStore
import com.android.systemui.statusbar.policy.Clock
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.window.StatusBarWindowStateController
@@ -98,7 +97,7 @@
@Mock private lateinit var windowRootView: Provider<WindowRootView>
@Mock private lateinit var shadeLogger: ShadeLogger
@Mock private lateinit var viewUtil: ViewUtil
- @Mock private lateinit var longPressGestureDetector: LongPressGestureDetector
+ @Mock private lateinit var mStatusBarLongPressGestureDetector: StatusBarLongPressGestureDetector
private lateinit var statusBarWindowStateController: StatusBarWindowStateController
private lateinit var view: PhoneStatusBarView
@@ -395,7 +394,7 @@
shadeControllerImpl,
shadeViewController,
panelExpansionInteractor,
- { longPressGestureDetector },
+ { mStatusBarLongPressGestureDetector },
windowRootView,
shadeLogger,
viewUtil,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
index 4d293b9..6326e73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
@@ -102,6 +102,7 @@
logBuffer = FakeLogBuffer.Factory.create(),
verboseLogBuffer = FakeLogBuffer.Factory.create(),
systemClock,
+ context.resources,
)
val connectionState by collectLastValue(underTest.connectionState)
@@ -267,11 +268,7 @@
fun satelliteProvisioned_notSupported_defaultFalse() =
testScope.runTest {
// GIVEN satellite is not supported
- setUpRepo(
- uptime = MIN_UPTIME,
- satMan = satelliteManager,
- satelliteSupported = false,
- )
+ setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = false)
assertThat(underTest.isSatelliteProvisioned.value).isFalse()
}
@@ -280,11 +277,7 @@
fun satelliteProvisioned_supported_defaultFalse() =
testScope.runTest {
// GIVEN satellite is supported
- setUpRepo(
- uptime = MIN_UPTIME,
- satMan = satelliteManager,
- satelliteSupported = true,
- )
+ setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = true)
// THEN default provisioned state is false
assertThat(underTest.isSatelliteProvisioned.value).isFalse()
@@ -323,6 +316,7 @@
logBuffer = FakeLogBuffer.Factory.create(),
verboseLogBuffer = FakeLogBuffer.Factory.create(),
systemClock,
+ context.resources,
)
// WHEN we try to check for provisioned status
@@ -361,6 +355,7 @@
logBuffer = FakeLogBuffer.Factory.create(),
verboseLogBuffer = FakeLogBuffer.Factory.create(),
systemClock,
+ context.resources,
)
// WHEN we try to check for provisioned status
@@ -445,11 +440,7 @@
fun satelliteProvisioned_supported_tracksCallback_reRegistersOnCrash() =
testScope.runTest {
// GIVEN satellite is supported
- setUpRepo(
- uptime = MIN_UPTIME,
- satMan = satelliteManager,
- satelliteSupported = true,
- )
+ setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = true)
val provisioned by collectLastValue(underTest.isSatelliteProvisioned)
@@ -487,11 +478,7 @@
fun satelliteNotSupported_listenersAreNotRegistered() =
testScope.runTest {
// GIVEN satellite is not supported
- setUpRepo(
- uptime = MIN_UPTIME,
- satMan = satelliteManager,
- satelliteSupported = false,
- )
+ setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = false)
// WHEN data is requested from the repo
val connectionState by collectLastValue(underTest.connectionState)
@@ -517,11 +504,7 @@
fun satelliteNotSupported_registersCallbackForStateChanges() =
testScope.runTest {
// GIVEN satellite is not supported
- setUpRepo(
- uptime = MIN_UPTIME,
- satMan = satelliteManager,
- satelliteSupported = false,
- )
+ setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = false)
runCurrent()
// THEN the repo registers for state changes of satellite support
@@ -577,11 +560,7 @@
fun satelliteNotSupported_supportShowsUp_registersListeners() =
testScope.runTest {
// GIVEN satellite is not supported
- setUpRepo(
- uptime = MIN_UPTIME,
- satMan = satelliteManager,
- satelliteSupported = false,
- )
+ setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = false)
runCurrent()
val callback =
@@ -610,11 +589,7 @@
fun repoDoesNotCheckForSupportUntilMinUptime() =
testScope.runTest {
// GIVEN we init 100ms after sysui starts up
- setUpRepo(
- uptime = 100,
- satMan = satelliteManager,
- satelliteSupported = true,
- )
+ setUpRepo(uptime = 100, satMan = satelliteManager, satelliteSupported = true)
// WHEN data is requested
val connectionState by collectLastValue(underTest.connectionState)
@@ -726,6 +701,7 @@
logBuffer = FakeLogBuffer.Factory.create(),
verboseLogBuffer = FakeLogBuffer.Factory.create(),
systemClock,
+ context.resources,
)
}
diff --git a/packages/SystemUI/tests/utils/src/android/view/FakeWindowManager.kt b/packages/SystemUI/tests/utils/src/android/view/FakeWindowManager.kt
new file mode 100644
index 0000000..c28449f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/view/FakeWindowManager.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 android.view
+
+import android.content.Context
+import android.graphics.Region
+import android.view.WindowManager.LayoutParams
+
+class FakeWindowManager(private val context: Context) : WindowManager {
+
+ val addedViews = mutableMapOf<View, LayoutParams>()
+
+ override fun addView(view: View, params: ViewGroup.LayoutParams) {
+ addedViews[view] = params as LayoutParams
+ }
+
+ override fun removeView(view: View) {
+ addedViews.remove(view)
+ }
+
+ override fun updateViewLayout(view: View, params: ViewGroup.LayoutParams) {
+ addedViews[view] = params as LayoutParams
+ }
+
+ override fun getApplicationLaunchKeyboardShortcuts(deviceId: Int): KeyboardShortcutGroup {
+ return KeyboardShortcutGroup("Fake group")
+ }
+
+ override fun getCurrentImeTouchRegion(): Region {
+ return Region.obtain()
+ }
+
+ override fun getDefaultDisplay(): Display {
+ return context.display
+ }
+
+ override fun removeViewImmediate(view: View) {
+ addedViews.remove(view)
+ }
+
+ override fun requestAppKeyboardShortcuts(
+ receiver: WindowManager.KeyboardShortcutsReceiver,
+ deviceId: Int,
+ ) {
+ receiver.onKeyboardShortcutsReceived(emptyList())
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
index d5451ee..025f556 100644
--- a/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
@@ -16,9 +16,12 @@
package android.view
+import android.content.applicationContext
import com.android.systemui.kosmos.Kosmos
import org.mockito.Mockito.mock
+val Kosmos.fakeWindowManager by Kosmos.Fixture { FakeWindowManager(applicationContext) }
+
val Kosmos.mockWindowManager: WindowManager by Kosmos.Fixture { mock(WindowManager::class.java) }
var Kosmos.windowManager: WindowManager by Kosmos.Fixture { mockWindowManager }
diff --git a/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt
index e1c6699..021c7bb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt
@@ -16,11 +16,21 @@
package com.android.app.viewcapture
+import android.view.fakeWindowManager
import com.android.systemui.kosmos.Kosmos
import org.mockito.kotlin.mock
val Kosmos.mockViewCaptureAwareWindowManager by
Kosmos.Fixture { mock<ViewCaptureAwareWindowManager>() }
+val Kosmos.realCaptureAwareWindowManager by
+ Kosmos.Fixture {
+ ViewCaptureAwareWindowManager(
+ fakeWindowManager,
+ lazyViewCapture = lazy { mock<ViewCapture>() },
+ isViewCaptureEnabled = false,
+ )
+ }
+
var Kosmos.viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager by
Kosmos.Fixture { mockViewCaptureAwareWindowManager }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/OnTeardownRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/OnTeardownRule.kt
new file mode 100644
index 0000000..778614b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/OnTeardownRule.kt
@@ -0,0 +1,75 @@
+/*
+ * 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
+
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+import org.junit.runners.model.MultipleFailureException
+
+/**
+ * Rule that allows teardown steps to be added right next to the places where it becomes clear they
+ * are needed. This can avoid the need for complicated or conditional logic in a single teardown
+ * method. Examples:
+ * ```
+ * @get:Rule teardownRule = OnTeardownRule()
+ *
+ * // setup and teardown right next to each other
+ * @Before
+ * fun setUp() {
+ * val oldTimeout = getGlobalTimeout()
+ * teardownRule.onTeardown { setGlobalTimeout(oldTimeout) }
+ * overrideGlobalTimeout(5000)
+ * }
+ *
+ * // add teardown logic for fixtures that aren't used in every test
+ * fun addCustomer() {
+ * val id = globalDatabase.addCustomer(TEST_NAME, TEST_ADDRESS, ...)
+ * teardownRule.onTeardown { globalDatabase.deleteCustomer(id) }
+ * }
+ * ```
+ */
+class OnTeardownRule : TestWatcher() {
+ private var canAdd = true
+ private val teardowns = mutableListOf<() -> Unit>()
+
+ fun onTeardown(teardownRunnable: () -> Unit) {
+ if (!canAdd) {
+ throw IllegalStateException("Cannot add new teardown routines after test complete.")
+ }
+ teardowns.add(teardownRunnable)
+ }
+
+ fun onTeardown(teardownRunnable: Runnable) {
+ if (!canAdd) {
+ throw IllegalStateException("Cannot add new teardown routines after test complete.")
+ }
+ teardowns.add { teardownRunnable.run() }
+ }
+
+ override fun finished(description: Description?) {
+ canAdd = false
+ val errors = mutableListOf<Throwable>()
+ teardowns.reversed().forEach {
+ try {
+ it()
+ } catch (e: Throwable) {
+ errors.add(e)
+ }
+ }
+ MultipleFailureException.assertEmpty(errors)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index 27a2cab..153a8be 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -56,6 +56,8 @@
import java.io.FileInputStream;
import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
@@ -69,6 +71,17 @@
// background on Ravenwood is available at go/ravenwood-docs
@DisabledOnRavenwood
public abstract class SysuiTestCase {
+ /**
+ * Especially when self-testing test utilities, we may have classes that look like test
+ * classes, but we don't expect to ever actually run as a top-level test.
+ * For example, {@link com.android.systemui.TryToDoABadThing}.
+ * Verifying properties on these as a part of structural tests like
+ * AAAPlusPlusVerifySysuiRequiredTestPropertiesTest is a waste of our time, and makes things
+ * look more confusing, so this lets us skip when appropriate.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface SkipSysuiVerification {
+ }
private static final String TAG = "SysuiTestCase";
@@ -172,6 +185,15 @@
public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
new DexmakerShareClassLoaderRule();
+ @Rule public final OnTeardownRule mTearDownRule = new OnTeardownRule();
+
+ /**
+ * Schedule a cleanup routine to happen when the test state is torn down.
+ */
+ protected void onTeardown(Runnable tearDownRunnable) {
+ mTearDownRule.onTeardown(tearDownRunnable);
+ }
+
// set the highest order so it's the innermost rule
@Rule(order = Integer.MAX_VALUE)
public TestWithLooperRule mlooperRule = new TestWithLooperRule();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
index 3041240..b8be6aa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
@@ -29,6 +29,8 @@
import android.util.Log;
import android.view.Display;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.GuardedBy;
import com.android.systemui.res.R;
@@ -43,6 +45,9 @@
private final Map<UserHandle, Context> mContextForUser = new HashMap<>();
private final Map<String, Context> mContextForPackage = new HashMap<>();
+ @Nullable
+ private Display mCustomDisplay;
+
public SysuiTestableContext(Context base) {
super(base);
setTheme(R.style.Theme_SystemUI);
@@ -64,6 +69,18 @@
return context;
}
+ public void setDisplay(Display display) {
+ mCustomDisplay = display;
+ }
+
+ @Override
+ public Display getDisplay() {
+ if (mCustomDisplay != null) {
+ return mCustomDisplay;
+ }
+ return super.getDisplay();
+ }
+
public SysuiTestableContext createDefaultDisplayContext() {
Display display = getBaseContext().getSystemService(DisplayManager.class).getDisplays()[0];
return (SysuiTestableContext) createDisplayContext(display);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorKosmos.kt
index 7e0e5f3..f876003 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorKosmos.kt
@@ -20,4 +20,4 @@
import com.android.systemui.kosmos.Kosmos
var Kosmos.configurationInteractor: ConfigurationInteractor by
- Kosmos.Fixture { ConfigurationInteractor(configurationRepository) }
+ Kosmos.Fixture { ConfigurationInteractorImpl(configurationRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index b27dadc..3b175853de7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -42,15 +42,6 @@
val id = nextWidgetId++
val providerInfo = createAppWidgetProviderInfo(provider, user.identifier)
- fakeDatabase[id] =
- CommunalWidgetContentModel.Available(
- appWidgetId = id,
- rank = rank ?: 0,
- providerInfo = providerInfo,
- spanY = 3,
- )
- updateListFromDatabase()
-
val configured = configurator?.configureWidget(id) != false
if (configured) {
onConfigured(id, providerInfo, rank ?: -1)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryKosmos.kt
similarity index 73%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryKosmos.kt
index 94b2bdf..4bcff55 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryKosmos.kt
@@ -14,9 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.util.settings
+package com.android.systemui.decor
+import android.content.testableContext
import com.android.systemui.kosmos.Kosmos
-val Kosmos.userAwareSecureSettingsRepository by
- Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+var Kosmos.privacyDotDecorProviderFactory by
+ Kosmos.Fixture {
+ PrivacyDotDecorProviderFactory(testableContext.orCreateTestableResources.resources)
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
index 8c4ec4c..4a6e273 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
@@ -19,7 +19,7 @@
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -77,7 +77,7 @@
repository = repository,
powerInteractor = powerInteractor,
bouncerRepository = bouncerRepository,
- configurationInteractor = ConfigurationInteractor(configurationRepository),
+ configurationInteractor = ConfigurationInteractorImpl(configurationRepository),
shadeRepository = shadeRepository,
keyguardTransitionInteractor = keyguardTransitionInteractor,
sceneInteractorProvider = { sceneInteractor },
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
index ddae581..72cb1df 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
@@ -1,8 +1,10 @@
package com.android.systemui.kosmos
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.Kosmos.Fixture
import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
@@ -45,3 +47,5 @@
testScope.runTest { this@runTest.testBody() }
fun Kosmos.runCurrent() = testScope.runCurrent()
+
+fun <T> Kosmos.collectLastValue(flow: Flow<T>) = testScope.collectLastValue(flow)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 5eaa198..63e6eb6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -51,6 +51,8 @@
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.pulseExpansionInteractor
+import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGlanceableHubTransitionViewModel
import com.android.systemui.model.sceneContainerPlugin
import com.android.systemui.plugins.statusbar.statusBarStateController
import com.android.systemui.power.data.repository.fakePowerRepository
@@ -68,6 +70,8 @@
import com.android.systemui.shade.shadeController
import com.android.systemui.shade.ui.viewmodel.notificationShadeWindowModel
import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
+import com.android.systemui.statusbar.data.repository.fakeStatusBarModePerDisplayRepository
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
@@ -106,6 +110,7 @@
val bouncerRepository by lazy { kosmos.bouncerRepository }
val communalRepository by lazy { kosmos.fakeCommunalSceneRepository }
val communalTransitionViewModel by lazy { kosmos.communalTransitionViewModel }
+ val activeNotificationsInteractor by lazy { kosmos.activeNotificationsInteractor }
val headsUpNotificationInteractor by lazy { kosmos.headsUpNotificationInteractor }
val seenNotificationsInteractor by lazy { kosmos.seenNotificationsInteractor }
val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
@@ -119,6 +124,7 @@
val mobileConnectionsRepository by lazy { kosmos.fakeMobileConnectionsRepository }
val simBouncerInteractor by lazy { kosmos.simBouncerInteractor }
val statusBarStateController by lazy { kosmos.statusBarStateController }
+ val statusBarModePerDisplayRepository by lazy { kosmos.fakeStatusBarModePerDisplayRepository }
val interactionJankMonitor by lazy { kosmos.interactionJankMonitor }
val fakeSceneContainerConfig by lazy { kosmos.sceneContainerConfig }
val sceneInteractor by lazy { kosmos.sceneInteractor }
@@ -164,4 +170,11 @@
val msdlPlayer by lazy { kosmos.fakeMSDLPlayer }
val shadeModeInteractor by lazy { kosmos.shadeModeInteractor }
val bouncerHapticHelper by lazy { kosmos.bouncerHapticPlayer }
+
+ val glanceableHubToLockscreenTransitionViewModel by lazy {
+ kosmos.glanceableHubToLockscreenTransitionViewModel
+ }
+ val lockscreenToGlanceableHubTransitionViewModel by lazy {
+ kosmos.lockscreenToGlanceableHubTransitionViewModel
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryKosmos.kt
similarity index 78%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryKosmos.kt
index 94b2bdf..7e7eea2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryKosmos.kt
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.util.settings
+package com.android.systemui.media.controls.domain.pipeline
import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
-val Kosmos.userAwareSecureSettingsRepository by
- Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+var Kosmos.media3ActionFactory: Media3ActionFactory by Kosmos.Fixture { mock {} }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt
index cb7750f5..af6a0c5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt
@@ -34,6 +34,7 @@
fakeMediaControllerFactory,
mediaFlags,
imageLoader,
- statusBarManager
+ statusBarManager,
+ media3ActionFactory,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt
index 7f8348e..b833750 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt
@@ -18,21 +18,32 @@
import android.content.Context
import android.media.session.MediaController
-import android.media.session.MediaSession
import android.media.session.MediaSession.Token
+import android.os.Looper
+import androidx.media3.session.MediaController as Media3Controller
+import androidx.media3.session.SessionToken
class FakeMediaControllerFactory(context: Context) : MediaControllerFactory(context) {
private val mediaControllersForToken = mutableMapOf<Token, MediaController>()
+ private var media3Controller: Media3Controller? = null
- override fun create(token: MediaSession.Token): android.media.session.MediaController {
+ override fun create(token: Token): MediaController {
if (token !in mediaControllersForToken) {
super.create(token)
}
return mediaControllersForToken[token]!!
}
+ override suspend fun create(token: SessionToken, looper: Looper): Media3Controller {
+ return media3Controller ?: super.create(token, looper)
+ }
+
fun setControllerForToken(token: Token, mediaController: MediaController) {
mediaControllersForToken[token] = mediaController
}
+
+ fun setMedia3Controller(mediaController: Media3Controller) {
+ media3Controller = mediaController
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeSessionTokenFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeSessionTokenFactory.kt
new file mode 100644
index 0000000..94e0bca
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeSessionTokenFactory.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.media.controls.util
+
+import android.content.Context
+import android.media.session.MediaSession.Token
+import androidx.media3.session.SessionToken
+
+class FakeSessionTokenFactory(context: Context) : SessionTokenFactory(context) {
+ private var sessionToken: SessionToken? = null
+
+ override suspend fun createTokenFromLegacy(token: Token): SessionToken {
+ return sessionToken ?: super.createTokenFromLegacy(token)
+ }
+
+ fun setMedia3SessionToken(token: SessionToken) {
+ sessionToken = token
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/SessionTokenFactoryKosmos.kt
similarity index 77%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/SessionTokenFactoryKosmos.kt
index 94b2bdf..8e473042 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/SessionTokenFactoryKosmos.kt
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.util.settings
+package com.android.systemui.media.controls.util
+import android.content.applicationContext
import com.android.systemui.kosmos.Kosmos
-val Kosmos.userAwareSecureSettingsRepository by
- Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+val Kosmos.fakeSessionTokenFactory by Kosmos.Fixture { FakeSessionTokenFactory(applicationContext) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
index f66125a..6787b8e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
@@ -52,7 +52,7 @@
)
object : QSTileViewModel {
override val state: StateFlow<QSTileState?> =
- MutableStateFlow(QSTileState.build({ null }, tileSpec.spec) {})
+ MutableStateFlow(QSTileState.build(null, tileSpec.spec) {})
override val config: QSTileConfig = config
override val isAvailable: StateFlow<Boolean> = MutableStateFlow(true)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
index 5b6fd8c..ab1c181 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
@@ -44,7 +44,7 @@
check("other").that(other).isNotNull()
other ?: return
}
- check("icon").that(actual.icon()).isEqualTo(other.icon())
+ check("icon").that(actual.icon).isEqualTo(other.icon)
check("iconRes").that(actual.iconRes).isEqualTo(other.iconRes)
check("label").that(actual.label).isEqualTo(other.label)
check("activationState").that(actual.activationState).isEqualTo(other.activationState)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt
index 8c218be..50a19a9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt
@@ -16,11 +16,13 @@
package com.android.systemui.statusbar.core
+import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository
import com.android.systemui.statusbar.window.StatusBarWindowController
class FakeStatusBarInitializerFactory() : StatusBarInitializer.Factory {
override fun create(
- statusBarWindowController: StatusBarWindowController
+ statusBarWindowController: StatusBarWindowController,
+ statusBarModePerDisplayRepository: StatusBarModePerDisplayRepository,
): StatusBarInitializer = FakeStatusBarInitializer()
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt
index 303529b..6e99027 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt
@@ -19,6 +19,7 @@
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore
val Kosmos.fakeStatusBarInitializer by Kosmos.Fixture { FakeStatusBarInitializer() }
@@ -37,6 +38,7 @@
displayRepository,
fakeStatusBarInitializerFactory,
fakeStatusBarWindowControllerStore,
+ fakeStatusBarModeRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
index 87f7142..ad2654a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
@@ -29,6 +29,7 @@
import com.android.systemui.shade.mockNotificationShadeWindowViewController
import com.android.systemui.shade.mockShadeSurface
import com.android.systemui.statusbar.data.repository.fakeStatusBarModePerDisplayRepository
+import com.android.systemui.statusbar.data.repository.privacyDotWindowControllerStore
import com.android.systemui.statusbar.data.repository.statusBarModeRepository
import com.android.systemui.statusbar.mockNotificationRemoteInputManager
import com.android.systemui.statusbar.phone.mockAutoHideController
@@ -77,5 +78,6 @@
statusBarInitializerStore,
statusBarWindowControllerStore,
statusBarInitializerStore,
+ privacyDotWindowControllerStore,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotViewControllerStore.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotViewControllerStore.kt
new file mode 100644
index 0000000..27845aa
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotViewControllerStore.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.data.repository
+
+import android.view.Display
+import com.android.systemui.statusbar.events.PrivacyDotViewController
+import org.mockito.kotlin.mock
+
+class FakePrivacyDotViewControllerStore : PrivacyDotViewControllerStore {
+ private val perDisplayMockControllers = mutableMapOf<Int, PrivacyDotViewController>()
+
+ override val defaultDisplay: PrivacyDotViewController
+ get() = forDisplay(Display.DEFAULT_DISPLAY)
+
+ override fun forDisplay(displayId: Int): PrivacyDotViewController {
+ return perDisplayMockControllers.computeIfAbsent(displayId) { mock() }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotWindowControllerStore.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotWindowControllerStore.kt
new file mode 100644
index 0000000..f0aacc0d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotWindowControllerStore.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.data.repository
+
+import android.view.Display
+import com.android.systemui.statusbar.events.PrivacyDotWindowController
+import org.mockito.kotlin.mock
+
+class FakePrivacyDotWindowControllerStore : PrivacyDotWindowControllerStore {
+
+ private val perDisplayMockControllers = mutableMapOf<Int, PrivacyDotWindowController>()
+
+ override val defaultDisplay: PrivacyDotWindowController
+ get() = forDisplay(Display.DEFAULT_DISPLAY)
+
+ override fun forDisplay(displayId: Int): PrivacyDotWindowController {
+ return perDisplayMockControllers.computeIfAbsent(displayId) { mock() }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
index 285cebb..8712b02 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
@@ -20,8 +20,10 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.data.model.StatusBarAppearance
import com.android.systemui.statusbar.data.model.StatusBarMode
+import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent
import dagger.Binds
import dagger.Module
+import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
@@ -53,6 +55,14 @@
override fun clearTransient() {
isTransientShown.value = false
}
+
+ override fun start() {}
+
+ override fun stop() {}
+
+ override fun onStatusBarViewInitialized(component: HomeStatusBarComponent) {}
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {}
}
@Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeSystemEventChipAnimationControllerStore.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeSystemEventChipAnimationControllerStore.kt
new file mode 100644
index 0000000..fa9f1bf
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeSystemEventChipAnimationControllerStore.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.data.repository
+
+import android.view.Display
+import com.android.systemui.statusbar.events.SystemEventChipAnimationController
+import org.mockito.kotlin.mock
+
+class FakeSystemEventChipAnimationControllerStore : SystemEventChipAnimationControllerStore {
+
+ private val perDisplayMocks = mutableMapOf<Int, SystemEventChipAnimationController>()
+
+ override val defaultDisplay: SystemEventChipAnimationController
+ get() = forDisplay(Display.DEFAULT_DISPLAY)
+
+ override fun forDisplay(displayId: Int): SystemEventChipAnimationController {
+ return perDisplayMocks.computeIfAbsent(displayId) { mock() }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreKosmos.kt
new file mode 100644
index 0000000..5f33732
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.data.repository
+
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.display.data.repository.displayScopeRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import org.mockito.kotlin.mock
+
+val Kosmos.lightBarControllerStoreImpl by
+ Kosmos.Fixture {
+ LightBarControllerStoreImpl(
+ backgroundApplicationScope = applicationCoroutineScope,
+ displayRepository = displayRepository,
+ factory = { _, _, _ -> mock() },
+ displayScopeRepository = displayScopeRepository,
+ statusBarModeRepositoryStore = statusBarModeRepository,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStoreKosmos.kt
similarity index 68%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStoreKosmos.kt
index 94b2bdf..3d428a1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStoreKosmos.kt
@@ -14,9 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.util.settings
+package com.android.systemui.statusbar.data.repository
import com.android.systemui.kosmos.Kosmos
-val Kosmos.userAwareSecureSettingsRepository by
- Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+val Kosmos.fakePrivacyDotViewControllerStore by
+ Kosmos.Fixture { FakePrivacyDotViewControllerStore() }
+
+var Kosmos.privacyDotViewControllerStore: PrivacyDotViewControllerStore by
+ Kosmos.Fixture { fakePrivacyDotViewControllerStore }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreKosmos.kt
new file mode 100644
index 0000000..aae32cfa
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreKosmos.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.data.repository
+
+import android.view.WindowManager
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.display.data.repository.displayWindowPropertiesRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import org.mockito.kotlin.mock
+
+val Kosmos.fakePrivacyDotWindowControllerStore by
+ Kosmos.Fixture { FakePrivacyDotWindowControllerStore() }
+
+val Kosmos.privacyDotWindowControllerStoreImpl by
+ Kosmos.Fixture {
+ PrivacyDotWindowControllerStoreImpl(
+ backgroundApplicationScope = applicationCoroutineScope,
+ displayRepository = displayRepository,
+ windowControllerFactory = { _, _, _, _ -> mock() },
+ displayWindowPropertiesRepository = displayWindowPropertiesRepository,
+ privacyDotViewControllerStore = privacyDotViewControllerStore,
+ viewCaptureAwareWindowManagerFactory =
+ object : ViewCaptureAwareWindowManager.Factory {
+ override fun create(
+ windowManager: WindowManager
+ ): ViewCaptureAwareWindowManager {
+ return mock()
+ }
+ },
+ )
+ }
+
+var Kosmos.privacyDotWindowControllerStore: PrivacyDotWindowControllerStore by
+ Kosmos.Fixture { fakePrivacyDotWindowControllerStore }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt
index 12db2f741..a585602 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt
@@ -16,7 +16,10 @@
package com.android.systemui.statusbar.data.repository
+import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import org.mockito.kotlin.mock
val Kosmos.fakeStatusBarModePerDisplayRepository by
Kosmos.Fixture { FakeStatusBarModePerDisplayRepository() }
@@ -24,3 +27,21 @@
val Kosmos.statusBarModeRepository: StatusBarModeRepositoryStore by
Kosmos.Fixture { fakeStatusBarModeRepository }
val Kosmos.fakeStatusBarModeRepository by Kosmos.Fixture { FakeStatusBarModeRepository() }
+val Kosmos.fakeStatusBarModePerDisplayRepositoryFactory by
+ Kosmos.Fixture { FakeStatusBarModePerDisplayRepositoryFactory() }
+
+val Kosmos.multiDisplayStatusBarModeRepositoryStore by
+ Kosmos.Fixture {
+ MultiDisplayStatusBarModeRepositoryStore(
+ applicationCoroutineScope,
+ fakeStatusBarModePerDisplayRepositoryFactory,
+ displayRepository,
+ )
+ }
+
+class FakeStatusBarModePerDisplayRepositoryFactory : StatusBarModePerDisplayRepositoryFactory {
+
+ override fun create(displayId: Int): StatusBarModePerDisplayRepositoryImpl {
+ return mock<StatusBarModePerDisplayRepositoryImpl>()
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreKosmos.kt
new file mode 100644
index 0000000..f0c8f4b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreKosmos.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.data.repository
+
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.display.data.repository.displayWindowPropertiesRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.window.statusBarWindowControllerStore
+import org.mockito.kotlin.mock
+
+val Kosmos.fakeSystemEventChipAnimationControllerStore by
+ Kosmos.Fixture { FakeSystemEventChipAnimationControllerStore() }
+
+val Kosmos.systemEventChipAnimationControllerStoreImpl by
+ Kosmos.Fixture {
+ SystemEventChipAnimationControllerStoreImpl(
+ backgroundApplicationScope = applicationCoroutineScope,
+ displayRepository = displayRepository,
+ factory = { _, _, _ -> mock() },
+ displayWindowPropertiesRepository = displayWindowPropertiesRepository,
+ statusBarWindowControllerStore = statusBarWindowControllerStore,
+ statusBarContentInsetsProviderStore = statusBarContentInsetsProviderStore,
+ )
+ }
+
+var Kosmos.systemEventChipAnimationControllerStore by
+ Kosmos.Fixture { fakeSystemEventChipAnimationControllerStore }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/FakePrivacyDotViewController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/FakePrivacyDotViewController.kt
new file mode 100644
index 0000000..53c39a6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/FakePrivacyDotViewController.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.events
+
+import android.view.View
+
+class FakePrivacyDotViewController : PrivacyDotViewController {
+
+ var topLeft: View? = null
+ private set
+
+ var topRight: View? = null
+ private set
+
+ var bottomLeft: View? = null
+ private set
+
+ var bottomRight: View? = null
+ private set
+
+ var isInitialized = false
+ private set
+
+ override fun stop() {}
+
+ override var currentViewState: ViewState = ViewState()
+
+ override var showingListener: PrivacyDotViewController.ShowingListener? = null
+
+ override fun setNewRotation(rot: Int) {}
+
+ override fun hideDotView(dot: View, animate: Boolean) {}
+
+ override fun showDotView(dot: View, animate: Boolean) {}
+
+ override fun updateRotations(rotation: Int, paddingTop: Int) {}
+
+ override fun setCornerSizes(state: ViewState) {}
+
+ override fun initialize(topLeft: View, topRight: View, bottomLeft: View, bottomRight: View) {
+ this.topLeft = topLeft
+ this.topRight = topRight
+ this.bottomLeft = bottomLeft
+ this.bottomRight = bottomRight
+ isInitialized = true
+ }
+
+ override fun updateDotView(state: ViewState) {}
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerKosmos.kt
similarity index 64%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerKosmos.kt
index 94b2bdf..9cbc975 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerKosmos.kt
@@ -14,9 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.util.settings
+package com.android.systemui.statusbar.events
import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
-val Kosmos.userAwareSecureSettingsRepository by
- Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+val Kosmos.mockPrivacyDotViewController by Kosmos.Fixture { mock<PrivacyDotViewController>() }
+
+val Kosmos.fakePrivacyDotViewController by Kosmos.Fixture { FakePrivacyDotViewController() }
+
+var Kosmos.privacyDotViewController by Kosmos.Fixture { fakePrivacyDotViewController }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerKosmos.kt
new file mode 100644
index 0000000..c738387
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerKosmos.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.events
+
+import android.content.testableContext
+import android.view.layoutInflater
+import com.android.app.viewcapture.realCaptureAwareWindowManager
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.decor.privacyDotDecorProviderFactory
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.privacyDotWindowController by
+ Kosmos.Fixture {
+ PrivacyDotWindowController(
+ testableContext.displayId,
+ privacyDotViewController,
+ realCaptureAwareWindowManager,
+ layoutInflater,
+ fakeExecutor,
+ privacyDotDecorProviderFactory,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerKosmos.kt
new file mode 100644
index 0000000..186b045
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.events
+
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.data.repository.systemEventChipAnimationControllerStore
+
+val Kosmos.multiDisplaySystemEventChipAnimationController by
+ Kosmos.Fixture {
+ MultiDisplaySystemEventChipAnimationController(
+ displayRepository,
+ systemEventChipAnimationControllerStore,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
index 76bdc0d..32c582f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
@@ -28,6 +28,7 @@
key: String,
groupKey: String? = null,
whenTime: Long = 0L,
+ isPromoted: Boolean = false,
isAmbient: Boolean = false,
isRowDismissed: Boolean = false,
isSilent: Boolean = false,
@@ -50,6 +51,7 @@
key = key,
groupKey = groupKey,
whenTime = whenTime,
+ isPromoted = isPromoted,
isAmbient = isAmbient,
isRowDismissed = isRowDismissed,
isSilent = isSilent,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt
index f7acae9..067193f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt
@@ -19,8 +19,13 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.statusbar.notification.collection.provider.sectionStyleProvider
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.promoted.promotedNotificationsProvider
val Kosmos.renderNotificationListInteractor by
Kosmos.Fixture {
- RenderNotificationListInteractor(activeNotificationListRepository, sectionStyleProvider)
+ RenderNotificationListInteractor(
+ activeNotificationListRepository,
+ sectionStyleProvider,
+ promotedNotificationsProvider,
+ )
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderKosmos.kt
similarity index 80%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderKosmos.kt
index 94b2bdf..a7aa0b4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderKosmos.kt
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-package com.android.systemui.util.settings
+package com.android.systemui.statusbar.notification.promoted
import com.android.systemui.kosmos.Kosmos
-val Kosmos.userAwareSecureSettingsRepository by
- Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+val Kosmos.promotedNotificationsProvider by Kosmos.Fixture { PromotedNotificationsProviderImpl() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/viewmodel/StatusBarUserChipViewModelKosmos.kt
similarity index 65%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/viewmodel/StatusBarUserChipViewModelKosmos.kt
index 94b2bdf..01175a5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/viewmodel/StatusBarUserChipViewModelKosmos.kt
@@ -14,9 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.util.settings
+package com.android.systemui.statusbar.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.user.domain.interactor.userSwitcherInteractor
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel
-val Kosmos.userAwareSecureSettingsRepository by
- Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+val Kosmos.statusBarUserChipViewModel: StatusBarUserChipViewModel by
+ Kosmos.Fixture { StatusBarUserChipViewModel(userSwitcherInteractor) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeUserAwareSecureSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeUserAwareSecureSettingsRepository.kt
deleted file mode 100644
index 5054e29..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeUserAwareSecureSettingsRepository.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.util.settings
-
-import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.map
-
-class FakeUserAwareSecureSettingsRepository : UserAwareSecureSettingsRepository {
-
- private val settings = MutableStateFlow<Map<String, Boolean>>(mutableMapOf())
-
- override fun boolSettingForActiveUser(name: String, defaultValue: Boolean): Flow<Boolean> {
- return settings.map { it.getOrDefault(name, defaultValue) }
- }
-
- fun setBoolSettingForActiveUser(name: String, value: Boolean) {
- settings.value = settings.value.toMutableMap().apply { this[name] = value }
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/UserAwareSecureSettingsRepositoryKosmos.kt
new file mode 100644
index 0000000..dc10ca9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/UserAwareSecureSettingsRepositoryKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.settings.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.backgroundCoroutineContext
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.user.data.repository.userRepository
+import com.android.systemui.util.settings.fakeSettings
+import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
+
+val Kosmos.userAwareSecureSettingsRepository by
+ Kosmos.Fixture {
+ UserAwareSecureSettingsRepository(
+ fakeSettings,
+ userRepository,
+ testDispatcher,
+ backgroundCoroutineContext,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/UserAwareSystemSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/UserAwareSystemSettingsRepositoryKosmos.kt
new file mode 100644
index 0000000..ff77908
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/UserAwareSystemSettingsRepositoryKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.settings.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.backgroundCoroutineContext
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.user.data.repository.userRepository
+import com.android.systemui.util.settings.fakeSettings
+import com.android.systemui.util.settings.repository.UserAwareSystemSettingsRepository
+
+val Kosmos.userAwareSystemSettingsRepository by
+ Kosmos.Fixture {
+ UserAwareSystemSettingsRepository(
+ fakeSettings,
+ userRepository,
+ testDispatcher,
+ backgroundCoroutineContext,
+ )
+ }
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
index 66a6890..869d854 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
@@ -26,12 +26,10 @@
import android.os.Bundle;
import android.platform.test.annotations.RavenwoodTestRunnerInitializing;
import android.platform.test.annotations.internal.InnerRunner;
-import android.platform.test.ravenwood.RavenwoodTestStats.Result;
import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
-import org.junit.AssumptionViolatedException;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runner.Runner;
@@ -171,10 +169,11 @@
final var notifier = new RavenwoodRunNotifier(realNotifier);
final var description = getDescription();
+ RavenwoodTestStats.getInstance().attachToRunNotifier(notifier);
+
if (mRealRunner instanceof ClassSkippingTestRunner) {
- mRealRunner.run(notifier);
Log.i(TAG, "onClassSkipped: description=" + description);
- RavenwoodTestStats.getInstance().onClassSkipped(description);
+ mRealRunner.run(notifier);
return;
}
@@ -205,7 +204,6 @@
if (!skipRunnerHook) {
try {
- RavenwoodTestStats.getInstance().onClassFinished(description);
mState.exitTestClass();
} catch (Throwable th) {
notifier.reportAfterTestFailure(th);
@@ -295,8 +293,6 @@
// method-level annotations here.
if (scope == Scope.Instance && order == Order.Outer) {
if (!RavenwoodEnablementChecker.shouldEnableOnRavenwood(description, true)) {
- RavenwoodTestStats.getInstance().onTestFinished(
- classDescription, description, Result.Skipped);
return false;
}
}
@@ -317,16 +313,6 @@
// End of a test method.
mState.exitTestMethod();
- final Result result;
- if (th == null) {
- result = Result.Passed;
- } else if (th instanceof AssumptionViolatedException) {
- result = Result.Skipped;
- } else {
- result = Result.Failed;
- }
-
- RavenwoodTestStats.getInstance().onTestFinished(classDescription, description, result);
}
// If RUN_DISABLED_TESTS is set, and the method did _not_ throw, make it an error.
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
index 0f16352..28c262d 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -40,6 +40,7 @@
import android.os.Bundle;
import android.os.HandlerThread;
import android.os.Looper;
+import android.os.Process_ravenwood;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.provider.DeviceConfig_host;
@@ -52,6 +53,7 @@
import com.android.hoststubgen.hosthelper.HostTestUtils;
import com.android.internal.os.RuntimeInit;
import com.android.ravenwood.RavenwoodRuntimeNative;
+import com.android.ravenwood.RavenwoodRuntimeState;
import com.android.ravenwood.common.RavenwoodCommonUtils;
import com.android.ravenwood.common.RavenwoodRuntimeException;
import com.android.ravenwood.common.SneakyThrow;
@@ -199,7 +201,7 @@
*/
public static void init(RavenwoodAwareTestRunner runner) {
if (RAVENWOOD_VERBOSE_LOGGING) {
- Log.i(TAG, "init() called here: " + runner, new RuntimeException("STACKTRACE"));
+ Log.v(TAG, "init() called here: " + runner, new RuntimeException("STACKTRACE"));
}
if (sRunner == runner) {
return;
@@ -223,7 +225,9 @@
Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler);
}
- android.os.Process.init$ravenwood(config.mUid, config.mPid);
+ RavenwoodRuntimeState.sUid = config.mUid;
+ RavenwoodRuntimeState.sPid = config.mPid;
+ RavenwoodRuntimeState.sTargetSdkLevel = config.mTargetSdkLevel;
sOriginalIdentityToken = Binder.clearCallingIdentity();
reinit();
setSystemProperties(config.mSystemProperties);
@@ -310,7 +314,7 @@
*/
public static void reset() {
if (RAVENWOOD_VERBOSE_LOGGING) {
- Log.i(TAG, "reset() called here", new RuntimeException("STACKTRACE"));
+ Log.v(TAG, "reset() called here", new RuntimeException("STACKTRACE"));
}
if (sRunner == null) {
throw new RavenwoodRuntimeException("Internal error: reset() already called");
@@ -350,8 +354,8 @@
if (sOriginalIdentityToken != -1) {
Binder.restoreCallingIdentity(sOriginalIdentityToken);
}
- android.os.Process.reset$ravenwood();
-
+ RavenwoodRuntimeState.reset();
+ Process_ravenwood.reset();
DeviceConfig_host.reset();
try {
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
index 016de8e..7870585 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
@@ -18,6 +18,9 @@
import android.util.Log;
import org.junit.runner.Description;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunListener;
+import org.junit.runner.notification.RunNotifier;
import java.io.File;
import java.io.IOException;
@@ -27,7 +30,7 @@
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
-import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.Map;
/**
@@ -39,7 +42,7 @@
*/
public class RavenwoodTestStats {
private static final String TAG = "RavenwoodTestStats";
- private static final String HEADER = "Module,Class,ClassDesc,Passed,Failed,Skipped";
+ private static final String HEADER = "Module,Class,OuterClass,Passed,Failed,Skipped";
private static RavenwoodTestStats sInstance;
@@ -66,7 +69,7 @@
private final PrintWriter mOutputWriter;
private final String mTestModuleName;
- public final Map<Description, Map<Description, Result>> mStats = new HashMap<>();
+ public final Map<String, Map<String, Result>> mStats = new LinkedHashMap<>();
/** Ctor */
public RavenwoodTestStats() {
@@ -115,75 +118,129 @@
return cwd.getName();
}
- private void addResult(Description classDescription, Description methodDescription,
+ private void addResult(String className, String methodName,
Result result) {
- mStats.compute(classDescription, (classDesc, value) -> {
+ mStats.compute(className, (className_, value) -> {
if (value == null) {
- value = new HashMap<>();
+ value = new LinkedHashMap<>();
}
- value.put(methodDescription, result);
+ // If the result is already set, don't overwrite it.
+ if (!value.containsKey(methodName)) {
+ value.put(methodName, result);
+ }
return value;
});
}
/**
- * Call it when a test class is skipped.
- */
- public void onClassSkipped(Description classDescription) {
- addResult(classDescription, Description.EMPTY, Result.Skipped);
- onClassFinished(classDescription);
- }
-
- /**
* Call it when a test method is finished.
*/
- public void onTestFinished(Description classDescription, Description testDescription,
- Result result) {
- addResult(classDescription, testDescription, result);
+ private void onTestFinished(String className, String testName, Result result) {
+ addResult(className, testName, result);
}
/**
- * Call it when a test class is finished.
+ * Dump all the results and clear it.
*/
- public void onClassFinished(Description classDescription) {
- int passed = 0;
- int skipped = 0;
- int failed = 0;
- var stats = mStats.get(classDescription);
- if (stats == null) {
- return;
- }
- for (var e : stats.values()) {
- switch (e) {
- case Passed: passed++; break;
- case Skipped: skipped++; break;
- case Failed: failed++; break;
+ private void dumpAllAndClear() {
+ for (var entry : mStats.entrySet()) {
+ int passed = 0;
+ int skipped = 0;
+ int failed = 0;
+ var className = entry.getKey();
+
+ for (var e : entry.getValue().values()) {
+ switch (e) {
+ case Passed:
+ passed++;
+ break;
+ case Skipped:
+ skipped++;
+ break;
+ case Failed:
+ failed++;
+ break;
+ }
}
+
+ mOutputWriter.printf("%s,%s,%s,%d,%d,%d\n",
+ mTestModuleName, className, getOuterClassName(className),
+ passed, failed, skipped);
}
-
- var testClass = extractTestClass(classDescription);
-
- mOutputWriter.printf("%s,%s,%s,%d,%d,%d\n",
- mTestModuleName, (testClass == null ? "?" : testClass.getCanonicalName()),
- classDescription, passed, failed, skipped);
mOutputWriter.flush();
+ mStats.clear();
}
- /**
- * Try to extract the class from a description, which is needed because
- * ParameterizedAndroidJunit4's description doesn't contain a class.
- */
- private Class<?> extractTestClass(Description desc) {
- if (desc.getTestClass() != null) {
- return desc.getTestClass();
+ private static String getOuterClassName(String className) {
+ // Just delete the '$', because I'm not sure if the className we get here is actaully a
+ // valid class name that does exist. (it might have a parameter name, etc?)
+ int p = className.indexOf('$');
+ if (p < 0) {
+ return className;
}
- // Look into the children.
- for (var child : desc.getChildren()) {
- var fromChild = extractTestClass(child);
- if (fromChild != null) {
- return fromChild;
- }
- }
- return null;
+ return className.substring(0, p);
}
+
+ public void attachToRunNotifier(RunNotifier notifier) {
+ notifier.addListener(mRunListener);
+ }
+
+ private final RunListener mRunListener = new RunListener() {
+ @Override
+ public void testSuiteStarted(Description description) {
+ Log.d(TAG, "testSuiteStarted: " + description);
+ }
+
+ @Override
+ public void testSuiteFinished(Description description) {
+ Log.d(TAG, "testSuiteFinished: " + description);
+ }
+
+ @Override
+ public void testRunStarted(Description description) {
+ Log.d(TAG, "testRunStarted: " + description);
+ }
+
+ @Override
+ public void testRunFinished(org.junit.runner.Result result) {
+ Log.d(TAG, "testRunFinished: " + result);
+
+ dumpAllAndClear();
+ }
+
+ @Override
+ public void testStarted(Description description) {
+ Log.d(TAG, " testStarted: " + description);
+ }
+
+ @Override
+ public void testFinished(Description description) {
+ Log.d(TAG, " testFinished: " + description);
+
+ // Send "Passed", but if there's already another result sent for this, this won't
+ // override it.
+ onTestFinished(description.getClassName(), description.getMethodName(), Result.Passed);
+ }
+
+ @Override
+ public void testFailure(Failure failure) {
+ Log.d(TAG, " testFailure: " + failure);
+
+ var description = failure.getDescription();
+ onTestFinished(description.getClassName(), description.getMethodName(), Result.Failed);
+ }
+
+ @Override
+ public void testAssumptionFailure(Failure failure) {
+ Log.d(TAG, " testAssumptionFailure: " + failure);
+ var description = failure.getDescription();
+ onTestFinished(description.getClassName(), description.getMethodName(), Result.Skipped);
+ }
+
+ @Override
+ public void testIgnored(Description description) {
+ Log.d(TAG, " testIgnored: " + description);
+ onTestFinished(description.getClassName(), description.getMethodName(), Result.Skipped);
+ }
+ };
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
index 37b0abc..d8f2b70 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
@@ -23,6 +23,7 @@
import android.annotation.Nullable;
import android.app.Instrumentation;
import android.content.Context;
+import android.os.Build;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -67,7 +68,7 @@
String mTargetPackageName;
int mMinSdkLevel;
- int mTargetSdkLevel;
+ int mTargetSdkLevel = Build.VERSION_CODES.CUR_DEVELOPMENT;
boolean mProvideMainThread = false;
diff --git a/ravenwood/runtime-helper-src/framework/android/os/Process_ravenwood.java b/ravenwood/runtime-helper-src/framework/android/os/Process_ravenwood.java
new file mode 100644
index 0000000..3c6a4d7
--- /dev/null
+++ b/ravenwood/runtime-helper-src/framework/android/os/Process_ravenwood.java
@@ -0,0 +1,70 @@
+/*
+ * 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.os;
+
+import android.util.Pair;
+
+public class Process_ravenwood {
+
+ private static volatile ThreadLocal<Pair<Integer, Boolean>> sThreadPriority;
+
+ static {
+ reset();
+ }
+
+ public static void reset() {
+ // Reset the thread local variable
+ sThreadPriority = ThreadLocal.withInitial(
+ () -> Pair.create(Process.THREAD_PRIORITY_DEFAULT, true));
+ }
+
+ /**
+ * Called by {@link Process#setThreadPriority(int, int)}
+ */
+ public static void setThreadPriority(int tid, int priority) {
+ if (Process.myTid() == tid) {
+ boolean backgroundOk = sThreadPriority.get().second;
+ if (priority >= Process.THREAD_PRIORITY_BACKGROUND && !backgroundOk) {
+ throw new IllegalArgumentException(
+ "Priority " + priority + " blocked by setCanSelfBackground()");
+ }
+ sThreadPriority.set(Pair.create(priority, backgroundOk));
+ } else {
+ throw new UnsupportedOperationException(
+ "Cross-thread priority management not yet available in Ravenwood");
+ }
+ }
+
+ /**
+ * Called by {@link Process#setCanSelfBackground(boolean)}
+ */
+ public static void setCanSelfBackground(boolean backgroundOk) {
+ int priority = sThreadPriority.get().first;
+ sThreadPriority.set(Pair.create(priority, backgroundOk));
+ }
+
+ /**
+ * Called by {@link Process#getThreadPriority(int)}
+ */
+ public static int getThreadPriority(int tid) {
+ if (Process.myTid() == tid) {
+ return sThreadPriority.get().first;
+ } else {
+ throw new UnsupportedOperationException(
+ "Cross-thread priority management not yet available in Ravenwood");
+ }
+ }
+}
diff --git a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayContainer_host.java b/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayContainer_host.java
deleted file mode 100644
index c18c307..0000000
--- a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayContainer_host.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.internal.os;
-
-import java.util.Arrays;
-import java.util.HashMap;
-
-public class LongArrayContainer_host {
- private static final HashMap<Long, long[]> sInstances = new HashMap<>();
- private static long sNextId = 1;
-
- public static long native_init(int arrayLength) {
- long[] array = new long[arrayLength];
- long instanceId = sNextId++;
- sInstances.put(instanceId, array);
- return instanceId;
- }
-
- static long[] getInstance(long instanceId) {
- return sInstances.get(instanceId);
- }
-
- public static void native_setValues(long instanceId, long[] values) {
- System.arraycopy(values, 0, getInstance(instanceId), 0, values.length);
- }
-
- public static void native_getValues(long instanceId, long[] values) {
- System.arraycopy(getInstance(instanceId), 0, values, 0, values.length);
- }
-
- public static boolean native_combineValues(long instanceId, long[] array, int[] indexMap) {
- long[] values = getInstance(instanceId);
-
- boolean nonZero = false;
- Arrays.fill(array, 0);
-
- for (int i = 0; i < values.length; i++) {
- int index = indexMap[i];
- if (index < 0 || index >= array.length) {
- throw new IndexOutOfBoundsException("Index " + index + " is out of bounds: [0, "
- + (array.length - 1) + "]");
- }
- if (values[i] != 0) {
- array[index] += values[i];
- nonZero = true;
- }
- }
- return nonZero;
- }
-}
diff --git a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayMultiStateCounter_host.java b/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayMultiStateCounter_host.java
index 9ce8ea8..90608f6 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayMultiStateCounter_host.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayMultiStateCounter_host.java
@@ -286,15 +286,12 @@
return getInstance(instanceId).mArrayLength;
}
- public static void native_setValues(long instanceId, int state, long containerInstanceId) {
- getInstance(instanceId).setValue(state,
- LongArrayContainer_host.getInstance(containerInstanceId));
+ public static void native_setValues(long instanceId, int state, long[] values) {
+ getInstance(instanceId).setValue(state, values);
}
- public static void native_updateValues(long instanceId, long containerInstanceId,
- long timestampMs) {
- getInstance(instanceId).updateValue(
- LongArrayContainer_host.getInstance(containerInstanceId), timestampMs);
+ public static void native_updateValues(long instanceId, long[] values, long timestampMs) {
+ getInstance(instanceId).updateValue(values, timestampMs);
}
public static void native_setState(long instanceId, int state, long timestampMs) {
@@ -305,19 +302,16 @@
getInstance(targetInstanceId).copyStatesFrom(getInstance(sourceInstanceId));
}
- public static void native_incrementValues(long instanceId, long containerInstanceId,
- long timestampMs) {
- getInstance(instanceId).incrementValues(
- LongArrayContainer_host.getInstance(containerInstanceId), timestampMs);
+ public static void native_incrementValues(long instanceId, long[] delta, long timestampMs) {
+ getInstance(instanceId).incrementValues(delta, timestampMs);
}
- public static void native_addCounts(long instanceId, long containerInstanceId) {
- getInstance(instanceId).addCounts(LongArrayContainer_host.getInstance(containerInstanceId));
+ public static void native_addCounts(long instanceId, long[] counts) {
+ getInstance(instanceId).addCounts(counts);
}
- public static void native_getCounts(long instanceId, long containerInstanceId, int state) {
- getInstance(instanceId).getValues(LongArrayContainer_host.getInstance(containerInstanceId),
- state);
+ public static void native_getCounts(long instanceId, long[] counts, int state) {
+ getInstance(instanceId).getValues(counts, state);
}
public static void native_reset(long instanceId) {
diff --git a/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java b/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java
index e12ff24..b65668b 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java
@@ -23,14 +23,6 @@
}
/**
- * Called from {@link RavenwoodEnvironment#ensureRavenwoodInitialized()}.
- */
- public static void ensureRavenwoodInitialized() {
- // Initialization is now done by RavenwoodAwareTestRunner.
- // Should we remove it?
- }
-
- /**
* Called from {@link RavenwoodEnvironment#getRavenwoodRuntimePath()}.
*/
public static String getRavenwoodRuntimePath(RavenwoodEnvironment env) {
diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
index c94ef31..0298171 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
@@ -16,6 +16,7 @@
package android.system;
import com.android.ravenwood.RavenwoodRuntimeNative;
+import com.android.ravenwood.RavenwoodRuntimeState;
import com.android.ravenwood.common.JvmWorkaround;
import java.io.FileDescriptor;
@@ -97,4 +98,16 @@
public static void setenv(String name, String value, boolean overwrite) throws ErrnoException {
RavenwoodRuntimeNative.setenv(name, value, overwrite);
}
+
+ public static int getpid() {
+ return RavenwoodRuntimeState.sPid;
+ }
+
+ public static int getuid() {
+ return RavenwoodRuntimeState.sUid;
+ }
+
+ public static int gettid() {
+ return RavenwoodRuntimeNative.gettid();
+ }
}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
index f13189f..7b940b4 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
@@ -58,6 +58,8 @@
public static native void clearSystemProperties();
+ public static native int gettid();
+
public static long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException {
return nLseek(JvmWorkaround.getInstance().getFdInt(fd), offset, whence);
}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeState.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeState.java
new file mode 100644
index 0000000..175e020
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeState.java
@@ -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.ravenwood;
+
+public class RavenwoodRuntimeState {
+ // This must match VMRuntime.SDK_VERSION_CUR_DEVELOPMENT.
+ public static final int CUR_DEVELOPMENT = 10000;
+
+ public static volatile int sUid;
+ public static volatile int sPid;
+ public static volatile int sTargetSdkLevel;
+
+ static {
+ reset();
+ }
+
+ public static void reset() {
+ sUid = -1;
+ sPid = -1;
+ sTargetSdkLevel = CUR_DEVELOPMENT;
+ }
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java b/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java
index ba89f71..eaadac6 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java
@@ -19,6 +19,7 @@
// The original is here:
// $ANDROID_BUILD_TOP/libcore/libart/src/main/java/dalvik/system/VMRuntime.java
+import com.android.ravenwood.RavenwoodRuntimeState;
import com.android.ravenwood.common.JvmWorkaround;
import java.lang.reflect.Array;
@@ -52,4 +53,8 @@
public long addressOf(Object obj) {
return JvmWorkaround.getInstance().addressOf(obj);
}
+
+ public int getTargetSdkVersion() {
+ return RavenwoodRuntimeState.sTargetSdkLevel;
+ }
}
diff --git a/ravenwood/runtime-jni/ravenwood_runtime.cpp b/ravenwood/runtime-jni/ravenwood_runtime.cpp
index 2a3c26e..5b75e98 100644
--- a/ravenwood/runtime-jni/ravenwood_runtime.cpp
+++ b/ravenwood/runtime-jni/ravenwood_runtime.cpp
@@ -17,6 +17,7 @@
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
+#include <sys/syscall.h>
#include <unistd.h>
#include <utils/misc.h>
@@ -173,6 +174,12 @@
throwIfMinusOne(env, "setenv", setenv(name.c_str(), value.c_str(), overwrite ? 1 : 0));
}
+
+static jint Linux_gettid(JNIEnv* env, jobject) {
+ // gettid(2() was added in glibc 2.30 but Android uses an older version in prebuilt.
+ return syscall(__NR_gettid);
+}
+
static void maybeRedirectLog() {
auto ravenwoodLogOut = getenv("RAVENWOOD_LOG_OUT");
if (ravenwoodLogOut == NULL) {
@@ -207,6 +214,7 @@
{ "stat", "(Ljava/lang/String;)Landroid/system/StructStat;", (void*)Linux_stat },
{ "nOpen", "(Ljava/lang/String;II)I", (void*)Linux_open },
{ "setenv", "(Ljava/lang/String;Ljava/lang/String;Z)V", (void*)Linux_setenv },
+ { "gettid", "()I", (void*)Linux_gettid },
};
extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
diff --git a/ravenwood/scripts/run-ravenwood-tests.sh b/ravenwood/scripts/run-ravenwood-tests.sh
index 5d623e0..672c685 100755
--- a/ravenwood/scripts/run-ravenwood-tests.sh
+++ b/ravenwood/scripts/run-ravenwood-tests.sh
@@ -18,12 +18,38 @@
# Options:
#
# -s: "Smoke" test -- skip slow tests (SysUI, ICU)
+#
+# -x PCRE: Specify exclusion filter in PCRE
+# Example: -x '^(Cts|hoststub)' # Exclude CTS and hoststubgen tests.
+#
+# -f PCRE: Specify inclusion filter in PCRE
+
+
+# Regex to identify slow tests, in PCRE
+SLOW_TEST_RE='^(SystemUiRavenTests|CtsIcuTestCasesRavenwood)$'
smoke=0
-while getopts "s" opt; do
+include_re=""
+exclude_re=""
+smoke_exclude_re=""
+dry_run=""
+while getopts "sx:f:d" opt; do
case "$opt" in
s)
- smoke=1
+ # Remove slow tests.
+ smoke_exclude_re="$SLOW_TEST_RE"
+ ;;
+ x)
+ # Take a PCRE from the arg, and use it as an exclusion filter.
+ exclude_re="$OPTARG"
+ ;;
+ f)
+ # Take a PCRE from the arg, and use it as an inclusion filter.
+ include_re="$OPTARG"
+ ;;
+ d)
+ # Dry run
+ dry_run="echo"
;;
'?')
exit 1
@@ -35,21 +61,46 @@
all_tests=(hoststubgentest tiny-framework-dump-test hoststubgen-invoke-test ravenwood-stats-checker)
all_tests+=( $(${0%/*}/list-ravenwood-tests.sh) )
-# Regex to identify slow tests, in PCRE
-slow_tests_re='^(SystemUiRavenTests|CtsIcuTestCasesRavenwood)$'
-
-if (( $smoke )) ; then
- # Remove the slow tests.
- all_tests=( $(
- for t in "${all_tests[@]}"; do
- echo $t | grep -vP "$slow_tests_re"
- done
- ) )
-fi
-
-run() {
- echo "Running: $*"
- "${@}"
+filter() {
+ local re="$1"
+ local grep_arg="$2"
+ if [[ "$re" == "" ]] ; then
+ cat # No filtering
+ else
+ grep $grep_arg -P "$re"
+ fi
}
-run ${ATEST:-atest} "${all_tests[@]}"
+filter_in() {
+ filter "$1"
+}
+
+filter_out() {
+ filter "$1" -v
+}
+
+
+# Remove the slow tests.
+targets=( $(
+ for t in "${all_tests[@]}"; do
+ echo $t | filter_in "$include_re" | filter_out "$smoke_exclude_re" | filter_out "$exclude_re"
+ done
+) )
+
+# Show the target tests
+
+echo "Target tests:"
+for t in "${targets[@]}"; do
+ echo " $t"
+done
+
+# Calculate the removed tests.
+
+diff="$(diff <(echo "${all_tests[@]}" | tr ' ' '\n') <(echo "${targets[@]}" | tr ' ' '\n') )"
+
+if [[ "$diff" != "" ]]; then
+ echo "Excluded tests:"
+ echo "$diff"
+fi
+
+$dry_run ${ATEST:-atest} "${targets[@]}"
diff --git a/ravenwood/tests/runtime-test/Android.bp b/ravenwood/tests/runtime-test/Android.bp
index 4102920..0c0df1f 100644
--- a/ravenwood/tests/runtime-test/Android.bp
+++ b/ravenwood/tests/runtime-test/Android.bp
@@ -10,6 +10,9 @@
android_ravenwood_test {
name: "RavenwoodRuntimeTest",
+ libs: [
+ "ravenwood-helper-runtime",
+ ],
static_libs: [
"androidx.annotation_annotation",
"androidx.test.ext.junit",
diff --git a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java
new file mode 100644
index 0000000..8e04b69
--- /dev/null
+++ b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.ravenwoodtest.runtimetest;
+
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+import static android.os.Process.FIRST_APPLICATION_UID;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Binder;
+import android.os.Build;
+import android.os.Process;
+import android.platform.test.ravenwood.RavenwoodConfig;
+import android.system.Os;
+
+import com.android.ravenwood.RavenwoodRuntimeState;
+
+import dalvik.system.VMRuntime;
+
+import org.junit.Test;
+
+public class IdentityTest {
+
+ @RavenwoodConfig.Config
+ public static final RavenwoodConfig sConfig =
+ new RavenwoodConfig.Builder()
+ .setTargetSdkLevel(UPSIDE_DOWN_CAKE)
+ .setProcessApp()
+ .build();
+
+ @Test
+ public void testUid() {
+ assertEquals(FIRST_APPLICATION_UID, RavenwoodRuntimeState.sUid);
+ assertEquals(FIRST_APPLICATION_UID, Os.getuid());
+ assertEquals(FIRST_APPLICATION_UID, Process.myUid());
+ assertEquals(FIRST_APPLICATION_UID, Binder.getCallingUid());
+ }
+
+ @Test
+ public void testPid() {
+ int pid = RavenwoodRuntimeState.sPid;
+ assertEquals(pid, Os.getpid());
+ assertEquals(pid, Process.myPid());
+ assertEquals(pid, Binder.getCallingPid());
+ }
+
+ @Test
+ public void testTargetSdkLevel() {
+ assertEquals(Build.VERSION_CODES.CUR_DEVELOPMENT, RavenwoodRuntimeState.CUR_DEVELOPMENT);
+ assertEquals(UPSIDE_DOWN_CAKE, RavenwoodRuntimeState.sTargetSdkLevel);
+ assertEquals(UPSIDE_DOWN_CAKE, VMRuntime.getRuntime().getTargetSdkVersion());
+ }
+}
diff --git a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java
index c2230c7..c55506a 100644
--- a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java
+++ b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java
@@ -24,6 +24,8 @@
import static android.system.OsConstants.S_ISSOCK;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
@@ -51,10 +53,12 @@
import java.nio.file.attribute.PosixFilePermission;
import java.util.HashSet;
import java.util.Set;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@RunWith(AndroidJUnit4.class)
public class OsTest {
+
public interface ConsumerWithThrow<T> {
void accept(T var1) throws Exception;
}
@@ -165,6 +169,35 @@
});
}
+ private static class TestThread extends Thread {
+
+ final CountDownLatch mLatch = new CountDownLatch(1);
+ int mTid;
+
+ TestThread() {
+ setDaemon(true);
+ }
+
+ @Override
+ public void run() {
+ mTid = Os.gettid();
+ mLatch.countDown();
+ }
+ }
+
+ @Test
+ public void testGetTid() throws InterruptedException {
+ var t1 = new TestThread();
+ var t2 = new TestThread();
+ t1.start();
+ t2.start();
+ // Wait for thread execution
+ assertTrue(t1.mLatch.await(1, TimeUnit.SECONDS));
+ assertTrue(t2.mLatch.await(1, TimeUnit.SECONDS));
+ // Make sure the tid is unique per-thread
+ assertNotEquals(t1.mTid, t2.mTid);
+ }
+
// Verify StructStat values from libcore against native JVM PosixFileAttributes
private static void assertAttributesEqual(PosixFileAttributes attr, StructStat stat) {
assertEquals(attr.lastModifiedTime(), convertTimespecToFileTime(stat.st_mtim));
diff --git a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/ProcessTest.java b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/ProcessTest.java
new file mode 100644
index 0000000..d25b5c1
--- /dev/null
+++ b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/ProcessTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.ravenwoodtest.runtimetest;
+
+import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
+import static android.os.Process.THREAD_PRIORITY_DEFAULT;
+import static android.os.Process.THREAD_PRIORITY_FOREGROUND;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import android.os.Process;
+import android.system.Os;
+
+import org.junit.Test;
+
+public class ProcessTest {
+
+ @Test
+ public void testGetUidPidTid() {
+ assertEquals(Os.getuid(), Process.myUid());
+ assertEquals(Os.getpid(), Process.myPid());
+ assertEquals(Os.gettid(), Process.myTid());
+ }
+
+ @Test
+ public void testThreadPriority() {
+ assertThrows(UnsupportedOperationException.class,
+ () -> Process.getThreadPriority(Process.myTid() + 1));
+ assertThrows(UnsupportedOperationException.class,
+ () -> Process.setThreadPriority(Process.myTid() + 1, THREAD_PRIORITY_DEFAULT));
+ assertEquals(THREAD_PRIORITY_DEFAULT, Process.getThreadPriority(Process.myTid()));
+ Process.setThreadPriority(THREAD_PRIORITY_FOREGROUND);
+ assertEquals(THREAD_PRIORITY_FOREGROUND, Process.getThreadPriority(Process.myTid()));
+ Process.setCanSelfBackground(false);
+ Process.setThreadPriority(THREAD_PRIORITY_DEFAULT);
+ assertEquals(THREAD_PRIORITY_DEFAULT, Process.getThreadPriority(Process.myTid()));
+ assertThrows(IllegalArgumentException.class,
+ () -> Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND));
+ }
+}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 68ff972..8567ccb 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -17,6 +17,7 @@
package com.android.server.appwidget;
import static android.appwidget.flags.Flags.remoteAdapterConversion;
+import static android.appwidget.flags.Flags.remoteViewsProto;
import static android.appwidget.flags.Flags.removeAppWidgetServiceIoFromCriticalPath;
import static android.appwidget.flags.Flags.securityPolicyInteractAcrossUsers;
import static android.appwidget.flags.Flags.supportResumeRestoreAfterReboot;
@@ -31,6 +32,7 @@
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import android.Manifest;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.PermissionName;
@@ -104,6 +106,7 @@
import android.os.UserManager;
import android.provider.DeviceConfig;
import android.service.appwidget.AppWidgetServiceDumpProto;
+import android.service.appwidget.GeneratedPreviewsProto;
import android.service.appwidget.WidgetProto;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -122,7 +125,9 @@
import android.util.SparseLongArray;
import android.util.TypedValue;
import android.util.Xml;
+import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoUtils;
import android.view.Display;
import android.view.View;
import android.widget.RemoteViews;
@@ -134,6 +139,7 @@
import com.android.internal.appwidget.IAppWidgetHost;
import com.android.internal.appwidget.IAppWidgetService;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
+import com.android.internal.infra.AndroidFuture;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.ArrayUtils;
@@ -221,6 +227,10 @@
// XML attribute for widget ids that are pending deletion.
// See {@link Provider#pendingDeletedWidgetIds}.
private static final String PENDING_DELETED_IDS_ATTR = "pending_deleted_ids";
+ // Name of service directory in /data/system_ce/<user>/
+ private static final String APPWIDGET_CE_DATA_DIRNAME = "appwidget";
+ // Name of previews directory in /data/system_ce/<user>/appwidget/
+ private static final String WIDGET_PREVIEWS_DIRNAME = "previews";
// Hard limit of number of hosts an app can create, note that the app that hosts the widgets
// can have multiple instances of {@link AppWidgetHost}, typically in respect to different
@@ -320,6 +330,9 @@
// Handler to the background thread that saves states to disk.
private Handler mSaveStateHandler;
+ // Handler to the background thread that saves generated previews to disk. All operations that
+ // modify saved previews must be run on this Handler.
+ private Handler mSavePreviewsHandler;
// Handler to the foreground thread that handles broadcasts related to user
// and package events, as well as various internal events within
// AppWidgetService.
@@ -363,6 +376,7 @@
} else {
mSaveStateHandler = BackgroundThread.getHandler();
}
+ mSavePreviewsHandler = new Handler(BackgroundThread.get().getLooper());
final ServiceThread serviceThread = new ServiceThread(TAG,
android.os.Process.THREAD_PRIORITY_FOREGROUND, false /* allowIo */);
serviceThread.start();
@@ -382,7 +396,9 @@
SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_MAX_PROVIDERS,
DEFAULT_GENERATED_PREVIEW_MAX_PROVIDERS);
mGeneratedPreviewsApiCounter = new ApiCounter(generatedPreviewResetInterval,
- generatedPreviewMaxCallsPerInterval, generatedPreviewsMaxProviders);
+ generatedPreviewMaxCallsPerInterval,
+ // Set a limit on the number of providers if storing them in memory.
+ remoteViewsProto() ? Integer.MAX_VALUE : generatedPreviewsMaxProviders);
DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_SYSTEMUI,
new HandlerExecutor(mCallbackHandler), this::handleSystemUiDeviceConfigChange);
@@ -648,7 +664,14 @@
for (int i = 0; i < providerCount; i++) {
Provider provider = mProviders.get(i);
if (provider.id.uid == clearedUid) {
- changed |= provider.clearGeneratedPreviewsLocked();
+ if (remoteViewsProto()) {
+ changed |= clearGeneratedPreviewsAsync(provider);
+ } else {
+ changed |= provider.clearGeneratedPreviewsLocked();
+ }
+ if (DEBUG) {
+ Slog.e(TAG, "clearPreviewsForUidLocked " + provider + " changed " + changed);
+ }
}
}
return changed;
@@ -898,18 +921,30 @@
for (int j = 0; j < widgetCount; j++) {
Widget widget = provider.widgets.get(j);
if (targetWidget != null && targetWidget != widget) continue;
+ // Identify the user in the host process since the intent will be invoked by
+ // the host app.
+ final Host host = widget.host;
+ final UserHandle hostUser;
+ if (host != null && host.id != null) {
+ hostUser = UserHandle.getUserHandleForUid(host.id.uid);
+ } else {
+ // Fallback to the parent profile if the host is null.
+ Slog.w(TAG, "Host is null when masking widget: " + widget.appWidgetId);
+ hostUser = mUserManager.getProfileParent(appUserId).getUserHandle();
+ }
if (provider.maskedByStoppedPackage) {
Intent intent = createUpdateIntentLocked(provider,
new int[] { widget.appWidgetId });
views.setOnClickPendingIntent(android.R.id.background,
- PendingIntent.getBroadcast(mContext, widget.appWidgetId,
+ PendingIntent.getBroadcastAsUser(mContext, widget.appWidgetId,
intent, PendingIntent.FLAG_UPDATE_CURRENT
- | PendingIntent.FLAG_IMMUTABLE));
+ | PendingIntent.FLAG_IMMUTABLE, hostUser));
} else if (onClickIntent != null) {
views.setOnClickPendingIntent(android.R.id.background,
- PendingIntent.getActivity(mContext, widget.appWidgetId, onClickIntent,
- PendingIntent.FLAG_UPDATE_CURRENT
- | PendingIntent.FLAG_IMMUTABLE));
+ PendingIntent.getActivityAsUser(mContext, widget.appWidgetId,
+ onClickIntent, PendingIntent.FLAG_UPDATE_CURRENT
+ | PendingIntent.FLAG_IMMUTABLE, null /* options */,
+ hostUser));
}
if (widget.replaceWithMaskedViewsLocked(views)) {
scheduleNotifyUpdateAppWidgetLocked(widget, widget.getEffectiveViewsLocked());
@@ -3246,6 +3281,9 @@
deleteWidgetsLocked(provider, UserHandle.USER_ALL);
mProviders.remove(provider);
mGeneratedPreviewsApiCounter.remove(provider.id);
+ if (remoteViewsProto()) {
+ clearGeneratedPreviewsAsync(provider);
+ }
// no need to send the DISABLE broadcast, since the receiver is gone anyway
cancelBroadcastsLocked(provider);
@@ -3824,6 +3862,14 @@
} catch (IOException e) {
Slog.w(TAG, "Failed to read state: " + e);
}
+
+ if (remoteViewsProto()) {
+ try {
+ loadGeneratedPreviewCategoriesLocked(profileId);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to read preview categories: " + e);
+ }
+ }
}
if (version >= 0) {
@@ -4593,6 +4639,12 @@
keep.add(providerId);
// Use the new AppWidgetProviderInfo.
provider.setPartialInfoLocked(info);
+ // Clear old previews
+ if (remoteViewsProto()) {
+ clearGeneratedPreviewsAsync(provider);
+ } else {
+ provider.clearGeneratedPreviewsLocked();
+ }
// If it's enabled
final int M = provider.widgets.size();
if (M > 0) {
@@ -4884,6 +4936,7 @@
mSecurityPolicy.enforceCallFromPackage(callingPackage);
ensureWidgetCategoryCombinationIsValid(widgetCategory);
+ AndroidFuture<RemoteViews> result = null;
synchronized (mLock) {
ensureGroupStateLoadedLocked(profileId);
final int providerCount = mProviders.size();
@@ -4917,10 +4970,23 @@
callingPackage);
if (providerIsInCallerProfile && !shouldFilterAppAccess
&& (providerIsInCallerPackage || hasBindAppWidgetPermission)) {
- return provider.getGeneratedPreviewLocked(widgetCategory);
+ if (remoteViewsProto()) {
+ result = getGeneratedPreviewsAsync(provider, widgetCategory);
+ } else {
+ return provider.getGeneratedPreviewLocked(widgetCategory);
+ }
}
}
}
+
+ if (result != null) {
+ try {
+ return result.get();
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to get generated previews Future result", e);
+ return null;
+ }
+ }
// Either the provider does not exist or the caller does not have permission to access its
// previews.
return null;
@@ -4950,8 +5016,12 @@
providerComponent + " is not a valid AppWidget provider");
}
if (mGeneratedPreviewsApiCounter.tryApiCall(providerId)) {
- provider.setGeneratedPreviewLocked(widgetCategories, preview);
- scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+ if (remoteViewsProto()) {
+ setGeneratedPreviewsAsync(provider, widgetCategories, preview);
+ } else {
+ provider.setGeneratedPreviewLocked(widgetCategories, preview);
+ scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+ }
return true;
}
return false;
@@ -4979,11 +5049,361 @@
throw new IllegalArgumentException(
providerComponent + " is not a valid AppWidget provider");
}
- final boolean changed = provider.removeGeneratedPreviewLocked(widgetCategories);
- if (changed) scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+
+ if (remoteViewsProto()) {
+ removeGeneratedPreviewsAsync(provider, widgetCategories);
+ } else {
+ final boolean changed = provider.removeGeneratedPreviewLocked(widgetCategories);
+ if (changed) scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+ }
}
}
+ /**
+ * Return previews for the specified provider from a background thread. The result of the future
+ * is nullable.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ @NonNull
+ private AndroidFuture<RemoteViews> getGeneratedPreviewsAsync(
+ @NonNull Provider provider, @AppWidgetProviderInfo.CategoryFlags int widgetCategory) {
+ AndroidFuture<RemoteViews> result = new AndroidFuture<>();
+ mSavePreviewsHandler.post(() -> {
+ SparseArray<RemoteViews> previews = loadGeneratedPreviews(provider);
+ for (int i = 0; i < previews.size(); i++) {
+ if ((widgetCategory & previews.keyAt(i)) != 0) {
+ result.complete(previews.valueAt(i));
+ return;
+ }
+ }
+ result.complete(null);
+ });
+ return result;
+ }
+
+ /**
+ * Set previews for the specified provider on a background thread.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ private void setGeneratedPreviewsAsync(@NonNull Provider provider, int widgetCategories,
+ @NonNull RemoteViews preview) {
+ mSavePreviewsHandler.post(() -> {
+ SparseArray<RemoteViews> previews = loadGeneratedPreviews(provider);
+ for (int flag : Provider.WIDGET_CATEGORY_FLAGS) {
+ if ((widgetCategories & flag) != 0) {
+ previews.put(flag, preview);
+ }
+ }
+ saveGeneratedPreviews(provider, previews, /* notify= */ true);
+ });
+ }
+
+ /**
+ * Remove previews for the specified provider on a background thread.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ private void removeGeneratedPreviewsAsync(@NonNull Provider provider, int widgetCategories) {
+ mSavePreviewsHandler.post(() -> {
+ SparseArray<RemoteViews> previews = loadGeneratedPreviews(provider);
+ boolean changed = false;
+ for (int flag : Provider.WIDGET_CATEGORY_FLAGS) {
+ if ((widgetCategories & flag) != 0) {
+ changed |= previews.removeReturnOld(flag) != null;
+ }
+ }
+ if (changed) {
+ saveGeneratedPreviews(provider, previews, /* notify= */ true);
+ }
+ });
+ }
+
+ /**
+ * Clear previews for the specified provider on a background thread. Returns true if changed
+ * (i.e. there are previews to clear). If returns true, the caller should schedule a providers
+ * changed notification.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ private boolean clearGeneratedPreviewsAsync(@NonNull Provider provider) {
+ mSavePreviewsHandler.post(() -> {
+ saveGeneratedPreviews(provider, /* previews= */ null, /* notify= */ false);
+ });
+ return provider.info.generatedPreviewCategories != 0;
+ }
+
+ private void checkSavePreviewsThread() {
+ if (DEBUG && !mSavePreviewsHandler.getLooper().isCurrentThread()) {
+ throw new IllegalStateException("Only modify previews on the background thread");
+ }
+ }
+
+ /**
+ * Load previews from file for the given provider. If there are no previews, returns an empty
+ * SparseArray. Else, returns a SparseArray of the previews mapped by widget category.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ @NonNull
+ private SparseArray<RemoteViews> loadGeneratedPreviews(@NonNull Provider provider) {
+ checkSavePreviewsThread();
+ try {
+ AtomicFile previewsFile = getWidgetPreviewsFile(provider);
+ if (!previewsFile.exists()) {
+ return new SparseArray<>();
+ }
+ ProtoInputStream input = new ProtoInputStream(previewsFile.readFully());
+ SparseArray<RemoteViews> entries = readGeneratedPreviewsFromProto(input);
+ SparseArray<RemoteViews> singleCategoryKeyedEntries = new SparseArray<>();
+ for (int i = 0; i < entries.size(); i++) {
+ int widgetCategories = entries.keyAt(i);
+ RemoteViews preview = entries.valueAt(i);
+ for (int flag : Provider.WIDGET_CATEGORY_FLAGS) {
+ if ((widgetCategories & flag) != 0) {
+ singleCategoryKeyedEntries.put(flag, preview);
+ }
+ }
+ }
+ return singleCategoryKeyedEntries;
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to load generated previews for " + provider, e);
+ return new SparseArray<>();
+ }
+ }
+
+ /**
+ * This is called when loading profile/group state to populate
+ * AppWidgetProviderInfo.generatedPreviewCategories based on what previews are saved.
+ *
+ * This is the only time previews are read while not on mSavePreviewsHandler. It happens once
+ * per profile during initialization, before any calls to get/set/removeWidgetPreviewAsync
+ * happen for that profile.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ @GuardedBy("mLock")
+ private void loadGeneratedPreviewCategoriesLocked(int profileId) throws IOException {
+ for (Provider provider : mProviders) {
+ if (provider.id.getProfile().getIdentifier() != profileId) {
+ continue;
+ }
+ AtomicFile previewsFile = getWidgetPreviewsFile(provider);
+ if (!previewsFile.exists()) {
+ continue;
+ }
+ ProtoInputStream input = new ProtoInputStream(previewsFile.readFully());
+ provider.info.generatedPreviewCategories = readGeneratedPreviewCategoriesFromProto(
+ input);
+ if (DEBUG) {
+ Slog.i(TAG, TextUtils.formatSimple(
+ "loadGeneratedPreviewCategoriesLocked %d %s categories %d", profileId,
+ provider, provider.info.generatedPreviewCategories));
+ }
+ }
+ }
+
+ /**
+ * Save the given previews into storage.
+ *
+ * @param provider Provider for which to save previews
+ * @param previews Previews to save. If null or empty, clears any saved previews for this
+ * provider.
+ * @param notify If true, then this function will notify hosts of updated provider info.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ private void saveGeneratedPreviews(@NonNull Provider provider,
+ @Nullable SparseArray<RemoteViews> previews, boolean notify) {
+ checkSavePreviewsThread();
+ AtomicFile file = null;
+ FileOutputStream stream = null;
+ try {
+ file = getWidgetPreviewsFile(provider);
+ if (previews == null || previews.size() == 0) {
+ if (file.exists()) {
+ if (DEBUG) {
+ Slog.i(TAG, "Deleting widget preview file " + file);
+ }
+ file.delete();
+ }
+ } else {
+ if (DEBUG) {
+ Slog.i(TAG, "Writing widget preview file " + file);
+ }
+ ProtoOutputStream out = new ProtoOutputStream();
+ writePreviewsToProto(out, previews);
+ stream = file.startWrite();
+ stream.write(out.getBytes());
+ file.finishWrite(stream);
+ }
+
+ synchronized (mLock) {
+ provider.updateGeneratedPreviewCategoriesLocked(previews);
+ if (notify) {
+ scheduleNotifyGroupHostsForProvidersChangedLocked(provider.getUserId());
+ }
+ }
+ } catch (IOException e) {
+ if (file != null && stream != null) {
+ file.failWrite(stream);
+ }
+ Slog.w(TAG, "Failed to save widget previews for provider " + provider.id.componentName);
+ }
+ }
+
+
+ /**
+ * Write the given previews as a GeneratedPreviewsProto to the output stream.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ private void writePreviewsToProto(@NonNull ProtoOutputStream out,
+ @NonNull SparseArray<RemoteViews> generatedPreviews) {
+ // Collect RemoteViews mapped by hashCode in order to avoid writing duplicates.
+ SparseArray<Pair<Integer, RemoteViews>> previewsToWrite = new SparseArray<>();
+ for (int i = 0; i < generatedPreviews.size(); i++) {
+ int widgetCategory = generatedPreviews.keyAt(i);
+ RemoteViews views = generatedPreviews.valueAt(i);
+ if (!previewsToWrite.contains(views.hashCode())) {
+ previewsToWrite.put(views.hashCode(), new Pair<>(widgetCategory, views));
+ } else {
+ Pair<Integer, RemoteViews> entry = previewsToWrite.get(views.hashCode());
+ previewsToWrite.put(views.hashCode(),
+ Pair.create(entry.first | widgetCategory, views));
+ }
+ }
+
+ for (int i = 0; i < previewsToWrite.size(); i++) {
+ final long token = out.start(GeneratedPreviewsProto.PREVIEWS);
+ Pair<Integer, RemoteViews> entry = previewsToWrite.valueAt(i);
+ out.write(GeneratedPreviewsProto.Preview.WIDGET_CATEGORIES, entry.first);
+ final long viewsToken = out.start(GeneratedPreviewsProto.Preview.VIEWS);
+ entry.second.writePreviewToProto(mContext, out);
+ out.end(viewsToken);
+ out.end(token);
+ }
+ }
+
+ /**
+ * Read a GeneratedPreviewsProto message from the input stream.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ @NonNull
+ private SparseArray<RemoteViews> readGeneratedPreviewsFromProto(@NonNull ProtoInputStream input)
+ throws IOException {
+ SparseArray<RemoteViews> entries = new SparseArray<>();
+ while (input.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (input.getFieldNumber()) {
+ case (int) GeneratedPreviewsProto.PREVIEWS:
+ final long token = input.start(GeneratedPreviewsProto.PREVIEWS);
+ Pair<Integer, RemoteViews> entry = readSinglePreviewFromProto(input,
+ /* skipViews= */ false);
+ entries.put(entry.first, entry.second);
+ input.end(token);
+ break;
+ default:
+ Slog.w(TAG, "Unknown field while reading GeneratedPreviewsProto! "
+ + ProtoUtils.currentFieldToString(input));
+ }
+ }
+ return entries;
+ }
+
+ /**
+ * Read the widget categories from GeneratedPreviewsProto and return an int representing the
+ * combined widget categories of all the previews.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ @AppWidgetProviderInfo.CategoryFlags
+ private int readGeneratedPreviewCategoriesFromProto(@NonNull ProtoInputStream input)
+ throws IOException {
+ int widgetCategories = 0;
+ while (input.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (input.getFieldNumber()) {
+ case (int) GeneratedPreviewsProto.PREVIEWS:
+ final long token = input.start(GeneratedPreviewsProto.PREVIEWS);
+ Pair<Integer, RemoteViews> entry = readSinglePreviewFromProto(input,
+ /* skipViews= */ true);
+ widgetCategories |= entry.first;
+ input.end(token);
+ break;
+ default:
+ Slog.w(TAG, "Unknown field while reading GeneratedPreviewsProto! "
+ + ProtoUtils.currentFieldToString(input));
+ }
+ }
+ return widgetCategories;
+ }
+
+ /**
+ * Read a single GeneratedPreviewsProto.Preview message from the input stream, and returns a
+ * pair of widget category and corresponding RemoteViews. If skipViews is true, this function
+ * will only read widget categories and the returned RemoteViews will be null.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ @NonNull
+ private Pair<Integer, RemoteViews> readSinglePreviewFromProto(@NonNull ProtoInputStream input,
+ boolean skipViews) throws IOException {
+ int widgetCategories = 0;
+ RemoteViews views = null;
+ while (input.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (input.getFieldNumber()) {
+ case (int) GeneratedPreviewsProto.Preview.VIEWS:
+ if (skipViews) {
+ // ProtoInputStream will skip over the nested message when nextField() is
+ // called.
+ continue;
+ }
+ final long token = input.start(GeneratedPreviewsProto.Preview.VIEWS);
+ try {
+ views = RemoteViews.createPreviewFromProto(mContext, input);
+ } catch (Exception e) {
+ Slog.e(TAG, "Unable to deserialize RemoteViews", e);
+ }
+ input.end(token);
+ break;
+ case (int) GeneratedPreviewsProto.Preview.WIDGET_CATEGORIES:
+ widgetCategories = input.readInt(
+ GeneratedPreviewsProto.Preview.WIDGET_CATEGORIES);
+ break;
+ default:
+ Slog.w(TAG, "Unknown field while reading GeneratedPreviewsProto! "
+ + ProtoUtils.currentFieldToString(input));
+ }
+ }
+ return Pair.create(widgetCategories, views);
+ }
+
+ /**
+ * Returns the file in which all generated previews for this provider are stored. This will be
+ * a path of the form:
+ * {@literal /data/system_ce/<userId>/appwidget/previews/<package>-<class>-<uid>.binpb}
+ *
+ * This function will not create the file if it does not already exist.
+ */
+ @NonNull
+ private static AtomicFile getWidgetPreviewsFile(@NonNull Provider provider) throws IOException {
+ int userId = provider.getUserId();
+ File previewsDirectory = getWidgetPreviewsDirectory(userId);
+ File providerPreviews = Environment.buildPath(previewsDirectory,
+ TextUtils.formatSimple("%s-%s-%d.binpb", provider.id.componentName.getPackageName(),
+ provider.id.componentName.getClassName(), provider.id.uid));
+ return new AtomicFile(providerPreviews);
+ }
+
+ /**
+ * Returns the widget previews directory for the given user, creating it if it does not exist.
+ * This will be a path of the form:
+ * {@literal /data/system_ce/<userId>/appwidget/previews}
+ */
+ @NonNull
+ private static File getWidgetPreviewsDirectory(int userId) throws IOException {
+ File dataSystemCeDirectory = Environment.getDataSystemCeDirectory(userId);
+ File previewsDirectory = Environment.buildPath(dataSystemCeDirectory,
+ APPWIDGET_CE_DATA_DIRNAME, WIDGET_PREVIEWS_DIRNAME);
+ if (!previewsDirectory.exists()) {
+ if (!previewsDirectory.mkdirs()) {
+ throw new IOException("Unable to create widget preview directory "
+ + previewsDirectory.getPath());
+ }
+ }
+ return previewsDirectory;
+ }
+
private static void ensureWidgetCategoryCombinationIsValid(int widgetCategories) {
int validCategories = AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN
| AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD
@@ -5414,11 +5834,11 @@
AppWidgetManager.META_DATA_APPWIDGET_PROVIDER);
}
if (newInfo != null) {
+ newInfo.generatedPreviewCategories = info.generatedPreviewCategories;
info = newInfo;
if (DEBUG) {
Objects.requireNonNull(info);
}
- updateGeneratedPreviewCategoriesLocked();
}
}
mInfoParsed = true;
@@ -5475,7 +5895,7 @@
generatedPreviews.put(flag, preview);
}
}
- updateGeneratedPreviewCategoriesLocked();
+ updateGeneratedPreviewCategoriesLocked(generatedPreviews);
}
@GuardedBy("this.mLock")
@@ -5487,7 +5907,7 @@
}
}
if (changed) {
- updateGeneratedPreviewCategoriesLocked();
+ updateGeneratedPreviewCategoriesLocked(generatedPreviews);
}
return changed;
}
@@ -5496,17 +5916,19 @@
public boolean clearGeneratedPreviewsLocked() {
if (generatedPreviews.size() > 0) {
generatedPreviews.clear();
- updateGeneratedPreviewCategoriesLocked();
+ updateGeneratedPreviewCategoriesLocked(generatedPreviews);
return true;
}
return false;
}
-
@GuardedBy("this.mLock")
- private void updateGeneratedPreviewCategoriesLocked() {
+ private void updateGeneratedPreviewCategoriesLocked(
+ @Nullable SparseArray<RemoteViews> previews) {
info.generatedPreviewCategories = 0;
- for (int i = 0; i < generatedPreviews.size(); i++) {
- info.generatedPreviewCategories |= generatedPreviews.keyAt(i);
+ if (previews != null) {
+ for (int i = 0; i < previews.size(); i++) {
+ info.generatedPreviewCategories |= previews.keyAt(i);
+ }
}
}
diff --git a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
index ef39846..8a4b1fa 100644
--- a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
+++ b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
@@ -59,14 +59,14 @@
private int mObserverCount = 0;
@GuardedBy("mLock")
- private ArrayMap<String, InjectionSessionData> mPackageToSessionData = new ArrayMap<>();
+ private final ArrayMap<String, InjectionSessionData> mPackageToSessionData = new ArrayMap<>();
/**
* Mapping from camera ID to open camera app associations. Key is the camera id, value is the
* information of the app's uid and package name.
*/
@GuardedBy("mLock")
- private ArrayMap<String, OpenCameraInfo> mAppsToBlockOnVirtualDevice = new ArrayMap<>();
+ private final ArrayMap<String, OpenCameraInfo> mAppsToBlockOnVirtualDevice = new ArrayMap<>();
static class InjectionSessionData {
public int appUid;
@@ -179,6 +179,15 @@
Slog.w(TAG, "Unexpected close with observers remaining: " + mObserverCount);
}
}
+ // Clean up camera injection sessions (if any).
+ synchronized (mLock) {
+ for (InjectionSessionData sessionData : mPackageToSessionData.values()) {
+ for (CameraInjectionSession session : sessionData.cameraIdToSession.values()) {
+ session.close();
+ }
+ }
+ mPackageToSessionData.clear();
+ }
mCameraManager.unregisterAvailabilityCallback(this);
}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 281a2ce..8b5b93e 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -1465,28 +1465,28 @@
public int createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig,
@NonNull IVirtualDisplayCallback callback) {
checkCallerIsDeviceOwner();
+
+ int displayId;
+ boolean showPointer;
+ boolean isTrustedDisplay;
GenericWindowPolicyController gwpc;
synchronized (mVirtualDeviceLock) {
gwpc = createWindowPolicyControllerLocked(virtualDisplayConfig.getDisplayCategories());
- }
- int displayId;
- displayId = mDisplayManagerInternal.createVirtualDisplay(virtualDisplayConfig, callback,
- this, gwpc, mOwnerPackageName);
- boolean isMirrorDisplay =
- mDisplayManagerInternal.getDisplayIdToMirror(displayId) != Display.INVALID_DISPLAY;
- gwpc.setDisplayId(displayId, isMirrorDisplay);
- boolean isTrustedDisplay =
- (mDisplayManagerInternal.getDisplayInfo(displayId).flags & Display.FLAG_TRUSTED)
- == Display.FLAG_TRUSTED;
- if (!isTrustedDisplay) {
- if (getDevicePolicy(POLICY_TYPE_CLIPBOARD) != DEVICE_POLICY_DEFAULT) {
- throw new SecurityException("All displays must be trusted for devices with custom"
- + "clipboard policy.");
+ displayId = mDisplayManagerInternal.createVirtualDisplay(virtualDisplayConfig,
+ callback, this, gwpc, mOwnerPackageName);
+ boolean isMirrorDisplay =
+ mDisplayManagerInternal.getDisplayIdToMirror(displayId)
+ != Display.INVALID_DISPLAY;
+ gwpc.setDisplayId(displayId, isMirrorDisplay);
+ isTrustedDisplay =
+ (mDisplayManagerInternal.getDisplayInfo(displayId).flags & Display.FLAG_TRUSTED)
+ == Display.FLAG_TRUSTED;
+ if (!isTrustedDisplay
+ && getDevicePolicy(POLICY_TYPE_CLIPBOARD) != DEVICE_POLICY_DEFAULT) {
+ throw new SecurityException("All displays must be trusted for devices with "
+ + "custom clipboard policy.");
}
- }
- boolean showPointer;
- synchronized (mVirtualDeviceLock) {
if (mVirtualDisplays.contains(displayId)) {
gwpc.unregisterRunningAppsChangedListener(this);
throw new IllegalStateException(
@@ -1523,6 +1523,9 @@
}
private PowerManager.WakeLock createAndAcquireWakeLockForDisplay(int displayId) {
+ if (android.companion.virtualdevice.flags.Flags.deviceAwareDisplayPower()) {
+ return null;
+ }
final long token = Binder.clearCallingIdentity();
try {
PowerManager powerManager = mContext.getSystemService(PowerManager.class);
@@ -1681,6 +1684,14 @@
return mOwnerUid;
}
+ long getDimDurationMillis() {
+ return mParams.getDimDuration().toMillis();
+ }
+
+ long getScreenOffTimeoutMillis() {
+ return mParams.getScreenOffTimeout().toMillis();
+ }
+
@Override // Binder call
public int[] getDisplayIds() {
synchronized (mVirtualDeviceLock) {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index f87e3c3..6729231d 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -17,6 +17,7 @@
package com.android.server.companion.virtual;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS;
import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
import static com.android.server.wm.ActivityInterceptorCallback.VIRTUAL_DEVICE_SERVICE_ORDERED_ID;
@@ -27,6 +28,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.app.ActivityOptions;
+import android.app.compat.CompatChanges;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
import android.companion.CompanionDeviceManager;
@@ -41,11 +43,14 @@
import android.companion.virtual.flags.Flags;
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtualnative.IVirtualDeviceManagerNative;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.content.AttributionSource;
import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManagerInternal;
import android.os.Binder;
+import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.LocaleList;
@@ -88,7 +93,6 @@
import java.util.function.Consumer;
import java.util.stream.Collectors;
-
@SuppressLint("LongLogTag")
public class VirtualDeviceManagerService extends SystemService {
@@ -101,6 +105,11 @@
AssociationRequest.DEVICE_PROFILE_APP_STREAMING,
AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING);
+ /** Enable default device camera access for apps running on virtual devices. */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public static final long ENABLE_DEFAULT_DEVICE_CAMERA_ACCESS = 371173368L;
+
/**
* A virtual device association id corresponding to no CDM association.
*/
@@ -110,7 +119,7 @@
private final VirtualDeviceManagerImpl mImpl;
private final VirtualDeviceManagerNativeImpl mNativeImpl;
private final VirtualDeviceManagerInternal mLocalService;
- private VirtualDeviceLog mVirtualDeviceLog = new VirtualDeviceLog(getContext());
+ private final VirtualDeviceLog mVirtualDeviceLog = new VirtualDeviceLog(getContext());
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final PendingTrampolineMap mPendingTrampolines = new PendingTrampolineMap(mHandler);
@@ -236,7 +245,7 @@
}
}
- void onCameraAccessBlocked(int appUid) {
+ private void onCameraAccessBlocked(int appUid) {
ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot();
for (int i = 0; i < virtualDevicesSnapshot.size(); i++) {
VirtualDeviceImpl virtualDevice = virtualDevicesSnapshot.get(i);
@@ -248,8 +257,13 @@
}
}
- CameraAccessController getCameraAccessController(UserHandle userHandle) {
- if (Flags.streamCamera()) {
+ private CameraAccessController getCameraAccessController(UserHandle userHandle,
+ VirtualDeviceParams params, String callingPackage) {
+ if (CompatChanges.isChangeEnabled(ENABLE_DEFAULT_DEVICE_CAMERA_ACCESS, callingPackage,
+ userHandle)
+ && android.companion.virtualdevice.flags.Flags.defaultDeviceCameraAccessPolicy()
+ && (params.getDevicePolicy(POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS)
+ == DEVICE_POLICY_DEFAULT)) {
return null;
}
int userId = userHandle.getIdentifier();
@@ -496,7 +510,8 @@
final UserHandle userHandle = getCallingUserHandle();
final CameraAccessController cameraAccessController =
- getCameraAccessController(userHandle);
+ getCameraAccessController(userHandle, params,
+ attributionSource.getPackageName());
final int deviceId = sNextUniqueIndex.getAndIncrement();
final Consumer<ArraySet<Integer>> runningAppsChangedCallback =
runningUids -> notifyRunningAppsChanged(deviceId, runningUids);
@@ -576,7 +591,6 @@
}
}
-
@Override // Binder call
public int getDeviceIdForDisplayId(int displayId) {
if (displayId == Display.INVALID_DISPLAY || displayId == Display.DEFAULT_DISPLAY) {
@@ -911,6 +925,22 @@
}
@Override
+ public long getDimDurationMillisForDeviceId(int deviceId) {
+ synchronized (mVirtualDeviceManagerLock) {
+ VirtualDeviceImpl virtualDevice = mVirtualDevices.get(deviceId);
+ return virtualDevice == null ? -1 : virtualDevice.getDimDurationMillis();
+ }
+ }
+
+ @Override
+ public long getScreenOffTimeoutMillisForDeviceId(int deviceId) {
+ synchronized (mVirtualDeviceManagerLock) {
+ VirtualDeviceImpl virtualDevice = mVirtualDevices.get(deviceId);
+ return virtualDevice == null ? -1 : virtualDevice.getScreenOffTimeoutMillis();
+ }
+ }
+
+ @Override
public boolean isValidVirtualDeviceId(int deviceId) {
return mImpl.isValidVirtualDeviceId(deviceId);
}
diff --git a/services/core/java/com/android/server/SerialService.java b/services/core/java/com/android/server/SerialService.java
index dbf144f..95ae11e 100644
--- a/services/core/java/com/android/server/SerialService.java
+++ b/services/core/java/com/android/server/SerialService.java
@@ -18,22 +18,30 @@
import android.annotation.EnforcePermission;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.hardware.ISerialManager;
import android.hardware.SerialManagerInternal;
import android.os.ParcelFileDescriptor;
import android.os.PermissionEnforcer;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import java.io.File;
+import java.io.FileDescriptor;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.function.Supplier;
@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class SerialService extends ISerialManager.Stub {
+ private static final String TAG = "SerialService";
+
private final Context mContext;
@GuardedBy("mSerialPorts")
@@ -50,7 +58,7 @@
final String[] serialPorts = getSerialPorts(context);
for (String serialPort : serialPorts) {
mSerialPorts.put(serialPort, () -> {
- return native_open(serialPort);
+ return tryOpen(serialPort);
});
}
}
@@ -130,5 +138,14 @@
}
};
- private native ParcelFileDescriptor native_open(String path);
+ private static @Nullable ParcelFileDescriptor tryOpen(String path) {
+ try {
+ FileDescriptor fd = Os.open(path, OsConstants.O_RDWR | OsConstants.O_NOCTTY, 0);
+ return new ParcelFileDescriptor(fd);
+ } catch (ErrnoException e) {
+ Slog.e(TAG, "Could not open: " + path, e);
+ // We return null to preserve API semantics from earlier implementation variants.
+ return null;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/TradeInModeService.java b/services/core/java/com/android/server/TradeInModeService.java
index 9ad550b..70a0330 100644
--- a/services/core/java/com/android/server/TradeInModeService.java
+++ b/services/core/java/com/android/server/TradeInModeService.java
@@ -110,6 +110,8 @@
stopTradeInMode();
} else {
watchForSetupCompletion();
+ watchForNetworkChange();
+ watchForAccountsCreated();
}
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 1c3569d..3e7bcb8 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5155,6 +5155,11 @@
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
+
String[] pkgs = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
if (pkgs != null) {
for (String pkg : pkgs) {
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index de3e2c9..08632fe 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -114,6 +114,7 @@
import static com.android.server.am.ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ;
import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ;
import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
+import static com.android.server.am.ProcessList.PREVIOUS_APP_MAX_ADJ;
import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
import static com.android.server.am.ProcessList.SCHED_GROUP_DEFAULT;
import static com.android.server.am.ProcessList.SCHED_GROUP_FOREGROUND_WINDOW;
@@ -696,7 +697,7 @@
// In case the app goes from non-cached to cached but it doesn't have other reachable
// processes, its adj could be still unknown as of now, assign one.
processes.add(app);
- assignCachedAdjIfNecessary(processes);
+ applyLruAdjust(processes);
applyOomAdjLSP(app, false, mInjector.getUptimeMillis(),
mInjector.getElapsedRealtimeMillis(), oomAdjReason);
}
@@ -1086,7 +1087,7 @@
}
mProcessesInCycle.clear();
- assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+ applyLruAdjust(mProcessList.getLruProcessesLOSP());
postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime, true);
@@ -1148,8 +1149,9 @@
}
@GuardedBy({"mService", "mProcLock"})
- protected void assignCachedAdjIfNecessary(ArrayList<ProcessRecord> lruList) {
+ protected void applyLruAdjust(ArrayList<ProcessRecord> lruList) {
final int numLru = lruList.size();
+ int nextPreviousAppAdj = PREVIOUS_APP_ADJ;
if (mConstants.USE_TIERED_CACHED_ADJ) {
final long now = mInjector.getUptimeMillis();
int uiTargetAdj = 10;
@@ -1159,9 +1161,12 @@
ProcessRecord app = lruList.get(i);
final ProcessStateRecord state = app.mState;
final ProcessCachedOptimizerRecord opt = app.mOptRecord;
- if (!app.isKilledByAm() && app.getThread() != null
- && (state.getCurAdj() >= UNKNOWN_ADJ
- || (state.hasShownUi() && state.getCurAdj() >= CACHED_APP_MIN_ADJ))) {
+ final int curAdj = state.getCurAdj();
+ if (PREVIOUS_APP_ADJ <= curAdj && curAdj <= PREVIOUS_APP_MAX_ADJ) {
+ state.setCurAdj(nextPreviousAppAdj);
+ nextPreviousAppAdj = Math.min(nextPreviousAppAdj + 1, PREVIOUS_APP_MAX_ADJ);
+ } else if (!app.isKilledByAm() && app.getThread() != null && (curAdj >= UNKNOWN_ADJ
+ || (state.hasShownUi() && curAdj >= CACHED_APP_MIN_ADJ))) {
final ProcessServiceRecord psr = app.mServices;
int targetAdj = CACHED_APP_MIN_ADJ;
@@ -1228,10 +1233,13 @@
for (int i = numLru - 1; i >= 0; i--) {
ProcessRecord app = lruList.get(i);
final ProcessStateRecord state = app.mState;
- // If we haven't yet assigned the final cached adj
- // to the process, do that now.
- if (!app.isKilledByAm() && app.getThread() != null && state.getCurAdj()
- >= UNKNOWN_ADJ) {
+ final int curAdj = state.getCurAdj();
+ if (PREVIOUS_APP_ADJ <= curAdj && curAdj <= PREVIOUS_APP_MAX_ADJ) {
+ state.setCurAdj(nextPreviousAppAdj);
+ nextPreviousAppAdj = Math.min(nextPreviousAppAdj + 1, PREVIOUS_APP_MAX_ADJ);
+ } else if (!app.isKilledByAm() && app.getThread() != null
+ && curAdj >= UNKNOWN_ADJ) {
+ // If we haven't yet assigned the final cached adj to the process, do that now.
final ProcessServiceRecord psr = app.mServices;
switch (state.getCurProcState()) {
case PROCESS_STATE_LAST_ACTIVITY:
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index e452c45..8b66055 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -54,6 +54,7 @@
import static com.android.server.am.ProcessList.PERSISTENT_PROC_ADJ;
import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ;
import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
+import static com.android.server.am.ProcessList.PREVIOUS_APP_MAX_ADJ;
import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
import static com.android.server.am.ProcessList.SERVICE_ADJ;
import static com.android.server.am.ProcessList.SERVICE_B_ADJ;
@@ -968,7 +969,7 @@
mTmpOomAdjusterArgs.update(topApp, now, UNKNOWN_ADJ, oomAdjReason, null, true);
computeConnectionsLSP();
- assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+ applyLruAdjust(mProcessList.getLruProcessesLOSP());
postUpdateOomAdjInnerLSP(oomAdjReason, mActiveUids, now, nowElapsed, oldTime, true);
}
@@ -1049,20 +1050,24 @@
// Now traverse and compute the connections of processes with changed importance.
computeConnectionsLSP();
- boolean unassignedAdj = false;
+ boolean needLruAdjust = false;
for (int i = 0, size = reachables.size(); i < size; i++) {
final ProcessStateRecord state = reachables.get(i).mState;
state.setReachable(false);
state.setCompletedAdjSeq(mAdjSeq);
- if (state.getCurAdj() >= UNKNOWN_ADJ) {
- unassignedAdj = true;
+ final int curAdj = state.getCurAdj();
+ // Processes assigned the PREV oomscore will have a laddered oomscore with respect to
+ // their positions in the LRU list. i.e. prev+0, prev+1, prev+2, etc.
+ final boolean isPrevApp = PREVIOUS_APP_ADJ <= curAdj && curAdj <= PREVIOUS_APP_MAX_ADJ;
+ if (curAdj >= UNKNOWN_ADJ || (Flags.oomadjusterPrevLaddering() && isPrevApp)) {
+ needLruAdjust = true;
}
}
// If all processes have an assigned adj, no need to calculate and assign cached adjs.
- if (unassignedAdj) {
+ if (needLruAdjust) {
// TODO: b/319163103 - optimize cache adj assignment to not require the whole lru list.
- assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+ applyLruAdjust(mProcessList.getLruProcessesLOSP());
}
// Repopulate any uid record that may have changed.
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index cdb0188..f86474f 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -225,6 +225,7 @@
// UI flow such as clicking on a URI in the e-mail app to view in the browser,
// and then pressing back to return to e-mail.
public static final int PREVIOUS_APP_ADJ = 700;
+ public static final int PREVIOUS_APP_MAX_ADJ = Flags.oomadjusterPrevLaddering() ? 799 : 700;
// This is a process holding the home application -- we want to try
// avoiding killing it, even if it would normally be in the background,
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 56cfdfb..7b4d6c7 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -242,4 +242,12 @@
metadata {
purpose: PURPOSE_BUGFIX
}
+}
+
+flag {
+ name: "oomadjuster_prev_laddering"
+ namespace: "system_performance"
+ is_fixed_read_only: true
+ description: "Add +X to the prev scores according to their positions in the process LRU list"
+ bug: "359912586"
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/appbinding/AppBindingService.java b/services/core/java/com/android/server/appbinding/AppBindingService.java
index 5db6dc7..6ccb3ee 100644
--- a/services/core/java/com/android/server/appbinding/AppBindingService.java
+++ b/services/core/java/com/android/server/appbinding/AppBindingService.java
@@ -235,6 +235,9 @@
}
final String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
if (Intent.ACTION_USER_REMOVED.equals(action)) {
onUserRemoved(userId);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 985155d..0cf55bb 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -493,7 +493,7 @@
private static final int MSG_INIT_ADI_DEVICE_STATES = 103;
private static final int MSG_INIT_INPUT_GAINS = 104;
- private static final int MSG_SET_INPUT_GAIN_INDEX = 105;
+ private static final int MSG_APPLY_INPUT_GAIN_INDEX = 105;
private static final int MSG_PERSIST_INPUT_GAIN_INDEX = 106;
// end of messages handled under wakelock
@@ -1626,7 +1626,6 @@
new InputDeviceVolumeHelper(
mSettings,
mContentResolver,
- mSettingsLock,
System.INPUT_GAIN_INDEX_SETTINGS);
}
@@ -5804,7 +5803,7 @@
// to persist).
sendMsg(
mAudioHandler,
- MSG_SET_INPUT_GAIN_INDEX,
+ MSG_APPLY_INPUT_GAIN_INDEX,
SENDMSG_QUEUE,
/*arg1*/ index,
/*arg2*/ 0,
@@ -5813,22 +5812,22 @@
}
}
- private void setInputGainIndexInt(@NonNull AudioDeviceAttributes ada, int index) {
+ private void onApplyInputGainIndex(@NonNull AudioDeviceAttributes ada, int index) {
// TODO(b/364923030): call AudioSystem to apply input gain in native layer.
// Post a persist input gain msg.
sendMsg(
mAudioHandler,
MSG_PERSIST_INPUT_GAIN_INDEX,
- SENDMSG_QUEUE,
- /*arg1*/ index,
+ SENDMSG_REPLACE,
+ /*arg1*/ 0,
/*arg2*/ 0,
/*obj*/ ada,
PERSIST_DELAY);
}
- private void persistInputGainIndex(@NonNull AudioDeviceAttributes ada, int index) {
- mInputDeviceVolumeHelper.persistInputGainIndex(ada, index);
+ private void onPersistInputGainIndex(@NonNull AudioDeviceAttributes ada) {
+ mInputDeviceVolumeHelper.persistInputGainIndex(ada);
}
/**
@@ -10213,12 +10212,12 @@
vgs.persistVolumeGroup(msg.arg1);
break;
- case MSG_SET_INPUT_GAIN_INDEX:
- setInputGainIndexInt((AudioDeviceAttributes) msg.obj, msg.arg1);
+ case MSG_APPLY_INPUT_GAIN_INDEX:
+ onApplyInputGainIndex((AudioDeviceAttributes) msg.obj, msg.arg1);
break;
case MSG_PERSIST_INPUT_GAIN_INDEX:
- persistInputGainIndex((AudioDeviceAttributes) msg.obj, msg.arg1);
+ onPersistInputGainIndex((AudioDeviceAttributes) msg.obj);
break;
case MSG_PERSIST_RINGER_MODE:
diff --git a/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java b/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java
index d83dca6..d094629 100644
--- a/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java
+++ b/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 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.
@@ -43,7 +43,6 @@
private final SettingsAdapter mSettings;
private final ContentResolver mContentResolver;
- private final Object mSettingsLock;
private final String mInputGainIndexSettingsName;
// A map between device internal type (e.g. AudioSystem.DEVICE_IN_BUILTIN_MIC) to its input gain
@@ -54,11 +53,9 @@
InputDeviceVolumeHelper(
SettingsAdapter settings,
ContentResolver contentResolver,
- Object settingsLock,
String settingsName) {
mSettings = settings;
mContentResolver = contentResolver;
- mSettingsLock = settingsLock;
mInputGainIndexSettingsName = settingsName;
IntArray internalDeviceTypes = new IntArray();
@@ -82,34 +79,27 @@
readSettings();
}
- public void readSettings() {
+ private void readSettings() {
synchronized (InputDeviceVolumeHelper.class) {
for (int inputDeviceType : mSupportedDeviceTypes) {
// Retrieve current input gain for device. If no input gain stored for current
// device, use default input gain.
- int index;
- if (!hasValidSettingsName()) {
- index = INDEX_DEFAULT;
- } else {
- String name = getSettingNameForDevice(inputDeviceType);
- index =
- mSettings.getSystemIntForUser(
- mContentResolver, name, INDEX_DEFAULT, UserHandle.USER_CURRENT);
- }
+ String name = getSettingNameForDevice(inputDeviceType);
+ int index = name == null
+ ? INDEX_DEFAULT
+ : mSettings.getSystemIntForUser(
+ mContentResolver, name, INDEX_DEFAULT, UserHandle.USER_CURRENT);
mInputGainIndexMap.put(inputDeviceType, getValidIndex(index));
}
}
}
- public boolean hasValidSettingsName() {
- return mInputGainIndexSettingsName != null && !mInputGainIndexSettingsName.isEmpty();
- }
-
- public @Nullable String getSettingNameForDevice(int inputDeviceType) {
- if (!hasValidSettingsName()) {
+ private @Nullable String getSettingNameForDevice(int inputDeviceType) {
+ if (mInputGainIndexSettingsName == null || mInputGainIndexSettingsName.isEmpty()) {
return null;
}
+
final String suffix = AudioSystem.getInputDeviceName(inputDeviceType);
if (suffix.isEmpty()) {
return mInputGainIndexSettingsName;
@@ -158,29 +148,27 @@
ensureValidInputDeviceType(inputDeviceType);
int oldIndex;
- synchronized (mSettingsLock) {
- synchronized (InputDeviceVolumeHelper.class) {
- oldIndex = getInputGainIndex(ada);
- index = getValidIndex(index);
+ synchronized (InputDeviceVolumeHelper.class) {
+ oldIndex = getInputGainIndex(ada);
+ index = getValidIndex(index);
- if (oldIndex == index) {
- return false;
- }
-
- mInputGainIndexMap.put(inputDeviceType, index);
- return true;
+ if (oldIndex == index) {
+ return false;
}
+
+ mInputGainIndexMap.put(inputDeviceType, index);
+ return true;
}
}
- public void persistInputGainIndex(@NonNull AudioDeviceAttributes ada, int index) {
+ public void persistInputGainIndex(@NonNull AudioDeviceAttributes ada) {
int inputDeviceType = AudioDeviceInfo.convertDeviceTypeToInternalInputDevice(ada.getType());
- ensureValidInputDeviceType(inputDeviceType);
-
- if (hasValidSettingsName()) {
+ String name = getSettingNameForDevice(inputDeviceType);
+ if (name != null) {
+ int index = getInputGainIndex(ada);
mSettings.putSystemIntForUser(
mContentResolver,
- getSettingNameForDevice(inputDeviceType),
+ name,
index,
UserHandle.USER_CURRENT);
}
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index 1da62d7..1604e94 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -1068,6 +1068,7 @@
switch (attr.getUsage()) {
case AudioAttributes.USAGE_MEDIA:
case AudioAttributes.USAGE_GAME:
+ case AudioAttributes.USAGE_SPEAKER_CLEANUP:
return 1000;
case AudioAttributes.USAGE_ALARM:
case AudioAttributes.USAGE_NOTIFICATION_RINGTONE:
diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
index 6e38733..471b7b4 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -167,6 +167,12 @@
*/
public abstract int getDeviceIdForDisplayId(int displayId);
+ /** Returns the dim duration for the displays of the device with the given ID. */
+ public abstract long getDimDurationMillisForDeviceId(int deviceId);
+
+ /** Returns the screen off timeout of the displays of the device with the given ID. */
+ public abstract long getScreenOffTimeoutMillisForDeviceId(int deviceId);
+
/**
* Gets the persistent ID for the VirtualDevice with the given device ID.
*
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 19305de..76e5ef0 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -605,7 +605,7 @@
private ComponentName chooseDreamForUser(boolean doze, int userId) {
if (doze) {
ComponentName dozeComponent = getDozeComponent(userId);
- return validateDream(dozeComponent) ? dozeComponent : null;
+ return validateDream(dozeComponent, userId) ? dozeComponent : null;
}
if (mSystemDreamComponent != null) {
@@ -616,11 +616,11 @@
return dreams != null && dreams.length != 0 ? dreams[0] : null;
}
- private boolean validateDream(ComponentName component) {
+ private boolean validateDream(ComponentName component, int userId) {
if (component == null) return false;
- final ServiceInfo serviceInfo = getServiceInfo(component);
+ final ServiceInfo serviceInfo = getServiceInfo(component, userId);
if (serviceInfo == null) {
- Slog.w(TAG, "Dream " + component + " does not exist");
+ Slog.w(TAG, "Dream " + component + " does not exist on user " + userId);
return false;
} else if (serviceInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP
&& !BIND_DREAM_SERVICE.equals(serviceInfo.permission)) {
@@ -647,7 +647,7 @@
List<ComponentName> validComponents = new ArrayList<>();
if (components != null) {
for (ComponentName component : components) {
- if (validateDream(component)) {
+ if (validateDream(component, userId)) {
validComponents.add(component);
}
}
@@ -718,9 +718,10 @@
return userId == mainUserId;
}
- private ServiceInfo getServiceInfo(ComponentName name) {
+ private ServiceInfo getServiceInfo(ComponentName name, int userId) {
+ final Context userContext = mContext.createContextAsUser(UserHandle.of(userId), 0);
try {
- return name != null ? mContext.getPackageManager().getServiceInfo(name,
+ return name != null ? userContext.getPackageManager().getServiceInfo(name,
PackageManager.MATCH_DEBUG_TRIAGED_MISSING) : null;
} catch (NameNotFoundException e) {
return null;
@@ -813,7 +814,7 @@
private void writePulseGestureEnabled() {
ComponentName name = getDozeComponent();
- boolean dozeEnabled = validateDream(name);
+ boolean dozeEnabled = validateDream(name, ActivityManager.getCurrentUser());
LocalServices.getService(InputManagerInternal.class).setPulseGestureEnabled(dozeEnabled);
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index b696c54..1b527da 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -273,13 +273,8 @@
private class DelayedStandbyOnActiveSourceLostRunnable implements Runnable {
@Override
public void run() {
- if (mService.getPowerManagerInternal().wasDeviceIdleFor(
- STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS)) {
+ if (!isActiveSource()) {
mService.standby();
- } else {
- mService.setAndBroadcastActiveSource(mService.getPhysicalAddress(),
- getDeviceInfo().getDeviceType(), Constants.ADDR_TV,
- "DelayedActiveSourceLostStandbyRunnable");
}
}
}
diff --git a/services/core/java/com/android/server/input/AppLaunchShortcutManager.java b/services/core/java/com/android/server/input/AppLaunchShortcutManager.java
new file mode 100644
index 0000000..aef207f
--- /dev/null
+++ b/services/core/java/com/android/server/input/AppLaunchShortcutManager.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input;
+
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.role.RoleManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.XmlResourceParser;
+import android.hardware.input.AppLaunchData;
+import android.hardware.input.InputGestureData;
+import android.hardware.input.KeyGestureEvent;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.SparseArray;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+
+import com.android.internal.R;
+import com.android.internal.policy.IShortcutService;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Manages quick launch app shortcuts by parsing {@code bookmarks.xml} and intercepting the
+ * correct key combinations for the app shortcuts defined.
+ *
+ * Currently there are 2 ways of defining shortcuts:
+ * - Adding shortcuts to {@code bookmarks.xml}
+ * - Calling into {@code registerShortcutKey()}.
+ */
+final class AppLaunchShortcutManager {
+ private static final String TAG = "AppShortcutManager";
+
+ private static final String TAG_BOOKMARKS = "bookmarks";
+ private static final String TAG_BOOKMARK = "bookmark";
+
+ private static final String ATTRIBUTE_PACKAGE = "package";
+ private static final String ATTRIBUTE_CLASS = "class";
+ private static final String ATTRIBUTE_SHORTCUT = "shortcut";
+ private static final String ATTRIBUTE_CATEGORY = "category";
+ private static final String ATTRIBUTE_SHIFT = "shift";
+ private static final String ATTRIBUTE_ROLE = "role";
+
+ private static final int SHORTCUT_CODE_META_MASK =
+ KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON
+ | KeyEvent.META_META_ON;
+
+ private LongSparseArray<IShortcutService> mShortcutKeyServices = new LongSparseArray<>();
+
+ /* Table of Application Launch keys. Maps from key codes to intent categories.
+ *
+ * These are special keys that are used to launch particular kinds of applications,
+ * such as a web browser. HID defines nearly a hundred of them in the Consumer (0x0C)
+ * usage page. We don't support quite that many yet...
+ */
+ private static final SparseArray<String> sApplicationLaunchKeyCategories;
+ private static final SparseArray<String> sApplicationLaunchKeyRoles;
+ static {
+ sApplicationLaunchKeyRoles = new SparseArray<>();
+ sApplicationLaunchKeyCategories = new SparseArray<>();
+ sApplicationLaunchKeyRoles.append(
+ KeyEvent.KEYCODE_EXPLORER, RoleManager.ROLE_BROWSER);
+ sApplicationLaunchKeyCategories.append(
+ KeyEvent.KEYCODE_ENVELOPE, Intent.CATEGORY_APP_EMAIL);
+ sApplicationLaunchKeyCategories.append(
+ KeyEvent.KEYCODE_CONTACTS, Intent.CATEGORY_APP_CONTACTS);
+ sApplicationLaunchKeyCategories.append(
+ KeyEvent.KEYCODE_CALENDAR, Intent.CATEGORY_APP_CALENDAR);
+ sApplicationLaunchKeyCategories.append(
+ KeyEvent.KEYCODE_MUSIC, Intent.CATEGORY_APP_MUSIC);
+ sApplicationLaunchKeyCategories.append(
+ KeyEvent.KEYCODE_CALCULATOR, Intent.CATEGORY_APP_CALCULATOR);
+ }
+
+ private final Context mContext;
+ private boolean mSearchKeyShortcutPending = false;
+ private boolean mConsumeSearchKeyUp = true;
+ private final Map<InputGestureData.Trigger, InputGestureData> mBookmarks = new HashMap<>();
+
+ @SuppressLint("MissingPermission")
+ AppLaunchShortcutManager(Context context) {
+ mContext = context;
+ }
+
+ public void systemRunning() {
+ loadShortcuts();
+ }
+
+ private void loadShortcuts() {
+ try {
+ XmlResourceParser parser = mContext.getResources().getXml(R.xml.bookmarks);
+ XmlUtils.beginDocument(parser, TAG_BOOKMARKS);
+ KeyCharacterMap virtualKcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
+
+ while (true) {
+ XmlUtils.nextElement(parser);
+
+ if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
+ break;
+ }
+
+ if (!TAG_BOOKMARK.equals(parser.getName())) {
+ Log.w(TAG, "TAG_BOOKMARK not found");
+ break;
+ }
+
+ String packageName = parser.getAttributeValue(null, ATTRIBUTE_PACKAGE);
+ String className = parser.getAttributeValue(null, ATTRIBUTE_CLASS);
+ String categoryName = parser.getAttributeValue(null, ATTRIBUTE_CATEGORY);
+ String shiftName = parser.getAttributeValue(null, ATTRIBUTE_SHIFT);
+ String roleName = parser.getAttributeValue(null, ATTRIBUTE_ROLE);
+
+ // TODO(b/358569822): Shift bookmarks to use keycode instead of shortcutChar
+ int keycode = KeyEvent.KEYCODE_UNKNOWN;
+ String shortcut = parser.getAttributeValue(null, ATTRIBUTE_SHORTCUT);
+ if (!TextUtils.isEmpty(shortcut)) {
+ KeyEvent[] events = virtualKcm.getEvents(new char[]{shortcut.toLowerCase(
+ Locale.ROOT).charAt(0)});
+ // Single key press can generate the character
+ if (events != null && events.length == 2) {
+ keycode = events[0].getKeyCode();
+ }
+ }
+ if (keycode == KeyEvent.KEYCODE_UNKNOWN) {
+ Log.w(TAG, "Keycode required for bookmark with category=" + categoryName
+ + " packageName=" + packageName + " className=" + className
+ + " role=" + roleName + " shiftName=" + shiftName
+ + " shortcut=" + shortcut);
+ continue;
+ }
+
+ final boolean isShiftShortcut = (shiftName != null && shiftName.toLowerCase(
+ Locale.ROOT).equals("true"));
+ AppLaunchData launchData = null;
+ if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(className)) {
+ launchData = AppLaunchData.createLaunchDataForComponent(packageName, className);
+ } else if (!TextUtils.isEmpty(categoryName)) {
+ launchData = AppLaunchData.createLaunchDataForCategory(categoryName);
+ } else if (!TextUtils.isEmpty(roleName)) {
+ launchData = AppLaunchData.createLaunchDataForRole(roleName);
+ }
+ if (launchData != null) {
+ Log.d(TAG, "adding shortcut " + launchData + "shift="
+ + isShiftShortcut + " keycode=" + keycode);
+ // All bookmarks are based on Action key
+ int modifierState =
+ KeyEvent.META_META_ON | (isShiftShortcut ? KeyEvent.META_SHIFT_ON : 0);
+ InputGestureData bookmark = new InputGestureData.Builder()
+ .setTrigger(InputGestureData.createKeyTrigger(keycode, modifierState))
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION)
+ .setAppLaunchData(launchData)
+ .build();
+ mBookmarks.put(bookmark.getTrigger(), bookmark);
+ }
+ }
+ } catch (XmlPullParserException | IOException e) {
+ Log.e(TAG, "Got exception parsing bookmarks.", e);
+ }
+ }
+
+ public void registerShortcutKey(long shortcutCode, IShortcutService shortcutService)
+ throws RemoteException {
+ IShortcutService service = mShortcutKeyServices.get(shortcutCode);
+ if (service != null && service.asBinder().pingBinder()) {
+ throw new RemoteException("Key: " + shortcutCode + ", already exists.");
+ }
+
+ mShortcutKeyServices.put(shortcutCode, shortcutService);
+ }
+
+ /**
+ * Handle the shortcut to {@link IShortcutService}
+ * @param keyCode The key code of the event.
+ * @param metaState The meta key modifier state.
+ * @return True if invoked the shortcut, otherwise false.
+ */
+ private boolean handleShortcutService(int keyCode, int metaState) {
+ final long shortcutCodeMeta = metaState & SHORTCUT_CODE_META_MASK;
+ if (shortcutCodeMeta == 0) {
+ return false;
+ }
+ long shortcutCode = keyCode | (shortcutCodeMeta << Integer.SIZE);
+ IShortcutService shortcutService = mShortcutKeyServices.get(shortcutCode);
+ if (shortcutService != null) {
+ try {
+ shortcutService.notifyShortcutKeyPressed(shortcutCode);
+ } catch (RemoteException e) {
+ Log.w(TAG,
+ "Shortcut key service not found, deleting shortcut code: " + shortcutCode);
+ mShortcutKeyServices.delete(shortcutCode);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Handle the shortcut to Launch application.
+ *
+ * @param keyEvent The key event.
+ */
+ @SuppressLint("MissingPermission")
+ @Nullable
+ private AppLaunchData interceptShortcut(KeyEvent keyEvent) {
+ final int keyCode = keyEvent.getKeyCode();
+ final int modifierState = keyEvent.getMetaState() & SHORTCUT_CODE_META_MASK;
+ // Shortcuts are invoked through Search+key, so intercept those here
+ // Any printing key that is chorded with Search should be consumed
+ // even if no shortcut was invoked. This prevents text from being
+ // inadvertently inserted when using a keyboard that has built-in macro
+ // shortcut keys (that emit Search+x) and some of them are not registered.
+ if (mSearchKeyShortcutPending) {
+ KeyCharacterMap kcm = keyEvent.getKeyCharacterMap();
+ if (kcm != null && kcm.isPrintingKey(keyCode)) {
+ mConsumeSearchKeyUp = true;
+ mSearchKeyShortcutPending = false;
+ } else {
+ return null;
+ }
+ } else if (modifierState == 0) {
+ AppLaunchData appLaunchData = null;
+ // Handle application launch keys.
+ String role = sApplicationLaunchKeyRoles.get(keyCode);
+ String category = sApplicationLaunchKeyCategories.get(keyCode);
+ if (!TextUtils.isEmpty(role)) {
+ appLaunchData = AppLaunchData.createLaunchDataForRole(role);
+ } else if (!TextUtils.isEmpty(category)) {
+ appLaunchData = AppLaunchData.createLaunchDataForCategory(category);
+ }
+
+ return appLaunchData;
+ }
+
+ if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
+ return null;
+ }
+ InputGestureData gesture = mBookmarks.get(
+ InputGestureData.createKeyTrigger(keyCode, modifierState));
+ if (gesture == null) {
+ return null;
+ }
+ return gesture.getAction().appLaunchData();
+ }
+
+ /**
+ * Handle the shortcut from {@link KeyEvent}
+ *
+ * @param event Description of the key event.
+ */
+ public InterceptKeyResult interceptKey(KeyEvent event) {
+ if (event.getRepeatCount() != 0) {
+ return InterceptKeyResult.DO_NOTHING;
+ }
+
+ final int metaState = event.getModifiers();
+ final int keyCode = event.getKeyCode();
+ if (keyCode == KeyEvent.KEYCODE_SEARCH) {
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ mSearchKeyShortcutPending = true;
+ mConsumeSearchKeyUp = false;
+ } else {
+ mSearchKeyShortcutPending = false;
+ if (mConsumeSearchKeyUp) {
+ mConsumeSearchKeyUp = false;
+ return InterceptKeyResult.CONSUME_KEY;
+ }
+ }
+ return InterceptKeyResult.DO_NOTHING;
+ }
+
+ if (event.getAction() != KeyEvent.ACTION_DOWN) {
+ return InterceptKeyResult.DO_NOTHING;
+ }
+
+ // Intercept shortcuts defined in bookmarks or through application launch keycodes
+ AppLaunchData appLaunchData = interceptShortcut(event);
+
+ // TODO(b/358569822): Ideally shortcut service custom shortcuts should be either
+ // migrated to bookmarks or customizable shortcut APIs.
+ if (appLaunchData == null && handleShortcutService(keyCode, metaState)) {
+ return InterceptKeyResult.CONSUME_KEY;
+ }
+
+ return new InterceptKeyResult(/* consumed =*/ false, appLaunchData);
+ }
+
+ /**
+ * @return a list of {@link InputGestureData} containing the application launch shortcuts parsed
+ * at boot time from {@code bookmarks.xml}.
+ */
+ public List<InputGestureData> getBookmarks() {
+ return new ArrayList<>(mBookmarks.values());
+ }
+
+ public void dump(IndentingPrintWriter ipw) {
+ ipw.println("AppLaunchShortcutManager:");
+ ipw.increaseIndent();
+ for (InputGestureData data : mBookmarks.values()) {
+ ipw.println(data);
+ }
+ ipw.decreaseIndent();
+ }
+
+ public record InterceptKeyResult(boolean consumed, @Nullable AppLaunchData appLaunchData) {
+ private static final InterceptKeyResult DO_NOTHING = new InterceptKeyResult(false, null);
+ private static final InterceptKeyResult CONSUME_KEY = new InterceptKeyResult(true, null);
+ }
+}
diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java
index f4bd402..cf1cdaf 100644
--- a/services/core/java/com/android/server/input/InputGestureManager.java
+++ b/services/core/java/com/android/server/input/InputGestureManager.java
@@ -16,11 +16,21 @@
package com.android.server.input;
+import static android.hardware.input.InputGestureData.createKeyTrigger;
+import static com.android.hardware.input.Flags.keyboardA11yShortcutControl;
+import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
+import static com.android.window.flags.Flags.enableMoveToNextDisplayShortcut;
+import static com.android.window.flags.Flags.enableTaskResizingKeyboardShortcuts;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.content.Context;
import android.hardware.input.InputGestureData;
import android.hardware.input.InputManager;
+import android.hardware.input.InputSettings;
+import android.hardware.input.KeyGestureEvent;
+import android.os.SystemProperties;
import android.util.IndentingPrintWriter;
import android.util.SparseArray;
import android.view.KeyEvent;
@@ -29,15 +39,17 @@
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
/**
* A thread-safe component of {@link InputManagerService} responsible for managing pre-defined input
* gestures and custom gestures defined by other system components using Input APIs.
*
- * TODO(b/365064144): Add implementation to persist data, identify clashes with existing shortcuts.
+ * TODO(b/365064144): Add implementation to persist data.
*
*/
final class InputGestureManager {
@@ -47,13 +59,242 @@
KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON
| KeyEvent.META_META_ON;
- @GuardedBy("mCustomInputGestures")
+ private final Context mContext;
+
+ private static final Object mGestureLock = new Object();
+ @GuardedBy("mGestureLock")
private final SparseArray<Map<InputGestureData.Trigger, InputGestureData>>
mCustomInputGestures = new SparseArray<>();
+ @GuardedBy("mGestureLock")
+ private final Map<InputGestureData.Trigger, InputGestureData> mSystemShortcuts =
+ new HashMap<>();
+
+ @GuardedBy("mGestureLock")
+ private final Set<InputGestureData.Trigger> mBlockListedTriggers = new HashSet<>(Set.of(
+ createKeyTrigger(KeyEvent.KEYCODE_TAB, KeyEvent.META_ALT_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_TAB, KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_SPACE, KeyEvent.META_CTRL_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_SPACE,
+ KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_Z,
+ KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_C, KeyEvent.META_CTRL_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_V, KeyEvent.META_CTRL_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_X, KeyEvent.META_CTRL_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_Z, KeyEvent.META_CTRL_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_Y, KeyEvent.META_CTRL_ON)
+ ));
+
+ public InputGestureManager(Context context) {
+ mContext = context;
+ }
+
+ public void systemRunning() {
+ initSystemShortcuts();
+ blockListBookmarkedTriggers();
+ }
+
+ private void initSystemShortcuts() {
+ // Initialize all system shortcuts
+ List<InputGestureData> systemShortcuts = new ArrayList<>(List.of(
+ createKeyGesture(
+ KeyEvent.KEYCODE_A,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_H,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_HOME
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_ENTER,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_HOME
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_I,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_L,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_N,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_N,
+ KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_S,
+ KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_DEL,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_BACK
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_ESCAPE,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_BACK
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_DPAD_UP,
+ KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_DPAD_DOWN,
+ KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_DPAD_LEFT,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_BACK
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_DPAD_LEFT,
+ KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_DPAD_LEFT,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_DPAD_RIGHT,
+ KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_DPAD_RIGHT,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_SLASH,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_TAB,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS
+ )
+ ));
+ if (newBugreportKeyboardShortcut() && "1".equals(SystemProperties.get("ro.debuggable"))) {
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_DEL,
+ KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT
+ ));
+ }
+ if (enableMoveToNextDisplayShortcut()) {
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_D,
+ KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY
+ ));
+ }
+ if (keyboardA11yShortcutControl()) {
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_T,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK
+ ));
+ if (InputSettings.isAccessibilityBounceKeysFeatureEnabled()) {
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_3,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS
+ ));
+ }
+ if (InputSettings.isAccessibilityMouseKeysFeatureFlagEnabled()) {
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_4,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS
+ ));
+ }
+ if (InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_5,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS
+ ));
+ }
+ if (InputSettings.isAccessibilitySlowKeysFeatureFlagEnabled()) {
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_6,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS
+ ));
+ }
+ if (enableTaskResizingKeyboardShortcuts()) {
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_LEFT_BRACKET,
+ KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW
+ ));
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_RIGHT_BRACKET,
+ KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW
+ ));
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_EQUALS,
+ KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW
+ ));
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_MINUS,
+ KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE
+ ));
+ }
+ }
+ synchronized (mGestureLock) {
+ for (InputGestureData systemShortcut : systemShortcuts) {
+ mSystemShortcuts.put(systemShortcut.getTrigger(), systemShortcut);
+ }
+ }
+ }
+
+ private void blockListBookmarkedTriggers() {
+ synchronized (mGestureLock) {
+ InputManager im = Objects.requireNonNull(mContext.getSystemService(InputManager.class));
+ for (InputGestureData bookmark : im.getAppLaunchBookmarks()) {
+ mBlockListedTriggers.add(bookmark.getTrigger());
+ }
+ }
+ }
+
@InputManager.CustomInputGestureResult
public int addCustomInputGesture(int userId, InputGestureData newGesture) {
- synchronized (mCustomInputGestures) {
+ synchronized (mGestureLock) {
+ if (mBlockListedTriggers.contains(newGesture.getTrigger())
+ || mSystemShortcuts.containsKey(newGesture.getTrigger())) {
+ return InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE;
+ }
+ if (newGesture.getTrigger() instanceof InputGestureData.KeyTrigger keyTrigger) {
+ if (KeyEvent.isModifierKey(keyTrigger.getKeycode()) ||
+ KeyEvent.isSystemKey(keyTrigger.getKeycode())) {
+ return InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE;
+ }
+ }
if (!mCustomInputGestures.contains(userId)) {
mCustomInputGestures.put(userId, new HashMap<>());
}
@@ -69,7 +310,7 @@
@InputManager.CustomInputGestureResult
public int removeCustomInputGesture(int userId, InputGestureData data) {
- synchronized (mCustomInputGestures) {
+ synchronized (mGestureLock) {
if (!mCustomInputGestures.contains(userId)) {
return InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST;
}
@@ -88,14 +329,14 @@
}
public void removeAllCustomInputGestures(int userId) {
- synchronized (mCustomInputGestures) {
+ synchronized (mGestureLock) {
mCustomInputGestures.remove(userId);
}
}
@NonNull
public List<InputGestureData> getCustomInputGestures(int userId) {
- synchronized (mCustomInputGestures) {
+ synchronized (mGestureLock) {
if (!mCustomInputGestures.contains(userId)) {
return List.of();
}
@@ -109,7 +350,7 @@
if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
return null;
}
- synchronized (mCustomInputGestures) {
+ synchronized (mGestureLock) {
Map<InputGestureData.Trigger, InputGestureData> customGestures =
mCustomInputGestures.get(userId);
if (customGestures == null) {
@@ -120,10 +361,44 @@
}
}
+ @Nullable
+ public InputGestureData getSystemShortcutForKeyEvent(KeyEvent event) {
+ final int keyCode = event.getKeyCode();
+ if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
+ return null;
+ }
+ synchronized (mGestureLock) {
+ int modifierState = event.getMetaState() & KEY_GESTURE_META_MASK;
+ return mSystemShortcuts.get(InputGestureData.createKeyTrigger(keyCode, modifierState));
+ }
+ }
+
+ private static InputGestureData createKeyGesture(int keycode, int modifierState,
+ int keyGestureType) {
+ return new InputGestureData.Builder()
+ .setTrigger(createKeyTrigger(keycode, modifierState))
+ .setKeyGestureType(keyGestureType)
+ .build();
+ }
+
public void dump(IndentingPrintWriter ipw) {
ipw.println("InputGestureManager:");
ipw.increaseIndent();
- synchronized (mCustomInputGestures) {
+ synchronized (mGestureLock) {
+ ipw.println("System Shortcuts:");
+ ipw.increaseIndent();
+ for (InputGestureData systemShortcut : mSystemShortcuts.values()) {
+ ipw.println(systemShortcut);
+ }
+ ipw.decreaseIndent();
+ ipw.println("Blocklisted Triggers:");
+ ipw.increaseIndent();
+ for (InputGestureData.Trigger blocklistedTrigger : mBlockListedTriggers) {
+ ipw.println(blocklistedTrigger);
+ }
+ ipw.decreaseIndent();
+ ipw.println("Custom Gestures:");
+ ipw.increaseIndent();
int size = mCustomInputGestures.size();
for (int i = 0; i < size; i++) {
Map<InputGestureData.Trigger, InputGestureData> customGestures =
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index 1c5bd59..265e453 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -23,11 +23,13 @@
import android.hardware.display.DisplayViewport;
import android.hardware.input.KeyGestureEvent;
import android.os.IBinder;
+import android.os.RemoteException;
import android.util.SparseBooleanArray;
import android.view.InputChannel;
import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.inputmethod.InputMethodSubtypeHandle;
+import com.android.internal.policy.IShortcutService;
import java.util.List;
@@ -278,6 +280,15 @@
*/
public abstract void setAccessibilityPointerIconScaleFactor(int displayId, float scaleFactor);
+
+ /**
+ * Register shortcuts for input manager to dispatch.
+ * Shortcut code is packed as (metaState << Integer.SIZE) | keyCode
+ * @hide
+ */
+ public abstract void registerShortcutKey(long shortcutCode,
+ IShortcutService shortcutKeyReceiver) throws RemoteException;
+
/**
* Set whether the given input device can wake up the kernel from sleep
* when it generates input events. By default, usually only internal (built-in)
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 26929f5..78e3b84 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -64,7 +64,6 @@
import android.hardware.input.IStickyModifierStateListener;
import android.hardware.input.ITabletModeChangedListener;
import android.hardware.input.InputDeviceIdentifier;
-import android.hardware.input.InputGestureData;
import android.hardware.input.InputManager;
import android.hardware.input.InputSensorInfo;
import android.hardware.input.InputSettings;
@@ -130,6 +129,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.inputmethod.InputMethodSubtypeHandle;
import com.android.internal.os.SomeArgs;
+import com.android.internal.policy.IShortcutService;
import com.android.internal.policy.KeyInterceptionInfo;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
@@ -2313,6 +2313,12 @@
// Native callback.
@SuppressWarnings("unused")
+ private void notifyTouchpadThreeFingerTap() {
+ mKeyGestureController.handleTouchpadThreeFingerTap();
+ }
+
+ // Native callback.
+ @SuppressWarnings("unused")
private void notifySwitch(long whenNanos, int switchValues, int switchMask) {
if (DEBUG) {
Slog.d(TAG, "notifySwitch: values=" + Integer.toHexString(switchValues)
@@ -3020,6 +3026,11 @@
return mKeyGestureController.getCustomInputGestures(UserHandle.getCallingUserId());
}
+ @Override
+ public AidlInputGestureData[] getAppLaunchBookmarks() {
+ return mKeyGestureController.getAppLaunchBookmarks();
+ }
+
private void handleCurrentUserChanged(@UserIdInt int userId) {
mCurrentUserId = userId;
mKeyGestureController.setCurrentUserId(userId);
@@ -3564,6 +3575,12 @@
}
@Override
+ public void registerShortcutKey(long shortcutCode, IShortcutService shortcutKeyReceiver)
+ throws RemoteException {
+ mKeyGestureController.registerShortcutKey(shortcutCode, shortcutKeyReceiver);
+ }
+
+ @Override
public boolean setKernelWakeEnabled(int deviceId, boolean enabled) {
return mNative.setKernelWakeEnabled(deviceId, enabled);
}
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index d1a6d3b..420db90 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -143,6 +143,10 @@
observer.accept("just booted");
}
+ // TODO(b/365063048): add an entry to mObservers that calls this instead, once we have a
+ // setting that can be observed.
+ updateTouchpadThreeFingerTapShortcutEnabled();
+
configureUserActivityPokeInterval();
}
@@ -205,6 +209,11 @@
mNative.setTouchpadRightClickZoneEnabled(InputSettings.useTouchpadRightClickZone(mContext));
}
+ private void updateTouchpadThreeFingerTapShortcutEnabled() {
+ mNative.setTouchpadThreeFingerTapShortcutEnabled(
+ InputSettings.useTouchpadThreeFingerTapShortcut(mContext));
+ }
+
private void updateShowTouches() {
mNative.setShowTouches(getBoolean(Settings.System.SHOW_TOUCHES, false));
}
diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java
index ebeef65..e0991ec 100644
--- a/services/core/java/com/android/server/input/KeyGestureController.java
+++ b/services/core/java/com/android/server/input/KeyGestureController.java
@@ -20,12 +20,8 @@
import static android.content.pm.PackageManager.FEATURE_WATCH;
import static android.view.WindowManagerPolicyConstants.FLAG_INTERACTIVE;
-import static com.android.hardware.input.Flags.keyboardA11yShortcutControl;
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 static com.android.window.flags.Flags.enableMoveToNextDisplayShortcut;
-import static com.android.window.flags.Flags.enableTaskResizingKeyboardShortcuts;
import android.annotation.BinderThread;
import android.annotation.MainThread;
@@ -69,6 +65,7 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.policy.IShortcutService;
import com.android.server.policy.KeyCombinationManager;
import java.util.ArrayDeque;
@@ -119,7 +116,8 @@
private final int mSystemPid;
private final KeyCombinationManager mKeyCombinationManager;
private final SettingsObserver mSettingsObserver;
- private final InputGestureManager mInputGestureManager = new InputGestureManager();
+ private final AppLaunchShortcutManager mAppLaunchShortcutManager;
+ private final InputGestureManager mInputGestureManager;
private static final Object mUserLock = new Object();
@UserIdInt
@GuardedBy("mUserLock")
@@ -131,7 +129,6 @@
private boolean mPendingHideRecentSwitcher;
// Platform behaviors
- private boolean mEnableBugReportKeyboardShortcut;
private boolean mHasFeatureWatch;
private boolean mHasFeatureLeanback;
@@ -178,13 +175,13 @@
});
mKeyCombinationManager = new KeyCombinationManager(mHandler);
mSettingsObserver = new SettingsObserver(mHandler);
+ mAppLaunchShortcutManager = new AppLaunchShortcutManager(mContext);
+ mInputGestureManager = new InputGestureManager(mContext);
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);
@@ -437,6 +434,8 @@
public void systemRunning() {
mSettingsObserver.observe();
+ mAppLaunchShortcutManager.systemRunning();
+ mInputGestureManager.systemRunning();
}
public boolean interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
@@ -513,15 +512,34 @@
mPendingCapsLockToggle = false;
}
+ // Handle App launch shortcuts
+ AppLaunchShortcutManager.InterceptKeyResult result = mAppLaunchShortcutManager.interceptKey(
+ event);
+ if (result.consumed()) {
+ return true;
+ }
+ if (result.appLaunchData() != null) {
+ return handleKeyGesture(deviceId, new int[]{keyCode}, metaState,
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+ focusedToken, /* flags = */0, result.appLaunchData());
+ }
+
+ // Handle system shortcuts
+ if (firstDown) {
+ InputGestureData systemShortcut = mInputGestureManager.getSystemShortcutForKeyEvent(
+ event);
+ if (systemShortcut != null) {
+ return handleKeyGesture(deviceId, new int[]{keyCode}, metaState,
+ systemShortcut.getAction().keyGestureType(),
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+ displayId, focusedToken, /* flags = */0,
+ systemShortcut.getAction().appLaunchData());
+ }
+ }
+
+ // Handle system keys
switch (keyCode) {
- case KeyEvent.KEYCODE_A:
- if (firstDown && event.isMetaPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- break;
case KeyEvent.KEYCODE_RECENT_APPS:
if (firstDown) {
handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
@@ -544,257 +562,6 @@
/* appLaunchData = */null);
}
return true;
- case KeyEvent.KEYCODE_H:
- case KeyEvent.KEYCODE_ENTER:
- if (firstDown && event.isMetaPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_HOME,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- break;
- case KeyEvent.KEYCODE_I:
- if (firstDown && event.isMetaPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- break;
- case KeyEvent.KEYCODE_L:
- if (firstDown && event.isMetaPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- break;
- case KeyEvent.KEYCODE_N:
- if (firstDown && event.isMetaPressed()) {
- if (event.isCtrlPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- } else {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- }
- break;
- case KeyEvent.KEYCODE_S:
- if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- break;
- case KeyEvent.KEYCODE_T:
- if (keyboardA11yShortcutControl()) {
- if (firstDown && event.isMetaPressed() && event.isAltPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- }
- break;
- case KeyEvent.KEYCODE_3:
- if (InputSettings.isAccessibilityBounceKeysFeatureEnabled()
- && keyboardA11yShortcutControl()) {
- if (firstDown && event.isMetaPressed() && event.isAltPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- }
- break;
- case KeyEvent.KEYCODE_4:
- if (InputSettings.isAccessibilityMouseKeysFeatureFlagEnabled()
- && keyboardA11yShortcutControl()) {
- if (firstDown && event.isMetaPressed() && event.isAltPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- }
- break;
- case KeyEvent.KEYCODE_5:
- if (InputSettings.isAccessibilityStickyKeysFeatureEnabled()
- && keyboardA11yShortcutControl()) {
- if (firstDown && event.isMetaPressed() && event.isAltPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- }
- break;
- case KeyEvent.KEYCODE_6:
- if (InputSettings.isAccessibilitySlowKeysFeatureFlagEnabled()
- && keyboardA11yShortcutControl()) {
- if (firstDown && event.isMetaPressed() && event.isAltPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- }
- break;
- case KeyEvent.KEYCODE_DEL:
- if (newBugreportKeyboardShortcut()) {
- if (firstDown && mEnableBugReportKeyboardShortcut && event.isMetaPressed()
- && event.isCtrlPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- }
- // fall through
- case KeyEvent.KEYCODE_ESCAPE:
- if (firstDown && event.isMetaPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_BACK,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- break;
- case KeyEvent.KEYCODE_DPAD_UP:
- if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- break;
- case KeyEvent.KEYCODE_DPAD_DOWN:
- if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- break;
- case KeyEvent.KEYCODE_DPAD_LEFT:
- if (firstDown && event.isMetaPressed()) {
- if (event.isCtrlPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- } else if (event.isAltPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- } else {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_BACK,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- }
- break;
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- if (firstDown && event.isMetaPressed()) {
- if (event.isCtrlPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- } else if (event.isAltPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- }
- break;
- case KeyEvent.KEYCODE_D:
- if (enableMoveToNextDisplayShortcut()) {
- if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE,
- displayId, focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- }
- break;
- case KeyEvent.KEYCODE_LEFT_BRACKET:
- if (enableTaskResizingKeyboardShortcuts()) {
- if (firstDown && event.isAltPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE,
- displayId, focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- }
- break;
- case KeyEvent.KEYCODE_RIGHT_BRACKET:
- if (enableTaskResizingKeyboardShortcuts()) {
- if (firstDown && event.isAltPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE,
- displayId, focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- }
- break;
- case KeyEvent.KEYCODE_EQUALS:
- if (enableTaskResizingKeyboardShortcuts()) {
- if (firstDown && event.isAltPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE,
- displayId, focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- }
- break;
- case KeyEvent.KEYCODE_MINUS:
- if (enableTaskResizingKeyboardShortcuts()) {
- if (firstDown && event.isAltPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE,
- displayId, focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- }
- break;
- case KeyEvent.KEYCODE_SLASH:
- if (firstDown && event.isMetaPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- break;
case KeyEvent.KEYCODE_BRIGHTNESS_UP:
case KeyEvent.KEYCODE_BRIGHTNESS_DOWN:
if (down) {
@@ -938,12 +705,7 @@
return true;
case KeyEvent.KEYCODE_TAB:
if (firstDown) {
- if (event.isMetaPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- } else if (!mPendingHideRecentSwitcher) {
+ if (!mPendingHideRecentSwitcher) {
final int shiftlessModifiers =
event.getModifiers() & ~KeyEvent.META_SHIFT_MASK;
if (KeyEvent.metaStateHasModifiers(
@@ -1004,6 +766,7 @@
return true;
}
+ // Handle custom shortcuts
if (firstDown) {
InputGestureData customGesture;
synchronized (mUserLock) {
@@ -1134,6 +897,13 @@
handleKeyGesture(event, null /*focusedToken*/);
}
+ public void handleTouchpadThreeFingerTap() {
+ // TODO(b/365063048): trigger a custom shortcut based on the three-finger tap.
+ if (DEBUG) {
+ Slog.d(TAG, "Three-finger touchpad tap occurred");
+ }
+ }
+
@MainThread
public void setCurrentUserId(@UserIdInt int userId) {
synchronized (mUserLock) {
@@ -1251,6 +1021,16 @@
return result;
}
+ @BinderThread
+ public AidlInputGestureData[] getAppLaunchBookmarks() {
+ List<InputGestureData> bookmarks = mAppLaunchShortcutManager.getBookmarks();
+ AidlInputGestureData[] result = new AidlInputGestureData[bookmarks.size()];
+ for (int i = 0; i < bookmarks.size(); i++) {
+ result[i] = bookmarks.get(i).getAidlData();
+ }
+ return result;
+ }
+
private void onKeyGestureEventListenerDied(int pid) {
synchronized (mKeyGestureEventListenerRecords) {
mKeyGestureEventListenerRecords.remove(pid);
@@ -1322,6 +1102,15 @@
}
}
+ public void registerShortcutKey(long shortcutCode, IShortcutService shortcutKeyReceiver)
+ throws RemoteException {
+ mAppLaunchShortcutManager.registerShortcutKey(shortcutCode, shortcutKeyReceiver);
+ }
+
+ public List<InputGestureData> getBookmarks() {
+ return mAppLaunchShortcutManager.getBookmarks();
+ }
+
private void onKeyGestureHandlerDied(int pid) {
synchronized (mKeyGestureHandlerRecords) {
mKeyGestureHandlerRecords.remove(pid);
@@ -1464,6 +1253,7 @@
}
ipw.decreaseIndent();
mKeyCombinationManager.dump("", ipw);
+ mAppLaunchShortcutManager.dump(ipw);
mInputGestureManager.dump(ipw);
}
}
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 283fdea..8903c27 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -143,6 +143,8 @@
void setTouchpadRightClickZoneEnabled(boolean enabled);
+ void setTouchpadThreeFingerTapShortcutEnabled(boolean enabled);
+
void setShowTouches(boolean enabled);
void setNonInteractiveDisplays(int[] displayIds);
@@ -427,6 +429,9 @@
public native void setTouchpadRightClickZoneEnabled(boolean enabled);
@Override
+ public native void setTouchpadThreeFingerTapShortcutEnabled(boolean enabled);
+
+ @Override
public native void setShowTouches(boolean enabled);
@Override
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 0104373..d8483f7 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -4969,7 +4969,7 @@
final var userData = getUserData(userId);
if (Flags.refactorInsetsController()) {
setImeVisibilityOnFocusedWindowClient(false, userData,
- null /* TODO(b329229469) check statsToken */);
+ null /* TODO(b/353463205) check statsToken */);
} else {
hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
diff --git a/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java b/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java
deleted file mode 100644
index e831e40..0000000
--- a/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.parser;
-
-import android.annotation.Nullable;
-import android.util.Xml;
-
-import com.android.modules.utils.TypedXmlPullParser;
-import com.android.server.integrity.model.RuleMetadata;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/** Helper class for parsing rule metadata. */
-public class RuleMetadataParser {
-
- public static final String RULE_PROVIDER_TAG = "P";
- public static final String VERSION_TAG = "V";
-
- /** Parse the rule metadata from an input stream. */
- @Nullable
- public static RuleMetadata parse(InputStream inputStream)
- throws XmlPullParserException, IOException {
-
- String ruleProvider = "";
- String version = "";
-
- TypedXmlPullParser xmlPullParser = Xml.resolvePullParser(inputStream);
-
- int eventType;
- while ((eventType = xmlPullParser.next()) != XmlPullParser.END_DOCUMENT) {
- if (eventType == XmlPullParser.START_TAG) {
- String tag = xmlPullParser.getName();
- switch (tag) {
- case RULE_PROVIDER_TAG:
- ruleProvider = xmlPullParser.nextText();
- break;
- case VERSION_TAG:
- version = xmlPullParser.nextText();
- break;
- default:
- throw new IllegalStateException("Unknown tag in metadata: " + tag);
- }
- }
- }
-
- return new RuleMetadata(ruleProvider, version);
- }
-}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
deleted file mode 100644
index 8ba5870..0000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
+++ /dev/null
@@ -1,324 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START;
-import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END;
-import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START;
-import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.DEFAULT_FORMAT_VERSION;
-import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.FORMAT_VERSION_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.INSTALLER_ALLOWED_BY_MANIFEST_START;
-import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
-import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY;
-import static com.android.server.integrity.model.IndexingFileConstants.INDEXING_BLOCK_SIZE;
-import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED;
-
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.InstallerAllowedByManifestFormula;
-import android.content.integrity.IntegrityFormula;
-import android.content.integrity.IntegrityUtils;
-import android.content.integrity.Rule;
-
-import com.android.internal.util.Preconditions;
-import com.android.server.integrity.model.BitOutputStream;
-import com.android.server.integrity.model.ByteTrackedOutputStream;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.stream.Collectors;
-
-/** A helper class to serialize rules from the {@link Rule} model to Binary representation. */
-public class RuleBinarySerializer implements RuleSerializer {
- static final int TOTAL_RULE_SIZE_LIMIT = 200000;
- static final int INDEXED_RULE_SIZE_LIMIT = 100000;
- static final int NONINDEXED_RULE_SIZE_LIMIT = 1000;
-
- // Get the byte representation for a list of rules.
- @Override
- public byte[] serialize(List<Rule> rules, Optional<Integer> formatVersion)
- throws RuleSerializeException {
- try {
- ByteArrayOutputStream rulesOutputStream = new ByteArrayOutputStream();
- serialize(rules, formatVersion, rulesOutputStream, new ByteArrayOutputStream());
- return rulesOutputStream.toByteArray();
- } catch (Exception e) {
- throw new RuleSerializeException(e.getMessage(), e);
- }
- }
-
- // Get the byte representation for a list of rules, and write them to an output stream.
- @Override
- public void serialize(
- List<Rule> rules,
- Optional<Integer> formatVersion,
- OutputStream rulesFileOutputStream,
- OutputStream indexingFileOutputStream)
- throws RuleSerializeException {
- try {
- if (rules == null) {
- throw new IllegalArgumentException("Null rules cannot be serialized.");
- }
-
- if (rules.size() > TOTAL_RULE_SIZE_LIMIT) {
- throw new IllegalArgumentException("Too many rules provided: " + rules.size());
- }
-
- // Determine the indexing groups and the order of the rules within each indexed group.
- Map<Integer, Map<String, List<Rule>>> indexedRules =
- RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets(rules);
-
- // Validate the rule blocks are not larger than expected limits.
- verifySize(indexedRules.get(PACKAGE_NAME_INDEXED), INDEXED_RULE_SIZE_LIMIT);
- verifySize(indexedRules.get(APP_CERTIFICATE_INDEXED), INDEXED_RULE_SIZE_LIMIT);
- verifySize(indexedRules.get(NOT_INDEXED), NONINDEXED_RULE_SIZE_LIMIT);
-
- // Serialize the rules.
- ByteTrackedOutputStream ruleFileByteTrackedOutputStream =
- new ByteTrackedOutputStream(rulesFileOutputStream);
- serializeRuleFileMetadata(formatVersion, ruleFileByteTrackedOutputStream);
- LinkedHashMap<String, Integer> packageNameIndexes =
- serializeRuleList(
- indexedRules.get(PACKAGE_NAME_INDEXED),
- ruleFileByteTrackedOutputStream);
- LinkedHashMap<String, Integer> appCertificateIndexes =
- serializeRuleList(
- indexedRules.get(APP_CERTIFICATE_INDEXED),
- ruleFileByteTrackedOutputStream);
- LinkedHashMap<String, Integer> unindexedRulesIndexes =
- serializeRuleList(
- indexedRules.get(NOT_INDEXED), ruleFileByteTrackedOutputStream);
-
- // Serialize their indexes.
- BitOutputStream indexingBitOutputStream = new BitOutputStream(indexingFileOutputStream);
- serializeIndexGroup(packageNameIndexes, indexingBitOutputStream, /* isIndexed= */ true);
- serializeIndexGroup(
- appCertificateIndexes, indexingBitOutputStream, /* isIndexed= */ true);
- serializeIndexGroup(
- unindexedRulesIndexes, indexingBitOutputStream, /* isIndexed= */ false);
- indexingBitOutputStream.flush();
- } catch (Exception e) {
- throw new RuleSerializeException(e.getMessage(), e);
- }
- }
-
- private void verifySize(Map<String, List<Rule>> ruleListMap, int ruleSizeLimit) {
- int totalRuleCount =
- ruleListMap.values().stream()
- .map(list -> list.size())
- .collect(Collectors.summingInt(Integer::intValue));
- if (totalRuleCount > ruleSizeLimit) {
- throw new IllegalArgumentException(
- "Too many rules provided in the indexing group. Provided "
- + totalRuleCount
- + " limit "
- + ruleSizeLimit);
- }
- }
-
- private void serializeRuleFileMetadata(
- Optional<Integer> formatVersion, ByteTrackedOutputStream outputStream)
- throws IOException {
- int formatVersionValue = formatVersion.orElse(DEFAULT_FORMAT_VERSION);
-
- BitOutputStream bitOutputStream = new BitOutputStream(outputStream);
- bitOutputStream.setNext(FORMAT_VERSION_BITS, formatVersionValue);
- bitOutputStream.flush();
- }
-
- private LinkedHashMap<String, Integer> serializeRuleList(
- Map<String, List<Rule>> rulesMap, ByteTrackedOutputStream outputStream)
- throws IOException {
- Preconditions.checkArgument(
- rulesMap != null, "serializeRuleList should never be called with null rule list.");
-
- BitOutputStream bitOutputStream = new BitOutputStream(outputStream);
- LinkedHashMap<String, Integer> indexMapping = new LinkedHashMap();
- indexMapping.put(START_INDEXING_KEY, outputStream.getWrittenBytesCount());
-
- List<String> sortedKeys = rulesMap.keySet().stream().sorted().collect(Collectors.toList());
- int indexTracker = 0;
- for (String key : sortedKeys) {
- if (indexTracker >= INDEXING_BLOCK_SIZE) {
- indexMapping.put(key, outputStream.getWrittenBytesCount());
- indexTracker = 0;
- }
-
- for (Rule rule : rulesMap.get(key)) {
- serializeRule(rule, bitOutputStream);
- bitOutputStream.flush();
- indexTracker++;
- }
- }
- indexMapping.put(END_INDEXING_KEY, outputStream.getWrittenBytesCount());
-
- return indexMapping;
- }
-
- private void serializeRule(Rule rule, BitOutputStream bitOutputStream) throws IOException {
- if (rule == null) {
- throw new IllegalArgumentException("Null rule can not be serialized");
- }
-
- // Start with a '1' bit to mark the start of a rule.
- bitOutputStream.setNext();
-
- serializeFormula(rule.getFormula(), bitOutputStream);
- bitOutputStream.setNext(EFFECT_BITS, rule.getEffect());
-
- // End with a '1' bit to mark the end of a rule.
- bitOutputStream.setNext();
- }
-
- private void serializeFormula(IntegrityFormula formula, BitOutputStream bitOutputStream)
- throws IOException {
- if (formula instanceof AtomicFormula) {
- serializeAtomicFormula((AtomicFormula) formula, bitOutputStream);
- } else if (formula instanceof CompoundFormula) {
- serializeCompoundFormula((CompoundFormula) formula, bitOutputStream);
- } else if (formula instanceof InstallerAllowedByManifestFormula) {
- bitOutputStream.setNext(SEPARATOR_BITS, INSTALLER_ALLOWED_BY_MANIFEST_START);
- } else {
- throw new IllegalArgumentException(
- String.format("Invalid formula type: %s", formula.getClass()));
- }
- }
-
- private void serializeCompoundFormula(
- CompoundFormula compoundFormula, BitOutputStream bitOutputStream) throws IOException {
- if (compoundFormula == null) {
- throw new IllegalArgumentException("Null compound formula can not be serialized");
- }
-
- bitOutputStream.setNext(SEPARATOR_BITS, COMPOUND_FORMULA_START);
- bitOutputStream.setNext(CONNECTOR_BITS, compoundFormula.getConnector());
- for (IntegrityFormula formula : compoundFormula.getFormulas()) {
- serializeFormula(formula, bitOutputStream);
- }
- bitOutputStream.setNext(SEPARATOR_BITS, COMPOUND_FORMULA_END);
- }
-
- private void serializeAtomicFormula(
- AtomicFormula atomicFormula, BitOutputStream bitOutputStream) throws IOException {
- if (atomicFormula == null) {
- throw new IllegalArgumentException("Null atomic formula can not be serialized");
- }
-
- bitOutputStream.setNext(SEPARATOR_BITS, ATOMIC_FORMULA_START);
- bitOutputStream.setNext(KEY_BITS, atomicFormula.getKey());
- if (atomicFormula.getTag() == AtomicFormula.STRING_ATOMIC_FORMULA_TAG) {
- AtomicFormula.StringAtomicFormula stringAtomicFormula =
- (AtomicFormula.StringAtomicFormula) atomicFormula;
- bitOutputStream.setNext(OPERATOR_BITS, AtomicFormula.EQ);
- serializeStringValue(
- stringAtomicFormula.getValue(),
- stringAtomicFormula.getIsHashedValue(),
- bitOutputStream);
- } else if (atomicFormula.getTag() == AtomicFormula.LONG_ATOMIC_FORMULA_TAG) {
- AtomicFormula.LongAtomicFormula longAtomicFormula =
- (AtomicFormula.LongAtomicFormula) atomicFormula;
- bitOutputStream.setNext(OPERATOR_BITS, longAtomicFormula.getOperator());
- // TODO(b/147880712): Temporary hack until we support long values in bitOutputStream
- long value = longAtomicFormula.getValue();
- serializeIntValue((int) (value >>> 32), bitOutputStream);
- serializeIntValue((int) value, bitOutputStream);
- } else if (atomicFormula.getTag() == AtomicFormula.BOOLEAN_ATOMIC_FORMULA_TAG) {
- AtomicFormula.BooleanAtomicFormula booleanAtomicFormula =
- (AtomicFormula.BooleanAtomicFormula) atomicFormula;
- bitOutputStream.setNext(OPERATOR_BITS, AtomicFormula.EQ);
- serializeBooleanValue(booleanAtomicFormula.getValue(), bitOutputStream);
- } else {
- throw new IllegalArgumentException(
- String.format("Invalid atomic formula type: %s", atomicFormula.getClass()));
- }
- }
-
- private void serializeIndexGroup(
- LinkedHashMap<String, Integer> indexes,
- BitOutputStream bitOutputStream,
- boolean isIndexed)
- throws IOException {
- // Output the starting location of this indexing group.
- serializeStringValue(START_INDEXING_KEY, /* isHashedValue= */ false, bitOutputStream);
- serializeIntValue(indexes.get(START_INDEXING_KEY), bitOutputStream);
-
- // If the group is indexed, output the locations of the indexes.
- if (isIndexed) {
- for (Map.Entry<String, Integer> entry : indexes.entrySet()) {
- if (!entry.getKey().equals(START_INDEXING_KEY)
- && !entry.getKey().equals(END_INDEXING_KEY)) {
- serializeStringValue(
- entry.getKey(), /* isHashedValue= */ false, bitOutputStream);
- serializeIntValue(entry.getValue(), bitOutputStream);
- }
- }
- }
-
- // Output the end location of this indexing group.
- serializeStringValue(END_INDEXING_KEY, /*isHashedValue= */ false, bitOutputStream);
- serializeIntValue(indexes.get(END_INDEXING_KEY), bitOutputStream);
- }
-
- private void serializeStringValue(
- String value, boolean isHashedValue, BitOutputStream bitOutputStream)
- throws IOException {
- if (value == null) {
- throw new IllegalArgumentException("String value can not be null.");
- }
- byte[] valueBytes = getBytesForString(value, isHashedValue);
-
- bitOutputStream.setNext(isHashedValue);
- bitOutputStream.setNext(VALUE_SIZE_BITS, valueBytes.length);
- for (byte valueByte : valueBytes) {
- bitOutputStream.setNext(/* numOfBits= */ 8, valueByte);
- }
- }
-
- private void serializeIntValue(int value, BitOutputStream bitOutputStream) throws IOException {
- bitOutputStream.setNext(/* numOfBits= */ 32, value);
- }
-
- private void serializeBooleanValue(boolean value, BitOutputStream bitOutputStream)
- throws IOException {
- bitOutputStream.setNext(value);
- }
-
- // Get the byte array for a value.
- // If the value is not hashed, use its byte array form directly.
- // If the value is hashed, get the raw form decoding of the value. All hashed values are
- // hex-encoded. Serialized values are in raw form.
- private static byte[] getBytesForString(String value, boolean isHashedValue) {
- if (!isHashedValue) {
- return value.getBytes(StandardCharsets.UTF_8);
- }
- return IntegrityUtils.getBytesFromHexDigest(value);
- }
-}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java
deleted file mode 100644
index 2cbd4ede..0000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import android.annotation.IntDef;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/** Holds the indexing type and indexing key of a given formula. */
-class RuleIndexingDetails {
-
- static final int NOT_INDEXED = 0;
- static final int PACKAGE_NAME_INDEXED = 1;
- static final int APP_CERTIFICATE_INDEXED = 2;
-
- static final String DEFAULT_RULE_KEY = "N/A";
-
- /** Represents which indexed file the rule should be located. */
- @IntDef(
- value = {
- NOT_INDEXED,
- PACKAGE_NAME_INDEXED,
- APP_CERTIFICATE_INDEXED
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface IndexType {
- }
-
- private @IndexType int mIndexType;
- private String mRuleKey;
-
- /** Constructor without a ruleKey for {@code NOT_INDEXED}. */
- RuleIndexingDetails(@IndexType int indexType) {
- this.mIndexType = indexType;
- this.mRuleKey = DEFAULT_RULE_KEY;
- }
-
- /** Constructor with a ruleKey for indexed rules. */
- RuleIndexingDetails(@IndexType int indexType, String ruleKey) {
- this.mIndexType = indexType;
- this.mRuleKey = ruleKey;
- }
-
- /** Returns the indexing type for the rule. */
- @IndexType
- public int getIndexType() {
- return mIndexType;
- }
-
- /** Returns the identified rule key. */
- public String getRuleKey() {
- return mRuleKey;
- }
-}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
deleted file mode 100644
index e723559..0000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED;
-
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.IntegrityFormula;
-import android.content.integrity.Rule;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-
-/** A helper class for identifying the indexing type and key of a given rule. */
-class RuleIndexingDetailsIdentifier {
-
- /**
- * Splits a given rule list into three indexing categories. Each rule category is returned as a
- * TreeMap that is sorted by their indexing keys -- where keys correspond to package name for
- * PACKAGE_NAME_INDEXED rules, app certificate for APP_CERTIFICATE_INDEXED rules and N/A for
- * NOT_INDEXED rules.
- */
- public static Map<Integer, Map<String, List<Rule>>> splitRulesIntoIndexBuckets(
- List<Rule> rules) {
- if (rules == null) {
- throw new IllegalArgumentException(
- "Index buckets cannot be created for null rule list.");
- }
-
- Map<Integer, Map<String, List<Rule>>> typeOrganizedRuleMap = new HashMap();
- typeOrganizedRuleMap.put(NOT_INDEXED, new HashMap());
- typeOrganizedRuleMap.put(PACKAGE_NAME_INDEXED, new HashMap<>());
- typeOrganizedRuleMap.put(APP_CERTIFICATE_INDEXED, new HashMap<>());
-
- // Split the rules into the appropriate indexed pattern. The Tree Maps help us to keep the
- // entries sorted by their index key.
- for (Rule rule : rules) {
- RuleIndexingDetails indexingDetails;
- try {
- indexingDetails = getIndexingDetails(rule.getFormula());
- } catch (Exception e) {
- throw new IllegalArgumentException(
- String.format("Malformed rule identified. [%s]", rule.toString()));
- }
-
- int ruleIndexType = indexingDetails.getIndexType();
- String ruleKey = indexingDetails.getRuleKey();
-
- if (!typeOrganizedRuleMap.get(ruleIndexType).containsKey(ruleKey)) {
- typeOrganizedRuleMap.get(ruleIndexType).put(ruleKey, new ArrayList());
- }
-
- typeOrganizedRuleMap.get(ruleIndexType).get(ruleKey).add(rule);
- }
-
- return typeOrganizedRuleMap;
- }
-
- private static RuleIndexingDetails getIndexingDetails(IntegrityFormula formula) {
- switch (formula.getTag()) {
- case IntegrityFormula.COMPOUND_FORMULA_TAG:
- return getIndexingDetailsForCompoundFormula((CompoundFormula) formula);
- case IntegrityFormula.STRING_ATOMIC_FORMULA_TAG:
- return getIndexingDetailsForStringAtomicFormula(
- (AtomicFormula.StringAtomicFormula) formula);
- case IntegrityFormula.LONG_ATOMIC_FORMULA_TAG:
- case IntegrityFormula.INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG:
- case IntegrityFormula.BOOLEAN_ATOMIC_FORMULA_TAG:
- // Package name and app certificate related formulas are string atomic formulas.
- return new RuleIndexingDetails(NOT_INDEXED);
- default:
- throw new IllegalArgumentException(
- String.format("Invalid formula tag type: %s", formula.getTag()));
- }
- }
-
- private static RuleIndexingDetails getIndexingDetailsForCompoundFormula(
- CompoundFormula compoundFormula) {
- int connector = compoundFormula.getConnector();
- List<IntegrityFormula> formulas = compoundFormula.getFormulas();
-
- switch (connector) {
- case CompoundFormula.AND:
- case CompoundFormula.OR:
- // If there is a package name related atomic rule, return package name indexed.
- Optional<RuleIndexingDetails> packageNameRule =
- formulas.stream()
- .map(formula -> getIndexingDetails(formula))
- .filter(ruleIndexingDetails -> ruleIndexingDetails.getIndexType()
- == PACKAGE_NAME_INDEXED)
- .findAny();
- if (packageNameRule.isPresent()) {
- return packageNameRule.get();
- }
-
- // If there is an app certificate related atomic rule but no package name related
- // atomic rule, return app certificate indexed.
- Optional<RuleIndexingDetails> appCertificateRule =
- formulas.stream()
- .map(formula -> getIndexingDetails(formula))
- .filter(ruleIndexingDetails -> ruleIndexingDetails.getIndexType()
- == APP_CERTIFICATE_INDEXED)
- .findAny();
- if (appCertificateRule.isPresent()) {
- return appCertificateRule.get();
- }
-
- // Do not index when there is not package name or app certificate indexing.
- return new RuleIndexingDetails(NOT_INDEXED);
- default:
- // Having a NOT operator in the indexing messes up the indexing; e.g., deny
- // installation if app certificate is NOT X (should not be indexed with app cert
- // X). We will not keep these rules indexed.
- // Also any other type of unknown operators will not be indexed.
- return new RuleIndexingDetails(NOT_INDEXED);
- }
- }
-
- private static RuleIndexingDetails getIndexingDetailsForStringAtomicFormula(
- AtomicFormula.StringAtomicFormula atomicFormula) {
- switch (atomicFormula.getKey()) {
- case AtomicFormula.PACKAGE_NAME:
- return new RuleIndexingDetails(PACKAGE_NAME_INDEXED, atomicFormula.getValue());
- case AtomicFormula.APP_CERTIFICATE:
- return new RuleIndexingDetails(APP_CERTIFICATE_INDEXED, atomicFormula.getValue());
- default:
- return new RuleIndexingDetails(NOT_INDEXED);
- }
- }
-}
-
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java
deleted file mode 100644
index 022b4b8..0000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import static com.android.server.integrity.parser.RuleMetadataParser.RULE_PROVIDER_TAG;
-import static com.android.server.integrity.parser.RuleMetadataParser.VERSION_TAG;
-
-import android.util.Xml;
-
-import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.integrity.model.RuleMetadata;
-
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
-
-/** Helper class for writing rule metadata. */
-public class RuleMetadataSerializer {
- /** Serialize the rule metadata to an output stream. */
- public static void serialize(RuleMetadata ruleMetadata, OutputStream outputStream)
- throws IOException {
- TypedXmlSerializer xmlSerializer = Xml.resolveSerializer(outputStream);
-
- serializeTaggedValue(xmlSerializer, RULE_PROVIDER_TAG, ruleMetadata.getRuleProvider());
- serializeTaggedValue(xmlSerializer, VERSION_TAG, ruleMetadata.getVersion());
-
- xmlSerializer.endDocument();
- }
-
- private static void serializeTaggedValue(TypedXmlSerializer xmlSerializer, String tag,
- String value) throws IOException {
- xmlSerializer.startTag(/* namespace= */ null, tag);
- xmlSerializer.text(value);
- xmlSerializer.endTag(/* namespace= */ null, tag);
- }
-}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleSerializeException.java b/services/core/java/com/android/server/integrity/serializer/RuleSerializeException.java
deleted file mode 100644
index 60cfc48..0000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleSerializeException.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import android.annotation.NonNull;
-
-/**
- * Thrown when rule serialization fails.
- */
-public class RuleSerializeException extends Exception {
- public RuleSerializeException(@NonNull String message) {
- super(message);
- }
-
- public RuleSerializeException(@NonNull String message, @NonNull Throwable cause) {
- super(message, cause);
- }
-}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java
deleted file mode 100644
index 2941856..0000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import android.content.integrity.Rule;
-
-import java.io.OutputStream;
-import java.util.List;
-import java.util.Optional;
-
-/** A helper class to serialize rules from the {@link Rule} model. */
-public interface RuleSerializer {
-
- /** Serialize rules to an output stream */
- void serialize(
- List<Rule> rules,
- Optional<Integer> formatVersion,
- OutputStream ruleFileOutputStream,
- OutputStream indexingFileOutputStream)
- throws RuleSerializeException;
-
- /** Serialize rules to a ByteArray. */
- byte[] serialize(List<Rule> rules, Optional<Integer> formatVersion)
- throws RuleSerializeException;
-}
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java
index d265b6a..a45ea1d 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -17,8 +17,14 @@
package com.android.server.media.quality;
import android.content.Context;
+import android.media.quality.AmbientBacklightSettings;
+import android.media.quality.IAmbientBacklightCallback;
import android.media.quality.IMediaQualityManager;
+import android.media.quality.IPictureProfileCallback;
+import android.media.quality.ISoundProfileCallback;
+import android.media.quality.ParamCapability;
import android.media.quality.PictureProfile;
+import android.media.quality.SoundProfile;
import com.android.server.SystemService;
@@ -53,6 +59,14 @@
return pp;
}
@Override
+ public void updatePictureProfile(long id, PictureProfile pp) {
+ // TODO: implement
+ }
+ @Override
+ public void removePictureProfile(long id) {
+ // TODO: implement
+ }
+ @Override
public PictureProfile getPictureProfileById(long id) {
return null;
}
@@ -65,8 +79,96 @@
return new ArrayList<>();
}
@Override
- public List<PictureProfile> getAvailableAllPictureProfiles() {
+ public List<PictureProfile> getAllPictureProfiles() {
return new ArrayList<>();
}
+
+ @Override
+ public SoundProfile createSoundProfile(SoundProfile pp) {
+ // TODO: implement
+ return pp;
+ }
+ @Override
+ public void updateSoundProfile(long id, SoundProfile pp) {
+ // TODO: implement
+ }
+ @Override
+ public void removeSoundProfile(long id) {
+ // TODO: implement
+ }
+ @Override
+ public SoundProfile getSoundProfileById(long id) {
+ return null;
+ }
+ @Override
+ public List<SoundProfile> getSoundProfilesByPackage(String packageName) {
+ return new ArrayList<>();
+ }
+ @Override
+ public List<SoundProfile> getAvailableSoundProfiles() {
+ return new ArrayList<>();
+ }
+ @Override
+ public List<SoundProfile> getAllSoundProfiles() {
+ return new ArrayList<>();
+ }
+
+
+ @Override
+ public void registerPictureProfileCallback(final IPictureProfileCallback callback) {
+ }
+ @Override
+ public void registerSoundProfileCallback(final ISoundProfileCallback callback) {
+ }
+
+ @Override
+ public void registerAmbientBacklightCallback(IAmbientBacklightCallback callback) {
+ }
+
+ @Override
+ public void setAmbientBacklightSettings(AmbientBacklightSettings settings) {
+ }
+
+ @Override
+ public void setAmbientBacklightEnabled(boolean enabled) {
+ }
+
+ @Override
+ public List<ParamCapability> getParamCapabilities(List<String> names) {
+ return new ArrayList<>();
+ }
+
+
+ @Override
+ public boolean isSupported() {
+ return false;
+ }
+
+ @Override
+ public void setAutoPictureQualityEnabled(boolean enabled) {
+ }
+
+ @Override
+ public boolean isAutoPictureQualityEnabled() {
+ return false;
+ }
+
+ @Override
+ public void setSuperResolutionEnabled(boolean enabled) {
+ }
+
+ @Override
+ public boolean isSuperResolutionEnabled() {
+ return false;
+ }
+
+ @Override
+ public void setAutoSoundQualityEnabled(boolean enabled) {
+ }
+
+ @Override
+ public boolean isAutoSoundQualityEnabled() {
+ return false;
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index 20cca969..af2bb17 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -404,9 +404,11 @@
void handlePackageRemove(String packageName, int userId) {
initBackgroundInstalledPackages();
+ if (mBackgroundInstalledPackages.contains(userId, packageName)) {
+ mCallbackHelper.notifyAllCallbacks(userId, packageName, INSTALL_EVENT_TYPE_UNINSTALL);
+ }
mBackgroundInstalledPackages.remove(userId, packageName);
writeBackgroundInstalledPackagesToDisk();
- mCallbackHelper.notifyAllCallbacks(userId, packageName, INSTALL_EVENT_TYPE_UNINSTALL);
}
void handleUsageEvent(UsageEvents.Event event, int userId) {
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 355184e..d9e7696 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -85,6 +85,7 @@
import static com.android.server.pm.PackageManagerService.SCAN_REQUIRE_KNOWN;
import static com.android.server.pm.PackageManagerService.SCAN_UPDATE_SIGNATURE;
import static com.android.server.pm.PackageManagerService.TAG;
+import static com.android.server.pm.PackageManagerService.WATCHDOG_TIMEOUT;
import static com.android.server.pm.PackageManagerServiceUtils.comparePackageSignatures;
import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
import static com.android.server.pm.PackageManagerServiceUtils.compressedFileExists;
@@ -133,9 +134,11 @@
import android.os.Environment;
import android.os.FileUtils;
import android.os.Message;
+import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.SELinux;
+import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
@@ -174,7 +177,6 @@
import com.android.server.EventLogTags;
import com.android.server.SystemConfig;
import com.android.server.criticalevents.CriticalEventLog;
-import com.android.server.pm.dex.ArtManagerService;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.parsing.PackageCacher;
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
@@ -210,6 +212,10 @@
final class InstallPackageHelper {
+ // One minute over PM WATCHDOG_TIMEOUT
+ private static final long WAKELOCK_TIMEOUT_MS = WATCHDOG_TIMEOUT + 1000 * 60;
+ private static final String INSTALLER_WAKE_LOCK_TAG = "installer:packages";
+
private final PackageManagerService mPm;
private final AppDataHelper mAppDataHelper;
private final BroadcastHelper mBroadcastHelper;
@@ -218,14 +224,16 @@
private final IncrementalManager mIncrementalManager;
private final ApexManager mApexManager;
private final DexManager mDexManager;
- private final ArtManagerService mArtManagerService;
private final Context mContext;
- private final PackageDexOptimizer mPackageDexOptimizer;
private final PackageAbiHelper mPackageAbiHelper;
private final SharedLibrariesImpl mSharedLibraries;
private final PackageManagerServiceInjector mInjector;
private final UpdateOwnershipHelper mUpdateOwnershipHelper;
+ private final Object mInternalLock = new Object();
+ @GuardedBy("mInternalLock")
+ private PowerManager.WakeLock mInstallingWakeLock;
+
// TODO(b/198166813): remove PMS dependency
InstallPackageHelper(PackageManagerService pm,
AppDataHelper appDataHelper,
@@ -241,9 +249,7 @@
mIncrementalManager = pm.mInjector.getIncrementalManager();
mApexManager = pm.mInjector.getApexManager();
mDexManager = pm.mInjector.getDexManager();
- mArtManagerService = pm.mInjector.getArtManagerService();
mContext = pm.mInjector.getContext();
- mPackageDexOptimizer = pm.mInjector.getPackageDexOptimizer();
mPackageAbiHelper = pm.mInjector.getAbiHelper();
mSharedLibraries = pm.mInjector.getSharedLibrariesImpl();
mUpdateOwnershipHelper = pm.mInjector.getUpdateOwnershipHelper();
@@ -1013,6 +1019,7 @@
boolean success = false;
final Map<String, Boolean> createdAppId = new ArrayMap<>(requests.size());
final Map<String, Settings.VersionInfo> versionInfos = new ArrayMap<>(requests.size());
+ final long acquireTime = acquireWakeLock(requests.size());
try {
CriticalEventLog.getInstance().logInstallPackagesStarted();
if (prepareInstallPackages(requests)
@@ -1033,6 +1040,46 @@
} finally {
completeInstallProcess(requests, createdAppId, success);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ releaseWakeLock(acquireTime, requests.size());
+ }
+ }
+
+ private long acquireWakeLock(int count) {
+ if (!mPm.isSystemReady()) {
+ return -1;
+ }
+ synchronized (mInternalLock) {
+ if (mInstallingWakeLock == null) {
+ PowerManager pwm = mContext.getSystemService(PowerManager.class);
+ if (pwm != null) {
+ mInstallingWakeLock = pwm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ INSTALLER_WAKE_LOCK_TAG);
+ } else {
+ Slog.w(TAG, "Unable to obtain power manager while obtaining wake lock");
+ return -1;
+ }
+ }
+
+ mInstallingWakeLock.acquire(WAKELOCK_TIMEOUT_MS * count);
+ return SystemClock.elapsedRealtime();
+ }
+ }
+
+ private void releaseWakeLock(final long acquireTime, int count) {
+ if (acquireTime < 0) {
+ return;
+ }
+ synchronized (mInternalLock) {
+ try {
+ if (mInstallingWakeLock == null) {
+ return;
+ }
+ if (mInstallingWakeLock.isHeld()) {
+ mInstallingWakeLock.release();
+ }
+ } catch (RuntimeException e) {
+ Slog.wtf(TAG, "Error while releasing installer lock", e);
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 5fc3e33..24933ca 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1015,7 +1015,8 @@
permission, attributionSource, message, forDataDelivery, startDataDelivery,
fromDatasource, attributedOp);
// Finish any started op if some step in the attribution chain failed.
- if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED) {
+ if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED
+ && result != PermissionChecker.PERMISSION_SOFT_DENIED) {
if (attributedOp == AppOpsManager.OP_NONE) {
finishDataDelivery(AppOpsManager.permissionToOpCode(permission),
attributionSource.asState(), fromDatasource);
@@ -1244,6 +1245,7 @@
final boolean hasChain = attributionChainId != ATTRIBUTION_CHAIN_ID_NONE;
AttributionSource current = attributionSource;
AttributionSource next = null;
+ AttributionSource prev = null;
// We consider the chain trusted if the start node has UPDATE_APP_OPS_STATS, and
// every attributionSource in the chain is registered with the system.
final boolean isChainStartTrusted = !hasChain || checkPermission(context,
@@ -1310,8 +1312,21 @@
selfAccess, singleReceiverFromDatasource, attributedOp,
proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
- switch (opMode) {
- case AppOpsManager.MODE_ERRORED: {
+ if (opMode != AppOpsManager.MODE_ALLOWED) {
+ // Current failed the perm check, so if we are part-way through an attr chain,
+ // we need to clean up the already started proxy op higher up the chain. Note,
+ // proxy ops are verified two by two, which means we have to clear the 2nd next
+ // from the previous iteration (since it is actually curr.next which failed
+ // to pass the perm check).
+ if (prev != null) {
+ final var cutAttrSourceState = prev.asState();
+ if (cutAttrSourceState.next.length > 0) {
+ cutAttrSourceState.next[0].next = new AttributionSourceState[0];
+ }
+ finishDataDelivery(context, attributedOp,
+ cutAttrSourceState, fromDatasource);
+ }
+ if (opMode == AppOpsManager.MODE_ERRORED) {
if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as op"
+ " mode is MODE_ERRORED. Permission check was requested for: "
@@ -1319,8 +1334,7 @@
+ current);
}
return PermissionChecker.PERMISSION_HARD_DENIED;
- }
- case AppOpsManager.MODE_IGNORED: {
+ } else {
return PermissionChecker.PERMISSION_SOFT_DENIED;
}
}
@@ -1335,6 +1349,8 @@
return PermissionChecker.PERMISSION_GRANTED;
}
+ // an attribution we have already possibly started an op for
+ prev = current;
current = next;
}
}
diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
index a1236e5..4f67318 100644
--- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java
+++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
@@ -32,6 +32,7 @@
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Icon;
import android.hardware.input.AppLaunchData;
+import android.hardware.input.InputGestureData;
import android.hardware.input.KeyGestureEvent;
import android.os.Handler;
import android.os.RemoteException;
@@ -769,6 +770,30 @@
shortcuts);
}
+ /**
+ * @return a {@link KeyboardShortcutGroup} containing the application launch keyboard
+ * shortcuts based on provided list of shortcut data.
+ */
+ public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId,
+ List<InputGestureData> shortcutData) {
+ List<KeyboardShortcutInfo> shortcuts = new ArrayList<>();
+ KeyCharacterMap kcm = KeyCharacterMap.load(deviceId);
+ for (InputGestureData data : shortcutData) {
+ if (data.getTrigger() instanceof InputGestureData.KeyTrigger trigger) {
+ KeyboardShortcutInfo info = shortcutInfoFromIntent(
+ kcm.getDisplayLabel(trigger.getKeycode()),
+ getIntentFromAppLaunchData(data.getAction().appLaunchData()),
+ (trigger.getModifierState() & KeyEvent.META_SHIFT_ON) != 0);
+ if (info != null) {
+ shortcuts.add(info);
+ }
+ }
+ }
+ return new KeyboardShortcutGroup(
+ mContext.getString(R.string.keyboard_shortcut_group_applications),
+ shortcuts);
+ }
+
private Intent getIntentFromAppLaunchData(@NonNull AppLaunchData data) {
Context context = mContext.createContextAsUser(mCurrentUser, 0);
synchronized (mAppIntentCache) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 2893430..fc24e62d 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3414,6 +3414,10 @@
@Override
public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) {
+ if (useKeyGestureEventHandler()) {
+ return mModifierShortcutManager.getApplicationLaunchKeyboardShortcuts(deviceId,
+ mInputManager.getAppLaunchBookmarks());
+ }
return mModifierShortcutManager.getApplicationLaunchKeyboardShortcuts(deviceId);
}
@@ -4004,14 +4008,7 @@
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);
@@ -4753,6 +4750,10 @@
public void registerShortcutKey(long shortcutCode, IShortcutService shortcutService)
throws RemoteException {
synchronized (mLock) {
+ if (useKeyGestureEventHandler()) {
+ mInputManagerInternal.registerShortcutKey(shortcutCode, shortcutService);
+ return;
+ }
mModifierShortcutManager.registerShortcutKey(shortcutCode, shortcutService);
}
}
diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java
index a928814..01a2045 100644
--- a/services/core/java/com/android/server/power/PowerGroup.java
+++ b/services/core/java/com/android/server/power/PowerGroup.java
@@ -42,6 +42,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.LatencyTracker;
+import com.android.server.LocalServices;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.power.feature.PowerManagerFlags;
/**
@@ -56,6 +58,11 @@
private static final String TAG = PowerGroup.class.getSimpleName();
private static final boolean DEBUG = false;
+ /**
+ * Indicates that the default dim/sleep timeouts should be used.
+ */
+ private static final long INVALID_TIMEOUT = -1;
+
@VisibleForTesting
final DisplayPowerRequest mDisplayPowerRequest = new DisplayPowerRequest();
private final PowerGroupListener mWakefulnessListener;
@@ -91,6 +98,9 @@
private @PowerManager.GoToSleepReason int mLastSleepReason =
PowerManager.GO_TO_SLEEP_REASON_UNKNOWN;
+ private final long mDimDuration;
+ private final long mScreenOffTimeout;
+
PowerGroup(int groupId, PowerGroupListener wakefulnessListener, Notifier notifier,
DisplayManagerInternal displayManagerInternal, int wakefulness, boolean ready,
boolean supportsSandman, long eventTime, PowerManagerFlags featureFlags) {
@@ -104,6 +114,30 @@
mLastWakeTime = eventTime;
mLastSleepTime = eventTime;
mFeatureFlags = featureFlags;
+
+ long dimDuration = INVALID_TIMEOUT;
+ long screenOffTimeout = INVALID_TIMEOUT;
+ if (android.companion.virtualdevice.flags.Flags.deviceAwareDisplayPower()
+ && mGroupId != Display.DEFAULT_DISPLAY_GROUP) {
+ VirtualDeviceManagerInternal vdm =
+ LocalServices.getService(VirtualDeviceManagerInternal.class);
+ if (vdm != null) {
+ int[] displayIds = mDisplayManagerInternal.getDisplayIdsForGroup(mGroupId);
+ if (displayIds != null && displayIds.length > 0) {
+ int deviceId = vdm.getDeviceIdForDisplayId(displayIds[0]);
+ if (vdm.isValidVirtualDeviceId(deviceId)) {
+ dimDuration = vdm.getDimDurationMillisForDeviceId(deviceId);
+ screenOffTimeout = vdm.getScreenOffTimeoutMillisForDeviceId(deviceId);
+ if (dimDuration > 0 && dimDuration > screenOffTimeout) {
+ // If the dim duration is set, cap it to the screen off timeout.
+ dimDuration = screenOffTimeout;
+ }
+ }
+ }
+ }
+ }
+ mDimDuration = dimDuration;
+ mScreenOffTimeout = screenOffTimeout;
}
PowerGroup(int wakefulness, PowerGroupListener wakefulnessListener, Notifier notifier,
@@ -119,6 +153,16 @@
mLastWakeTime = eventTime;
mLastSleepTime = eventTime;
mFeatureFlags = featureFlags;
+ mDimDuration = INVALID_TIMEOUT;
+ mScreenOffTimeout = INVALID_TIMEOUT;
+ }
+
+ long getScreenOffTimeoutOverrideLocked(long defaultScreenOffTimeout) {
+ return mScreenOffTimeout == INVALID_TIMEOUT ? defaultScreenOffTimeout : mScreenOffTimeout;
+ }
+
+ long getScreenDimDurationOverrideLocked(long defaultScreenDimDuration) {
+ return mDimDuration == INVALID_TIMEOUT ? defaultScreenDimDuration : mDimDuration;
}
long getLastWakeTimeLocked() {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 3a5afac..0acfe92 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -2971,8 +2971,8 @@
mHandler.removeMessages(MSG_USER_ACTIVITY_TIMEOUT);
final long attentiveTimeout = getAttentiveTimeoutLocked();
- final long sleepTimeout = getSleepTimeoutLocked(attentiveTimeout);
- final long defaultScreenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout,
+ final long defaultSleepTimeout = getSleepTimeoutLocked(attentiveTimeout);
+ final long defaultScreenOffTimeout = getScreenOffTimeoutLocked(defaultSleepTimeout,
attentiveTimeout);
final long defaultScreenDimDuration = getScreenDimDurationLocked(defaultScreenOffTimeout);
@@ -2985,13 +2985,25 @@
final PowerGroup powerGroup = mPowerGroups.valueAt(idx);
final int wakefulness = powerGroup.getWakefulnessLocked();
- // The default display screen timeout could be overridden by policy.
+ // The timeouts could be overridden by the power group policy.
long screenOffTimeout = defaultScreenOffTimeout;
long screenDimDuration = defaultScreenDimDuration;
+ long sleepTimeout = defaultSleepTimeout;
+ // TODO(b/376211497): Consolidate the timeout logic for all power groups.
if (powerGroup.getGroupId() == Display.DEFAULT_DISPLAY_GROUP) {
screenOffTimeout =
- getScreenOffTimeoutOverrideLocked(screenOffTimeout, screenDimDuration);
+ getDefaultGroupScreenOffTimeoutOverrideLocked(screenOffTimeout,
+ screenDimDuration);
screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
+ } else {
+ screenOffTimeout = powerGroup.getScreenOffTimeoutOverrideLocked(screenOffTimeout);
+ screenDimDuration =
+ powerGroup.getScreenDimDurationOverrideLocked(screenDimDuration);
+ if (sleepTimeout > 0 && screenOffTimeout > 0) {
+ // If both sleep and screen off timeouts are set, make sure that the sleep
+ // timeout is not smaller than the screen off one.
+ sleepTimeout = Math.max(sleepTimeout, screenOffTimeout);
+ }
}
if (wakefulness != WAKEFULNESS_ASLEEP) {
@@ -3273,7 +3285,8 @@
@VisibleForTesting
@GuardedBy("mLock")
- long getScreenOffTimeoutOverrideLocked(long screenOffTimeout, long screenDimDuration) {
+ long getDefaultGroupScreenOffTimeoutOverrideLocked(long screenOffTimeout,
+ long screenDimDuration) {
long shortestScreenOffTimeout = screenOffTimeout;
if (mScreenTimeoutOverridePolicy != null) {
shortestScreenOffTimeout =
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 48174a6..940a509 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -809,17 +809,16 @@
mKernelSingleUidTimeReader.addDelta(uid, onBatteryScreenOffCounter, elapsedRealtimeMs);
if (u.mChildUids != null) {
- LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
- getCpuTimeInFreqContainer();
+ long[] delta = getCpuTimeInFreqContainer();
int childUidCount = u.mChildUids.size();
for (int j = childUidCount - 1; j >= 0; --j) {
LongArrayMultiStateCounter cpuTimeInFreqCounter =
u.mChildUids.valueAt(j).cpuTimeInFreqCounter;
if (cpuTimeInFreqCounter != null) {
mKernelSingleUidTimeReader.addDelta(u.mChildUids.keyAt(j),
- cpuTimeInFreqCounter, elapsedRealtimeMs, deltaContainer);
- onBatteryCounter.addCounts(deltaContainer);
- onBatteryScreenOffCounter.addCounts(deltaContainer);
+ cpuTimeInFreqCounter, elapsedRealtimeMs, delta);
+ onBatteryCounter.addCounts(delta);
+ onBatteryScreenOffCounter.addCounts(delta);
}
}
}
@@ -890,8 +889,7 @@
if (childUid != null) {
final LongArrayMultiStateCounter counter = childUid.cpuTimeInFreqCounter;
if (counter != null) {
- final LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
- getCpuTimeInFreqContainer();
+ final long[] deltaContainer = getCpuTimeInFreqContainer();
mKernelSingleUidTimeReader.addDelta(uid, counter, elapsedRealtimeMs,
deltaContainer);
onBatteryCounter.addCounts(deltaContainer);
@@ -1741,7 +1739,7 @@
private long mBatteryTimeToFullSeconds = -1;
- private LongArrayMultiStateCounter.LongArrayContainer mTmpCpuTimeInFreq;
+ private long[] mTmpCpuTimeInFreq;
/**
* Times spent by the system server threads handling incoming binder requests.
@@ -10956,9 +10954,7 @@
// Set initial values to all 0. This is a child UID and we want to include
// the entirety of its CPU time-in-freq stats into the parent's stats.
- cpuTimeInFreqCounter.updateValues(
- new LongArrayMultiStateCounter.LongArrayContainer(cpuFreqCount),
- timestampMs);
+ cpuTimeInFreqCounter.updateValues(new long[cpuFreqCount], timestampMs);
} else {
cpuTimeInFreqCounter = null;
}
@@ -11361,11 +11357,9 @@
}
@GuardedBy("this")
- private LongArrayMultiStateCounter.LongArrayContainer getCpuTimeInFreqContainer() {
+ private long[] getCpuTimeInFreqContainer() {
if (mTmpCpuTimeInFreq == null) {
- mTmpCpuTimeInFreq =
- new LongArrayMultiStateCounter.LongArrayContainer(
- mCpuScalingPolicies.getScalingStepCount());
+ mTmpCpuTimeInFreq = new long[mCpuScalingPolicies.getScalingStepCount()];
}
return mTmpCpuTimeInFreq;
}
diff --git a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsAccumulator.java b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsAccumulator.java
index e798bc4..3f7fcee 100644
--- a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsAccumulator.java
+++ b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsAccumulator.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.net.NetworkStats;
import android.net.NetworkTemplate;
+import android.util.Log;
import java.util.Objects;
@@ -33,6 +34,7 @@
*/
public class NetworkStatsAccumulator {
+ private static final String TAG = "NetworkStatsAccumulator";
private final NetworkTemplate mTemplate;
private final boolean mWithTags;
private final long mBucketDurationMillis;
@@ -57,8 +59,9 @@
@NonNull
public NetworkStats queryStats(long currentTimeMillis,
@NonNull StatsQueryFunction queryFunction) {
- maybeExpandSnapshot(currentTimeMillis, queryFunction);
- return snapshotPlusFollowingStats(currentTimeMillis, queryFunction);
+ NetworkStats completeStats = snapshotPlusFollowingStats(currentTimeMillis, queryFunction);
+ maybeExpandSnapshot(currentTimeMillis, completeStats, queryFunction);
+ return completeStats;
}
/**
@@ -72,15 +75,28 @@
* Expands the internal cumulative stats snapshot, if possible, by querying NetworkStats.
*/
private void maybeExpandSnapshot(long currentTimeMillis,
+ NetworkStats completeStatsUntilCurrentTime,
@NonNull StatsQueryFunction queryFunction) {
// Update snapshot only if it is possible to expand it by at least one full bucket, and only
// if the new snapshot's end is not in the active bucket.
long newEndTimeMillis = currentTimeMillis - mBucketDurationMillis;
if (newEndTimeMillis - mSnapshotEndTimeMillis > mBucketDurationMillis) {
- NetworkStats extraStats = queryFunction.queryNetworkStats(mTemplate, mWithTags,
- mSnapshotEndTimeMillis, newEndTimeMillis);
+ Log.v(TAG,
+ "Expanding snapshot (mTemplate=" + mTemplate + ", mWithTags=" + mWithTags
+ + ") from " + mSnapshotEndTimeMillis + " to " + newEndTimeMillis
+ + " at " + currentTimeMillis);
+ NetworkStats extraStats = queryFunction.queryNetworkStats(
+ mTemplate, mWithTags, mSnapshotEndTimeMillis, newEndTimeMillis);
mSnapshot = mSnapshot.add(extraStats);
mSnapshotEndTimeMillis = newEndTimeMillis;
+
+ // NetworkStats queries interpolate historical data using integers maths, which makes
+ // queries non-transitive: Query(t0, t1) + Query(t1, t2) <= Query(t0, t2).
+ // Compute interpolation data loss from moving the snapshot's end-point, and add it to
+ // the snapshot to avoid under-counting.
+ NetworkStats newStats = snapshotPlusFollowingStats(currentTimeMillis, queryFunction);
+ NetworkStats interpolationLoss = completeStatsUntilCurrentTime.subtract(newStats);
+ mSnapshot = mSnapshot.add(interpolationLoss);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 6964300..73ae51c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -707,9 +707,6 @@
*/
private boolean mOccludesParent;
- /** Whether the activity have style floating */
- private boolean mStyleFloating;
-
/**
* Unlike {@link #mOccludesParent} which can be changed at runtime. This is a static attribute
* from the style of activity. Because we don't want {@link WindowContainer#getOrientation()}
@@ -791,10 +788,10 @@
// and therefore #isLetterboxedForFixedOrientationAndAspectRatio returns false.
private boolean mIsEligibleForFixedOrientationLetterbox;
- // activity is not displayed?
- // TODO: rename to mNoDisplay
- @VisibleForTesting
- boolean noDisplay;
+ /**
+ * Whether the activity is to be displayed. See {@link android.R.attr#windowNoDisplay}.
+ */
+ private boolean mNoDisplay;
final boolean mShowForAllUsers;
// TODO: Make this final
int mTargetSdk;
@@ -1178,7 +1175,7 @@
pw.print(" inHistory="); pw.print(inHistory);
pw.print(" idle="); pw.println(idle);
pw.print(prefix); pw.print("occludesParent="); pw.print(occludesParent());
- pw.print(" noDisplay="); pw.print(noDisplay);
+ pw.print(" mNoDisplay="); pw.print(mNoDisplay);
pw.print(" immersive="); pw.print(immersive);
pw.print(" launchMode="); pw.println(launchMode);
pw.print(prefix); pw.print("mActivityType=");
@@ -2011,20 +2008,19 @@
if (ent != null) {
final boolean styleTranslucent = ent.array.getBoolean(
com.android.internal.R.styleable.Window_windowIsTranslucent, false);
- mStyleFloating = ent.array.getBoolean(
+ final boolean styleFloating = ent.array.getBoolean(
com.android.internal.R.styleable.Window_windowIsFloating, false);
- mOccludesParent = !(styleTranslucent || mStyleFloating)
+ mOccludesParent = !(styleTranslucent || styleFloating)
// This style is propagated to the main window attributes with
// FLAG_SHOW_WALLPAPER from PhoneWindow#generateLayout.
|| ent.array.getBoolean(R.styleable.Window_windowShowWallpaper, false);
mStyleFillsParent = mOccludesParent;
- noDisplay = ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false);
+ mNoDisplay = ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false);
mOptOutEdgeToEdge = ent.array.getBoolean(
R.styleable.Window_windowOptOutEdgeToEdgeEnforcement, false);
} else {
- mStyleFloating = false;
mStyleFillsParent = mOccludesParent = true;
- noDisplay = false;
+ mNoDisplay = false;
mOptOutEdgeToEdge = false;
}
@@ -3091,8 +3087,16 @@
return occludesParent(true /* includingFinishing */);
}
- boolean isStyleFloating() {
- return mStyleFloating;
+ boolean isNoDisplay() {
+ return mNoDisplay;
+ }
+
+ /**
+ * Exposed only for testing and should not be used to modify value of {@link #mNoDisplay}.
+ */
+ @VisibleForTesting
+ void setIsNoDisplay(boolean isNoDisplay) {
+ mNoDisplay = isNoDisplay;
}
/** Returns true if this activity is not finishing, is opaque and fills the entire space of
@@ -3192,6 +3196,9 @@
if (mWmService.mConstants.isPackageOptOutIgnoreActivityOrientationRequest(packageName)) {
return false;
}
+ if (mAppCompatController.mAllowRestrictedResizability.getAsBoolean()) {
+ return false;
+ }
// If the user preference respects aspect ratio, then it becomes non-resizable.
return !mAppCompatController.getAppCompatOverrides().getAppCompatAspectRatioOverrides()
.shouldApplyUserMinAspectRatioOverride();
@@ -6069,7 +6076,7 @@
void notifyUnknownVisibilityLaunchedForKeyguardTransition() {
// No display activities never add a window, so there is no point in waiting them for
// relayout.
- if (noDisplay || !isKeyguardLocked()) {
+ if (mNoDisplay || !isKeyguardLocked()) {
return;
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 05a96d9..2e2ca14 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1037,7 +1037,6 @@
mLastStartReason = request.reason;
mLastStartActivityTimeMs = System.currentTimeMillis();
- final ActivityRecord previousStart = mLastStartActivityRecord;
final IApplicationThread caller = request.caller;
Intent intent = request.intent;
NeededUriGrants intentGrants = request.intentGrants;
@@ -2350,7 +2349,8 @@
// When there is a reused activity and the current result is a trampoline activity,
// set the reused activity as the result.
if (mLastStartActivityRecord != null
- && (mLastStartActivityRecord.finishing || mLastStartActivityRecord.noDisplay)) {
+ && (mLastStartActivityRecord.finishing
+ || mLastStartActivityRecord.isNoDisplay())) {
mLastStartActivityRecord = targetTaskTop;
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 7963d09..198e14a 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1854,6 +1854,8 @@
}
assertPackageMatchesCallingUid(callingPackage);
+ mAmInternal.addCreatorToken(intent, callingPackage);
+
final ActivityOptions activityOptions = ActivityOptions.makeBasic();
activityOptions.setLaunchTaskId(taskId);
// Pass in the system UID to allow setting launch taskId with MANAGE_GAME_ACTIVITY.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 30b53d1..cf17804 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2478,7 +2478,7 @@
/** Notifies that the top activity of the task is forced to be resizeable. */
private void handleForcedResizableTaskIfNeeded(Task task, int reason) {
final ActivityRecord topActivity = task.getTopNonFinishingActivity();
- if (topActivity == null || topActivity.noDisplay
+ if (topActivity == null || topActivity.isNoDisplay()
|| !topActivity.canForceResizeNonResizable(task.getWindowingMode())) {
return;
}
@@ -2894,10 +2894,9 @@
private boolean mIncludeInvisibleAndFinishing;
private boolean mIgnoringKeyguard;
- ActivityRecord getOpaqueActivity(
- @NonNull WindowContainer<?> container, boolean ignoringKeyguard) {
+ ActivityRecord getOpaqueActivity(@NonNull WindowContainer<?> container) {
mIncludeInvisibleAndFinishing = true;
- mIgnoringKeyguard = ignoringKeyguard;
+ mIgnoringKeyguard = true;
return container.getActivity(this,
true /* traverseTopToBottom */, null /* boundary */);
}
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
index 548c0a3..fa2c716 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
@@ -128,10 +128,11 @@
}
if (!aspectRatioOverrides.shouldOverrideMinAspectRatio()
&& !AppCompatCameraPolicy.shouldOverrideMinAspectRatioForCamera(mActivityRecord)) {
- if (mActivityRecord.isUniversalResizeable()) {
+ final float minAspectRatio = info.getMinAspectRatio();
+ if (minAspectRatio == 0 || mActivityRecord.isUniversalResizeable()) {
return 0;
}
- return info.getMinAspectRatio();
+ return minAspectRatio;
}
if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY)
@@ -173,10 +174,11 @@
if (mTransparentPolicy.isRunning()) {
return mTransparentPolicy.getInheritedMaxAspectRatio();
}
- if (mActivityRecord.isUniversalResizeable()) {
+ final float maxAspectRatio = mActivityRecord.info.getMaxAspectRatio();
+ if (maxAspectRatio == 0 || mActivityRecord.isUniversalResizeable()) {
return 0;
}
- return mActivityRecord.info.getMaxAspectRatio();
+ return maxAspectRatio;
}
@Nullable
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index 6c344c6..145a376 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -15,12 +15,15 @@
*/
package com.android.server.wm;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY;
+
import android.annotation.NonNull;
import android.content.pm.PackageManager;
import com.android.server.wm.utils.OptPropFactory;
import java.io.PrintWriter;
+import java.util.function.BooleanSupplier;
/**
* Allows the interaction with all the app compat policies and configurations
@@ -47,6 +50,8 @@
private final AppCompatLetterboxPolicy mAppCompatLetterboxPolicy;
@NonNull
private final AppCompatSizeCompatModePolicy mAppCompatSizeCompatModePolicy;
+ @NonNull
+ final BooleanSupplier mAllowRestrictedResizability;
AppCompatController(@NonNull WindowManagerService wmService,
@NonNull ActivityRecord activityRecord) {
@@ -70,6 +75,17 @@
mAppCompatOverrides, mTransparentPolicy, wmService.mAppCompatConfiguration);
mAppCompatSizeCompatModePolicy = new AppCompatSizeCompatModePolicy(mActivityRecord,
mAppCompatOverrides);
+ mAllowRestrictedResizability = AppCompatUtils.asLazy(() -> {
+ try {
+ return packageManager.getPropertyAsUser(
+ PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY,
+ mActivityRecord.mActivityComponent.getPackageName(),
+ mActivityRecord.mActivityComponent.getClassName(),
+ mActivityRecord.mUserId).getBoolean();
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ });
}
@NonNull
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 2fe023e..4ed8b09 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -26,6 +26,8 @@
import static android.view.WindowManager.TRANSIT_OLD_NONE;
import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_FINISH_AND_REMOVE_TASK;
+import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_BACK_PREVIEW;
import static com.android.server.wm.BackNavigationProto.ANIMATION_IN_PROGRESS;
@@ -60,6 +62,7 @@
import android.window.IBackAnimationFinishedCallback;
import android.window.IWindowlessStartingSurfaceCallback;
import android.window.OnBackInvokedCallbackInfo;
+import android.window.SystemOverrideOnBackInvokedCallback;
import android.window.TaskSnapshot;
import com.android.internal.annotations.VisibleForTesting;
@@ -226,10 +229,19 @@
&& callbackInfo.isAnimationCallback());
mNavigationMonitor.startMonitor(window, navigationObserver);
+ int requestOverride = callbackInfo.getOverrideBehavior();
ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "startBackNavigation currentTask=%s, "
+ "topRunningActivity=%s, callbackInfo=%s, currentFocus=%s",
currentTask, currentActivity, callbackInfo, window);
-
+ if (requestOverride == OVERRIDE_FINISH_AND_REMOVE_TASK) {
+ final ActivityRecord rootR = currentTask != null ? currentTask.getRootActivity()
+ : null;
+ if (currentActivity != null && rootR != currentActivity) {
+ // The top activity is not root activity, the activity cannot remove task when
+ // finishAndRemoveTask called.
+ requestOverride = OVERRIDE_UNDEFINED;
+ }
+ }
// Clear the pointer down outside focus if any.
mWindowManagerService.clearPointerDownOutsideFocusRunnable();
@@ -274,7 +286,8 @@
} else if (hasTranslucentActivity(currentActivity, prevActivities)) {
// skip if one of participant activity is translucent
backType = BackNavigationInfo.TYPE_CALLBACK;
- } else if (prevActivities.size() > 0) {
+ } else if (prevActivities.size() > 0
+ && requestOverride == SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED) {
if ((!isOccluded || isAllActivitiesCanShowWhenLocked(prevActivities))
&& isAllActivitiesCreated(prevActivities)) {
// We have another Activity in the same currentTask to go to
@@ -1025,6 +1038,12 @@
return;
}
+ if (mWindowManagerService.mRoot.mTransitionController.isCollecting()) {
+ Slog.v(TAG, "Skip predictive back transition, another transition is collecting");
+ cancelPendingAnimation();
+ return;
+ }
+
// Ensure the final animation targets which hidden by transition could be visible.
for (int i = 0; i < targets.size(); i++) {
final WindowContainer wc = targets.get(i).mContainer;
@@ -1053,6 +1072,7 @@
Slog.e(TAG, "Remote animation gone", e);
}
mPendingAnimationBuilder = null;
+ mNavigationMonitor.stopMonitorTransition();
}
/**
@@ -1550,6 +1570,9 @@
}
void createStartingSurface(@Nullable TaskSnapshot snapshot) {
+ if (Flags.deferPredictiveAnimationIfNoSnapshot() && snapshot == null) {
+ return;
+ }
if (mAdaptors[0].mSwitchType == DIALOG_CLOSE) {
return;
}
@@ -1851,7 +1874,7 @@
tc.requestStartTransition(prepareOpen,
null /*startTask */, null /* remoteTransition */,
null /* displayChange */);
- prepareOpen.setReady(makeVisibles.get(0), true);
+ prepareOpen.setReady(mCloseTarget, true);
return prepareOpen;
} else if (mSnapshot == null) {
return setLaunchBehind(visibleOpenActivities);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e827f44..e9e550e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -159,7 +159,6 @@
import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields;
import static com.android.server.wm.utils.RegionUtils.forEachRectReverse;
import static com.android.server.wm.utils.RegionUtils.rectListToRegion;
-import static com.android.window.flags.Flags.explicitRefreshRateHints;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -3426,14 +3425,6 @@
if (!mWmService.mSupportsHighPerfTransitions) {
return;
}
- if (!explicitRefreshRateHints()) {
- if (enable) {
- getPendingTransaction().setEarlyWakeupStart();
- } else {
- getPendingTransaction().setEarlyWakeupEnd();
- }
- return;
- }
if (enable) {
if (mTransitionPrefSession == null) {
mTransitionPrefSession = mWmService.mSystemPerformanceHinter.createSession(
@@ -3446,10 +3437,6 @@
}
void enableHighFrameRate(boolean enable) {
- if (!explicitRefreshRateHints()) {
- // Done by RefreshRatePolicy.
- return;
- }
if (enable) {
if (mHighFrameRateSession == null) {
mHighFrameRateSession = mWmService.mSystemPerformanceHinter.createSession(
@@ -7072,7 +7059,7 @@
@Override
public void setImeInputTargetRequestedVisibility(boolean visible) {
if (android.view.inputmethod.Flags.refactorInsetsController()) {
- // TODO(b/329229469) we won't have the statsToken in all cases, but should still log
+ // TODO(b/353463205) we won't have the statsToken in all cases, but should still log
try {
mRemoteInsetsController.setImeInputTargetRequestedVisibility(visible);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index c7d57fe..ee07d2e 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1735,9 +1735,9 @@
}
// Show IME over the keyguard if the target allows it.
- final boolean showImeOverKeyguard = imeTarget != null && imeTarget.isVisible()
- && win.mIsImWindow && (imeTarget.canShowWhenLocked()
- || !imeTarget.canBeHiddenByKeyguard());
+ final boolean showImeOverKeyguard =
+ imeTarget != null && imeTarget.isOnScreen() && win.mIsImWindow && (
+ imeTarget.canShowWhenLocked() || !imeTarget.canBeHiddenByKeyguard());
if (showImeOverKeyguard) {
return false;
}
@@ -3053,7 +3053,7 @@
@InsetsType int insetsType) {
for (int i = insetsState.sourceSize() - 1; i >= 0; i--) {
final InsetsSource source = insetsState.sourceAt(i);
- if ((source.getType() & insetsType) == 0 || !source.isVisible()) {
+ if ((source.getType() & insetsType) == 0) {
continue;
}
if (Rect.intersects(bounds, source.getFrame())) {
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index e9c6e93..5ed9612 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -100,13 +100,13 @@
// isLeashReadyForDispatching (used to dispatch the leash of the control) is
// depending on mGivenInsetsReady. Therefore, triggering notifyControlChanged here
// again, so that the control with leash can be eventually dispatched
- if (!mGivenInsetsReady && mServerVisible && !givenInsetsPending) {
+ if (!mGivenInsetsReady && isServerVisible() && !givenInsetsPending) {
mGivenInsetsReady = true;
ImeTracker.forLogging().onProgress(mStatsToken,
ImeTracker.PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED);
mStateController.notifyControlChanged(mControlTarget, this);
setImeShowing(true);
- } else if (wasServerVisible && mServerVisible && mGivenInsetsReady
+ } else if (wasServerVisible && isServerVisible() && mGivenInsetsReady
&& givenInsetsPending) {
// If the server visibility didn't change (still visible), and mGivenInsetsReady
// is set, we won't call into notifyControlChanged. Therefore, we can reset the
@@ -114,7 +114,7 @@
ImeTracker.forLogging().onCancelled(mStatsToken,
ImeTracker.PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED);
mStatsToken = null;
- } else if (wasServerVisible && !mServerVisible) {
+ } else if (wasServerVisible && !isServerVisible()) {
setImeShowing(false);
}
}
@@ -134,11 +134,15 @@
@Override
protected boolean isLeashReadyForDispatching() {
if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ // We should only dispatch the leash, if the following conditions are fulfilled:
+ // 1. parent isLeashReadyForDispatching, 2. mGivenInsetsReady (means there are no
+ // givenInsetsPending), 3. the IME surface is drawn, 4. either the IME is
+ // serverVisible (the unfrozen state)
final WindowState ws =
mWindowContainer != null ? mWindowContainer.asWindowState() : null;
final boolean isDrawn = ws != null && ws.isDrawn();
return super.isLeashReadyForDispatching()
- && mServerVisible && isDrawn && mGivenInsetsReady;
+ && isServerVisible() && isDrawn && mGivenInsetsReady;
} else {
return super.isLeashReadyForDispatching();
}
@@ -254,7 +258,7 @@
// Refer WindowState#getImeControlTarget().
target = target.getWindow().getImeControlTarget();
}
- // TODO(b/329229469) make sure that the statsToken of all callers is non-null (currently
+ // TODO(b/353463205) make sure that the statsToken of all callers is non-null (currently
// not the case)
super.updateControlForTarget(target, force, statsToken);
if (Flags.refactorInsetsController()) {
@@ -290,12 +294,14 @@
changed |= mDisplayContent.onImeInsetsClientVisibilityUpdate();
if (Flags.refactorInsetsController()) {
if (changed) {
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_SERVER_UPDATE_CLIENT_VISIBILITY);
invokeOnImeRequestedChangedListener(mDisplayContent.getImeInputTarget(),
statsToken);
} else {
- // TODO(b/329229469) change phase and check cancelled / failed
+ // TODO(b/353463205) check cancelled / failed
ImeTracker.forLogging().onCancelled(statsToken,
- ImeTracker.PHASE_CLIENT_REPORT_REQUESTED_VISIBLE_TYPES);
+ ImeTracker.PHASE_SERVER_UPDATE_CLIENT_VISIBILITY);
}
}
return changed;
@@ -460,7 +466,7 @@
// This can later become ready, so we don't want to cancel the pending request here.
return;
}
- // TODO(b/329229469) check if this is still triggered, as we don't go into STATE_SHOW_IME
+ // TODO(b/353463205) check if this is still triggered, as we don't go into STATE_SHOW_IME
// (DefaultImeVisibilityApplier)
if (android.view.inputmethod.Flags.refactorInsetsController()) {
// The IME is drawn, so call into {@link WindowState#notifyInsetsControlChanged}
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 1d4d6eb..7276007 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -730,6 +730,10 @@
return mFakeControlTarget;
}
+ boolean isServerVisible() {
+ return mServerVisible;
+ }
+
boolean isClientVisible() {
return mClientVisible;
}
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 5dddf36..4b2d454 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -317,9 +317,9 @@
// aborted.
provider.updateFakeControlTarget(target);
} else {
- // TODO(b/329229469) if the IME controlTarget changes, any pending requests should fail
+ // TODO(b/353463205) if the IME controlTarget changes, any pending requests should fail
provider.updateControlForTarget(target, false /* force */,
- null /* TODO(b/329229469) check if needed here */);
+ null /* TODO(b/353463205) check if needed here */);
// Get control target again in case the provider didn't accept the one we passed to it.
target = provider.getControlTarget();
diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
index 8cab7d9..e4c34ed 100644
--- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java
+++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
@@ -19,8 +19,6 @@
import static android.hardware.display.DisplayManager.SWITCHING_TYPE_NONE;
import static android.hardware.display.DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY;
-import static com.android.window.flags.Flags.explicitRefreshRateHints;
-
import android.hardware.display.DisplayManager;
import android.view.Display;
import android.view.Display.Mode;
@@ -60,7 +58,6 @@
}
private final DisplayInfo mDisplayInfo;
- private final Mode mDefaultMode;
private final Mode mLowRefreshRateMode;
private final PackageRefreshRate mNonHighRefreshRatePackages = new PackageRefreshRate();
private final HighRefreshRateDenylist mHighRefreshRateDenylist;
@@ -92,8 +89,7 @@
RefreshRatePolicy(WindowManagerService wmService, DisplayInfo displayInfo,
HighRefreshRateDenylist denylist) {
mDisplayInfo = displayInfo;
- mDefaultMode = displayInfo.getDefaultMode();
- mLowRefreshRateMode = findLowRefreshRateMode(displayInfo, mDefaultMode);
+ mLowRefreshRateMode = findLowRefreshRateMode(displayInfo);
mHighRefreshRateDenylist = denylist;
mWmService = wmService;
}
@@ -102,7 +98,8 @@
* Finds the mode id with the lowest refresh rate which is >= 60hz and same resolution as the
* default mode.
*/
- private Mode findLowRefreshRateMode(DisplayInfo displayInfo, Mode defaultMode) {
+ private Mode findLowRefreshRateMode(DisplayInfo displayInfo) {
+ final Mode defaultMode = displayInfo.getDefaultMode();
float[] refreshRates = displayInfo.getDefaultRefreshRates();
float bestRefreshRate = defaultMode.getRefreshRate();
mMinSupportedRefreshRate = bestRefreshRate;
@@ -135,33 +132,6 @@
// Unspecified, use default mode.
return 0;
}
-
- // If app is animating, it's not able to control refresh rate because we want the animation
- // to run in default refresh rate. But if the display size of default mode is different
- // from the using preferred mode, then still keep the preferred mode to avoid disturbing
- // the animation.
- if (!explicitRefreshRateHints() && w.isAnimationRunningSelfOrParent()) {
- Display.Mode preferredMode = null;
- for (Display.Mode mode : mDisplayInfo.supportedModes) {
- if (preferredDisplayModeId == mode.getModeId()) {
- preferredMode = mode;
- break;
- }
- }
- if (preferredMode != null) {
- final int pW = preferredMode.getPhysicalWidth();
- final int pH = preferredMode.getPhysicalHeight();
- if ((pW != mDefaultMode.getPhysicalWidth()
- || pH != mDefaultMode.getPhysicalHeight())
- && pW == mDisplayInfo.getNaturalWidth()
- && pH == mDisplayInfo.getNaturalHeight()) {
- // Prefer not to change display size when animating.
- return preferredDisplayModeId;
- }
- }
- return 0;
- }
-
return preferredDisplayModeId;
}
@@ -264,12 +234,6 @@
return w.mFrameRateVote.reset();
}
- // If app is animating, it's not able to control refresh rate because we want the animation
- // to run in default refresh rate.
- if (!explicitRefreshRateHints() && w.isAnimationRunningSelfOrParent()) {
- return w.mFrameRateVote.reset();
- }
-
// If the app set a preferredDisplayModeId, the preferred refresh rate is the refresh rate
// of that mode id.
if (refreshRateSwitchingType != SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY) {
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 077127c..1bb4c41 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -715,7 +715,7 @@
if (embeddedWindow != null) {
// If there is no WindowState for the IWindow, it could be still an
// EmbeddedWindow. Therefore, check the EmbeddedWindowController as well
- // TODO(b/329229469) Use different phase here
+ // TODO(b/353463205) Use different phase here
ImeTracker.forLogging().onProgress(imeStatsToken,
ImeTracker.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES);
embeddedWindow.setRequestedVisibleTypes(
diff --git a/services/core/java/com/android/server/wm/SnapshotController.java b/services/core/java/com/android/server/wm/SnapshotController.java
index 52994c7..3ee2e60 100644
--- a/services/core/java/com/android/server/wm/SnapshotController.java
+++ b/services/core/java/com/android/server/wm/SnapshotController.java
@@ -152,7 +152,10 @@
if (mOpenActivities.isEmpty()) {
return false;
}
- if (Flags.alwaysCaptureActivitySnapshot()) {
+ // TODO (b/362183912) always capture activity snapshot will cause performance
+ // regression, remove flag after ramp up
+ if (!Flags.deferPredictiveAnimationIfNoSnapshot()
+ && Flags.alwaysCaptureActivitySnapshot()) {
return true;
}
for (int i = mOpenActivities.size() - 1; i >= 0; --i) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index e13577c..352dc52 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3432,9 +3432,9 @@
info.isFocused = isFocused();
info.isVisible = hasVisibleChildren();
info.isVisibleRequested = isVisibleRequested();
+ info.isTopActivityNoDisplay = top != null && top.isNoDisplay();
info.isSleeping = shouldSleepActivities();
info.isTopActivityTransparent = top != null && !top.fillsParent();
- info.isTopActivityStyleFloating = top != null && top.isStyleFloating();
info.lastNonFullscreenBounds = topTask.mLastNonFullscreenBounds;
final WindowState windowState = top != null ? top.findMainWindow() : null;
info.requestedVisibleTypes = (windowState != null && Flags.enableFullyImmersiveInDesktop())
@@ -3918,7 +3918,9 @@
sb.append(" aI=");
sb.append(affinityIntent.getComponent().flattenToShortString());
}
- sb.append(" isResizeable=").append(isResizeable());
+ if (!isResizeable()) {
+ sb.append(" nonResizable");
+ }
if (mMinWidth != INVALID_MIN_SIZE || mMinHeight != INVALID_MIN_SIZE) {
sb.append(" minWidth=").append(mMinWidth);
sb.append(" minHeight=").append(mMinHeight);
@@ -4722,7 +4724,7 @@
}
}
if (likelyResolvedMode != WINDOWING_MODE_FULLSCREEN
- && topActivity != null && !topActivity.noDisplay
+ && topActivity != null && !topActivity.isNoDisplay()
&& topActivity.canForceResizeNonResizable(likelyResolvedMode)) {
// Inform the user that they are starting an app that may not work correctly in
// multi-window mode.
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 606d51d..e090b19 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1090,8 +1090,7 @@
return true;
}
// Including finishing Activity if the TaskFragment is becoming invisible in the transition.
- return mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(this,
- true /* ignoringKeyguard */) == null;
+ return mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(this) == null;
}
/**
@@ -1734,6 +1733,12 @@
if (!hasDirectChildActivities()) {
return false;
}
+ if (mResumedActivity != null && mTransitionController.isTransientLaunch(mResumedActivity)) {
+ // Even if the transient activity is occluded, defer pausing (addToStopping will still
+ // be called) it until the transient transition is done. So the current resuming
+ // activity won't need to wait for additional pause complete.
+ return false;
+ }
ProtoLog.d(WM_DEBUG_STATES, "startPausing: taskFrag =%s " + "mResumedActivity=%s", this,
mResumedActivity);
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index 5c9a84d..c39671d 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -449,7 +449,7 @@
// If the source activity is a no-display activity, pass on the launch display area token
// from source activity as currently preferred.
- if (taskDisplayArea == null && source != null && source.noDisplay) {
+ if (taskDisplayArea == null && source != null && source.isNoDisplay()) {
taskDisplayArea = source.mHandoverTaskDisplayArea;
if (taskDisplayArea != null) {
if (DEBUG) appendLog("display-area-from-no-display-source=" + taskDisplayArea);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 454e431..72d45e4 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -477,20 +477,17 @@
if (transientRoot == null) continue;
final WindowContainer<?> rootParent = transientRoot.getParent();
if (rootParent == null || rootParent.getTopChild() == transientRoot) continue;
- final ActivityRecord topOpaque = mController.mAtm.mTaskSupervisor.mOpaqueActivityHelper
- .getOpaqueActivity(rootParent, true /* ignoringKeyguard */);
- if (transientRoot.compareTo(topOpaque.getRootTask()) < 0) {
- occludedCount++;
+ for (int j = rootParent.getChildCount() - 1; j >= 0; --j) {
+ final WindowContainer<?> sibling = rootParent.getChildAt(j);
+ if (sibling == transientRoot) break;
+ if (!sibling.getWindowConfiguration().isAlwaysOnTop() && mController.mAtm
+ .mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(sibling) != null) {
+ occludedCount++;
+ break;
+ }
}
}
if (occludedCount == numTransient) {
- for (int i = mTransientLaunches.size() - 1; i >= 0; --i) {
- if (mTransientLaunches.keyAt(i).isDescendantOf(task)) {
- // Keep transient activity visible until transition finished, so it won't pause
- // with transient-hide tasks that may delay resuming the next top.
- return true;
- }
- }
// Let transient-hide activities pause before transition is finished.
return false;
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 166d74b..dac8f69 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -813,6 +813,10 @@
mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */);
if (deferResume) {
mService.mTaskSupervisor.endDeferResume();
+ // Transient launching the Recents via HIERARCHY_OP_TYPE_PENDING_INTENT directly
+ // resume the Recents activity with no TRANSACT_EFFECTS_LIFECYCLE. Explicitly
+ // checks if the top resumed activity should be updated after defer-resume ended.
+ mService.mTaskSupervisor.updateTopResumedActivityIfNeeded("endWCT");
}
mService.continueWindowLayout();
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index d01e29b..079170a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -157,6 +157,7 @@
import static com.android.server.wm.WindowStateProto.ANIMATOR;
import static com.android.server.wm.WindowStateProto.ATTRIBUTES;
import static com.android.server.wm.WindowStateProto.DESTROYING;
+import static com.android.server.wm.WindowStateProto.DIM_BOUNDS;
import static com.android.server.wm.WindowStateProto.DISPLAY_ID;
import static com.android.server.wm.WindowStateProto.FORCE_SEAMLESS_ROTATION;
import static com.android.server.wm.WindowStateProto.GIVEN_CONTENT_INSETS;
@@ -181,7 +182,6 @@
import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY;
import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER;
import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES;
-import static com.android.window.flags.Flags.explicitRefreshRateHints;
import static com.android.window.flags.Flags.secureWindowState;
import static com.android.window.flags.Flags.surfaceTrustedOverlay;
@@ -4118,6 +4118,12 @@
mMergedLocalInsetsSources.valueAt(i).dumpDebug(proto, MERGED_LOCAL_INSETS_SOURCES);
}
}
+ if (getDimController() != null) {
+ final Rect dimBounds = getDimController().getDimBounds();
+ if (dimBounds != null) {
+ dimBounds.dumpDebug(proto, DIM_BOUNDS);
+ }
+ }
proto.end(token);
}
@@ -5297,12 +5303,9 @@
if (voteChanged) {
getPendingTransaction()
.setFrameRate(mSurfaceControl, mFrameRateVote.mRefreshRate,
- mFrameRateVote.mCompatibility, Surface.CHANGE_FRAME_RATE_ALWAYS);
- if (explicitRefreshRateHints()) {
- getPendingTransaction().setFrameRateSelectionStrategy(mSurfaceControl,
- mFrameRateVote.mSelectionStrategy);
- }
-
+ mFrameRateVote.mCompatibility, Surface.CHANGE_FRAME_RATE_ALWAYS)
+ .setFrameRateSelectionStrategy(mSurfaceControl,
+ mFrameRateVote.mSelectionStrategy);
}
}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index ea0b02c..4dc3ca5 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -55,7 +55,6 @@
"com_android_server_powerstats_PowerStatsService.cpp",
"com_android_server_power_stats_CpuPowerStatsCollector.cpp",
"com_android_server_hint_HintManagerService.cpp",
- "com_android_server_SerialService.cpp",
"com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp",
"com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker.cpp",
"com_android_server_stats_pull_StatsPullAtomService.cpp",
diff --git a/services/core/jni/com_android_server_SerialService.cpp b/services/core/jni/com_android_server_SerialService.cpp
deleted file mode 100644
index 6600c98..0000000
--- a/services/core/jni/com_android_server_SerialService.cpp
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2011 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.
- */
-
-#define LOG_TAG "SerialServiceJNI"
-#include "utils/Log.h"
-
-#include "jni.h"
-#include <nativehelper/JNIPlatformHelp.h>
-#include "android_runtime/AndroidRuntime.h"
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-
-namespace android
-{
-
-static struct parcel_file_descriptor_offsets_t
-{
- jclass mClass;
- jmethodID mConstructor;
-} gParcelFileDescriptorOffsets;
-
-static jobject android_server_SerialService_open(JNIEnv *env, jobject /* thiz */, jstring path)
-{
- const char *pathStr = env->GetStringUTFChars(path, NULL);
-
- int fd = open(pathStr, O_RDWR | O_NOCTTY);
- if (fd < 0) {
- ALOGE("could not open %s", pathStr);
- env->ReleaseStringUTFChars(path, pathStr);
- return NULL;
- }
- env->ReleaseStringUTFChars(path, pathStr);
-
- jobject fileDescriptor = jniCreateFileDescriptor(env, fd);
- if (fileDescriptor == NULL) {
- close(fd);
- return NULL;
- }
- return env->NewObject(gParcelFileDescriptorOffsets.mClass,
- gParcelFileDescriptorOffsets.mConstructor, fileDescriptor);
-}
-
-
-static const JNINativeMethod method_table[] = {
- { "native_open", "(Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;",
- (void*)android_server_SerialService_open },
-};
-
-int register_android_server_SerialService(JNIEnv *env)
-{
- jclass clazz = env->FindClass("com/android/server/SerialService");
- if (clazz == NULL) {
- ALOGE("Can't find com/android/server/SerialService");
- return -1;
- }
-
- clazz = env->FindClass("android/os/ParcelFileDescriptor");
- LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.ParcelFileDescriptor");
- gParcelFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
- gParcelFileDescriptorOffsets.mConstructor = env->GetMethodID(clazz, "<init>", "(Ljava/io/FileDescriptor;)V");
- LOG_FATAL_IF(gParcelFileDescriptorOffsets.mConstructor == NULL,
- "Unable to find constructor for android.os.ParcelFileDescriptor");
-
- return jniRegisterNativeMethods(env, "com/android/server/SerialService",
- method_table, NELEM(method_table));
-}
-
-};
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 416e60f..e4ac826 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -110,6 +110,7 @@
jmethodID notifyInputDevicesChanged;
jmethodID notifyTouchpadHardwareState;
jmethodID notifyTouchpadGestureInfo;
+ jmethodID notifyTouchpadThreeFingerTap;
jmethodID notifySwitch;
jmethodID notifyInputChannelBroken;
jmethodID notifyNoFocusedWindowAnr;
@@ -345,6 +346,7 @@
void setTouchpadTapDraggingEnabled(bool enabled);
void setShouldNotifyTouchpadHardwareState(bool enabled);
void setTouchpadRightClickZoneEnabled(bool enabled);
+ void setTouchpadThreeFingerTapShortcutEnabled(bool enabled);
void setInputDeviceEnabled(uint32_t deviceId, bool enabled);
void setShowTouches(bool enabled);
void setNonInteractiveDisplays(const std::set<ui::LogicalDisplayId>& displayIds);
@@ -370,6 +372,7 @@
void notifyTouchpadHardwareState(const SelfContainedHardwareState& schs,
int32_t deviceId) override;
void notifyTouchpadGestureInfo(enum GestureType type, int32_t deviceId) override;
+ void notifyTouchpadThreeFingerTap() override;
std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
const InputDeviceIdentifier& identifier,
const std::optional<KeyboardLayoutInfo> keyboardLayoutInfo) override;
@@ -510,6 +513,10 @@
// into context (a.k.a. "right") clicks.
bool touchpadRightClickZoneEnabled{false};
+ // True to use three-finger tap as a customizable shortcut; false to use it as a
+ // middle-click.
+ bool touchpadThreeFingerTapShortcutEnabled{false};
+
// True if a pointer icon should be shown for stylus pointers.
bool stylusPointerIconEnabled{false};
@@ -780,6 +787,8 @@
outConfig->touchpadTapDraggingEnabled = mLocked.touchpadTapDraggingEnabled;
outConfig->shouldNotifyTouchpadHardwareState = mLocked.shouldNotifyTouchpadHardwareState;
outConfig->touchpadRightClickZoneEnabled = mLocked.touchpadRightClickZoneEnabled;
+ outConfig->touchpadThreeFingerTapShortcutEnabled =
+ mLocked.touchpadThreeFingerTapShortcutEnabled;
outConfig->disabledDevices = mLocked.disabledInputDevices;
@@ -1034,6 +1043,13 @@
checkAndClearExceptionFromCallback(env, "notifyTouchpadGestureInfo");
}
+void NativeInputManager::notifyTouchpadThreeFingerTap() {
+ ATRACE_CALL();
+ JNIEnv* env = jniEnv();
+ env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyTouchpadThreeFingerTap);
+ checkAndClearExceptionFromCallback(env, "notifyTouchpadThreeFingerTap");
+}
+
std::shared_ptr<KeyCharacterMap> NativeInputManager::getKeyboardLayoutOverlay(
const InputDeviceIdentifier& identifier,
const std::optional<KeyboardLayoutInfo> keyboardLayoutInfo) {
@@ -1495,6 +1511,22 @@
InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
}
+void NativeInputManager::setTouchpadThreeFingerTapShortcutEnabled(bool enabled) {
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+
+ if (mLocked.touchpadThreeFingerTapShortcutEnabled == enabled) {
+ return;
+ }
+
+ ALOGI("Setting touchpad three finger tap shortcut to %s.", toString(enabled));
+ mLocked.touchpadThreeFingerTapShortcutEnabled = enabled;
+ } // release lock
+
+ mInputManager->getReader().requestRefreshConfiguration(
+ InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
+}
+
void NativeInputManager::setInputDeviceEnabled(uint32_t deviceId, bool enabled) {
bool refresh = false;
@@ -2437,6 +2469,11 @@
im->setTouchpadRightClickZoneEnabled(enabled);
}
+static void nativeSetTouchpadThreeFingerTapShortcutEnabled(JNIEnv* env, jobject nativeImplObj,
+ jboolean enabled) {
+ getNativeInputManager(env, nativeImplObj)->setTouchpadThreeFingerTapShortcutEnabled(enabled);
+}
+
static void nativeSetShowTouches(JNIEnv* env, jobject nativeImplObj, jboolean enabled) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -3119,6 +3156,8 @@
{"setShouldNotifyTouchpadHardwareState", "(Z)V",
(void*)nativeSetShouldNotifyTouchpadHardwareState},
{"setTouchpadRightClickZoneEnabled", "(Z)V", (void*)nativeSetTouchpadRightClickZoneEnabled},
+ {"setTouchpadThreeFingerTapShortcutEnabled", "(Z)V",
+ (void*)nativeSetTouchpadThreeFingerTapShortcutEnabled},
{"setShowTouches", "(Z)V", (void*)nativeSetShowTouches},
{"setNonInteractiveDisplays", "([I)V", (void*)nativeSetNonInteractiveDisplays},
{"reloadCalibration", "()V", (void*)nativeReloadCalibration},
@@ -3229,6 +3268,8 @@
GET_METHOD_ID(gServiceClassInfo.notifyTouchpadGestureInfo, clazz, "notifyTouchpadGestureInfo",
"(II)V")
+ GET_METHOD_ID(gServiceClassInfo.notifyTouchpadThreeFingerTap, clazz,
+ "notifyTouchpadThreeFingerTap", "()V")
GET_METHOD_ID(gServiceClassInfo.notifySwitch, clazz,
"notifySwitch", "(JII)V");
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 3c55d18..59d7365 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -33,7 +33,6 @@
int register_android_server_power_stats_CpuPowerStatsCollector(JNIEnv* env);
int register_android_server_HintManagerService(JNIEnv* env);
int register_android_server_storage_AppFuse(JNIEnv* env);
-int register_android_server_SerialService(JNIEnv* env);
int register_android_server_SystemServer(JNIEnv* env);
int register_android_server_UsbAlsaJackDetector(JNIEnv* env);
int register_android_server_UsbAlsaMidiDevice(JNIEnv* env);
@@ -94,7 +93,6 @@
register_android_server_PowerStatsService(env);
register_android_server_power_stats_CpuPowerStatsCollector(env);
register_android_server_HintManagerService(env);
- register_android_server_SerialService(env);
register_android_server_InputManager(env);
register_android_server_LightsService(env);
register_android_server_UsbDeviceManager(vm, env);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index a58da81..9841058 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -90,6 +90,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
/**
* Class responsible for setting, resolving, and enforcing policies set by multiple management
@@ -1030,11 +1031,11 @@
}
}
- private <V> void enforcePolicy(PolicyDefinition<V> policyDefinition,
+ private <V> CompletableFuture<Boolean> enforcePolicy(PolicyDefinition<V> policyDefinition,
@Nullable PolicyValue<V> policyValue, int userId) {
// null policyValue means remove any enforced policies, ensure callbacks handle this
// properly
- policyDefinition.enforcePolicy(
+ return policyDefinition.enforcePolicy(
policyValue == null ? null : policyValue.getValue(), mContext, userId);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index f271162..6cb1756 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -53,6 +53,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
final class PolicyDefinition<V> {
@@ -504,7 +505,8 @@
private final int mPolicyFlags;
// A function that accepts policy to apply, context, userId, callback arguments, and returns
// true if the policy has been enforced successfully.
- private final QuadFunction<V, Context, Integer, PolicyKey, Boolean> mPolicyEnforcerCallback;
+ private final QuadFunction<V, Context, Integer, PolicyKey, CompletableFuture<Boolean>>
+ mPolicyEnforcerCallback;
private final PolicySerializer<V> mPolicySerializer;
private PolicyDefinition<V> createPolicyDefinition(PolicyKey key) {
@@ -574,7 +576,7 @@
return mResolutionMechanism.resolve(adminsPolicy);
}
- boolean enforcePolicy(@Nullable V value, Context context, int userId) {
+ CompletableFuture<Boolean> enforcePolicy(@Nullable V value, Context context, int userId) {
return mPolicyEnforcerCallback.apply(value, context, userId, mPolicyKey);
}
@@ -592,7 +594,6 @@
POLICY_DEFINITIONS.put(key.getIdentifier(), definition);
}
-
/**
* Callers must ensure that {@code policyType} have implemented an appropriate
* {@link Object#equals} implementation.
@@ -600,7 +601,8 @@
private PolicyDefinition(
@NonNull PolicyKey key,
ResolutionMechanism<V> resolutionMechanism,
- QuadFunction<V, Context, Integer, PolicyKey, Boolean> policyEnforcerCallback,
+ QuadFunction<V, Context, Integer, PolicyKey, CompletableFuture<Boolean>>
+ policyEnforcerCallback,
PolicySerializer<V> policySerializer) {
this(key, resolutionMechanism, POLICY_FLAG_NONE, policyEnforcerCallback, policySerializer);
}
@@ -610,10 +612,11 @@
* {@link Object#equals} and {@link Object#hashCode()} implementation.
*/
private PolicyDefinition(
- @NonNull PolicyKey policyKey,
+ @NonNull PolicyKey policyKey,
ResolutionMechanism<V> resolutionMechanism,
int policyFlags,
- QuadFunction<V, Context, Integer, PolicyKey, Boolean> policyEnforcerCallback,
+ QuadFunction<V, Context, Integer, PolicyKey, CompletableFuture<Boolean>>
+ policyEnforcerCallback,
PolicySerializer<V> policySerializer) {
Objects.requireNonNull(policyKey);
mPolicyKey = policyKey;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index 4d9abf1..1454162 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -55,6 +55,7 @@
import android.util.Slog;
import android.view.IWindowManager;
+import com.android.internal.infra.AndroidFuture;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
@@ -65,6 +66,7 @@
import java.util.List;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@@ -73,33 +75,36 @@
private static final String LOG_TAG = "PolicyEnforcerCallbacks";
- static <T> boolean noOp(T value, Context context, Integer userId, PolicyKey policyKey) {
- return true;
+ static <T> CompletableFuture<Boolean> noOp(T value, Context context, Integer userId,
+ PolicyKey policyKey) {
+ return AndroidFuture.completedFuture(true);
}
- static boolean setAutoTimezoneEnabled(@Nullable Boolean enabled, @NonNull Context context) {
+ static CompletableFuture<Boolean> setAutoTimezoneEnabled(@Nullable Boolean enabled,
+ @NonNull Context context) {
if (!Flags.setAutoTimeZoneEnabledCoexistence()) {
Slogf.w(LOG_TAG, "Trying to enforce setAutoTimezoneEnabled while flag is off.");
- return true;
+ return AndroidFuture.completedFuture(true);
}
return Binder.withCleanCallingIdentity(() -> {
Objects.requireNonNull(context);
int value = enabled != null && enabled ? 1 : 0;
- return Settings.Global.putInt(
- context.getContentResolver(), Settings.Global.AUTO_TIME_ZONE,
- value);
+ return AndroidFuture.completedFuture(
+ Settings.Global.putInt(
+ context.getContentResolver(), Settings.Global.AUTO_TIME_ZONE,
+ value));
});
}
- static boolean setPermissionGrantState(
+ static CompletableFuture<Boolean> setPermissionGrantState(
@Nullable Integer grantState, @NonNull Context context, int userId,
@NonNull PolicyKey policyKey) {
if (!Flags.setPermissionGrantStateCoexistence()) {
Slogf.w(LOG_TAG, "Trying to enforce setPermissionGrantState while flag is off.");
- return true;
+ return AndroidFuture.completedFuture(true);
}
- return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
+ return Binder.withCleanCallingIdentity(() -> {
if (!(policyKey instanceof PackagePermissionPolicyKey)) {
throw new IllegalArgumentException("policyKey is not of type "
+ "PermissionGrantStatePolicyKey, passed in policyKey is: " + policyKey);
@@ -125,12 +130,13 @@
.setRuntimePermissionGrantStateByDeviceAdmin(context.getPackageName(),
permissionParams, context.getMainExecutor(), callback::trigger);
try {
- return callback.await(20_000, TimeUnit.MILLISECONDS);
+ return AndroidFuture.completedFuture(
+ callback.await(20_000, TimeUnit.MILLISECONDS));
} catch (Exception e) {
// TODO: add logging
- return false;
+ return AndroidFuture.completedFuture(false);
}
- }));
+ });
}
@NonNull
@@ -149,23 +155,23 @@
}
}
- static boolean enforceSecurityLogging(
+ static CompletableFuture<Boolean> enforceSecurityLogging(
@Nullable Boolean value, @NonNull Context context, int userId,
@NonNull PolicyKey policyKey) {
final var dpmi = LocalServices.getService(DevicePolicyManagerInternal.class);
dpmi.enforceSecurityLoggingPolicy(Boolean.TRUE.equals(value));
- return true;
+ return AndroidFuture.completedFuture(true);
}
- static boolean enforceAuditLogging(
+ static CompletableFuture<Boolean> enforceAuditLogging(
@Nullable Boolean value, @NonNull Context context, int userId,
@NonNull PolicyKey policyKey) {
final var dpmi = LocalServices.getService(DevicePolicyManagerInternal.class);
dpmi.enforceAuditLoggingPolicy(Boolean.TRUE.equals(value));
- return true;
+ return AndroidFuture.completedFuture(true);
}
- static boolean setLockTask(
+ static CompletableFuture<Boolean> setLockTask(
@Nullable LockTaskPolicy policy, @NonNull Context context, int userId) {
List<String> packages = Collections.emptyList();
int flags = LockTaskPolicy.DEFAULT_LOCK_TASK_FLAG;
@@ -175,7 +181,7 @@
}
DevicePolicyManagerService.updateLockTaskPackagesLocked(context, packages, userId);
DevicePolicyManagerService.updateLockTaskFeaturesLocked(flags, userId);
- return true;
+ return AndroidFuture.completedFuture(true);
}
@@ -187,8 +193,8 @@
* rely on the POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED flag so DPE only invokes this callback
* when the policy is set, and not during system boot or other situations.
*/
- static boolean setApplicationRestrictions(Bundle bundle, Context context, Integer userId,
- PolicyKey policyKey) {
+ static CompletableFuture<Boolean> setApplicationRestrictions(Bundle bundle, Context context,
+ Integer userId, PolicyKey policyKey) {
Binder.withCleanCallingIdentity(() -> {
PackagePolicyKey key = (PackagePolicyKey) policyKey;
String packageName = key.getPackageName();
@@ -198,7 +204,7 @@
changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
context.sendBroadcastAsUser(changeIntent, UserHandle.of(userId));
});
- return true;
+ return AndroidFuture.completedFuture(true);
}
private static class BlockingCallback {
@@ -220,7 +226,7 @@
// TODO: when a local policy exists for a user, this callback will be invoked for this user
// individually as well as for USER_ALL. This can be optimized by separating local and global
// enforcement in the policy engine.
- static boolean setUserControlDisabledPackages(
+ static CompletableFuture<Boolean> setUserControlDisabledPackages(
@Nullable Set<String> packages, Context context, int userId, PolicyKey policyKey) {
Binder.withCleanCallingIdentity(() -> {
PackageManagerInternal pmi =
@@ -246,7 +252,7 @@
}
}
});
- return true;
+ return AndroidFuture.completedFuture(true);
}
/** Handles USER_ALL expanding it into the list of all intact users. */
@@ -271,7 +277,7 @@
}
}
- static boolean addPersistentPreferredActivity(
+ static CompletableFuture<Boolean> addPersistentPreferredActivity(
@Nullable ComponentName preferredActivity, @NonNull Context context, int userId,
@NonNull PolicyKey policyKey) {
Binder.withCleanCallingIdentity(() -> {
@@ -297,13 +303,13 @@
Slog.wtf(LOG_TAG, "Error adding/removing persistent preferred activity", re);
}
});
- return true;
+ return AndroidFuture.completedFuture(true);
}
- static boolean setUninstallBlocked(
+ static CompletableFuture<Boolean> setUninstallBlocked(
@Nullable Boolean uninstallBlocked, @NonNull Context context, int userId,
@NonNull PolicyKey policyKey) {
- return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
+ return Binder.withCleanCallingIdentity(() -> {
if (!(policyKey instanceof PackagePolicyKey)) {
throw new IllegalArgumentException("policyKey is not of type "
+ "PackagePolicyKey, passed in policyKey is: " + policyKey);
@@ -314,14 +320,14 @@
packageName,
uninstallBlocked != null && uninstallBlocked,
userId);
- return true;
- }));
+ return AndroidFuture.completedFuture(true);
+ });
}
- static boolean setUserRestriction(
+ static CompletableFuture<Boolean> setUserRestriction(
@Nullable Boolean enabled, @NonNull Context context, int userId,
@NonNull PolicyKey policyKey) {
- return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
+ return Binder.withCleanCallingIdentity(() -> {
if (!(policyKey instanceof UserRestrictionPolicyKey)) {
throw new IllegalArgumentException("policyKey is not of type "
+ "UserRestrictionPolicyKey, passed in policyKey is: " + policyKey);
@@ -331,14 +337,14 @@
UserManagerInternal userManager = LocalServices.getService(UserManagerInternal.class);
userManager.setUserRestriction(
userId, parsedKey.getRestriction(), enabled != null && enabled);
- return true;
- }));
+ return AndroidFuture.completedFuture(true);
+ });
}
- static boolean setApplicationHidden(
+ static CompletableFuture<Boolean> setApplicationHidden(
@Nullable Boolean hide, @NonNull Context context, int userId,
@NonNull PolicyKey policyKey) {
- return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
+ return Binder.withCleanCallingIdentity(() -> {
if (!(policyKey instanceof PackagePolicyKey)) {
throw new IllegalArgumentException("policyKey is not of type "
+ "PackagePolicyKey, passed in policyKey is: " + policyKey);
@@ -346,12 +352,13 @@
PackagePolicyKey parsedKey = (PackagePolicyKey) policyKey;
String packageName = Objects.requireNonNull(parsedKey.getPackageName());
IPackageManager packageManager = AppGlobals.getPackageManager();
- return packageManager.setApplicationHiddenSettingAsUser(
- packageName, hide != null && hide, userId);
- }));
+ return AndroidFuture.completedFuture(
+ packageManager.setApplicationHiddenSettingAsUser(
+ packageName, hide != null && hide, userId));
+ });
}
- static boolean setScreenCaptureDisabled(
+ static CompletableFuture<Boolean> setScreenCaptureDisabled(
@Nullable Boolean disabled, @NonNull Context context, int userId,
@NonNull PolicyKey policyKey) {
Binder.withCleanCallingIdentity(() -> {
@@ -363,10 +370,10 @@
updateScreenCaptureDisabled();
}
});
- return true;
+ return AndroidFuture.completedFuture(true);
}
- static boolean setContentProtectionPolicy(
+ static CompletableFuture<Boolean> setContentProtectionPolicy(
@Nullable Integer value,
@NonNull Context context,
@UserIdInt Integer userId,
@@ -378,7 +385,7 @@
cacheImpl.setContentProtectionPolicy(userId, value);
}
});
- return true;
+ return AndroidFuture.completedFuture(true);
}
private static void updateScreenCaptureDisabled() {
@@ -393,7 +400,7 @@
});
}
- static boolean setPersonalAppsSuspended(
+ static CompletableFuture<Boolean> setPersonalAppsSuspended(
@Nullable Boolean suspended, @NonNull Context context, int userId,
@NonNull PolicyKey policyKey) {
Binder.withCleanCallingIdentity(() -> {
@@ -404,7 +411,7 @@
.unsuspendAdminSuspendedPackages(userId);
}
});
- return true;
+ return AndroidFuture.completedFuture(true);
}
private static void suspendPersonalAppsInPackageManager(Context context, int userId) {
@@ -418,13 +425,14 @@
}
}
- static boolean setUsbDataSignalingEnabled(@Nullable Boolean value, @NonNull Context context) {
+ static CompletableFuture<Boolean> setUsbDataSignalingEnabled(@Nullable Boolean value,
+ @NonNull Context context) {
return Binder.withCleanCallingIdentity(() -> {
Objects.requireNonNull(context);
boolean enabled = value == null || value;
DevicePolicyManagerService.updateUsbDataSignal(context, enabled);
- return true;
+ return AndroidFuture.completedFuture(true);
});
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/OWNERS b/services/tests/mockingservicestests/src/com/android/server/OWNERS
index dc5cb8d6..0c9f70c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/OWNERS
+++ b/services/tests/mockingservicestests/src/com/android/server/OWNERS
@@ -1,6 +1,6 @@
per-file *Alarm* = file:/apex/jobscheduler/OWNERS
per-file *AppStateTracker* = file:/apex/jobscheduler/OWNERS
-per-file *DeviceIdleController* = file:/apex/jobscheduler/OWNERS
+per-file *DeviceIdleController* = file:/apex/jobscheduler/DEVICE_IDLE_OWNERS
per-file SensitiveContentProtectionManagerService* = file:/core/java/android/permission/OWNERS
per-file RescuePartyTest.java = file:/packages/CrashRecovery/OWNERS
per-file *Storage* = file:/core/java/android/os/storage/OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index b005358..4a131558 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -60,6 +60,7 @@
import static com.android.server.am.ProcessList.PERSISTENT_PROC_ADJ;
import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ;
import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
+import static com.android.server.am.ProcessList.PREVIOUS_APP_MAX_ADJ;
import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
import static com.android.server.am.ProcessList.SCHED_GROUP_DEFAULT;
import static com.android.server.am.ProcessList.SCHED_GROUP_FOREGROUND_WINDOW;
@@ -129,6 +130,7 @@
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
/**
* Test class for {@link OomAdjuster}.
@@ -899,8 +901,25 @@
@SuppressWarnings("GuardedBy")
@Test
+ public void testUpdateOomAdj_DoPending_PreviousApp() {
+ testUpdateOomAdj_PreviousApp(apps -> {
+ for (ProcessRecord app : apps) {
+ mProcessStateController.enqueueUpdateTarget(app);
+ }
+ mProcessStateController.runPendingUpdate(OOM_ADJ_REASON_NONE);
+ });
+ }
+
+ @SuppressWarnings("GuardedBy")
+ @Test
public void testUpdateOomAdj_DoAll_PreviousApp() {
- final int numberOfApps = 15;
+ testUpdateOomAdj_PreviousApp(apps -> {
+ mProcessStateController.runFullUpdate(OOM_ADJ_REASON_NONE);
+ });
+ }
+
+ private void testUpdateOomAdj_PreviousApp(Consumer<ProcessRecord[]> updater) {
+ final int numberOfApps = 105;
final ProcessRecord[] apps = new ProcessRecord[numberOfApps];
for (int i = 0; i < numberOfApps; i++) {
apps[i] = spy(makeDefaultProcessRecord(MOCKAPP_PID + i, MOCKAPP_UID + i,
@@ -911,10 +930,11 @@
}
setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
setProcessesToLru(apps);
- mProcessStateController.runFullUpdate(OOM_ADJ_REASON_NONE);
-
+ updater.accept(apps);
for (int i = 0; i < numberOfApps; i++) {
- assertProcStates(apps[i], PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
+ final int mruIndex = numberOfApps - i - 1;
+ final int expectedAdj = Math.min(PREVIOUS_APP_ADJ + mruIndex, PREVIOUS_APP_MAX_ADJ);
+ assertProcStates(apps[i], PROCESS_STATE_LAST_ACTIVITY, expectedAdj,
SCHED_GROUP_BACKGROUND, "previous");
}
@@ -3184,7 +3204,8 @@
setProcessesToLru(app1, app2);
mProcessStateController.runFullUpdate(OOM_ADJ_REASON_NONE);
- assertProcStates(app1, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
+ assertProcStates(app1, PROCESS_STATE_LAST_ACTIVITY,
+ PREVIOUS_APP_ADJ + (Flags.oomadjusterPrevLaddering() ? 1 : 0),
SCHED_GROUP_BACKGROUND, "recent-provider");
assertProcStates(app2, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
SCHED_GROUP_BACKGROUND, "recent-provider");
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
index 648da65..4e29e74 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
@@ -49,17 +49,23 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.Context;
import android.hardware.display.DisplayManagerInternal;
import android.os.PowerManager;
import android.os.PowerSaveState;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.view.Display;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.util.LatencyTracker;
+import com.android.server.LocalServices;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.power.feature.PowerManagerFlags;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -73,6 +79,9 @@
public class PowerGroupTest {
private static final int GROUP_ID = 0;
+ private static final int NON_DEFAULT_GROUP_ID = 1;
+ private static final int NON_DEFAULT_DISPLAY_ID = 2;
+ private static final int VIRTUAL_DEVICE_ID = 3;
private static final int UID = 11;
private static final long TIMESTAMP_CREATE = 1;
private static final long TIMESTAMP1 = 999;
@@ -87,10 +96,16 @@
private static final LatencyTracker LATENCY_TRACKER = LatencyTracker.getInstance(
InstrumentationRegistry.getInstrumentation().getContext());
+ private static final long DEFAULT_TIMEOUT = 1234L;
+
+ @Rule
+ public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private PowerGroup mPowerGroup;
@Mock private PowerGroup.PowerGroupListener mWakefulnessCallbackMock;
@Mock private Notifier mNotifier;
@Mock private DisplayManagerInternal mDisplayManagerInternal;
+ @Mock private VirtualDeviceManagerInternal mVirtualDeviceManagerInternal;
@Mock private PowerManagerFlags mFeatureFlags;
@@ -740,4 +755,111 @@
assertThat(displayPowerRequest.screenLowPowerBrightnessFactor).isWithin(PRECISION).of(
brightnessFactor);
}
+
+ @Test
+ public void testTimeoutsOverride_defaultGroup_noOverride() {
+ assertThat(mPowerGroup.getScreenDimDurationOverrideLocked(DEFAULT_TIMEOUT))
+ .isEqualTo(DEFAULT_TIMEOUT);
+ assertThat(mPowerGroup.getScreenOffTimeoutOverrideLocked(DEFAULT_TIMEOUT))
+ .isEqualTo(DEFAULT_TIMEOUT);
+ }
+
+ @EnableFlags(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ @Test
+ public void testTimeoutsOverride_noVdm_noOverride() {
+ LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
+ LocalServices.addService(VirtualDeviceManagerInternal.class, null);
+
+ mPowerGroup = new PowerGroup(NON_DEFAULT_GROUP_ID, mWakefulnessCallbackMock, mNotifier,
+ mDisplayManagerInternal, WAKEFULNESS_AWAKE, /* ready= */ true,
+ /* supportsSandman= */ true, TIMESTAMP_CREATE, mFeatureFlags);
+
+ assertThat(mPowerGroup.getScreenDimDurationOverrideLocked(DEFAULT_TIMEOUT))
+ .isEqualTo(DEFAULT_TIMEOUT);
+ assertThat(mPowerGroup.getScreenOffTimeoutOverrideLocked(DEFAULT_TIMEOUT))
+ .isEqualTo(DEFAULT_TIMEOUT);
+ }
+
+ @EnableFlags(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ @Test
+ public void testTimeoutsOverride_notValidVirtualDeviceId_noOverride() {
+ LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
+ LocalServices.addService(VirtualDeviceManagerInternal.class, mVirtualDeviceManagerInternal);
+
+ when(mDisplayManagerInternal.getDisplayIdsForGroup(NON_DEFAULT_GROUP_ID))
+ .thenReturn(new int[] {NON_DEFAULT_DISPLAY_ID});
+ when(mVirtualDeviceManagerInternal.getDeviceIdForDisplayId(NON_DEFAULT_DISPLAY_ID))
+ .thenReturn(Context.DEVICE_ID_DEFAULT);
+ when(mVirtualDeviceManagerInternal.isValidVirtualDeviceId(Context.DEVICE_ID_DEFAULT))
+ .thenReturn(false);
+
+ mPowerGroup = new PowerGroup(NON_DEFAULT_GROUP_ID, mWakefulnessCallbackMock, mNotifier,
+ mDisplayManagerInternal, WAKEFULNESS_AWAKE, /* ready= */ true,
+ /* supportsSandman= */ true, TIMESTAMP_CREATE, mFeatureFlags);
+
+ assertThat(mPowerGroup.getScreenDimDurationOverrideLocked(DEFAULT_TIMEOUT))
+ .isEqualTo(DEFAULT_TIMEOUT);
+ assertThat(mPowerGroup.getScreenOffTimeoutOverrideLocked(DEFAULT_TIMEOUT))
+ .isEqualTo(DEFAULT_TIMEOUT);
+ }
+
+ @EnableFlags(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ @Test
+ public void testTimeoutsOverride_validVirtualDeviceId_timeoutsAreOverridden() {
+ LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
+ LocalServices.addService(VirtualDeviceManagerInternal.class, mVirtualDeviceManagerInternal);
+
+ final long dimDurationOverride = DEFAULT_TIMEOUT * 3;
+ final long screenOffTimeoutOverride = DEFAULT_TIMEOUT * 5;
+
+ when(mDisplayManagerInternal.getDisplayIdsForGroup(NON_DEFAULT_GROUP_ID))
+ .thenReturn(new int[] {NON_DEFAULT_DISPLAY_ID});
+ when(mVirtualDeviceManagerInternal.getDeviceIdForDisplayId(NON_DEFAULT_DISPLAY_ID))
+ .thenReturn(VIRTUAL_DEVICE_ID);
+ when(mVirtualDeviceManagerInternal.isValidVirtualDeviceId(VIRTUAL_DEVICE_ID))
+ .thenReturn(true);
+ when(mVirtualDeviceManagerInternal.getDimDurationMillisForDeviceId(VIRTUAL_DEVICE_ID))
+ .thenReturn(dimDurationOverride);
+ when(mVirtualDeviceManagerInternal.getScreenOffTimeoutMillisForDeviceId(VIRTUAL_DEVICE_ID))
+ .thenReturn(screenOffTimeoutOverride);
+
+ mPowerGroup = new PowerGroup(NON_DEFAULT_GROUP_ID, mWakefulnessCallbackMock, mNotifier,
+ mDisplayManagerInternal, WAKEFULNESS_AWAKE, /* ready= */ true,
+ /* supportsSandman= */ true, TIMESTAMP_CREATE, mFeatureFlags);
+
+ assertThat(mPowerGroup.getScreenDimDurationOverrideLocked(DEFAULT_TIMEOUT))
+ .isEqualTo(dimDurationOverride);
+ assertThat(mPowerGroup.getScreenOffTimeoutOverrideLocked(DEFAULT_TIMEOUT))
+ .isEqualTo(screenOffTimeoutOverride);
+ }
+
+ @EnableFlags(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ @Test
+ public void testTimeoutsOverrides_dimDurationIsCapped() {
+ LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
+ LocalServices.addService(VirtualDeviceManagerInternal.class, mVirtualDeviceManagerInternal);
+
+ final long dimDurationOverride = DEFAULT_TIMEOUT * 5;
+ final long screenOffTimeoutOverride = DEFAULT_TIMEOUT * 3;
+
+ when(mDisplayManagerInternal.getDisplayIdsForGroup(NON_DEFAULT_GROUP_ID))
+ .thenReturn(new int[] {NON_DEFAULT_DISPLAY_ID});
+ when(mVirtualDeviceManagerInternal.getDeviceIdForDisplayId(NON_DEFAULT_DISPLAY_ID))
+ .thenReturn(VIRTUAL_DEVICE_ID);
+ when(mVirtualDeviceManagerInternal.isValidVirtualDeviceId(VIRTUAL_DEVICE_ID))
+ .thenReturn(true);
+ when(mVirtualDeviceManagerInternal.getDimDurationMillisForDeviceId(VIRTUAL_DEVICE_ID))
+ .thenReturn(dimDurationOverride);
+ when(mVirtualDeviceManagerInternal.getScreenOffTimeoutMillisForDeviceId(VIRTUAL_DEVICE_ID))
+ .thenReturn(screenOffTimeoutOverride);
+
+ mPowerGroup = new PowerGroup(NON_DEFAULT_GROUP_ID, mWakefulnessCallbackMock, mNotifier,
+ mDisplayManagerInternal, WAKEFULNESS_AWAKE, /* ready= */ true,
+ /* supportsSandman= */ true, TIMESTAMP_CREATE, mFeatureFlags);
+
+ assertThat(mPowerGroup.getScreenDimDurationOverrideLocked(DEFAULT_TIMEOUT))
+ .isEqualTo(screenOffTimeoutOverride);
+ assertThat(mPowerGroup.getScreenOffTimeoutOverrideLocked(DEFAULT_TIMEOUT))
+ .isEqualTo(screenOffTimeoutOverride);
+ }
}
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
index b10200d..359cf63 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -85,6 +85,7 @@
import android.os.PowerSaveState;
import android.os.UserHandle;
import android.os.test.TestLooper;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.provider.Settings;
@@ -102,6 +103,7 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.display.feature.DeviceConfigParameterProvider;
import com.android.server.lights.LightsManager;
import com.android.server.policy.WindowManagerPolicy;
@@ -167,6 +169,7 @@
@Mock private BatteryManagerInternal mBatteryManagerInternalMock;
@Mock private ActivityManagerInternal mActivityManagerInternalMock;
@Mock private AttentionManagerInternal mAttentionManagerInternalMock;
+ @Mock private VirtualDeviceManagerInternal mVirtualDeviceManagerInternalMock;
@Mock private DreamManagerInternal mDreamManagerInternalMock;
@Mock private PowerManagerService.NativeWrapper mNativeWrapperMock;
@Mock private FoldGracePeriodProvider mFoldGracePeriodProvider;
@@ -246,6 +249,7 @@
addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock);
addLocalServiceMock(AttentionManagerInternal.class, mAttentionManagerInternalMock);
addLocalServiceMock(DreamManagerInternal.class, mDreamManagerInternalMock);
+ addLocalServiceMock(VirtualDeviceManagerInternal.class, mVirtualDeviceManagerInternalMock);
mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
mResourcesSpy = spy(mContextSpy.getResources());
@@ -1200,6 +1204,59 @@
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
}
+ @EnableFlags(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testNonDefaultDisplayGroupWithCustomTimeout_afterTimeout_goesToDozing() {
+ final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+ final int nonDefaultDisplayId = Display.DEFAULT_DISPLAY + 2;
+ final int virtualDeviceId = Context.DEVICE_ID_DEFAULT + 3;
+ final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+ new AtomicReference<>();
+ doAnswer((Answer<Void>) invocation -> {
+ listener.set(invocation.getArgument(0));
+ return null;
+ }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+ final DisplayInfo info = new DisplayInfo();
+ info.displayGroupId = nonDefaultDisplayGroupId;
+ when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplayId)).thenReturn(info);
+ when(mDisplayManagerInternalMock.getDisplayIdsForGroup(nonDefaultDisplayGroupId))
+ .thenReturn(new int[] {nonDefaultDisplayId});
+ when(mVirtualDeviceManagerInternalMock.getDeviceIdForDisplayId(nonDefaultDisplayId))
+ .thenReturn(virtualDeviceId);
+ when(mVirtualDeviceManagerInternalMock.isValidVirtualDeviceId(virtualDeviceId))
+ .thenReturn(true);
+ when(mVirtualDeviceManagerInternalMock
+ .getScreenOffTimeoutMillisForDeviceId(virtualDeviceId))
+ .thenReturn(20000L);
+
+ setMinimumScreenOffTimeoutConfig(10000);
+ createService();
+ startSystem();
+ listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+ assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP))
+ .isEqualTo(WAKEFULNESS_AWAKE);
+ assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId))
+ .isEqualTo(WAKEFULNESS_AWAKE);
+
+ // The default timeout is 10s, the custom group timeout is 20s.
+
+ advanceTime(15000);
+
+ assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP))
+ .isEqualTo(WAKEFULNESS_ASLEEP);
+ assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId))
+ .isEqualTo(WAKEFULNESS_AWAKE);
+
+ advanceTime(10000);
+
+ assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP))
+ .isEqualTo(WAKEFULNESS_ASLEEP);
+ assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId))
+ .isEqualTo(WAKEFULNESS_DOZING);
+ }
+
@SuppressWarnings("GuardedBy")
@Test
public void testAmbientSuppression_disablesDreamingAndWakesDevice() {
@@ -3348,7 +3405,8 @@
null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY,
null /* callback */);
}
- assertThat(mService.getScreenOffTimeoutOverrideLocked(screenTimeout, screenDimTimeout))
+ assertThat(mService.getDefaultGroupScreenOffTimeoutOverrideLocked(screenTimeout,
+ screenDimTimeout))
.isEqualTo(expect[2]);
if (acquireWakeLock) {
mService.getBinderServiceInstance().releaseWakeLock(token, 0);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
index 0a1fc31..f02a389 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
@@ -438,10 +438,7 @@
doAnswer(invocation -> {
LongArrayMultiStateCounter counter = invocation.getArgument(1);
long timestampMs = invocation.getArgument(2);
- LongArrayMultiStateCounter.LongArrayContainer container =
- new LongArrayMultiStateCounter.LongArrayContainer(NUM_CPU_FREQS);
- container.setValues(cpuTimes);
- counter.updateValues(container, timestampMs);
+ counter.updateValues(cpuTimes, timestampMs);
return null;
}).when(mKernelSingleUidTimeReader).addDelta(eq(testUid),
any(LongArrayMultiStateCounter.class), anyLong());
@@ -451,20 +448,13 @@
doAnswer(invocation -> {
LongArrayMultiStateCounter counter = invocation.getArgument(1);
long timestampMs = invocation.getArgument(2);
- LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
- invocation.getArgument(3);
-
- LongArrayMultiStateCounter.LongArrayContainer container =
- new LongArrayMultiStateCounter.LongArrayContainer(NUM_CPU_FREQS);
- container.setValues(cpuTimes);
- counter.updateValues(container, timestampMs);
- if (deltaContainer != null) {
- deltaContainer.setValues(delta);
- }
+ long[] deltaOut = invocation.getArgument(3);
+ counter.updateValues(cpuTimes, timestampMs);
+ System.arraycopy(delta, 0, deltaOut, 0, delta.length);
return null;
}).when(mKernelSingleUidTimeReader).addDelta(eq(testUid),
any(LongArrayMultiStateCounter.class), anyLong(),
- any(LongArrayMultiStateCounter.LongArrayContainer.class));
+ any(long[].class));
}
@Test
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
index 71a65c8..4cea728 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
@@ -394,10 +394,7 @@
doAnswer(invocation -> {
LongArrayMultiStateCounter counter = invocation.getArgument(1);
long timestampMs = invocation.getArgument(2);
- LongArrayMultiStateCounter.LongArrayContainer container =
- new LongArrayMultiStateCounter.LongArrayContainer(NUM_CPU_FREQS);
- container.setValues(cpuTimes);
- counter.updateValues(container, timestampMs);
+ counter.updateValues(cpuTimes, timestampMs);
return null;
}).when(mMockKernelSingleUidTimeReader).addDelta(eq(uid),
any(LongArrayMultiStateCounter.class), anyLong());
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index e386808..727d1b5 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -64,6 +64,7 @@
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorCallback;
import android.companion.virtual.sensor.VirtualSensorConfig;
+import android.companion.virtualdevice.flags.Flags;
import android.content.AttributionSource;
import android.content.ComponentName;
import android.content.Context;
@@ -103,6 +104,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.WorkSource;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
@@ -999,6 +1001,7 @@
nullable(String.class), anyInt(), eq(null));
}
+ @DisableFlags(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
@Test
public void onVirtualDisplayCreatedLocked_wakeLockIsAcquired() throws RemoteException {
verify(mIPowerManagerMock, never()).acquireWakeLock(any(Binder.class), anyInt(),
@@ -1010,6 +1013,7 @@
nullable(String.class), eq(DISPLAY_ID_1), eq(null));
}
+ @DisableFlags(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
@Test
public void onVirtualDisplayCreatedLocked_duplicateCalls_onlyOneWakeLockIsAcquired()
throws RemoteException {
@@ -1022,6 +1026,7 @@
nullable(String.class), eq(DISPLAY_ID_1), eq(null));
}
+ @DisableFlags(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
@Test
public void onVirtualDisplayRemovedLocked_wakeLockIsReleased() throws RemoteException {
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
@@ -1037,6 +1042,7 @@
verify(mIPowerManagerMock).releaseWakeLock(eq(wakeLock), anyInt());
}
+ @DisableFlags(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
@Test
public void addVirtualDisplay_displayNotReleased_wakeLockIsReleased() throws RemoteException {
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index c741c6c..077bb03 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -2562,7 +2562,13 @@
mTestLooper.dispatchAll();
// User interacted with the DUT, so the device will not go to standby.
- skipActiveSourceLostUi(0, true, true);
+ mHdmiControlService.oneTouchPlay(new IHdmiControlCallback.Stub() {
+ @Override
+ public void onComplete(int result) {
+ }
+ });
+ mTestLooper.dispatchAll();
+
assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
assertThat(mPowerManager.isInteractive()).isTrue();
assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
deleted file mode 100644
index 9ed2e88..0000000
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
+++ /dev/null
@@ -1,914 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START;
-import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END;
-import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START;
-import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.DEFAULT_FORMAT_VERSION;
-import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.FORMAT_VERSION_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
-import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY;
-import static com.android.server.integrity.model.IndexingFileConstants.INDEXING_BLOCK_SIZE;
-import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY;
-import static com.android.server.integrity.serializer.RuleBinarySerializer.INDEXED_RULE_SIZE_LIMIT;
-import static com.android.server.integrity.serializer.RuleBinarySerializer.NONINDEXED_RULE_SIZE_LIMIT;
-import static com.android.server.integrity.utils.TestUtils.getBits;
-import static com.android.server.integrity.utils.TestUtils.getBytes;
-import static com.android.server.integrity.utils.TestUtils.getValueBits;
-import static com.android.server.testutils.TestUtils.assertExpectException;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.integrity.AppInstallMetadata;
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.IntegrityFormula;
-import android.content.integrity.IntegrityUtils;
-import android.content.integrity.Rule;
-
-import androidx.annotation.NonNull;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.io.ByteArrayOutputStream;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-
-@RunWith(JUnit4.class)
-public class RuleBinarySerializerTest {
-
- private static final String SAMPLE_INSTALLER_NAME = "com.test.installer";
- private static final String SAMPLE_INSTALLER_CERT = "installer_cert";
-
- private static final String COMPOUND_FORMULA_START_BITS =
- getBits(COMPOUND_FORMULA_START, SEPARATOR_BITS);
- private static final String COMPOUND_FORMULA_END_BITS =
- getBits(COMPOUND_FORMULA_END, SEPARATOR_BITS);
- private static final String ATOMIC_FORMULA_START_BITS =
- getBits(ATOMIC_FORMULA_START, SEPARATOR_BITS);
-
- private static final String NOT = getBits(CompoundFormula.NOT, CONNECTOR_BITS);
- private static final String AND = getBits(CompoundFormula.AND, CONNECTOR_BITS);
- private static final String OR = getBits(CompoundFormula.OR, CONNECTOR_BITS);
-
- private static final String PACKAGE_NAME = getBits(AtomicFormula.PACKAGE_NAME, KEY_BITS);
- private static final String APP_CERTIFICATE = getBits(AtomicFormula.APP_CERTIFICATE, KEY_BITS);
- private static final String INSTALLER_NAME = getBits(AtomicFormula.INSTALLER_NAME, KEY_BITS);
- private static final String INSTALLER_CERTIFICATE =
- getBits(AtomicFormula.INSTALLER_CERTIFICATE, KEY_BITS);
- private static final String VERSION_CODE = getBits(AtomicFormula.VERSION_CODE, KEY_BITS);
- private static final String PRE_INSTALLED = getBits(AtomicFormula.PRE_INSTALLED, KEY_BITS);
-
- private static final String EQ = getBits(AtomicFormula.EQ, OPERATOR_BITS);
-
- private static final String IS_NOT_HASHED = "0";
- private static final String IS_HASHED = "1";
-
- private static final String DENY = getBits(Rule.DENY, EFFECT_BITS);
-
- private static final String START_BIT = "1";
- private static final String END_BIT = "1";
-
- private static final byte[] DEFAULT_FORMAT_VERSION_BYTES =
- getBytes(getBits(DEFAULT_FORMAT_VERSION, FORMAT_VERSION_BITS));
-
- private static final String SERIALIZED_START_INDEXING_KEY =
- IS_NOT_HASHED
- + getBits(START_INDEXING_KEY.length(), VALUE_SIZE_BITS)
- + getValueBits(START_INDEXING_KEY);
- private static final String SERIALIZED_END_INDEXING_KEY =
- IS_NOT_HASHED
- + getBits(END_INDEXING_KEY.length(), VALUE_SIZE_BITS)
- + getValueBits(END_INDEXING_KEY);
-
- @Test
- public void testBinaryString_serializeNullRules() {
- RuleSerializer binarySerializer = new RuleBinarySerializer();
-
- assertExpectException(
- RuleSerializeException.class,
- /* expectedExceptionMessageRegex= */ "Null rules cannot be serialized.",
- () -> binarySerializer.serialize(null, /* formatVersion= */ Optional.empty()));
- }
-
- @Test
- public void testBinaryString_emptyRules() throws Exception {
- ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
- ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
- RuleSerializer binarySerializer = new RuleBinarySerializer();
-
- binarySerializer.serialize(
- Collections.emptyList(),
- /* formatVersion= */ Optional.empty(),
- ruleOutputStream,
- indexingOutputStream);
-
- ByteArrayOutputStream expectedRuleOutputStream = new ByteArrayOutputStream();
- expectedRuleOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
- assertThat(ruleOutputStream.toByteArray())
- .isEqualTo(expectedRuleOutputStream.toByteArray());
-
- ByteArrayOutputStream expectedIndexingOutputStream = new ByteArrayOutputStream();
- String serializedIndexingBytes =
- SERIALIZED_START_INDEXING_KEY
- + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
- + SERIALIZED_END_INDEXING_KEY
- + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32);
- byte[] expectedIndexingBytes =
- getBytes(
- serializedIndexingBytes
- + serializedIndexingBytes
- + serializedIndexingBytes);
- expectedIndexingOutputStream.write(expectedIndexingBytes);
- assertThat(indexingOutputStream.toByteArray())
- .isEqualTo(expectedIndexingOutputStream.toByteArray());
- }
-
- @Test
- public void testBinaryStream_serializeValidCompoundFormula() throws Exception {
- String packageName = "com.test.app";
- Rule rule =
- new Rule(
- new CompoundFormula(
- CompoundFormula.NOT,
- Collections.singletonList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- packageName,
- /* isHashedValue= */ false))),
- Rule.DENY);
-
- ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
- ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
- RuleSerializer binarySerializer = new RuleBinarySerializer();
- binarySerializer.serialize(
- Collections.singletonList(rule),
- /* formatVersion= */ Optional.empty(),
- ruleOutputStream,
- indexingOutputStream);
-
- String expectedBits =
- START_BIT
- + COMPOUND_FORMULA_START_BITS
- + NOT
- + ATOMIC_FORMULA_START_BITS
- + PACKAGE_NAME
- + EQ
- + IS_NOT_HASHED
- + getBits(packageName.length(), VALUE_SIZE_BITS)
- + getValueBits(packageName)
- + COMPOUND_FORMULA_END_BITS
- + DENY
- + END_BIT;
- ByteArrayOutputStream expectedRuleOutputStream = new ByteArrayOutputStream();
- expectedRuleOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
- expectedRuleOutputStream.write(getBytes(expectedBits));
- assertThat(ruleOutputStream.toByteArray())
- .isEqualTo(expectedRuleOutputStream.toByteArray());
-
- ByteArrayOutputStream expectedIndexingOutputStream = new ByteArrayOutputStream();
- String expectedIndexingBitsForIndexed =
- SERIALIZED_START_INDEXING_KEY
- + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
- + SERIALIZED_END_INDEXING_KEY
- + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32);
- String expectedIndexingBitsForUnindexed =
- SERIALIZED_START_INDEXING_KEY
- + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
- + SERIALIZED_END_INDEXING_KEY
- + getBits(
- DEFAULT_FORMAT_VERSION_BYTES.length + getBytes(expectedBits).length,
- /* numOfBits= */ 32);
- expectedIndexingOutputStream.write(
- getBytes(
- expectedIndexingBitsForIndexed
- + expectedIndexingBitsForIndexed
- + expectedIndexingBitsForUnindexed));
-
- assertThat(indexingOutputStream.toByteArray())
- .isEqualTo(expectedIndexingOutputStream.toByteArray());
- }
-
- @Test
- public void testBinaryString_serializeValidCompoundFormula_notConnector() throws Exception {
- String packageName = "com.test.app";
- Rule rule =
- new Rule(
- new CompoundFormula(
- CompoundFormula.NOT,
- Collections.singletonList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- packageName,
- /* isHashedValue= */ false))),
- Rule.DENY);
- RuleSerializer binarySerializer = new RuleBinarySerializer();
- String expectedBits =
- START_BIT
- + COMPOUND_FORMULA_START_BITS
- + NOT
- + ATOMIC_FORMULA_START_BITS
- + PACKAGE_NAME
- + EQ
- + IS_NOT_HASHED
- + getBits(packageName.length(), VALUE_SIZE_BITS)
- + getValueBits(packageName)
- + COMPOUND_FORMULA_END_BITS
- + DENY
- + END_BIT;
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
- byteArrayOutputStream.write(getBytes(expectedBits));
- byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
- byte[] actualRules =
- binarySerializer.serialize(
- Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
- assertThat(actualRules).isEqualTo(expectedRules);
- }
-
- @Test
- public void testBinaryString_serializeValidCompoundFormula_andConnector() throws Exception {
- String packageName = "com.test.app";
- String appCertificate = "test_cert";
- Rule rule =
- new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- packageName,
- /* isHashedValue= */ false),
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.APP_CERTIFICATE,
- appCertificate,
- /* isHashedValue= */ false))),
- Rule.DENY);
- RuleSerializer binarySerializer = new RuleBinarySerializer();
- String expectedBits =
- START_BIT
- + COMPOUND_FORMULA_START_BITS
- + AND
- + ATOMIC_FORMULA_START_BITS
- + PACKAGE_NAME
- + EQ
- + IS_NOT_HASHED
- + getBits(packageName.length(), VALUE_SIZE_BITS)
- + getValueBits(packageName)
- + ATOMIC_FORMULA_START_BITS
- + APP_CERTIFICATE
- + EQ
- + IS_NOT_HASHED
- + getBits(appCertificate.length(), VALUE_SIZE_BITS)
- + getValueBits(appCertificate)
- + COMPOUND_FORMULA_END_BITS
- + DENY
- + END_BIT;
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
- byteArrayOutputStream.write(getBytes(expectedBits));
- byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
- byte[] actualRules =
- binarySerializer.serialize(
- Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
- assertThat(actualRules).isEqualTo(expectedRules);
- }
-
- @Test
- public void testBinaryString_serializeValidCompoundFormula_orConnector() throws Exception {
- String packageName = "com.test.app";
- String appCertificate = "test_cert";
- Rule rule =
- new Rule(
- new CompoundFormula(
- CompoundFormula.OR,
- Arrays.asList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- packageName,
- /* isHashedValue= */ false),
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.APP_CERTIFICATE,
- appCertificate,
- /* isHashedValue= */ false))),
- Rule.DENY);
- RuleSerializer binarySerializer = new RuleBinarySerializer();
- String expectedBits =
- START_BIT
- + COMPOUND_FORMULA_START_BITS
- + OR
- + ATOMIC_FORMULA_START_BITS
- + PACKAGE_NAME
- + EQ
- + IS_NOT_HASHED
- + getBits(packageName.length(), VALUE_SIZE_BITS)
- + getValueBits(packageName)
- + ATOMIC_FORMULA_START_BITS
- + APP_CERTIFICATE
- + EQ
- + IS_NOT_HASHED
- + getBits(appCertificate.length(), VALUE_SIZE_BITS)
- + getValueBits(appCertificate)
- + COMPOUND_FORMULA_END_BITS
- + DENY
- + END_BIT;
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
- byteArrayOutputStream.write(getBytes(expectedBits));
- byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
- byte[] actualRules =
- binarySerializer.serialize(
- Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
- assertThat(actualRules).isEqualTo(expectedRules);
- }
-
- @Test
- public void testBinaryString_serializeValidAtomicFormula_stringValue() throws Exception {
- String packageName = "com.test.app";
- Rule rule =
- new Rule(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- packageName,
- /* isHashedValue= */ false),
- Rule.DENY);
- RuleSerializer binarySerializer = new RuleBinarySerializer();
- String expectedBits =
- START_BIT
- + ATOMIC_FORMULA_START_BITS
- + PACKAGE_NAME
- + EQ
- + IS_NOT_HASHED
- + getBits(packageName.length(), VALUE_SIZE_BITS)
- + getValueBits(packageName)
- + DENY
- + END_BIT;
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
- byteArrayOutputStream.write(getBytes(expectedBits));
- byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
- byte[] actualRules =
- binarySerializer.serialize(
- Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
- assertThat(actualRules).isEqualTo(expectedRules);
- }
-
- @Test
- public void testBinaryString_serializeValidAtomicFormula_hashedValue() throws Exception {
- String appCertificate = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
- Rule rule =
- new Rule(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.APP_CERTIFICATE,
- IntegrityUtils.getHexDigest(
- appCertificate.getBytes(StandardCharsets.UTF_8)),
- /* isHashedValue= */ true),
- Rule.DENY);
- RuleSerializer binarySerializer = new RuleBinarySerializer();
- String expectedBits =
- START_BIT
- + ATOMIC_FORMULA_START_BITS
- + APP_CERTIFICATE
- + EQ
- + IS_HASHED
- + getBits(appCertificate.length(), VALUE_SIZE_BITS)
- + getValueBits(appCertificate)
- + DENY
- + END_BIT;
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
- byteArrayOutputStream.write(getBytes(expectedBits));
- byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
- byte[] actualRules =
- binarySerializer.serialize(
- Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
- assertThat(actualRules).isEqualTo(expectedRules);
- }
-
- @Test
- public void testBinaryString_serializeValidAtomicFormula_integerValue() throws Exception {
- long versionCode = 1;
- Rule rule =
- new Rule(
- new AtomicFormula.LongAtomicFormula(
- AtomicFormula.VERSION_CODE, AtomicFormula.EQ, versionCode),
- Rule.DENY);
- RuleSerializer binarySerializer = new RuleBinarySerializer();
- String expectedBits =
- START_BIT
- + ATOMIC_FORMULA_START_BITS
- + VERSION_CODE
- + EQ
- + getBits(versionCode, /* numOfBits= */ 64)
- + DENY
- + END_BIT;
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
- byteArrayOutputStream.write(getBytes(expectedBits));
- byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
- byte[] actualRules =
- binarySerializer.serialize(
- Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
- assertThat(actualRules).isEqualTo(expectedRules);
- }
-
- @Test
- public void testBinaryString_serializeValidAtomicFormula_booleanValue() throws Exception {
- String preInstalled = "1";
- Rule rule =
- new Rule(
- new AtomicFormula.BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true),
- Rule.DENY);
- RuleSerializer binarySerializer = new RuleBinarySerializer();
- String expectedBits =
- START_BIT
- + ATOMIC_FORMULA_START_BITS
- + PRE_INSTALLED
- + EQ
- + preInstalled
- + DENY
- + END_BIT;
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
- byteArrayOutputStream.write(getBytes(expectedBits));
- byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
- byte[] actualRules =
- binarySerializer.serialize(
- Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
- assertThat(actualRules).isEqualTo(expectedRules);
- }
-
- @Test
- public void testBinaryString_serializeInvalidFormulaType() throws Exception {
- IntegrityFormula invalidFormula = getInvalidFormula();
- Rule rule = new Rule(invalidFormula, Rule.DENY);
- RuleSerializer binarySerializer = new RuleBinarySerializer();
-
- assertExpectException(
- RuleSerializeException.class,
- /* expectedExceptionMessageRegex= */ "Malformed rule identified.",
- () ->
- binarySerializer.serialize(
- Collections.singletonList(rule),
- /* formatVersion= */ Optional.empty()));
- }
-
- @Test
- public void testBinaryString_serializeFormatVersion() throws Exception {
- int formatVersion = 1;
- RuleSerializer binarySerializer = new RuleBinarySerializer();
- String expectedBits = getBits(formatVersion, FORMAT_VERSION_BITS);
- byte[] expectedRules = getBytes(expectedBits);
-
- byte[] actualRules =
- binarySerializer.serialize(
- Collections.emptyList(), /* formatVersion= */ Optional.of(formatVersion));
-
- assertThat(actualRules).isEqualTo(expectedRules);
- }
-
- @Test
- public void testBinaryString_verifyManyRulesAreIndexedCorrectly() throws Exception {
- int ruleCount = 225;
- String packagePrefix = "package.name.";
- String appCertificatePrefix = "app.cert.";
- String installerNamePrefix = "installer.";
-
- // Create the rule set with 225 package name based rules, 225 app certificate indexed rules,
- // and 225 non-indexed rules..
- List<Rule> ruleList = new ArrayList();
- for (int count = 0; count < ruleCount; count++) {
- ruleList.add(
- getRuleWithPackageNameAndSampleInstallerName(
- String.format("%s%04d", packagePrefix, count)));
- }
- for (int count = 0; count < ruleCount; count++) {
- ruleList.add(
- getRuleWithAppCertificateAndSampleInstallerName(
- String.format("%s%04d", appCertificatePrefix, count)));
- }
- for (int count = 0; count < ruleCount; count++) {
- ruleList.add(
- getNonIndexedRuleWithInstallerName(
- String.format("%s%04d", installerNamePrefix, count)));
- }
-
- // Serialize the rules.
- ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
- ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
- RuleSerializer binarySerializer = new RuleBinarySerializer();
- binarySerializer.serialize(
- ruleList,
- /* formatVersion= */ Optional.empty(),
- ruleOutputStream,
- indexingOutputStream);
-
- // Verify the rules file and index files.
- ByteArrayOutputStream expectedOrderedRuleOutputStream = new ByteArrayOutputStream();
- ByteArrayOutputStream expectedIndexingOutputStream = new ByteArrayOutputStream();
-
- expectedOrderedRuleOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
- int totalBytesWritten = DEFAULT_FORMAT_VERSION_BYTES.length;
-
- String expectedIndexingBytesForPackageNameIndexed =
- SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
- for (int count = 0; count < ruleCount; count++) {
- String packageName = String.format("%s%04d", packagePrefix, count);
- if (count > 0 && count % INDEXING_BLOCK_SIZE == 0) {
- expectedIndexingBytesForPackageNameIndexed +=
- IS_NOT_HASHED
- + getBits(packageName.length(), VALUE_SIZE_BITS)
- + getValueBits(packageName)
- + getBits(totalBytesWritten, /* numOfBits= */ 32);
- }
-
- byte[] bytesForPackage =
- getBytes(
- getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
- packageName));
- expectedOrderedRuleOutputStream.write(bytesForPackage);
- totalBytesWritten += bytesForPackage.length;
- }
- expectedIndexingBytesForPackageNameIndexed +=
- SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
-
- String expectedIndexingBytesForAppCertificateIndexed =
- SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
- for (int count = 0; count < ruleCount; count++) {
- String appCertificate = String.format("%s%04d", appCertificatePrefix, count);
- if (count > 0 && count % INDEXING_BLOCK_SIZE == 0) {
- expectedIndexingBytesForAppCertificateIndexed +=
- IS_NOT_HASHED
- + getBits(appCertificate.length(), VALUE_SIZE_BITS)
- + getValueBits(appCertificate)
- + getBits(totalBytesWritten, /* numOfBits= */ 32);
- }
-
- byte[] bytesForPackage =
- getBytes(
- getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
- appCertificate));
- expectedOrderedRuleOutputStream.write(bytesForPackage);
- totalBytesWritten += bytesForPackage.length;
- }
- expectedIndexingBytesForAppCertificateIndexed +=
- SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
-
- String expectedIndexingBytesForUnindexed =
- SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
- for (int count = 0; count < ruleCount; count++) {
- byte[] bytesForPackage =
- getBytes(
- getSerializedCompoundRuleWithInstallerNameAndInstallerCert(
- String.format("%s%04d", installerNamePrefix, count)));
- expectedOrderedRuleOutputStream.write(bytesForPackage);
- totalBytesWritten += bytesForPackage.length;
- }
- expectedIndexingBytesForUnindexed +=
- SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
- expectedIndexingOutputStream.write(
- getBytes(
- expectedIndexingBytesForPackageNameIndexed
- + expectedIndexingBytesForAppCertificateIndexed
- + expectedIndexingBytesForUnindexed));
-
- assertThat(ruleOutputStream.toByteArray())
- .isEqualTo(expectedOrderedRuleOutputStream.toByteArray());
- assertThat(indexingOutputStream.toByteArray())
- .isEqualTo(expectedIndexingOutputStream.toByteArray());
- }
-
- @Test
- public void testBinaryString_totalRuleSizeLimitReached() {
- int ruleCount = INDEXED_RULE_SIZE_LIMIT - 1;
- String packagePrefix = "package.name.";
- String appCertificatePrefix = "app.cert.";
- String installerNamePrefix = "installer.";
-
- // Create the rule set with more rules than the system can handle in total.
- List<Rule> ruleList = new ArrayList();
- for (int count = 0; count < ruleCount; count++) {
- ruleList.add(
- getRuleWithPackageNameAndSampleInstallerName(
- String.format("%s%04d", packagePrefix, count)));
- }
- for (int count = 0; count < ruleCount; count++) {
- ruleList.add(
- getRuleWithAppCertificateAndSampleInstallerName(
- String.format("%s%04d", appCertificatePrefix, count)));
- }
- for (int count = 0; count < NONINDEXED_RULE_SIZE_LIMIT - 1; count++) {
- ruleList.add(
- getNonIndexedRuleWithInstallerName(
- String.format("%s%04d", installerNamePrefix, count)));
- }
-
- // Serialize the rules.
- ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
- ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
- RuleSerializer binarySerializer = new RuleBinarySerializer();
-
- assertExpectException(
- RuleSerializeException.class,
- "Too many rules provided",
- () ->
- binarySerializer.serialize(
- ruleList,
- /* formatVersion= */ Optional.empty(),
- ruleOutputStream,
- indexingOutputStream));
- }
-
- @Test
- public void testBinaryString_tooManyPackageNameIndexedRules() {
- String packagePrefix = "package.name.";
-
- // Create a rule set with too many package name indexed rules.
- List<Rule> ruleList = new ArrayList();
- for (int count = 0; count < INDEXED_RULE_SIZE_LIMIT + 1; count++) {
- ruleList.add(
- getRuleWithPackageNameAndSampleInstallerName(
- String.format("%s%04d", packagePrefix, count)));
- }
-
- // Serialize the rules.
- ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
- ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
- RuleSerializer binarySerializer = new RuleBinarySerializer();
-
- assertExpectException(
- RuleSerializeException.class,
- "Too many rules provided in the indexing group.",
- () ->
- binarySerializer.serialize(
- ruleList,
- /* formatVersion= */ Optional.empty(),
- ruleOutputStream,
- indexingOutputStream));
- }
-
- @Test
- public void testBinaryString_tooManyAppCertificateIndexedRules() {
- String appCertificatePrefix = "app.cert.";
-
- // Create a rule set with too many app certificate indexed rules.
- List<Rule> ruleList = new ArrayList();
- for (int count = 0; count < INDEXED_RULE_SIZE_LIMIT + 1; count++) {
- ruleList.add(
- getRuleWithAppCertificateAndSampleInstallerName(
- String.format("%s%04d", appCertificatePrefix, count)));
- }
-
- // Serialize the rules.
- ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
- ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
- RuleSerializer binarySerializer = new RuleBinarySerializer();
-
- assertExpectException(
- RuleSerializeException.class,
- "Too many rules provided in the indexing group.",
- () ->
- binarySerializer.serialize(
- ruleList,
- /* formatVersion= */ Optional.empty(),
- ruleOutputStream,
- indexingOutputStream));
- }
-
- @Test
- public void testBinaryString_tooManyNonIndexedRules() {
- String installerNamePrefix = "installer.";
-
- // Create a rule set with too many unindexed rules.
- List<Rule> ruleList = new ArrayList();
- for (int count = 0; count < NONINDEXED_RULE_SIZE_LIMIT + 1; count++) {
- ruleList.add(
- getNonIndexedRuleWithInstallerName(
- String.format("%s%04d", installerNamePrefix, count)));
- }
-
- // Serialize the rules.
- ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
- ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
- RuleSerializer binarySerializer = new RuleBinarySerializer();
-
- assertExpectException(
- RuleSerializeException.class,
- "Too many rules provided in the indexing group.",
- () ->
- binarySerializer.serialize(
- ruleList,
- /* formatVersion= */ Optional.empty(),
- ruleOutputStream,
- indexingOutputStream));
- }
-
- private Rule getRuleWithPackageNameAndSampleInstallerName(String packageName) {
- return new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- packageName,
- /* isHashedValue= */ false),
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.INSTALLER_NAME,
- SAMPLE_INSTALLER_NAME,
- /* isHashedValue= */ false))),
- Rule.DENY);
- }
-
- private String getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
- String packageName) {
- return START_BIT
- + COMPOUND_FORMULA_START_BITS
- + AND
- + ATOMIC_FORMULA_START_BITS
- + PACKAGE_NAME
- + EQ
- + IS_NOT_HASHED
- + getBits(packageName.length(), VALUE_SIZE_BITS)
- + getValueBits(packageName)
- + ATOMIC_FORMULA_START_BITS
- + INSTALLER_NAME
- + EQ
- + IS_NOT_HASHED
- + getBits(SAMPLE_INSTALLER_NAME.length(), VALUE_SIZE_BITS)
- + getValueBits(SAMPLE_INSTALLER_NAME)
- + COMPOUND_FORMULA_END_BITS
- + DENY
- + END_BIT;
- }
-
- private Rule getRuleWithAppCertificateAndSampleInstallerName(String certificate) {
- return new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.APP_CERTIFICATE,
- certificate,
- /* isHashedValue= */ false),
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.INSTALLER_NAME,
- SAMPLE_INSTALLER_NAME,
- /* isHashedValue= */ false))),
- Rule.DENY);
- }
-
- private String getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
- String appCertificate) {
- return START_BIT
- + COMPOUND_FORMULA_START_BITS
- + AND
- + ATOMIC_FORMULA_START_BITS
- + APP_CERTIFICATE
- + EQ
- + IS_NOT_HASHED
- + getBits(appCertificate.length(), VALUE_SIZE_BITS)
- + getValueBits(appCertificate)
- + ATOMIC_FORMULA_START_BITS
- + INSTALLER_NAME
- + EQ
- + IS_NOT_HASHED
- + getBits(SAMPLE_INSTALLER_NAME.length(), VALUE_SIZE_BITS)
- + getValueBits(SAMPLE_INSTALLER_NAME)
- + COMPOUND_FORMULA_END_BITS
- + DENY
- + END_BIT;
- }
-
- private Rule getNonIndexedRuleWithInstallerName(String installerName) {
- return new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.INSTALLER_NAME,
- installerName,
- /* isHashedValue= */ false),
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.INSTALLER_CERTIFICATE,
- SAMPLE_INSTALLER_CERT,
- /* isHashedValue= */ false))),
- Rule.DENY);
- }
-
- private String getSerializedCompoundRuleWithInstallerNameAndInstallerCert(
- String installerName) {
- return START_BIT
- + COMPOUND_FORMULA_START_BITS
- + AND
- + ATOMIC_FORMULA_START_BITS
- + INSTALLER_NAME
- + EQ
- + IS_NOT_HASHED
- + getBits(installerName.length(), VALUE_SIZE_BITS)
- + getValueBits(installerName)
- + ATOMIC_FORMULA_START_BITS
- + INSTALLER_CERTIFICATE
- + EQ
- + IS_NOT_HASHED
- + getBits(SAMPLE_INSTALLER_CERT.length(), VALUE_SIZE_BITS)
- + getValueBits(SAMPLE_INSTALLER_CERT)
- + COMPOUND_FORMULA_END_BITS
- + DENY
- + END_BIT;
- }
-
- private static IntegrityFormula getInvalidFormula() {
- return new AtomicFormula(0) {
- @Override
- public int getTag() {
- return 0;
- }
-
- @Override
- public boolean matches(AppInstallMetadata appInstallMetadata) {
- return false;
- }
-
- @Override
- public boolean isAppCertificateFormula() {
- return false;
- }
-
- @Override
- public boolean isAppCertificateLineageFormula() {
- return false;
- }
-
- @Override
- public boolean isInstallerFormula() {
- return false;
- }
-
- @Override
- public int hashCode() {
- return super.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- return super.equals(obj);
- }
-
- @NonNull
- @Override
- protected Object clone() throws CloneNotSupportedException {
- return super.clone();
- }
-
- @Override
- public String toString() {
- return super.toString();
- }
-
- @Override
- protected void finalize() throws Throwable {
- super.finalize();
- }
- };
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
deleted file mode 100644
index 6dccdf5..0000000
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets;
-import static com.android.server.testutils.TestUtils.assertExpectException;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.integrity.AppInstallMetadata;
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.IntegrityFormula;
-import android.content.integrity.Rule;
-
-import androidx.annotation.NonNull;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-
-/** Unit tests for {@link RuleIndexingDetailsIdentifier}. */
-@RunWith(JUnit4.class)
-public class RuleIndexingDetailsIdentifierTest {
-
- private static final String SAMPLE_APP_CERTIFICATE = "testcert";
- private static final String SAMPLE_INSTALLER_NAME = "com.test.installer";
- private static final String SAMPLE_INSTALLER_CERTIFICATE = "installercert";
- private static final String SAMPLE_PACKAGE_NAME = "com.test.package";
-
- private static final AtomicFormula ATOMIC_FORMULA_WITH_PACKAGE_NAME =
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- SAMPLE_PACKAGE_NAME,
- /* isHashedValue= */ false);
- private static final AtomicFormula ATOMIC_FORMULA_WITH_APP_CERTIFICATE =
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.APP_CERTIFICATE,
- SAMPLE_APP_CERTIFICATE,
- /* isHashedValue= */ false);
- private static final AtomicFormula ATOMIC_FORMULA_WITH_INSTALLER_NAME =
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.INSTALLER_NAME,
- SAMPLE_INSTALLER_NAME,
- /* isHashedValue= */ false);
- private static final AtomicFormula ATOMIC_FORMULA_WITH_INSTALLER_CERTIFICATE =
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.INSTALLER_CERTIFICATE,
- SAMPLE_INSTALLER_CERTIFICATE,
- /* isHashedValue= */ false);
- private static final AtomicFormula ATOMIC_FORMULA_WITH_VERSION_CODE =
- new AtomicFormula.LongAtomicFormula(AtomicFormula.VERSION_CODE,
- AtomicFormula.EQ, 12);
- private static final AtomicFormula ATOMIC_FORMULA_WITH_ISPREINSTALLED =
- new AtomicFormula.BooleanAtomicFormula(
- AtomicFormula.PRE_INSTALLED, /* booleanValue= */
- true);
-
-
- private static final Rule RULE_WITH_PACKAGE_NAME =
- new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- ATOMIC_FORMULA_WITH_PACKAGE_NAME,
- ATOMIC_FORMULA_WITH_INSTALLER_NAME)),
- Rule.DENY);
- private static final Rule RULE_WITH_APP_CERTIFICATE =
- new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- ATOMIC_FORMULA_WITH_APP_CERTIFICATE,
- ATOMIC_FORMULA_WITH_INSTALLER_NAME)),
- Rule.DENY);
- private static final Rule RULE_WITH_INSTALLER_RESTRICTIONS =
- new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- ATOMIC_FORMULA_WITH_INSTALLER_NAME,
- ATOMIC_FORMULA_WITH_INSTALLER_CERTIFICATE)),
- Rule.DENY);
-
- private static final Rule RULE_WITH_NONSTRING_RESTRICTIONS =
- new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- ATOMIC_FORMULA_WITH_VERSION_CODE,
- ATOMIC_FORMULA_WITH_ISPREINSTALLED)),
- Rule.DENY);
- public static final int INVALID_FORMULA_TAG = -1;
-
- @Test
- public void getIndexType_nullRule() {
- List<Rule> ruleList = null;
-
- assertExpectException(
- IllegalArgumentException.class,
- /* expectedExceptionMessageRegex= */
- "Index buckets cannot be created for null rule list.",
- () -> splitRulesIntoIndexBuckets(ruleList));
- }
-
- @Test
- public void getIndexType_invalidFormula() {
- List<Rule> ruleList = new ArrayList();
- ruleList.add(new Rule(getInvalidFormula(), Rule.DENY));
-
- assertExpectException(
- IllegalArgumentException.class,
- /* expectedExceptionMessageRegex= */ "Malformed rule identified.",
- () -> splitRulesIntoIndexBuckets(ruleList));
- }
-
- @Test
- public void getIndexType_ruleContainingPackageNameFormula() {
- List<Rule> ruleList = new ArrayList();
- ruleList.add(RULE_WITH_PACKAGE_NAME);
-
- Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
- // Verify the resulting map content.
- assertThat(result.keySet())
- .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
- assertThat(result.get(NOT_INDEXED)).isEmpty();
- assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
- assertThat(result.get(PACKAGE_NAME_INDEXED).keySet()).containsExactly(SAMPLE_PACKAGE_NAME);
- assertThat(result.get(PACKAGE_NAME_INDEXED).get(SAMPLE_PACKAGE_NAME))
- .containsExactly(RULE_WITH_PACKAGE_NAME);
- }
-
- @Test
- public void getIndexType_ruleContainingAppCertificateFormula() {
- List<Rule> ruleList = new ArrayList();
- ruleList.add(RULE_WITH_APP_CERTIFICATE);
-
- Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
- assertThat(result.keySet())
- .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
- assertThat(result.get(NOT_INDEXED)).isEmpty();
- assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
- assertThat(result.get(APP_CERTIFICATE_INDEXED).keySet())
- .containsExactly(SAMPLE_APP_CERTIFICATE);
- assertThat(result.get(APP_CERTIFICATE_INDEXED).get(SAMPLE_APP_CERTIFICATE))
- .containsExactly(RULE_WITH_APP_CERTIFICATE);
- }
-
- @Test
- public void getIndexType_ruleWithUnindexedCompoundFormula() {
- List<Rule> ruleList = new ArrayList();
- ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS);
-
- Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
- assertThat(result.keySet())
- .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
- assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
- assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
- assertThat(result.get(NOT_INDEXED).get("N/A"))
- .containsExactly(RULE_WITH_INSTALLER_RESTRICTIONS);
- }
-
- @Test
- public void getIndexType_ruleContainingCompoundFormulaWithIntAndBoolean() {
- List<Rule> ruleList = new ArrayList();
- ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS);
-
- Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
- assertThat(result.keySet())
- .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
- assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
- assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
- assertThat(result.get(NOT_INDEXED).get("N/A"))
- .containsExactly(RULE_WITH_NONSTRING_RESTRICTIONS);
- }
-
- @Test
- public void getIndexType_negatedRuleContainingPackageNameFormula() {
- Rule negatedRule =
- new Rule(
- new CompoundFormula(
- CompoundFormula.NOT,
- Arrays.asList(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- ATOMIC_FORMULA_WITH_PACKAGE_NAME,
- ATOMIC_FORMULA_WITH_APP_CERTIFICATE)))),
- Rule.DENY);
- List<Rule> ruleList = new ArrayList();
- ruleList.add(negatedRule);
-
- Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
- assertThat(result.keySet())
- .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
- assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
- assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
- assertThat(result.get(NOT_INDEXED).get("N/A")).containsExactly(negatedRule);
- }
-
- @Test
- public void getIndexType_allRulesTogetherSplitCorrectly() {
- Rule packageNameRuleA = getRuleWithPackageName("aaa");
- Rule packageNameRuleB = getRuleWithPackageName("bbb");
- Rule packageNameRuleC = getRuleWithPackageName("ccc");
- Rule certificateRule1 = getRuleWithAppCertificate("cert1");
- Rule certificateRule2 = getRuleWithAppCertificate("cert2");
- Rule certificateRule3 = getRuleWithAppCertificate("cert3");
-
- List<Rule> ruleList = new ArrayList();
- ruleList.add(packageNameRuleB);
- ruleList.add(packageNameRuleC);
- ruleList.add(packageNameRuleA);
- ruleList.add(certificateRule3);
- ruleList.add(certificateRule2);
- ruleList.add(certificateRule1);
- ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS);
- ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS);
-
- Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
- assertThat(result.keySet())
- .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
-
- // We check asserts this way to ensure ordering based on package name.
- assertThat(result.get(PACKAGE_NAME_INDEXED).keySet()).containsExactly("aaa", "bbb", "ccc");
-
- // We check asserts this way to ensure ordering based on app certificate.
- assertThat(result.get(APP_CERTIFICATE_INDEXED).keySet()).containsExactly("cert1", "cert2",
- "cert3");
-
- assertThat(result.get(NOT_INDEXED).get("N/A"))
- .containsExactly(RULE_WITH_INSTALLER_RESTRICTIONS,
- RULE_WITH_NONSTRING_RESTRICTIONS);
- }
-
- private Rule getRuleWithPackageName(String packageName) {
- return new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- packageName,
- /* isHashedValue= */ false),
- ATOMIC_FORMULA_WITH_INSTALLER_NAME)),
- Rule.DENY);
- }
-
- private Rule getRuleWithAppCertificate(String certificate) {
- return new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.APP_CERTIFICATE,
- certificate,
- /* isHashedValue= */ false),
- ATOMIC_FORMULA_WITH_INSTALLER_NAME)),
- Rule.DENY);
- }
-
- private IntegrityFormula getInvalidFormula() {
- return new AtomicFormula(0) {
- @Override
- public int getTag() {
- return INVALID_FORMULA_TAG;
- }
-
- @Override
- public boolean matches(AppInstallMetadata appInstallMetadata) {
- return false;
- }
-
- @Override
- public boolean isAppCertificateFormula() {
- return false;
- }
-
- @Override
- public boolean isAppCertificateLineageFormula() {
- return false;
- }
-
- @Override
- public boolean isInstallerFormula() {
- return false;
- }
-
- @Override
- public int hashCode() {
- return super.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- return super.equals(obj);
- }
-
- @NonNull
- @Override
- protected Object clone() throws CloneNotSupportedException {
- return super.clone();
- }
-
- @Override
- public String toString() {
- return super.toString();
- }
-
- @Override
- protected void finalize() throws Throwable {
- super.finalize();
- }
- };
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
index 4a43c2e..9d7b6a1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
@@ -977,11 +977,19 @@
assertEquals(USER_ID_1, UserHandle.getUserId(uid));
mPackageListObserver.onPackageRemoved(PACKAGE_NAME_1, uid);
+ // Test that notifyAllCallbacks doesn't trigger for non-background-installed package
+ mPackageListObserver.onPackageRemoved(PACKAGE_NAME_3, uid);
mTestLooper.dispatchAll();
assertEquals(1, packages.size());
assertFalse(packages.contains(USER_ID_1, PACKAGE_NAME_1));
assertTrue(packages.contains(USER_ID_2, PACKAGE_NAME_2));
+
+ verify(mCallbackHelper)
+ .notifyAllCallbacks(
+ USER_ID_1,
+ PACKAGE_NAME_1,
+ BackgroundInstallControlService.INSTALL_EVENT_TYPE_UNINSTALL);
}
@Test
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 ad11c26..25a8db6 100644
--- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
@@ -123,51 +123,7 @@
{"MEDIA_PLAY_PAUSE key -> Media Control",
new int[]{KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE},
KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY,
- KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, 0},
- {"Meta + B -> Launch Default Browser", new int[]{META_KEY, KeyEvent.KEYCODE_B},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER,
- KeyEvent.KEYCODE_B, META_ON},
- {"EXPLORER key -> Launch Default Browser", new int[]{KeyEvent.KEYCODE_EXPLORER},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER,
- KeyEvent.KEYCODE_EXPLORER, 0},
- {"Meta + C -> Launch Default Contacts", new int[]{META_KEY, KeyEvent.KEYCODE_C},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS,
- KeyEvent.KEYCODE_C, META_ON},
- {"CONTACTS key -> Launch Default Contacts", new int[]{KeyEvent.KEYCODE_CONTACTS},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS,
- KeyEvent.KEYCODE_CONTACTS, 0},
- {"Meta + E -> Launch Default Email", new int[]{META_KEY, KeyEvent.KEYCODE_E},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL,
- KeyEvent.KEYCODE_E, META_ON},
- {"ENVELOPE key -> Launch Default Email", new int[]{KeyEvent.KEYCODE_ENVELOPE},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL,
- KeyEvent.KEYCODE_ENVELOPE, 0},
- {"Meta + K -> Launch Default Calendar", new int[]{META_KEY, KeyEvent.KEYCODE_K},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR,
- KeyEvent.KEYCODE_K, META_ON},
- {"CALENDAR key -> Launch Default Calendar", new int[]{KeyEvent.KEYCODE_CALENDAR},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR,
- KeyEvent.KEYCODE_CALENDAR, 0},
- {"Meta + P -> Launch Default Music", new int[]{META_KEY, KeyEvent.KEYCODE_P},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC,
- KeyEvent.KEYCODE_P, META_ON},
- {"MUSIC key -> Launch Default Music", new int[]{KeyEvent.KEYCODE_MUSIC},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC,
- KeyEvent.KEYCODE_MUSIC, 0},
- {"Meta + U -> Launch Default Calculator", new int[]{META_KEY, KeyEvent.KEYCODE_U},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR,
- KeyEvent.KEYCODE_U, META_ON},
- {"CALCULATOR key -> Launch Default Calculator",
- new int[]{KeyEvent.KEYCODE_CALCULATOR},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR,
- KeyEvent.KEYCODE_CALCULATOR, 0},
- {"Meta + M -> Launch Default Maps", new int[]{META_KEY, KeyEvent.KEYCODE_M},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS,
- KeyEvent.KEYCODE_M, META_ON},
- {"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_MEDIA_PLAY_PAUSE, 0}};
}
@Keep
@@ -295,7 +251,51 @@
new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_DOWN},
KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE,
KeyEvent.KEYCODE_DPAD_DOWN,
- META_ON | CTRL_ON}};
+ META_ON | CTRL_ON},
+ {"Meta + B -> Launch Default Browser", new int[]{META_KEY, KeyEvent.KEYCODE_B},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER,
+ KeyEvent.KEYCODE_B, META_ON},
+ {"EXPLORER key -> Launch Default Browser", new int[]{KeyEvent.KEYCODE_EXPLORER},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER,
+ KeyEvent.KEYCODE_EXPLORER, 0},
+ {"Meta + C -> Launch Default Contacts", new int[]{META_KEY, KeyEvent.KEYCODE_C},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS,
+ KeyEvent.KEYCODE_C, META_ON},
+ {"CONTACTS key -> Launch Default Contacts", new int[]{KeyEvent.KEYCODE_CONTACTS},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS,
+ KeyEvent.KEYCODE_CONTACTS, 0},
+ {"Meta + E -> Launch Default Email", new int[]{META_KEY, KeyEvent.KEYCODE_E},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL,
+ KeyEvent.KEYCODE_E, META_ON},
+ {"ENVELOPE key -> Launch Default Email", new int[]{KeyEvent.KEYCODE_ENVELOPE},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL,
+ KeyEvent.KEYCODE_ENVELOPE, 0},
+ {"Meta + K -> Launch Default Calendar", new int[]{META_KEY, KeyEvent.KEYCODE_K},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR,
+ KeyEvent.KEYCODE_K, META_ON},
+ {"CALENDAR key -> Launch Default Calendar", new int[]{KeyEvent.KEYCODE_CALENDAR},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR,
+ KeyEvent.KEYCODE_CALENDAR, 0},
+ {"Meta + P -> Launch Default Music", new int[]{META_KEY, KeyEvent.KEYCODE_P},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC,
+ KeyEvent.KEYCODE_P, META_ON},
+ {"MUSIC key -> Launch Default Music", new int[]{KeyEvent.KEYCODE_MUSIC},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC,
+ KeyEvent.KEYCODE_MUSIC, 0},
+ {"Meta + U -> Launch Default Calculator", new int[]{META_KEY, KeyEvent.KEYCODE_U},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR,
+ KeyEvent.KEYCODE_U, META_ON},
+ {"CALCULATOR key -> Launch Default Calculator",
+ new int[]{KeyEvent.KEYCODE_CALCULATOR},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR,
+ KeyEvent.KEYCODE_CALCULATOR, 0},
+ {"Meta + M -> Launch Default Maps", new int[]{META_KEY, KeyEvent.KEYCODE_M},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS,
+ KeyEvent.KEYCODE_M, META_ON},
+ {"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}};
}
@Keep
@@ -331,6 +331,7 @@
mPhoneWindowManager.setupAssistForLaunch();
mPhoneWindowManager.overrideTogglePanel();
mPhoneWindowManager.overrideInjectKeyEvent();
+ mPhoneWindowManager.overrideRoleManager();
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index d4ba3b2..9e7575f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -538,7 +538,7 @@
public void testConsecutiveLaunchNewTask() {
final IBinder launchCookie = mock(IBinder.class);
final WindowContainerToken launchRootTask = mock(WindowContainerToken.class);
- mTrampolineActivity.noDisplay = true;
+ mTrampolineActivity.setIsNoDisplay(true);
mTrampolineActivity.mLaunchCookie = launchCookie;
mTrampolineActivity.mLaunchRootTask = launchRootTask;
onActivityLaunched(mTrampolineActivity);
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index 20dcdde..d6be915 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -24,7 +24,9 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
import static android.window.BackNavigationInfo.typeToString;
+import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
@@ -103,6 +105,13 @@
@Before
public void setUp() throws Exception {
+ final TransitionController transitionController = mAtm.getTransitionController();
+ final Transition fakeTransition = new Transition(TRANSIT_PREPARE_BACK_NAVIGATION,
+ 0 /* flag */, transitionController, transitionController.mSyncEngine);
+ spyOn(transitionController);
+ doReturn(fakeTransition).when(transitionController)
+ .createTransition(anyInt(), anyInt());
+
final BackNavigationController original = new BackNavigationController();
original.setWindowManager(mWm);
mBackNavigationController = Mockito.spy(original);
@@ -111,6 +120,7 @@
LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal);
mBackAnimationAdapter = mock(BackAnimationAdapter.class);
doReturn(true).when(mBackAnimationAdapter).isAnimatable(anyInt());
+ Mockito.doNothing().when(mBackNavigationController).startAnimation();
mNavigationMonitor = mock(BackNavigationController.NavigationMonitor.class);
mRootHomeTask = initHomeActivity();
}
@@ -446,7 +456,7 @@
new OnBackInvokedCallbackInfo(
callback,
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
- /* isAnimationCallback = */ false));
+ /* isAnimationCallback = */ false, OVERRIDE_UNDEFINED));
BackNavigationInfo backNavigationInfo = startBackNavigation();
assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
@@ -467,7 +477,7 @@
new OnBackInvokedCallbackInfo(
callback,
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
- /* isAnimationCallback = */ true));
+ /* isAnimationCallback = */ true, OVERRIDE_UNDEFINED));
BackNavigationInfo backNavigationInfo = startBackNavigation();
assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
@@ -608,7 +618,7 @@
new OnBackInvokedCallbackInfo(
callback,
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
- /* isAnimationCallback = */ false));
+ /* isAnimationCallback = */ false, OVERRIDE_UNDEFINED));
BackNavigationInfo backNavigationInfo = startBackNavigation();
assertThat(backNavigationInfo).isNull();
@@ -722,7 +732,7 @@
new OnBackInvokedCallbackInfo(
callback,
OnBackInvokedDispatcher.PRIORITY_SYSTEM,
- /* isAnimationCallback = */ false));
+ /* isAnimationCallback = */ false, OVERRIDE_UNDEFINED));
return callback;
}
@@ -732,7 +742,7 @@
new OnBackInvokedCallbackInfo(
callback,
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
- /* isAnimationCallback = */ false));
+ /* isAnimationCallback = */ false, OVERRIDE_UNDEFINED));
return callback;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
index eacb8e9..a0c5b54 100644
--- a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
@@ -25,7 +25,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.window.flags.Flags.explicitRefreshRateHints;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -190,14 +189,9 @@
verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
eq(appWindow.getSurfaceControl()), anyFloat(),
eq(Surface.FRAME_RATE_COMPATIBILITY_EXACT), eq(Surface.CHANGE_FRAME_RATE_ALWAYS));
- if (explicitRefreshRateHints()) {
- verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
- appWindow.getSurfaceControl(),
- SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
- } else {
- verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
- any(SurfaceControl.class), anyInt());
- }
+ verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+ appWindow.getSurfaceControl(),
+ SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
}
@Test
@@ -226,14 +220,9 @@
verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
eq(appWindow.getSurfaceControl()), anyFloat(),
eq(Surface.FRAME_RATE_COMPATIBILITY_EXACT), eq(Surface.CHANGE_FRAME_RATE_ALWAYS));
- if (explicitRefreshRateHints()) {
- verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
- appWindow.getSurfaceControl(),
- SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
- } else {
- verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
- any(SurfaceControl.class), anyInt());
- }
+ verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+ appWindow.getSurfaceControl(),
+ SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
}
@Test
@@ -288,14 +277,9 @@
verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
appWindow.getSurfaceControl(), 60,
Surface.FRAME_RATE_COMPATIBILITY_EXACT, Surface.CHANGE_FRAME_RATE_ALWAYS);
- if (explicitRefreshRateHints()) {
- verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
- appWindow.getSurfaceControl(),
- SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
- } else {
- verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
- any(SurfaceControl.class), anyInt());
- }
+ verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+ appWindow.getSurfaceControl(),
+ SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
}
@Test
@@ -352,13 +336,8 @@
verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
appWindow.getSurfaceControl(), 60,
Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, Surface.CHANGE_FRAME_RATE_ALWAYS);
- if (explicitRefreshRateHints()) {
- verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
- appWindow.getSurfaceControl(),
- SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
- } else {
- verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
- any(SurfaceControl.class), anyInt());
- }
+ verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+ appWindow.getSurfaceControl(),
+ SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
index 3fa38bf..3d08ca2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -21,14 +21,11 @@
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.window.flags.Flags.explicitRefreshRateHints;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.hardware.display.DisplayManager;
@@ -36,7 +33,6 @@
import android.platform.test.annotations.Presubmit;
import android.view.Display.Mode;
import android.view.Surface;
-import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import androidx.test.filters.SmallTest;
@@ -274,97 +270,6 @@
}
@Test
- public void testAnimatingAppOverridePreferredModeId() {
- final WindowState overrideWindow = createWindow("overrideWindow");
- overrideWindow.mAttrs.packageName = "com.android.test";
- overrideWindow.mAttrs.preferredDisplayModeId = LOW_MODE_ID;
- parcelLayoutParams(overrideWindow);
- assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
- assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
- assertEquals(FRAME_RATE_VOTE_LOW_EXACT, overrideWindow.mFrameRateVote);
- assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
- assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
-
- if (explicitRefreshRateHints()) {
- return;
- }
- overrideWindow.mActivityRecord.mSurfaceAnimator.startAnimation(
- overrideWindow.getPendingTransaction(), mock(AnimationAdapter.class),
- false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
- assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
- assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
- assertEquals(FRAME_RATE_VOTE_NONE, overrideWindow.mFrameRateVote);
- assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
- assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
-
- // Use default mode if it is animating by shell transition.
- overrideWindow.mActivityRecord.mSurfaceAnimator.cancelAnimation();
- registerTestTransitionPlayer();
- final Transition transition = overrideWindow.mTransitionController.createTransition(
- WindowManager.TRANSIT_OPEN);
- transition.collect(overrideWindow.mActivityRecord);
- assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
-
- // If there will be display size change when switching from preferred mode to default mode,
- // then keep the current preferred mode during animating.
- mDisplayInfo = spy(mDisplayInfo);
- final Mode defaultMode = new Mode(4321 /* width */, 1234 /* height */, LOW_REFRESH_RATE);
- doReturn(defaultMode).when(mDisplayInfo).getDefaultMode();
- mPolicy = new RefreshRatePolicy(mWm, mDisplayInfo, mDenylist);
- assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
- }
-
- @Test
- public void testAnimatingAppOverridePreferredRefreshRate() {
- final WindowState overrideWindow = createWindow("overrideWindow");
- overrideWindow.mAttrs.packageName = "com.android.test";
- overrideWindow.mAttrs.preferredRefreshRate = LOW_REFRESH_RATE;
- parcelLayoutParams(overrideWindow);
- assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
- assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
- assertEquals(FRAME_RATE_VOTE_LOW_PREFERRED, overrideWindow.mFrameRateVote);
- assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
- assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
-
- if (explicitRefreshRateHints()) {
- return;
- }
- overrideWindow.mActivityRecord.mSurfaceAnimator.startAnimation(
- overrideWindow.getPendingTransaction(), mock(AnimationAdapter.class),
- false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
- assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
- assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
- assertEquals(FRAME_RATE_VOTE_NONE, overrideWindow.mFrameRateVote);
- assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
- assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
- }
-
- @Test
- public void testAnimatingDenylist() {
- final WindowState window = createWindow("overrideWindow");
- window.mAttrs.packageName = "com.android.test";
- parcelLayoutParams(window);
- when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
- assertEquals(0, mPolicy.getPreferredModeId(window));
- assertTrue(mPolicy.updateFrameRateVote(window));
- assertEquals(FRAME_RATE_VOTE_DENY_LIST, window.mFrameRateVote);
- assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
- assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
-
- if (explicitRefreshRateHints()) {
- return;
- }
- window.mActivityRecord.mSurfaceAnimator.startAnimation(
- window.getPendingTransaction(), mock(AnimationAdapter.class),
- false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
- assertEquals(0, mPolicy.getPreferredModeId(window));
- assertTrue(mPolicy.updateFrameRateVote(window));
- assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
- assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
- assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
- }
-
- @Test
public void testAnimatingCamera() {
final WindowState cameraUsingWindow = createWindow("cameraUsingWindow");
cameraUsingWindow.mAttrs.packageName = "com.android.test";
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 1c87802..62a4711 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -4812,6 +4812,23 @@
assertFalse(mActivity.isResizeable());
assertEquals(maxAspect, aspectRatioPolicy.getMaxAspectRatio(), 0 /* delta */);
assertNotEquals(SCREEN_ORIENTATION_UNSPECIFIED, mActivity.getOverrideOrientation());
+
+ // Activity can opt-out the resizability by component level property.
+ final ComponentName name = getUniqueComponentName(mContext.getPackageName());
+ final PackageManager pm = mContext.getPackageManager();
+ spyOn(pm);
+ final PackageManager.Property property = new PackageManager.Property("propertyName",
+ true /* value */, name.getPackageName(), name.getClassName());
+ try {
+ doReturn(property).when(pm).getPropertyAsUser(
+ WindowManager.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY,
+ name.getPackageName(), name.getClassName(), 0 /* userId */);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ final ActivityRecord optOutActivity = new ActivityBuilder(mAtm)
+ .setComponent(name).setTask(mTask).build();
+ assertFalse(optOutActivity.isUniversalResizeable());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index 3c921c6..4568c77 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -249,7 +249,7 @@
ActivityRecord reusableActivity = createSourceActivity(fullscreenDisplay);
ActivityRecord source = createSourceActivity(freeformDisplay);
source.mHandoverLaunchDisplayId = freeformDisplay.mDisplayId;
- source.noDisplay = true;
+ source.setIsNoDisplay(true);
assertEquals(RESULT_CONTINUE,
new CalculateRequestBuilder()
@@ -272,7 +272,7 @@
ActivityRecord reusableActivity = createSourceActivity(fullscreenDisplay);
ActivityRecord source = createSourceActivity(freeformDisplay);
source.mHandoverTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
- source.noDisplay = true;
+ source.setIsNoDisplay(true);
assertEquals(RESULT_CONTINUE,
new CalculateRequestBuilder()
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index e8779c2..039a3dd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -51,7 +51,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
-import static com.android.window.flags.Flags.explicitRefreshRateHints;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -1632,6 +1631,8 @@
transition.collect(taskA);
transition.setTransientLaunch(recent, taskA);
taskRecent.moveToFront("move-recent-to-front");
+ recent.setVisibility(true);
+ recent.setState(ActivityRecord.State.RESUMED, "test");
// During collecting and playing, the recent is on top so it is visible naturally.
// While B needs isTransientVisible to keep visibility because it is occluded by recents.
@@ -1644,15 +1645,21 @@
// Switch to another task. For example, use gesture navigation to switch tasks.
taskB.moveToFront("move-b-to-front");
+ appB.setVisibility(true);
// The previous app (taskA) should be paused first so it loses transient visible. Because
// visually it is taskA -> taskB, the pause -> resume order should be the same.
assertFalse(controller.isTransientVisible(taskA));
- // Keep the recent visible so there won't be 2 activities pausing at the same time. It is
- // to avoid the latency to resume the current top, i.e. appB.
- assertTrue(controller.isTransientVisible(taskRecent));
- // The recent is paused after the transient transition is finished.
- controller.finishTransition(ActionChain.testFinish(transition));
+ // The recent is occluded by appB.
assertFalse(controller.isTransientVisible(taskRecent));
+ // Active transient launch won't be paused if the transition is not finished. It is to
+ // avoid the latency to resume the current top (appB) by waiting for both recent and appA
+ // to complete pause.
+ assertEquals(recent, taskRecent.getResumedActivity());
+ assertFalse(taskRecent.startPausing(false /* uiSleeping */, appB /* resuming */, "test"));
+ // ActivityRecord#makeInvisible will add the invisible recent to the stopping list.
+ // So when the transition finished, the recent can still be notified to pause and stop.
+ mDisplayContent.ensureActivitiesVisible(null /* starting */, true /* notifyClients */);
+ assertTrue(mSupervisor.mStoppingActivities.contains(recent));
}
@Test
@@ -2883,17 +2890,14 @@
@Test
public void testTransitionsTriggerPerformanceHints() {
- final boolean explicitRefreshRateHints = explicitRefreshRateHints();
final var session = new SystemPerformanceHinter.HighPerfSession[1];
- if (explicitRefreshRateHints) {
- final SystemPerformanceHinter perfHinter = mWm.mSystemPerformanceHinter;
- spyOn(perfHinter);
- doAnswer(invocation -> {
- session[0] = (SystemPerformanceHinter.HighPerfSession) invocation.callRealMethod();
- spyOn(session[0]);
- return session[0];
- }).when(perfHinter).createSession(anyInt(), anyInt(), anyString());
- }
+ final SystemPerformanceHinter perfHinter = mWm.mSystemPerformanceHinter;
+ spyOn(perfHinter);
+ doAnswer(invocation -> {
+ session[0] = (SystemPerformanceHinter.HighPerfSession) invocation.callRealMethod();
+ spyOn(session[0]);
+ return session[0];
+ }).when(perfHinter).createSession(anyInt(), anyInt(), anyString());
final TransitionController controller = mDisplayContent.mTransitionController;
final TestTransitionPlayer player = registerTestTransitionPlayer();
final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
@@ -2905,15 +2909,11 @@
player.start();
verify(mDisplayContent).enableHighPerfTransition(true);
- if (explicitRefreshRateHints) {
- verify(session[0]).start();
- }
+ verify(session[0]).start();
player.finish();
verify(mDisplayContent).enableHighPerfTransition(false);
- if (explicitRefreshRateHints) {
- verify(session[0]).close();
- }
+ verify(session[0]).close();
}
@Test
diff --git a/services/usb/OWNERS b/services/usb/OWNERS
index d35dbb56..2dff392 100644
--- a/services/usb/OWNERS
+++ b/services/usb/OWNERS
@@ -1,9 +1,9 @@
-aprasath@google.com
-kumarashishg@google.com
-sarup@google.com
anothermark@google.com
+febinthattil@google.com
+aprasath@google.com
badhri@google.com
elaurent@google.com
albertccwang@google.com
jameswei@google.com
-howardyen@google.com
\ No newline at end of file
+howardyen@google.com
+kumarashishg@google.com
\ No newline at end of file
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index 45a7faf..07969bd 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -304,11 +304,17 @@
@Override
public void unloadModel(int modelHandle) {
synchronized (SoundTriggerModule.this) {
- int sessionId;
checkValid();
- sessionId = mLoadedModels.get(modelHandle).unload();
- mAudioSessionProvider.releaseSession(sessionId);
+ final var session = mLoadedModels.get(modelHandle).getSession();
+ mLoadedModels.remove(modelHandle);
+ mAudioSessionProvider.releaseSession(session.mSessionHandle);
}
+ // We don't need to post-synchronize on anything once the HAL has finished the unload
+ // and dispatched any appropriate callbacks -- since we don't do any state checking
+ // onModelUnloaded regardless.
+ // This is generally safe since there is no post-condition on the framework side when
+ // a model is unloaded. We assume that we won't ever have a modelHandle collision.
+ mHalService.unloadSoundModel(modelHandle);
}
@Override
@@ -402,6 +408,10 @@
return mState;
}
+ private SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession getSession() {
+ return mSession;
+ }
+
private void setState(@NonNull ModelState state) {
mState = state;
SoundTriggerModule.this.notifyAll();
@@ -426,16 +436,6 @@
return mHandle;
}
- /**
- * Unloads the model.
- * @return The audio session handle.
- */
- private int unload() {
- mHalService.unloadSoundModel(mHandle);
- mLoadedModels.remove(mHandle);
- return mSession.mSessionHandle;
- }
-
private IBinder startRecognition(@NonNull RecognitionConfig config) {
if (mIsStopping == true) {
throw new RecoverableException(Status.INTERNAL_ERROR, "Race occurred");
diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java
index 9b83719..be34619 100644
--- a/telephony/common/com/android/internal/telephony/SmsApplication.java
+++ b/telephony/common/com/android/internal/telephony/SmsApplication.java
@@ -47,8 +47,6 @@
import android.util.Log;
import android.util.SparseArray;
-import com.android.internal.annotations.VisibleForTesting;
-
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
@@ -538,7 +536,13 @@
}
private static String getDefaultSmsPackage(Context context, int userId) {
- return context.getSystemService(RoleManager.class).getSmsRoleHolder(userId);
+ // RoleManager might be null in unit tests running older mockito versions that do not
+ // support mocking final classes.
+ RoleManager roleManager = context.getSystemService(RoleManager.class);
+ if (roleManager == null) {
+ return "";
+ }
+ return roleManager.getSmsRoleHolder(userId);
}
/**
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 0808cc3..2a06c3d 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -10153,6 +10153,15 @@
"satellite_roaming_esos_inactivity_timeout_sec_int";
/**
+ * A string array containing the list of messaging package names that support satellite.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public static final String KEY_SATELLITE_SUPPORTED_MSG_APPS_STRING_ARRAY =
+ "satellite_supported_msg_apps_string_array";
+
+ /**
* Indicating whether DUN APN should be disabled when the device is roaming. In that case,
* the default APN (i.e. internet) will be used for tethering.
*
diff --git a/libs/hwui/utils/StatsUtils.h b/telephony/java/android/telephony/satellite/ISatelliteDisallowedReasonsCallback.aidl
similarity index 60%
rename from libs/hwui/utils/StatsUtils.h
rename to telephony/java/android/telephony/satellite/ISatelliteDisallowedReasonsCallback.aidl
index 0c247014..9a6f6b8 100644
--- a/libs/hwui/utils/StatsUtils.h
+++ b/telephony/java/android/telephony/satellite/ISatelliteDisallowedReasonsCallback.aidl
@@ -14,20 +14,17 @@
* limitations under the License.
*/
-#pragma once
+package android.telephony.satellite;
-#include <SkBitmap.h>
-#include <SkColorSpace.h>
-#include <SkColorType.h>
-#include <cutils/compiler.h>
-#include <hwui/Bitmap.h>
-
-namespace android {
-namespace uirenderer {
-
-ANDROID_API void logBitmapDecode(const SkImageInfo& info, bool hasGainmap);
-
-ANDROID_API void logBitmapDecode(const Bitmap& bitmap);
-
-} // namespace uirenderer
-} // namespace android
+/**
+ * Interface for satellite disallowed reason change callback.
+ *
+ * @hide
+ */
+oneway interface ISatelliteDisallowedReasonsCallback {
+ /**
+ * Indicates that disallowed reason of satellite has changed.
+ * @param disallowedReasons list of disallowed reasons.
+ */
+ void onSatelliteDisallowedReasonsChanged(in int[] disallowedReasons);
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteDisallowedReasonsCallback.java b/telephony/java/android/telephony/satellite/SatelliteDisallowedReasonsCallback.java
new file mode 100644
index 0000000..5e276aa
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteDisallowedReasonsCallback.java
@@ -0,0 +1,38 @@
+/*
+ * 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.telephony.satellite;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+
+import com.android.internal.telephony.flags.Flags;
+
+/**
+ * A callback class for disallowed reason of satellite change events.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+public interface SatelliteDisallowedReasonsCallback {
+
+ /**
+ * Called when disallowed reason of satellite has changed.
+ * @param disallowedReasons Integer array of disallowed reasons.
+ */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ void onSatelliteDisallowedReasonsChanged(@NonNull int[] disallowedReasons);
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index be02232..7be3f33 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -104,6 +104,11 @@
sSatelliteCommunicationAllowedStateCallbackMap =
new ConcurrentHashMap<>();
+ private static final ConcurrentHashMap<SatelliteDisallowedReasonsCallback,
+ ISatelliteDisallowedReasonsCallback>
+ sSatelliteDisallowedReasonsCallbackMap =
+ new ConcurrentHashMap<>();
+
private final int mSubId;
/**
@@ -1487,6 +1492,47 @@
public @interface SatelliteCommunicationRestrictionReason {}
/**
+ * Satellite is disallowed because it is not supported.
+ * @hide
+ */
+ public static final int SATELLITE_DISALLOWED_REASON_NOT_SUPPORTED = 0;
+
+ /**
+ * Satellite is disallowed because it has not been provisioned.
+ * @hide
+ */
+ public static final int SATELLITE_DISALLOWED_REASON_NOT_PROVISIONED = 1;
+
+ /**
+ * Satellite is disallowed because it is currently outside an allowed region.
+ * @hide
+ */
+ public static final int SATELLITE_DISALLOWED_REASON_NOT_IN_ALLOWED_REGION = 2;
+
+ /**
+ * Satellite is disallowed because an unsupported default message application is being used.
+ * @hide
+ */
+ public static final int SATELLITE_DISALLOWED_REASON_UNSUPPORTED_DEFAULT_MSG_APP = 3;
+
+ /**
+ * Satellite is disallowed because location settings have been disabled.
+ * @hide
+ */
+ public static final int SATELLITE_DISALLOWED_REASON_LOCATION_DISABLED = 4;
+
+ /** @hide */
+ @IntDef(prefix = "SATELLITE_DISALLOWED_REASON_", value = {
+ SATELLITE_DISALLOWED_REASON_NOT_SUPPORTED,
+ SATELLITE_DISALLOWED_REASON_NOT_PROVISIONED,
+ SATELLITE_DISALLOWED_REASON_NOT_IN_ALLOWED_REGION,
+ SATELLITE_DISALLOWED_REASON_UNSUPPORTED_DEFAULT_MSG_APP,
+ SATELLITE_DISALLOWED_REASON_LOCATION_DISABLED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SatelliteDisallowedReason {}
+
+ /**
* Start receiving satellite transmission updates.
* This can be called by the pointing UI when the user starts pointing to the satellite.
* Modem should continue to report the pointing input as the device or satellite moves.
@@ -2579,6 +2625,119 @@
}
/**
+ * Returns list of disallowed reasons of satellite.
+ *
+ * @return list of disallowed reasons of satellite.
+ *
+ * @throws SecurityException if caller doesn't have required permission.
+ * @throws IllegalStateException if Telephony process isn't available.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @SatelliteDisallowedReason
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ @NonNull
+ public List<Integer> getSatelliteDisallowedReasons() {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ int[] receivedArray = telephony.getSatelliteDisallowedReasons();
+ if (receivedArray.length == 0) {
+ logd("receivedArray is empty, create empty list");
+ return new ArrayList<>();
+ } else {
+ return Arrays.stream(receivedArray).boxed().collect(Collectors.toList());
+ }
+ } else {
+ throw new IllegalStateException("Telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("getSatelliteDisallowedReasons() RemoteException: " + ex);
+ ex.rethrowAsRuntimeException();
+ }
+ return new ArrayList<>();
+ }
+
+ /**
+ * Registers for disallowed reasons change event from satellite service.
+ *
+ * @param executor The executor on which the callback will be called.
+ * @param callback The callback to handle disallowed reasons changed event.
+ *
+ * @throws SecurityException if caller doesn't have required permission.
+ * @throws IllegalStateException if Telephony process is not available.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public void registerForSatelliteDisallowedReasonsChanged(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull SatelliteDisallowedReasonsCallback callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ ISatelliteDisallowedReasonsCallback internalCallback =
+ new ISatelliteDisallowedReasonsCallback.Stub() {
+ @Override
+ public void onSatelliteDisallowedReasonsChanged(
+ int[] disallowedReasons) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> callback.onSatelliteDisallowedReasonsChanged(
+ disallowedReasons)));
+ }
+ };
+ telephony.registerForSatelliteDisallowedReasonsChanged(internalCallback);
+ sSatelliteDisallowedReasonsCallbackMap.put(callback, internalCallback);
+ } else {
+ throw new IllegalStateException("Telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("registerForSatelliteDisallowedReasonsChanged() RemoteException" + ex);
+ ex.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Unregisters for disallowed reasons change event from satellite service.
+ *
+ * @param callback The callback that was passed to
+ * {@link #registerForSatelliteDisallowedReasonsChanged(
+ * Executor, SatelliteDisallowedReasonsCallback)}
+ *
+ * @throws SecurityException if caller doesn't have required permission.
+ * @throws IllegalStateException if Telephony process is not available.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public void unregisterForSatelliteDisallowedReasonsChanged(
+ @NonNull SatelliteDisallowedReasonsCallback callback) {
+ Objects.requireNonNull(callback);
+ ISatelliteDisallowedReasonsCallback internalCallback =
+ sSatelliteDisallowedReasonsCallbackMap.remove(callback);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ if (internalCallback != null) {
+ telephony.unregisterForSatelliteDisallowedReasonsChanged(internalCallback);
+ } else {
+ loge("unregisterForSatelliteDisallowedReasonsChanged: No internal callback.");
+ throw new IllegalArgumentException("callback is not valid");
+ }
+ } else {
+ throw new IllegalStateException("Telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("unregisterForSatelliteDisallowedReasonsChanged() RemoteException: " + ex);
+ ex.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
* Request to get the signal strength of the satellite connection.
*
* <p>
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 231c8f5..62cbb02 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -71,6 +71,7 @@
import android.telephony.satellite.ISatelliteCapabilitiesCallback;
import android.telephony.satellite.ISatelliteCommunicationAllowedStateCallback;
import android.telephony.satellite.ISatelliteDatagramCallback;
+import android.telephony.satellite.ISatelliteDisallowedReasonsCallback;
import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
import android.telephony.satellite.ISatelliteProvisionStateCallback;
import android.telephony.satellite.ISatelliteSupportedStateCallback;
@@ -2955,6 +2956,37 @@
in boolean needFullScreenPointingUI, IIntegerConsumer callback);
/**
+ * Returns integer array of disallowed reasons of satellite.
+ *
+ * @return Integer array of disallowed reasons of satellite.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ int[] getSatelliteDisallowedReasons();
+
+ /**
+ * Registers for disallowed reasons change event from satellite service.
+ *
+ * @param callback The callback to handle disallowed reasons changed event.
+ *
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ void registerForSatelliteDisallowedReasonsChanged(
+ ISatelliteDisallowedReasonsCallback callback);
+
+ /**
+ * Unregisters for disallowed reasons change event from satellite service.
+ * If callback was not registered before, the request will be ignored.
+ *
+ * @param callback The callback to handle disallowed reasons changed event.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ void unregisterForSatelliteDisallowedReasonsChanged(
+ ISatelliteDisallowedReasonsCallback callback);
+
+ /**
* Request to get whether satellite communication is allowed for the current location.
*
* @param subId The subId of the subscription to get whether satellite communication is allowed
diff --git a/tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java b/tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java
new file mode 100644
index 0000000..a3e5533
--- /dev/null
+++ b/tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.jank.tests;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.jank.Flags;
+import android.app.jank.JankTracker;
+import android.app.jank.StateTracker;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.view.Choreographer;
+import android.view.View;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+public class JankTrackerTest {
+ private Choreographer mChoreographer;
+ private JankTracker mJankTracker;
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ /**
+ * Start an empty activity so decore view is not null when creating the JankTracker instance.
+ */
+ private static ActivityScenario<EmptyActivity> sEmptyActivityRule;
+
+ private static String sActivityName;
+
+ private static View sActivityDecorView;
+
+ @BeforeClass
+ public static void classSetup() {
+ sEmptyActivityRule = ActivityScenario.launch(EmptyActivity.class);
+ sEmptyActivityRule.onActivity(activity -> {
+ sActivityDecorView = activity.getWindow().getDecorView();
+ sActivityName = activity.toString();
+ });
+ }
+
+ @AfterClass
+ public static void classTearDown() {
+ sEmptyActivityRule.close();
+ }
+
+ @Before
+ @UiThreadTest
+ public void setup() {
+ mChoreographer = Choreographer.getInstance();
+ mJankTracker = new JankTracker(mChoreographer, sActivityDecorView);
+ mJankTracker.setActivityName(sActivityName);
+ }
+
+ /**
+ * When jank tracking is enabled the activity name should be added as a state to associate
+ * frames to it.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void jankTracking_WhenEnabled_ActivityAdded() {
+ mJankTracker.enableAppJankTracking();
+
+ ArrayList<StateTracker.StateData> stateData = new ArrayList<>();
+ mJankTracker.getAllUiStates(stateData);
+
+ assertEquals(1, stateData.size());
+
+ StateTracker.StateData firstState = stateData.getFirst();
+
+ assertEquals(sActivityName, firstState.mWidgetId);
+ }
+
+ /**
+ * No states should be added when tracking is disabled.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void jankTrackingDisabled_StatesShouldNot_BeAddedToTracker() {
+ mJankTracker.disableAppJankTracking();
+
+ mJankTracker.addUiState("FAKE_CATEGORY", "FAKE_ID",
+ "FAKE_STATE");
+
+ ArrayList<StateTracker.StateData> stateData = new ArrayList<>();
+ mJankTracker.getAllUiStates(stateData);
+
+ assertEquals(0, stateData.size());
+ }
+
+ /**
+ * The activity name as well as the test state should be added for frame association.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void jankTrackingEnabled_StatesShould_BeAddedToTracker() {
+ mJankTracker.forceListenerRegistration();
+
+ mJankTracker.enableAppJankTracking();
+ mJankTracker.addUiState("FAKE_CATEGORY", "FAKE_ID",
+ "FAKE_STATE");
+
+ ArrayList<StateTracker.StateData> stateData = new ArrayList<>();
+ mJankTracker.getAllUiStates(stateData);
+
+ assertEquals(2, stateData.size());
+ }
+
+ /**
+ * Activity state should only be added once even if jank tracking is enabled multiple times.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void jankTrackingEnabled_EnabledCalledTwice_ActivityStateOnlyAddedOnce() {
+ mJankTracker.enableAppJankTracking();
+
+ ArrayList<StateTracker.StateData> stateData = new ArrayList<>();
+ mJankTracker.getAllUiStates(stateData);
+
+ assertEquals(1, stateData.size());
+
+ stateData.clear();
+
+ mJankTracker.enableAppJankTracking();
+ mJankTracker.getAllUiStates(stateData);
+
+ assertEquals(1, stateData.size());
+ }
+}
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index 1c6bd11..332b9b8 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -132,6 +132,24 @@
wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
}
+ private fun getMinimizeButtonForTheApp(caption: UiObject2?): UiObject2 {
+ return caption
+ ?.children
+ ?.find { it.resourceName.endsWith(MINIMIZE_BUTTON_VIEW) }
+ ?: error("Unable to find resource $MINIMIZE_BUTTON_VIEW\n")
+ }
+
+ fun minimizeDesktopApp(wmHelper: WindowManagerStateHelper, device: UiDevice) {
+ val caption = getCaptionForTheApp(wmHelper, device)
+ val minimizeButton = getMinimizeButtonForTheApp(caption)
+ minimizeButton.click()
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withWindowSurfaceDisappeared(innerHelper)
+ .waitForAndVerify()
+ }
+
/** Open maximize menu and click snap resize button on the app header for the given app. */
fun snapResizeDesktopApp(
wmHelper: WindowManagerStateHelper,
@@ -400,6 +418,7 @@
const val DESKTOP_MODE_BUTTON: String = "desktop_button"
const val SNAP_LEFT_BUTTON: String = "maximize_menu_snap_left_button"
const val SNAP_RIGHT_BUTTON: String = "maximize_menu_snap_right_button"
+ const val MINIMIZE_BUTTON_VIEW: String = "minimize_window"
val caption: BySelector
get() = By.res(SYSTEMUI_PACKAGE, CAPTION)
}
diff --git a/tests/Input/res/xml/bookmarks.xml b/tests/Input/res/xml/bookmarks.xml
new file mode 100644
index 0000000..ba3f187
--- /dev/null
+++ b/tests/Input/res/xml/bookmarks.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<bookmarks>
+ <!-- the key combinations for the following shortcuts must be in sync
+ with the key combinations sent by the test in KeyGestureControllerTests.java -->
+ <bookmark
+ role="android.app.role.BROWSER"
+ shortcut="b" />
+ <bookmark
+ category="android.intent.category.APP_CONTACTS"
+ shortcut="c" />
+ <bookmark
+ category="android.intent.category.APP_EMAIL"
+ shortcut="e" />
+ <bookmark
+ category="android.intent.category.APP_CALENDAR"
+ shortcut="k" />
+ <bookmark
+ category="android.intent.category.APP_MAPS"
+ shortcut="m" />
+ <bookmark
+ category="android.intent.category.APP_MUSIC"
+ shortcut="p" />
+ <bookmark
+ role="android.app.role.SMS"
+ shortcut="s" />
+ <bookmark
+ category="android.intent.category.APP_CALCULATOR"
+ shortcut="u" />
+
+ <bookmark
+ role="android.app.role.BROWSER"
+ shortcut="b"
+ shift="true" />
+
+ <bookmark
+ category="android.intent.category.APP_CONTACTS"
+ shortcut="c"
+ shift="true" />
+
+ <bookmark
+ package="com.test"
+ class="com.test.BookmarkTest"
+ shortcut="j"
+ shift="true" />
+</bookmarks>
\ No newline at end of file
diff --git a/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt b/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt
index 01c56b7..862886c 100644
--- a/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt
+++ b/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt
@@ -21,6 +21,7 @@
import android.hardware.input.KeyGestureEvent
import android.platform.test.annotations.Presubmit
import android.view.KeyEvent
+import androidx.test.core.app.ApplicationProvider
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
@@ -42,7 +43,7 @@
@Before
fun setup() {
- inputGestureManager = InputGestureManager()
+ inputGestureManager = InputGestureManager(ApplicationProvider.getApplicationContext())
}
@Test
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index 927958e..6eb0045 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -95,8 +95,11 @@
@get:Rule
val extendedMockitoRule =
- ExtendedMockitoRule.Builder(this).mockStatic(LocalServices::class.java)
- .mockStatic(PermissionChecker::class.java).build()!!
+ ExtendedMockitoRule.Builder(this)
+ .mockStatic(LocalServices::class.java)
+ .mockStatic(PermissionChecker::class.java)
+ .mockStatic(KeyCharacterMap::class.java)
+ .build()!!
@get:Rule
val setFlagsRule = SetFlagsRule()
@@ -122,6 +125,9 @@
@Mock
private lateinit var kbdController: InputManagerService.KeyboardBacklightControllerInterface
+ @Mock
+ private lateinit var kcm: KeyCharacterMap
+
private lateinit var service: InputManagerService
private lateinit var localService: InputManagerInternal
private lateinit var context: Context
@@ -171,6 +177,9 @@
ExtendedMockito.doReturn(packageManagerInternal).`when` {
LocalServices.getService(eq(PackageManagerInternal::class.java))
}
+ ExtendedMockito.doReturn(kcm).`when` {
+ KeyCharacterMap.load(anyInt())
+ }
assertTrue("Local service must be registered", this::localService.isInitialized)
service.setWindowManagerCallbacks(wmCallbacks)
@@ -206,6 +215,7 @@
verify(native).setTouchpadTapDraggingEnabled(anyBoolean())
verify(native).setShouldNotifyTouchpadHardwareState(anyBoolean())
verify(native).setTouchpadRightClickZoneEnabled(anyBoolean())
+ verify(native).setTouchpadThreeFingerTapShortcutEnabled(anyBoolean())
verify(native).setShowTouches(anyBoolean())
verify(native).setMotionClassifierEnabled(anyBoolean())
verify(native).setMaximumObscuringOpacityForTouch(anyFloat())
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index 0b147d6..6c9f764 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -16,13 +16,16 @@
package com.android.server.input
+import android.app.role.RoleManager
import android.content.Context
import android.content.ContextWrapper
+import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Resources
-import android.hardware.input.IInputManager
+import android.content.res.XmlResourceParser
import android.hardware.input.AidlKeyGestureEvent
import android.hardware.input.AppLaunchData
+import android.hardware.input.IInputManager
import android.hardware.input.IKeyGestureEventListener
import android.hardware.input.IKeyGestureHandler
import android.hardware.input.InputGestureData
@@ -34,14 +37,15 @@
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.dx.mockito.inline.extended.ExtendedMockito
import com.android.internal.R
import com.android.internal.annotations.Keep
import com.android.internal.util.FrameworkStatsLog
@@ -99,7 +103,9 @@
@Rule
val extendedMockitoRule = ExtendedMockitoRule.Builder(this)
.mockStatic(FrameworkStatsLog::class.java)
- .mockStatic(SystemProperties::class.java).build()!!
+ .mockStatic(SystemProperties::class.java)
+ .mockStatic(KeyCharacterMap::class.java)
+ .build()!!
@JvmField
@Rule
@@ -116,6 +122,7 @@
private var currentPid = 0
private lateinit var context: Context
+ private lateinit var keyGestureController: KeyGestureController
private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
private lateinit var testLooper: TestLooper
private var events = mutableListOf<KeyGestureEvent>()
@@ -123,8 +130,6 @@
@Before
fun setup() {
context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
- Mockito.`when`(context.resources).thenReturn(resources)
- inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager)
setupInputDevices()
setupBehaviors()
testLooper = TestLooper()
@@ -139,11 +144,13 @@
}
private fun setupBehaviors() {
- Mockito.`when`(
- resources.getBoolean(
- com.android.internal.R.bool.config_enableScreenshotChord
- )
- ).thenReturn(true)
+ Mockito.`when`(SystemProperties.get("ro.debuggable")).thenReturn("1")
+ Mockito.`when`(resources.getBoolean(R.bool.config_enableScreenshotChord)).thenReturn(true)
+ val testBookmarks: XmlResourceParser = context.resources.getXml(
+ com.android.test.input.R.xml.bookmarks
+ )
+ Mockito.`when`(resources.getXml(R.xml.bookmarks)).thenReturn(testBookmarks)
+ Mockito.`when`(context.resources).thenReturn(resources)
Mockito.`when`(packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH))
.thenReturn(true)
Mockito.`when`(packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK))
@@ -152,6 +159,10 @@
}
private fun setupInputDevices() {
+ val correctIm = context.getSystemService(InputManager::class.java)!!
+ val virtualDevice = correctIm.getInputDevice(KeyCharacterMap.VIRTUAL_KEYBOARD)!!
+ val kcm = virtualDevice.keyCharacterMap!!
+ inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager)
val inputManager = InputManager(context)
Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
.thenReturn(inputManager)
@@ -159,9 +170,17 @@
val keyboardDevice = InputDevice.Builder().setId(DEVICE_ID).build()
Mockito.`when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID))
Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardDevice)
+ ExtendedMockito.`when`(KeyCharacterMap.load(Mockito.anyInt())).thenReturn(kcm)
}
- private fun notifyHomeGestureCompleted(keyGestureController: KeyGestureController) {
+ private fun setupKeyGestureController() {
+ keyGestureController = KeyGestureController(context, testLooper.looper)
+ Mockito.`when`(iInputManager.getAppLaunchBookmarks())
+ .thenReturn(keyGestureController.appLaunchBookmarks)
+ keyGestureController.systemRunning()
+ }
+
+ private fun notifyHomeGestureCompleted() {
keyGestureController.notifyKeyGestureCompleted(
DEVICE_ID, intArrayOf(KeyEvent.KEYCODE_H),
KeyEvent.META_META_ON or KeyEvent.META_META_LEFT_ON,
@@ -171,12 +190,12 @@
@Test
fun testKeyGestureEvent_registerUnregisterListener() {
- val keyGestureController = KeyGestureController(context, testLooper.looper)
+ setupKeyGestureController()
val listener = KeyGestureEventListener()
// Register key gesture event listener
keyGestureController.registerKeyGestureEventListener(listener, 0)
- notifyHomeGestureCompleted(keyGestureController)
+ notifyHomeGestureCompleted()
testLooper.dispatchAll()
assertEquals(
"Listener should get callbacks on key gesture event completed",
@@ -192,7 +211,7 @@
// Unregister listener
events.clear()
keyGestureController.unregisterKeyGestureEventListener(listener, 0)
- notifyHomeGestureCompleted(keyGestureController)
+ notifyHomeGestureCompleted()
testLooper.dispatchAll()
assertEquals(
"Listener should not get callback after being unregistered",
@@ -203,7 +222,7 @@
@Test
fun testKeyGestureEvent_multipleGestureHandlers() {
- val keyGestureController = KeyGestureController(context, testLooper.looper)
+ setupKeyGestureController()
// Set up two callbacks.
var callbackCount1 = 0
@@ -267,7 +286,7 @@
}
@Keep
- private fun keyGestureEventHandlerTestArguments(): Array<TestData> {
+ private fun systemGesturesTestArguments(): Array<TestData> {
return arrayOf(
TestData(
"META + A -> Launch Assistant",
@@ -278,25 +297,6 @@
intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
),
TestData(
- "RECENT_APPS -> Show Overview",
- intArrayOf(KeyEvent.KEYCODE_RECENT_APPS),
- KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS,
- intArrayOf(KeyEvent.KEYCODE_RECENT_APPS),
- 0,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- ),
- TestData(
- "APP_SWITCH -> App Switch",
- intArrayOf(KeyEvent.KEYCODE_APP_SWITCH),
- KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH,
- intArrayOf(KeyEvent.KEYCODE_APP_SWITCH),
- 0,
- intArrayOf(
- KeyGestureEvent.ACTION_GESTURE_START,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE
- )
- ),
- TestData(
"META + H -> Go Home",
intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_H),
KeyGestureEvent.KEY_GESTURE_TYPE_HOME,
@@ -465,6 +465,379 @@
intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
),
TestData(
+ "META + ALT -> Toggle Caps Lock",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "ALT + META -> Toggle Caps Lock",
+ intArrayOf(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_META_LEFT),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + TAB -> Open Overview",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_TAB),
+ KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS,
+ intArrayOf(KeyEvent.KEYCODE_TAB),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "ALT + TAB -> Toggle Recent Apps Switcher",
+ intArrayOf(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_TAB),
+ KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER,
+ intArrayOf(KeyEvent.KEYCODE_TAB),
+ KeyEvent.META_ALT_ON,
+ intArrayOf(
+ KeyGestureEvent.ACTION_GESTURE_START,
+ 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(
+ "META + B -> Launch Default Browser",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_B),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_B),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER)
+ ),
+ TestData(
+ "META + C -> Launch Default Contacts",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_C),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_C),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS)
+ ),
+ TestData(
+ "META + E -> Launch Default Email",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_E),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_E),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_EMAIL)
+ ),
+ TestData(
+ "META + K -> Launch Default Calendar",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_K),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_K),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALENDAR)
+ ),
+ TestData(
+ "META + M -> Launch Default Maps",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_M),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_M),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_MAPS)
+ ),
+ TestData(
+ "META + P -> Launch Default Music",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_P),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_P),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_MUSIC)
+ ),
+ TestData(
+ "META + S -> Launch Default SMS",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_S),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_S),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_SMS)
+ ),
+ TestData(
+ "META + U -> Launch Default Calculator",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_U),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_U),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALCULATOR)
+ ),
+ TestData(
+ "META + SHIFT + B -> Launch Default Browser",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_SHIFT_LEFT,
+ KeyEvent.KEYCODE_B
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_B),
+ KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER)
+ ),
+ TestData(
+ "META + SHIFT + C -> Launch Default Contacts",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_SHIFT_LEFT,
+ KeyEvent.KEYCODE_C
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_C),
+ KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS)
+ ),
+ TestData(
+ "META + SHIFT + J -> Launch Target Activity",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_SHIFT_LEFT,
+ KeyEvent.KEYCODE_J
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_J),
+ KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForComponent("com.test", "com.test.BookmarkTest")
+ ),
+ 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)
+ ),
+ TestData(
+ "Meta + Alt + 3 -> Toggle Bounce Keys",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_3
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS,
+ intArrayOf(KeyEvent.KEYCODE_3),
+ KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "Meta + Alt + 4 -> Toggle Mouse Keys",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_4
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS,
+ intArrayOf(KeyEvent.KEYCODE_4),
+ KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "Meta + Alt + 5 -> Toggle Sticky Keys",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_5
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS,
+ intArrayOf(KeyEvent.KEYCODE_5),
+ KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "Meta + Alt + 6 -> Toggle Slow Keys",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_6
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS,
+ intArrayOf(KeyEvent.KEYCODE_6),
+ KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + CTRL + D -> Move a task to next display",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_CTRL_LEFT,
+ KeyEvent.KEYCODE_D
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY,
+ intArrayOf(KeyEvent.KEYCODE_D),
+ KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "ALT + [ -> Resizes a task to fit the left half of the screen",
+ intArrayOf(
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_LEFT_BRACKET
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW,
+ intArrayOf(KeyEvent.KEYCODE_LEFT_BRACKET),
+ KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "ALT + ] -> Resizes a task to fit the right half of the screen",
+ intArrayOf(
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_RIGHT_BRACKET
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW,
+ intArrayOf(KeyEvent.KEYCODE_RIGHT_BRACKET),
+ KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "ALT + '=' -> Maximizes a task to fit the screen",
+ intArrayOf(
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_EQUALS
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW,
+ intArrayOf(KeyEvent.KEYCODE_EQUALS),
+ KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "ALT + '-' -> Restores a task size to its previous bounds",
+ intArrayOf(
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_MINUS
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE,
+ intArrayOf(KeyEvent.KEYCODE_MINUS),
+ KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ )
+ )
+ }
+
+ @Test
+ @Parameters(method = "systemGesturesTestArguments")
+ @EnableFlags(
+ com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS,
+ com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT,
+ com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
+ )
+ fun testKeyGestures(test: TestData) {
+ setupKeyGestureController()
+ testKeyGestureInternal(test)
+ }
+
+ @Test
+ @Parameters(method = "systemGesturesTestArguments")
+ @EnableFlags(
+ com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS,
+ com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT,
+ com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
+ )
+ fun testCustomKeyGesturesNotAllowedForSystemGestures(test: TestData) {
+ setupKeyGestureController()
+ // Need to re-init so that bookmarks are correctly blocklisted
+ Mockito.`when`(iInputManager.getAppLaunchBookmarks())
+ .thenReturn(keyGestureController.appLaunchBookmarks)
+ keyGestureController.systemRunning()
+
+ val builder = InputGestureData.Builder()
+ .setKeyGestureType(test.expectedKeyGestureType)
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ test.expectedKeys[0],
+ test.expectedModifierState
+ )
+ )
+ if (test.expectedAppLaunchData != null) {
+ builder.setAppLaunchData(test.expectedAppLaunchData)
+ }
+ assertEquals(
+ test.toString(),
+ InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE,
+ keyGestureController.addCustomInputGesture(0, builder.build().aidlData)
+ )
+ }
+
+ @Keep
+ private fun systemKeysTestArguments(): Array<TestData> {
+ return arrayOf(
+ TestData(
+ "RECENT_APPS -> Show Overview",
+ intArrayOf(KeyEvent.KEYCODE_RECENT_APPS),
+ KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS,
+ intArrayOf(KeyEvent.KEYCODE_RECENT_APPS),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "APP_SWITCH -> App Switch",
+ intArrayOf(KeyEvent.KEYCODE_APP_SWITCH),
+ KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH,
+ intArrayOf(KeyEvent.KEYCODE_APP_SWITCH),
+ 0,
+ intArrayOf(
+ KeyGestureEvent.ACTION_GESTURE_START,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE
+ )
+ ),
+ TestData(
"BRIGHTNESS_UP -> Brightness Up",
intArrayOf(KeyEvent.KEYCODE_BRIGHTNESS_UP),
KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP,
@@ -553,73 +926,6 @@
intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
),
TestData(
- "META + ALT -> Toggle Caps Lock",
- intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT),
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
- intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT),
- 0,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- ),
- TestData(
- "ALT + META -> Toggle Caps Lock",
- intArrayOf(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_META_LEFT),
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
- intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT),
- 0,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- ),
- TestData(
- "META + TAB -> Open Overview",
- intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_TAB),
- KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS,
- intArrayOf(KeyEvent.KEYCODE_TAB),
- KeyEvent.META_META_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- ),
- TestData(
- "ALT + TAB -> Toggle Recent Apps Switcher",
- intArrayOf(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_TAB),
- KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER,
- intArrayOf(KeyEvent.KEYCODE_TAB),
- KeyEvent.META_ALT_ON,
- intArrayOf(
- KeyGestureEvent.ACTION_GESTURE_START,
- 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,
@@ -635,19 +941,73 @@
0,
intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
),
+ TestData(
+ "EXPLORER -> Launch Default Browser",
+ intArrayOf(KeyEvent.KEYCODE_EXPLORER),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_EXPLORER),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER)
+ ),
+ TestData(
+ "ENVELOPE -> Launch Default Email",
+ intArrayOf(KeyEvent.KEYCODE_ENVELOPE),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_ENVELOPE),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_EMAIL)
+ ),
+ TestData(
+ "CONTACTS -> Launch Default Contacts",
+ intArrayOf(KeyEvent.KEYCODE_CONTACTS),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_CONTACTS),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS)
+ ),
+ TestData(
+ "CALENDAR -> Launch Default Calendar",
+ intArrayOf(KeyEvent.KEYCODE_CALENDAR),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_CALENDAR),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALENDAR)
+ ),
+ TestData(
+ "MUSIC -> Launch Default Music",
+ intArrayOf(KeyEvent.KEYCODE_MUSIC),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_MUSIC),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_MUSIC)
+ ),
+ TestData(
+ "CALCULATOR -> Launch Default Calculator",
+ intArrayOf(KeyEvent.KEYCODE_CALCULATOR),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_CALCULATOR),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALCULATOR)
+ ),
)
}
@Test
- @Parameters(method = "keyGestureEventHandlerTestArguments")
- fun testKeyGestures(test: TestData) {
- val keyGestureController = KeyGestureController(context, testLooper.looper)
- testKeyGestureInternal(keyGestureController, test)
+ @Parameters(method = "systemKeysTestArguments")
+ fun testSystemKeys(test: TestData) {
+ setupKeyGestureController()
+ testKeyGestureInternal(test)
}
@Test
fun testKeycodesFullyConsumed_irrespectiveOfHandlers() {
- val keyGestureController = KeyGestureController(context, testLooper.looper)
+ setupKeyGestureController()
val testKeys = intArrayOf(
KeyEvent.KEYCODE_RECENT_APPS,
KeyEvent.KEYCODE_APP_SWITCH,
@@ -675,7 +1035,7 @@
keyGestureController.registerKeyGestureHandler(handler, 0)
for (key in testKeys) {
- sendKeys(keyGestureController, intArrayOf(key), assertNotSentToApps = true)
+ sendKeys(intArrayOf(key), assertNotSentToApps = true)
}
}
@@ -683,9 +1043,8 @@
fun testSearchKeyGestures_defaultSearch() {
Mockito.`when`(resources.getInteger(R.integer.config_searchKeyBehavior))
.thenReturn(SEARCH_KEY_BEHAVIOR_DEFAULT_SEARCH)
- val keyGestureController = KeyGestureController(context, testLooper.looper)
+ setupKeyGestureController()
testKeyGestureNotProduced(
- keyGestureController,
"SEARCH -> Default Search",
intArrayOf(KeyEvent.KEYCODE_SEARCH),
)
@@ -695,9 +1054,8 @@
fun testSearchKeyGestures_searchActivity() {
Mockito.`when`(resources.getInteger(R.integer.config_searchKeyBehavior))
.thenReturn(SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY)
- val keyGestureController = KeyGestureController(context, testLooper.looper)
+ setupKeyGestureController()
testKeyGestureInternal(
- keyGestureController,
TestData(
"SEARCH -> Launch Search Activity",
intArrayOf(KeyEvent.KEYCODE_SEARCH),
@@ -713,9 +1071,8 @@
fun testSettingKeyGestures_doNothing() {
Mockito.`when`(resources.getInteger(R.integer.config_settingsKeyBehavior))
.thenReturn(SETTINGS_KEY_BEHAVIOR_NOTHING)
- val keyGestureController = KeyGestureController(context, testLooper.looper)
+ setupKeyGestureController()
testKeyGestureNotProduced(
- keyGestureController,
"SETTINGS -> Do Nothing",
intArrayOf(KeyEvent.KEYCODE_SETTINGS),
)
@@ -725,9 +1082,8 @@
fun testSettingKeyGestures_settingsActivity() {
Mockito.`when`(resources.getInteger(R.integer.config_settingsKeyBehavior))
.thenReturn(SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY)
- val keyGestureController = KeyGestureController(context, testLooper.looper)
+ setupKeyGestureController()
testKeyGestureInternal(
- keyGestureController,
TestData(
"SETTINGS -> Launch Settings Activity",
intArrayOf(KeyEvent.KEYCODE_SETTINGS),
@@ -743,9 +1099,8 @@
fun testSettingKeyGestures_notificationPanel() {
Mockito.`when`(resources.getInteger(R.integer.config_settingsKeyBehavior))
.thenReturn(SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL)
- val keyGestureController = KeyGestureController(context, testLooper.looper)
+ setupKeyGestureController()
testKeyGestureInternal(
- keyGestureController,
TestData(
"SETTINGS -> Toggle Notification Panel",
intArrayOf(KeyEvent.KEYCODE_SETTINGS),
@@ -758,221 +1113,12 @@
}
@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
- @EnableFlags(
- com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL,
- com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG,
- com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG,
- com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
- com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS
- )
- fun testKeyboardAccessibilityToggleShortcutPress() {
- val keyGestureController = KeyGestureController(context, testLooper.looper)
- testKeyGestureInternal(
- keyGestureController,
- TestData(
- "Meta + Alt + 3 -> Toggle Bounce Keys",
- intArrayOf(
- KeyEvent.KEYCODE_META_LEFT,
- KeyEvent.KEYCODE_ALT_LEFT,
- KeyEvent.KEYCODE_3
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS,
- intArrayOf(KeyEvent.KEYCODE_3),
- KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)))
- testKeyGestureInternal(
- keyGestureController,
- TestData(
- "Meta + Alt + 4 -> Toggle Mouse Keys",
- intArrayOf(
- KeyEvent.KEYCODE_META_LEFT,
- KeyEvent.KEYCODE_ALT_LEFT,
- KeyEvent.KEYCODE_4
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS,
- intArrayOf(KeyEvent.KEYCODE_4),
- KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)))
- testKeyGestureInternal(
- keyGestureController,
- TestData(
- "Meta + Alt + 5 -> Toggle Sticky Keys",
- intArrayOf(
- KeyEvent.KEYCODE_META_LEFT,
- KeyEvent.KEYCODE_ALT_LEFT,
- KeyEvent.KEYCODE_5
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS,
- intArrayOf(KeyEvent.KEYCODE_5),
- KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)))
- testKeyGestureInternal(
- keyGestureController,
- TestData(
- "Meta + Alt + 6 -> Toggle Slow Keys",
- intArrayOf(
- KeyEvent.KEYCODE_META_LEFT,
- KeyEvent.KEYCODE_ALT_LEFT,
- KeyEvent.KEYCODE_6
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS,
- intArrayOf(KeyEvent.KEYCODE_6),
- KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)))
- }
-
- @Test
- @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT)
- fun testMoveToNextDisplay() {
- val keyGestureController = KeyGestureController(context, testLooper.looper)
- testKeyGestureInternal(
- keyGestureController,
- TestData(
- "META + CTRL + D -> Move a task to next display",
- intArrayOf(
- KeyEvent.KEYCODE_META_LEFT,
- KeyEvent.KEYCODE_CTRL_LEFT,
- KeyEvent.KEYCODE_D
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY,
- intArrayOf(KeyEvent.KEYCODE_D),
- KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- )
- )
- }
-
- @Test
- @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS)
- fun testSnapLeftFreeformTask() {
- val keyGestureController = KeyGestureController(context, testLooper.looper)
- testKeyGestureInternal(
- keyGestureController,
- TestData(
- "ALT + [ -> Resizes a task to fit the left half of the screen",
- intArrayOf(
- KeyEvent.KEYCODE_ALT_LEFT,
- KeyEvent.KEYCODE_LEFT_BRACKET
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW,
- intArrayOf(KeyEvent.KEYCODE_LEFT_BRACKET),
- KeyEvent.META_ALT_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- )
- )
- }
-
- @Test
- @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS)
- fun testSnapRightFreeformTask() {
- val keyGestureController = KeyGestureController(context, testLooper.looper)
- testKeyGestureInternal(
- keyGestureController,
- TestData(
- "ALT + ] -> Resizes a task to fit the right half of the screen",
- intArrayOf(
- KeyEvent.KEYCODE_ALT_LEFT,
- KeyEvent.KEYCODE_RIGHT_BRACKET
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW,
- intArrayOf(KeyEvent.KEYCODE_RIGHT_BRACKET),
- KeyEvent.META_ALT_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- )
- )
- }
-
- @Test
- @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS)
- fun testMaximizeFreeformTask() {
- val keyGestureController = KeyGestureController(context, testLooper.looper)
- testKeyGestureInternal(
- keyGestureController,
- TestData(
- "ALT + '=' -> Maximizes a task to fit the screen",
- intArrayOf(
- KeyEvent.KEYCODE_ALT_LEFT,
- KeyEvent.KEYCODE_EQUALS
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW,
- intArrayOf(KeyEvent.KEYCODE_EQUALS),
- KeyEvent.META_ALT_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- )
- )
- }
-
- @Test
- @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS)
- fun testRestoreFreeformTask() {
- val keyGestureController = KeyGestureController(context, testLooper.looper)
- testKeyGestureInternal(
- keyGestureController,
- TestData(
- "ALT + '-' -> Restores a task size to its previous bounds",
- intArrayOf(
- KeyEvent.KEYCODE_ALT_LEFT,
- KeyEvent.KEYCODE_MINUS
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE,
- intArrayOf(KeyEvent.KEYCODE_MINUS),
- KeyEvent.META_ALT_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- )
- )
- }
-
- @Test
fun testCapsLockPressNotified() {
- val keyGestureController = KeyGestureController(context, testLooper.looper)
+ setupKeyGestureController()
val listener = KeyGestureEventListener()
keyGestureController.registerKeyGestureEventListener(listener, 0)
- sendKeys(keyGestureController, intArrayOf(KeyEvent.KEYCODE_CAPS_LOCK))
+ sendKeys(intArrayOf(KeyEvent.KEYCODE_CAPS_LOCK))
testLooper.dispatchAll()
assertEquals(
"Listener should get callbacks on key gesture event completed",
@@ -987,7 +1133,7 @@
}
@Keep
- private fun keyGestureEventHandlerTestArguments_forKeyCombinations(): Array<TestData> {
+ private fun systemGesturesTestArguments_forKeyCombinations(): Array<TestData> {
return arrayOf(
TestData(
"VOLUME_DOWN + POWER -> Screenshot Chord",
@@ -1048,14 +1194,14 @@
}
@Test
- @Parameters(method = "keyGestureEventHandlerTestArguments_forKeyCombinations")
+ @Parameters(method = "systemGesturesTestArguments_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)
+ setupKeyGestureController()
+ testKeyGestureInternal(test)
}
@Keep
@@ -1096,7 +1242,7 @@
@Test
@Parameters(method = "customInputGesturesTestArguments")
fun testCustomKeyGestures(test: TestData) {
- val keyGestureController = KeyGestureController(context, testLooper.looper)
+ setupKeyGestureController()
val builder = InputGestureData.Builder()
.setKeyGestureType(test.expectedKeyGestureType)
.setTrigger(
@@ -1104,17 +1250,17 @@
test.expectedKeys[0],
test.expectedModifierState
)
- );
+ )
if (test.expectedAppLaunchData != null) {
builder.setAppLaunchData(test.expectedAppLaunchData)
}
- val inputGestureData = builder.build();
+ val inputGestureData = builder.build()
keyGestureController.addCustomInputGesture(0, inputGestureData.aidlData)
- testKeyGestureInternal(keyGestureController, test)
+ testKeyGestureInternal(test)
}
- private fun testKeyGestureInternal(keyGestureController: KeyGestureController, test: TestData) {
+ private fun testKeyGestureInternal(test: TestData) {
var handleEvents = mutableListOf<KeyGestureEvent>()
val handler = KeyGestureHandler { event, _ ->
handleEvents.add(KeyGestureEvent(event))
@@ -1123,7 +1269,7 @@
keyGestureController.registerKeyGestureHandler(handler, 0)
handleEvents.clear()
- sendKeys(keyGestureController, test.keys)
+ sendKeys(test.keys)
assertEquals(
"Test: $test doesn't produce correct number of key gesture events",
@@ -1162,11 +1308,7 @@
keyGestureController.unregisterKeyGestureHandler(handler, 0)
}
- private fun testKeyGestureNotProduced(
- keyGestureController: KeyGestureController,
- testName: String,
- testKeys: IntArray
- ) {
+ private fun testKeyGestureNotProduced(testName: String, testKeys: IntArray) {
var handleEvents = mutableListOf<KeyGestureEvent>()
val handler = KeyGestureHandler { event, _ ->
handleEvents.add(KeyGestureEvent(event))
@@ -1175,15 +1317,11 @@
keyGestureController.registerKeyGestureHandler(handler, 0)
handleEvents.clear()
- sendKeys(keyGestureController, testKeys)
+ sendKeys(testKeys)
assertEquals("Test: $testName should not produce Key gesture", 0, handleEvents.size)
}
- private fun sendKeys(
- keyGestureController: KeyGestureController,
- testKeys: IntArray,
- assertNotSentToApps: Boolean = false
- ) {
+ private fun sendKeys(testKeys: IntArray, assertNotSentToApps: Boolean = false) {
var metaState = 0
val now = SystemClock.uptimeMillis()
for (key in testKeys) {
@@ -1192,7 +1330,7 @@
DEVICE_ID, 0 /*scancode*/, 0 /*flags*/,
InputDevice.SOURCE_KEYBOARD
)
- interceptKey(keyGestureController, downEvent, assertNotSentToApps)
+ interceptKey(downEvent, assertNotSentToApps)
metaState = metaState or MODIFIER.getOrDefault(key, 0)
downEvent.recycle()
@@ -1205,7 +1343,7 @@
DEVICE_ID, 0 /*scancode*/, 0 /*flags*/,
InputDevice.SOURCE_KEYBOARD
)
- interceptKey(keyGestureController, upEvent, assertNotSentToApps)
+ interceptKey(upEvent, assertNotSentToApps)
metaState = metaState and MODIFIER.getOrDefault(key, 0).inv()
upEvent.recycle()
@@ -1213,11 +1351,7 @@
}
}
- private fun interceptKey(
- keyGestureController: KeyGestureController,
- event: KeyEvent,
- assertNotSentToApps: Boolean
- ) {
+ private fun interceptKey(event: KeyEvent, assertNotSentToApps: Boolean) {
keyGestureController.interceptKeyBeforeQueueing(event, FLAG_INTERACTIVE)
testLooper.dispatchAll()
diff --git a/tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt b/tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt
index b27b826..ded4679 100644
--- a/tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt
+++ b/tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt
@@ -17,7 +17,13 @@
package android.animation
import android.animation.AnimatorTestRuleToolkit.Companion.TAG
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
import android.util.Log
+import android.view.View
+import androidx.core.graphics.drawable.toBitmap
+import androidx.test.core.app.ActivityScenario
+import java.util.concurrent.TimeUnit
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
@@ -36,13 +42,45 @@
import platform.test.motion.golden.TimeSeries
import platform.test.motion.golden.TimeSeriesCaptureScope
import platform.test.motion.golden.TimestampFrameId
+import platform.test.screenshot.captureToBitmapAsync
-class AnimatorTestRuleToolkit(val animatorTestRule: AnimatorTestRule, val testScope: TestScope) {
+class AnimatorTestRuleToolkit(
+ internal val animatorTestRule: AnimatorTestRule,
+ internal val testScope: TestScope,
+ internal val currentActivityScenario: () -> ActivityScenario<*>,
+) {
internal companion object {
const val TAG = "AnimatorRuleToolkit"
}
}
+/** Capture utility to extract a [Bitmap] from a [drawable]. */
+fun captureDrawable(drawable: Drawable): Bitmap {
+ val width = drawable.bounds.right - drawable.bounds.left
+ val height = drawable.bounds.bottom - drawable.bounds.top
+
+ // If either dimension is 0 this will fail, so we set it to 1 pixel instead.
+ return drawable.toBitmap(
+ width =
+ if (width > 0) {
+ width
+ } else {
+ 1
+ },
+ height =
+ if (height > 0) {
+ height
+ } else {
+ 1
+ },
+ )
+}
+
+/** Capture utility to extract a [Bitmap] from a [view]. */
+fun captureView(view: View): Bitmap {
+ return view.captureToBitmapAsync().get(10, TimeUnit.SECONDS)
+}
+
/**
* Controls the timing of the motion recording.
*
@@ -71,24 +109,39 @@
/** Time interval between frame captures, in milliseconds. */
val frameDurationMs: Long = 16L,
- /** Produces the time-series, invoked on each animation frame. */
+ /** Whether a sequence of screenshots should also be recorded. */
+ val visualCapture: ((captureRoot: T) -> Bitmap)? = null,
+
+ /** Produces the time-series, invoked on each animation frame. */
val timeSeriesCapture: TimeSeriesCaptureScope<T>.() -> Unit,
)
/** Records the time-series of the features specified in [recordingSpec]. */
fun <T> MotionTestRule<AnimatorTestRuleToolkit>.recordMotion(
- recordingSpec: AnimatorRuleRecordingSpec<T>,
+ recordingSpec: AnimatorRuleRecordingSpec<T>
): RecordedMotion {
with(toolkit.animatorTestRule) {
+ val activityScenario = toolkit.currentActivityScenario()
val frameIdCollector = mutableListOf<FrameId>()
val propertyCollector = mutableMapOf<String, MutableList<DataPoint<*>>>()
+ val screenshotCollector =
+ if (recordingSpec.visualCapture != null) {
+ mutableListOf<Bitmap>()
+ } else {
+ null
+ }
fun recordFrame(frameId: FrameId) {
Log.i(TAG, "recordFrame($frameId)")
frameIdCollector.add(frameId)
- recordingSpec.timeSeriesCapture.invoke(
- TimeSeriesCaptureScope(recordingSpec.captureRoot, propertyCollector)
- )
+ activityScenario.onActivity {
+ recordingSpec.timeSeriesCapture.invoke(
+ TimeSeriesCaptureScope(recordingSpec.captureRoot, propertyCollector)
+ )
+ }
+
+ val bitmap = recordingSpec.visualCapture?.invoke(recordingSpec.captureRoot)
+ if (bitmap != null) screenshotCollector!!.add(bitmap)
}
val motionControl =
@@ -101,10 +154,13 @@
Log.i(TAG, "recordMotion() begin recording")
- val startFrameTime = currentTime
+ var startFrameTime: Long? = null
+ toolkit.currentActivityScenario().onActivity { startFrameTime = currentTime }
while (!motionControl.recordingEnded) {
- recordFrame(TimestampFrameId(currentTime - startFrameTime))
- motionControl.nextFrame()
+ var time: Long? = null
+ toolkit.currentActivityScenario().onActivity { time = currentTime }
+ recordFrame(TimestampFrameId(time!! - startFrameTime!!))
+ toolkit.currentActivityScenario().onActivity { motionControl.nextFrame() }
}
Log.i(TAG, "recordMotion() end recording")
@@ -115,7 +171,7 @@
propertyCollector.entries.map { entry -> Feature(entry.key, entry.value) },
)
- return create(timeSeries, null)
+ return create(timeSeries, screenshotCollector)
}
}
diff --git a/tests/testables/tests/Android.bp b/tests/testables/tests/Android.bp
index 7110564..f0cda53 100644
--- a/tests/testables/tests/Android.bp
+++ b/tests/testables/tests/Android.bp
@@ -37,10 +37,11 @@
"androidx.core_core-ktx",
"androidx.test.ext.junit",
"androidx.test.rules",
- "androidx.test.ext.junit",
"hamcrest-library",
"kotlinx_coroutines_test",
"mockito-target-inline-minus-junit4",
+ "platform-screenshot-diff-core",
+ "platform-test-annotations",
"testables",
"truth",
],
@@ -55,6 +56,7 @@
"android.test.mock.stubs.system",
],
certificate: "platform",
+ test_config: "AndroidTest.xml",
test_suites: [
"device-tests",
"automotive-tests",
diff --git a/tests/testables/tests/AndroidManifest.xml b/tests/testables/tests/AndroidManifest.xml
index 2bfb04f..6cba598 100644
--- a/tests/testables/tests/AndroidManifest.xml
+++ b/tests/testables/tests/AndroidManifest.xml
@@ -23,6 +23,10 @@
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
+ <activity
+ android:name="platform.test.screenshot.ScreenshotActivity"
+ android:exported="true">
+ </activity>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/testables/tests/AndroidTest.xml b/tests/testables/tests/AndroidTest.xml
new file mode 100644
index 0000000..85f6e62
--- /dev/null
+++ b/tests/testables/tests/AndroidTest.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs Tests for Testables.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="TestablesTests.apk" />
+ <option name="install-arg" value="-t" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+ <option name="force-root" value="true" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <option name="screen-always-on" value="on" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+ <option name="run-command" value="wm dismiss-keyguard" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="framework-base-presubmit" />
+ <option name="test-tag" value="TestableTests" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.testables" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="test-filter-dir" value="/data/data/com.android.testables" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="directory-keys" value="/data/user/0/com.android.testables/files"/>
+ <option name="collect-on-run-ended-only" value="true"/>
+ <option name="clean-up" value="true"/>
+ </metrics_collector>
+</configuration>
diff --git a/tests/testables/tests/goldens/recordFilmstrip_withAnimator.png b/tests/testables/tests/goldens/recordFilmstrip_withAnimator.png
new file mode 100644
index 0000000..9aed2e9
--- /dev/null
+++ b/tests/testables/tests/goldens/recordFilmstrip_withAnimator.png
Binary files differ
diff --git a/tests/testables/tests/goldens/recordFilmstrip_withSpring.png b/tests/testables/tests/goldens/recordFilmstrip_withSpring.png
new file mode 100644
index 0000000..1d0c0c3
--- /dev/null
+++ b/tests/testables/tests/goldens/recordFilmstrip_withSpring.png
Binary files differ
diff --git a/tests/testables/tests/goldens/recordMotion_withAnimator.json b/tests/testables/tests/goldens/recordTimeSeries_withAnimator.json
similarity index 97%
rename from tests/testables/tests/goldens/recordMotion_withAnimator.json
rename to tests/testables/tests/goldens/recordTimeSeries_withAnimator.json
index 87fece5..73eb6c7 100644
--- a/tests/testables/tests/goldens/recordMotion_withAnimator.json
+++ b/tests/testables/tests/goldens/recordTimeSeries_withAnimator.json
@@ -29,7 +29,7 @@
],
"features": [
{
- "name": "value",
+ "name": "alpha",
"type": "float",
"data_points": [
1,
diff --git a/tests/testables/tests/goldens/recordMotion_withSpring.json b/tests/testables/tests/goldens/recordTimeSeries_withSpring.json
similarity index 96%
rename from tests/testables/tests/goldens/recordMotion_withSpring.json
rename to tests/testables/tests/goldens/recordTimeSeries_withSpring.json
index e9fb5b4..2b97bad 100644
--- a/tests/testables/tests/goldens/recordMotion_withSpring.json
+++ b/tests/testables/tests/goldens/recordTimeSeries_withSpring.json
@@ -21,7 +21,7 @@
],
"features": [
{
- "name": "value",
+ "name": "alpha",
"type": "float",
"data_points": [
1,
diff --git a/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt b/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt
index fbef489..993c3fe 100644
--- a/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt
+++ b/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt
@@ -16,10 +16,15 @@
package android.animation
-import android.util.FloatProperty
+import android.graphics.Color
+import android.platform.test.annotations.MotionTest
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.internal.dynamicanimation.animation.DynamicAnimation
import com.android.internal.dynamicanimation.animation.SpringAnimation
import com.android.internal.dynamicanimation.animation.SpringForce
import kotlinx.coroutines.test.TestScope
@@ -28,102 +33,169 @@
import org.junit.runner.RunWith
import platform.test.motion.MotionTestRule
import platform.test.motion.RecordedMotion
-import platform.test.motion.golden.FeatureCapture
-import platform.test.motion.golden.asDataPoint
import platform.test.motion.testing.createGoldenPathManager
+import platform.test.motion.view.ViewFeatureCaptures
+import platform.test.screenshot.DeviceEmulationRule
+import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.DisplaySpec
+import platform.test.screenshot.ScreenshotActivity
+import platform.test.screenshot.ScreenshotTestRule
@SmallTest
+@MotionTest
@RunWith(AndroidJUnit4::class)
class AnimatorTestRuleToolkitTest {
companion object {
private val GOLDEN_PATH_MANAGER =
createGoldenPathManager("frameworks/base/tests/testables/tests/goldens")
- private val TEST_PROPERTY =
- object : FloatProperty<TestState>("value") {
- override fun get(state: TestState): Float {
- return state.animatedValue
- }
-
- override fun setValue(state: TestState, value: Float) {
- state.animatedValue = value
- }
- }
+ private val EMULATION_SPEC =
+ DeviceEmulationSpec(DisplaySpec("phone", width = 320, height = 690, densityDpi = 160))
}
- @get:Rule(order = 0) val animatorTestRule = AnimatorTestRule(this)
- @get:Rule(order = 1)
+ @get:Rule(order = 0) val deviceEmulationRule = DeviceEmulationRule(EMULATION_SPEC)
+ @get:Rule(order = 1) val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java)
+ @get:Rule(order = 2) val animatorTestRule = AnimatorTestRule(this)
+ @get:Rule(order = 3) val screenshotRule = ScreenshotTestRule(GOLDEN_PATH_MANAGER)
+ @get:Rule(order = 4)
val motionRule =
- MotionTestRule(AnimatorTestRuleToolkit(animatorTestRule, TestScope()), GOLDEN_PATH_MANAGER)
+ MotionTestRule(
+ AnimatorTestRuleToolkit(animatorTestRule, TestScope()) { activityRule.scenario },
+ GOLDEN_PATH_MANAGER,
+ bitmapDiffer = screenshotRule,
+ )
@Test
- fun recordMotion_withAnimator() {
- val state = TestState()
- AnimatorSet().apply {
- duration = 500
- play(
- ValueAnimator.ofFloat(state.animatedValue, 0f).apply {
- addUpdateListener { state.animatedValue = it.animatedValue as Float }
- }
+ fun recordFilmstrip_withAnimator() {
+ val animatedBox = createScene()
+ createAnimator(animatedBox).apply { getInstrumentation().runOnMainSync { start() } }
+
+ val recordedMotion =
+ record(
+ animatedBox,
+ MotionControl { awaitFrames(count = 26) },
+ sampleIntervalMs = 20L,
+ recordScreenshots = true,
)
+
+ motionRule.assertThat(recordedMotion).filmstripMatchesGolden("recordFilmstrip_withAnimator")
+ }
+
+ @Test
+ fun recordTimeSeries_withAnimator() {
+ val animatedBox = createScene()
+ createAnimator(animatedBox).apply { getInstrumentation().runOnMainSync { start() } }
+
+ val recordedMotion =
+ record(
+ animatedBox,
+ MotionControl { awaitFrames(count = 26) },
+ sampleIntervalMs = 20L,
+ recordScreenshots = false,
+ )
+
+ motionRule
+ .assertThat(recordedMotion)
+ .timeSeriesMatchesGolden("recordTimeSeries_withAnimator")
+ }
+
+ @Test
+ fun recordFilmstrip_withSpring() {
+ val animatedBox = createScene()
+ var isDone = false
+ createSpring(animatedBox).apply {
+ addEndListener { _, _, _, _ -> isDone = true }
getInstrumentation().runOnMainSync { start() }
}
val recordedMotion =
- record(state, MotionControl { awaitFrames(count = 26) }, sampleIntervalMs = 20L)
+ record(
+ animatedBox,
+ MotionControl { awaitCondition { isDone } },
+ sampleIntervalMs = 16L,
+ recordScreenshots = true,
+ )
- motionRule.assertThat(recordedMotion).timeSeriesMatchesGolden("recordMotion_withAnimator")
+ motionRule.assertThat(recordedMotion).filmstripMatchesGolden("recordFilmstrip_withSpring")
}
@Test
- fun recordMotion_withSpring() {
- val state = TestState()
+ fun recordTimeSeries_withSpring() {
+ val animatedBox = createScene()
var isDone = false
- SpringAnimation(state, TEST_PROPERTY).apply {
+ createSpring(animatedBox).apply {
+ addEndListener { _, _, _, _ -> isDone = true }
+ getInstrumentation().runOnMainSync { start() }
+ }
+
+ val recordedMotion =
+ record(
+ animatedBox,
+ MotionControl { awaitCondition { isDone } },
+ sampleIntervalMs = 16L,
+ recordScreenshots = false,
+ )
+
+ motionRule.assertThat(recordedMotion).timeSeriesMatchesGolden("recordTimeSeries_withSpring")
+ }
+
+ private fun createScene(): ViewGroup {
+ lateinit var sceneRoot: ViewGroup
+ activityRule.scenario.onActivity { activity ->
+ sceneRoot = FrameLayout(activity).apply { setBackgroundColor(Color.BLACK) }
+ activity.setContentView(sceneRoot)
+ }
+ getInstrumentation().waitForIdleSync()
+ return sceneRoot
+ }
+
+ private fun createAnimator(animatedBox: ViewGroup): AnimatorSet {
+ return AnimatorSet().apply {
+ duration = 500
+ play(
+ ValueAnimator.ofFloat(animatedBox.alpha, 0f).apply {
+ addUpdateListener { animatedBox.alpha = it.animatedValue as Float }
+ }
+ )
+ }
+ }
+
+ private fun createSpring(animatedBox: ViewGroup): SpringAnimation {
+ return SpringAnimation(animatedBox, DynamicAnimation.ALPHA).apply {
spring =
SpringForce(0f).apply {
stiffness = 500f
dampingRatio = 0.95f
}
- setStartValue(1f)
+ setStartValue(animatedBox.alpha)
setMinValue(0f)
setMaxValue(1f)
minimumVisibleChange = 0.01f
-
- addEndListener { _, _, _, _ -> isDone = true }
- getInstrumentation().runOnMainSync { start() }
}
-
- val recordedMotion =
- record(state, MotionControl { awaitCondition { isDone } }, sampleIntervalMs = 16L)
-
- motionRule.assertThat(recordedMotion).timeSeriesMatchesGolden("recordMotion_withSpring")
}
private fun record(
- state: TestState,
+ container: ViewGroup,
motionControl: MotionControl,
sampleIntervalMs: Long,
+ recordScreenshots: Boolean,
): RecordedMotion {
- var recordedMotion: RecordedMotion? = null
- getInstrumentation().runOnMainSync {
- recordedMotion =
- motionRule.recordMotion(
- AnimatorRuleRecordingSpec(
- state,
- motionControl,
- sampleIntervalMs,
- ) {
- feature(
- FeatureCapture("value") { state -> state.animatedValue.asDataPoint() },
- "value",
- )
- }
- )
- }
- return recordedMotion!!
+ val visualCapture =
+ if (recordScreenshots) {
+ ::captureView
+ } else {
+ null
+ }
+ return motionRule.recordMotion(
+ AnimatorRuleRecordingSpec(
+ container,
+ motionControl,
+ sampleIntervalMs,
+ visualCapture,
+ ) {
+ feature(ViewFeatureCaptures.alpha, "alpha")
+ }
+ )
}
-
- data class TestState(var animatedValue: Float = 1f)
}