Merge "Fix bug with home panel dream" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 98b62b3..e6e835b 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -19,6 +19,7 @@
":android.app.flags-aconfig-java{.generated_srcjars}",
":android.app.smartspace.flags-aconfig-java{.generated_srcjars}",
":android.app.usage.flags-aconfig-java{.generated_srcjars}",
+ ":android.app.wearable.flags-aconfig-java{.generated_srcjars}",
":android.appwidget.flags-aconfig-java{.generated_srcjars}",
":android.chre.flags-aconfig-java{.generated_srcjars}",
":android.companion.flags-aconfig-java{.generated_srcjars}",
@@ -36,7 +37,9 @@
":android.location.flags-aconfig-java{.generated_srcjars}",
":android.media.tv.flags-aconfig-java{.generated_srcjars}",
":android.multiuser.flags-aconfig-java{.generated_srcjars}",
+ ":android.net.platform.flags-aconfig-java{.generated_srcjars}",
":android.net.vcn.flags-aconfig-java{.generated_srcjars}",
+ ":android.net.wifi.flags-aconfig-java{.generated_srcjars}",
":android.nfc.flags-aconfig-java{.generated_srcjars}",
":android.os.flags-aconfig-java{.generated_srcjars}",
":android.os.vibrator.flags-aconfig-java{.generated_srcjars}",
@@ -60,13 +63,13 @@
":android.webkit.flags-aconfig-java{.generated_srcjars}",
":android.widget.flags-aconfig-java{.generated_srcjars}",
":audio-framework-aconfig",
+ ":backup_flags_lib{.generated_srcjars}",
":camera_platform_flags_core_java_lib{.generated_srcjars}",
":com.android.hardware.input-aconfig-java{.generated_srcjars}",
":com.android.input.flags-aconfig-java{.generated_srcjars}",
":com.android.internal.foldables.flags-aconfig-java{.generated_srcjars}",
":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}",
":com.android.media.flags.editing-aconfig-java{.generated_srcjars}",
- ":com.android.net.flags-aconfig-java{.generated_srcjars}",
":com.android.net.thread.flags-aconfig-java{.generated_srcjars}",
":com.android.server.flags.services-aconfig-java{.generated_srcjars}",
":com.android.text.flags-aconfig-java{.generated_srcjars}",
@@ -108,7 +111,9 @@
"android.media.midi-aconfig",
"android.media.tv.flags-aconfig",
"android.multiuser.flags-aconfig",
+ "android.net.platform.flags-aconfig",
"android.net.vcn.flags-aconfig",
+ "android.net.wifi.flags-aconfig",
"android.nfc.flags-aconfig",
"android.os.flags-aconfig",
"android.os.vibrator.flags-aconfig",
@@ -136,7 +141,6 @@
"com.android.hardware.input.input-aconfig",
"com.android.input.flags-aconfig",
"com.android.media.flags.bettertogether-aconfig",
- "com.android.net.flags-aconfig",
"com.android.net.thread.flags-aconfig",
"com.android.server.flags.services-aconfig",
"com.android.text.flags-aconfig",
@@ -775,9 +779,10 @@
// Networking
aconfig_declarations {
- name: "com.android.net.flags-aconfig",
- package: "com.android.net.flags",
+ name: "android.net.platform.flags-aconfig",
+ package: "android.net.platform.flags",
srcs: ["core/java/android/net/flags.aconfig"],
+ visibility: [":__subpackages__"],
}
// Thread network
@@ -788,9 +793,10 @@
}
java_aconfig_library {
- name: "com.android.net.flags-aconfig-java",
- aconfig_declarations: "com.android.net.flags-aconfig",
+ name: "android.net.platform.flags-aconfig-java",
+ aconfig_declarations: "android.net.platform.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
+ visibility: [":__subpackages__"],
}
java_aconfig_library {
@@ -1092,4 +1098,42 @@
name: "android.crashrecovery.flags-aconfig-java",
aconfig_declarations: "android.crashrecovery.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
-}
\ No newline at end of file
+}
+
+// Backup
+java_aconfig_library {
+ name: "backup_flags_lib",
+ aconfig_declarations: "backup_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+// Wifi
+aconfig_declarations {
+ name: "android.net.wifi.flags-aconfig",
+ package: "android.net.wifi.flags",
+ srcs: ["wifi/*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.net.wifi.flags-aconfig-java",
+ aconfig_declarations: "android.net.wifi.flags-aconfig",
+ min_sdk_version: "30",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.wifi",
+ ],
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+// Wearable Sensing
+aconfig_declarations {
+ name: "android.app.wearable.flags-aconfig",
+ package: "android.app.wearable",
+ srcs: ["core/java/android/app/wearable/*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.app.wearable.flags-aconfig-java",
+ aconfig_declarations: "android.app.wearable.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/OWNERS b/OWNERS
index 733157f..935b768 100644
--- a/OWNERS
+++ b/OWNERS
@@ -39,3 +39,5 @@
per-file *Ravenwood* = file:ravenwood/OWNERS
per-file PERFORMANCE_OWNERS = file:/PERFORMANCE_OWNERS
+
+per-file PACKAGE_MANAGER_OWNERS = file:/PACKAGE_MANAGER_OWNERS
\ No newline at end of file
diff --git a/PACKAGE_MANAGER_OWNERS b/PACKAGE_MANAGER_OWNERS
index e4549b4..eb5842b 100644
--- a/PACKAGE_MANAGER_OWNERS
+++ b/PACKAGE_MANAGER_OWNERS
@@ -1,3 +1,3 @@
-chiuwinson@google.com
+alexbuy@google.com
patb@google.com
schfan@google.com
\ No newline at end of file
diff --git a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
index e08200b..33f6899 100644
--- a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
+++ b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
@@ -743,7 +743,8 @@
private final class AppOpsWatcher extends IAppOpsCallback.Stub {
@Override
- public void opChanged(int op, int uid, String packageName) throws RemoteException {
+ public void opChanged(int op, int uid, String packageName,
+ String persistentDeviceId) throws RemoteException {
boolean restricted = false;
try {
restricted = mAppOpsService.checkOperation(TARGET_OP,
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index abf8008..39de0af 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -2026,8 +2026,8 @@
iAppOpsService.startWatchingMode(AppOpsManager.OP_SCHEDULE_EXACT_ALARM, null,
new IAppOpsCallback.Stub() {
@Override
- public void opChanged(int op, int uid, String packageName)
- throws RemoteException {
+ public void opChanged(int op, int uid, String packageName,
+ String persistentDeviceId) throws RemoteException {
final int userId = UserHandle.getUserId(uid);
if (op != AppOpsManager.OP_SCHEDULE_EXACT_ALARM
|| !isExactAlarmChangeEnabled(packageName, userId)) {
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index 357e1396..6635484 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -227,7 +227,7 @@
private final IAppOpsCallback mApbListener = new IAppOpsCallback.Stub() {
@Override
- public void opChanged(int op, int uid, String packageName) {
+ public void opChanged(int op, int uid, String packageName, String persistentDeviceId) {
boolean restricted = false;
try {
restricted = mAppOpsService.checkOperation(
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index e589c21..19bc716 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -671,7 +671,8 @@
/*packageName=*/ null,
new IAppOpsCallback.Stub() {
@Override
- public void opChanged(int op, int uid, String packageName) {
+ public void opChanged(int op, int uid, String packageName,
+ String persistentDeviceId) {
final int userId = UserHandle.getUserId(uid);
synchronized (mSystemExemptionAppOpMode) {
mSystemExemptionAppOpMode.delete(uid);
diff --git a/core/api/current.txt b/core/api/current.txt
index fa90651..b17e3343 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -13,6 +13,7 @@
field public static final String ACCESS_CHECKIN_PROPERTIES = "android.permission.ACCESS_CHECKIN_PROPERTIES";
field public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
field public static final String ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION";
+ field @FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles") public static final String ACCESS_HIDDEN_PROFILES = "android.permission.ACCESS_HIDDEN_PROFILES";
field public static final String ACCESS_LOCATION_EXTRA_COMMANDS = "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS";
field public static final String ACCESS_MEDIA_LOCATION = "android.permission.ACCESS_MEDIA_LOCATION";
field public static final String ACCESS_NETWORK_STATE = "android.permission.ACCESS_NETWORK_STATE";
@@ -89,6 +90,7 @@
field public static final String DELETE_PACKAGES = "android.permission.DELETE_PACKAGES";
field public static final String DELIVER_COMPANION_MESSAGES = "android.permission.DELIVER_COMPANION_MESSAGES";
field public static final String DETECT_SCREEN_CAPTURE = "android.permission.DETECT_SCREEN_CAPTURE";
+ field @FlaggedApi("com.android.window.flags.screen_recording_callbacks") public static final String DETECT_SCREEN_RECORDING = "android.permission.DETECT_SCREEN_RECORDING";
field public static final String DIAGNOSTIC = "android.permission.DIAGNOSTIC";
field public static final String DISABLE_KEYGUARD = "android.permission.DISABLE_KEYGUARD";
field public static final String DUMP = "android.permission.DUMP";
@@ -5095,10 +5097,12 @@
public static interface AppOpsManager.OnOpActiveChangedListener {
method public void onOpActiveChanged(@NonNull String, int, @NonNull String, boolean);
+ method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public default void onOpActiveChanged(@NonNull String, int, @NonNull String, @Nullable String, int, boolean, int, int);
}
public static interface AppOpsManager.OnOpChangedListener {
method public void onOpChanged(String, String);
+ method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public default void onOpChanged(@NonNull String, @NonNull String, int, @NonNull String);
}
public abstract static class AppOpsManager.OnOpNotedCallback {
@@ -6353,6 +6357,7 @@
field public static final String CATEGORY_STOPWATCH = "stopwatch";
field public static final String CATEGORY_SYSTEM = "sys";
field public static final String CATEGORY_TRANSPORT = "transport";
+ field @FlaggedApi("android.app.category_voicemail") public static final String CATEGORY_VOICEMAIL = "voicemail";
field public static final String CATEGORY_WORKOUT = "workout";
field @ColorInt public static final int COLOR_DEFAULT = 0; // 0x0
field @NonNull public static final android.os.Parcelable.Creator<android.app.Notification> CREATOR;
@@ -15571,6 +15576,7 @@
method public boolean clipRect(float, float, float, float);
method public boolean clipRect(int, int, int, int);
method public void concat(@Nullable android.graphics.Matrix);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void concat44(@Nullable android.graphics.Matrix44);
method public void disableZ();
method public void drawARGB(int, int, int, int);
method public void drawArc(@NonNull android.graphics.RectF, float, float, boolean, @NonNull android.graphics.Paint);
@@ -16219,6 +16225,24 @@
enum_constant public static final android.graphics.Matrix.ScaleToFit START;
}
+ @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public class Matrix44 {
+ ctor @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public Matrix44();
+ ctor @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public Matrix44(@NonNull android.graphics.Matrix);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 concat(@NonNull android.graphics.Matrix44);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public float get(int, int);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void getValues(@NonNull float[]);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public boolean invert();
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public boolean isIdentity();
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public float[] map(float, float, float, float);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void map(float, float, float, float, @NonNull float[]);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void reset();
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 rotate(float, float, float, float);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 scale(float, float, float);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void set(int, int, float);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void setValues(@NonNull float[]);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 translate(float, float, float);
+ }
+
public class Mesh {
ctor public Mesh(@NonNull android.graphics.MeshSpecification, int, @NonNull java.nio.Buffer, int, @NonNull android.graphics.RectF);
ctor public Mesh(@NonNull android.graphics.MeshSpecification, int, @NonNull java.nio.Buffer, int, @NonNull java.nio.ShortBuffer, @NonNull android.graphics.RectF);
@@ -21172,6 +21196,7 @@
method public int getStreamMinVolume(int);
method public int getStreamVolume(int);
method public float getStreamVolumeDb(int, int, int);
+ method @FlaggedApi("android.media.audio.supported_device_types_api") @NonNull public java.util.Set<java.lang.Integer> getSupportedDeviceTypes(int);
method @NonNull public java.util.List<android.media.AudioMixerAttributes> getSupportedMixerAttributes(@NonNull android.media.AudioDeviceInfo);
method @Deprecated public int getVibrateSetting(int);
method public int getVolumeGroupIdForAttributes(@NonNull android.media.AudioAttributes);
@@ -25790,6 +25815,7 @@
field public static final int FINAL_STATE_CANCELED = 2; // 0x2
field public static final int FINAL_STATE_ERROR = 3; // 0x3
field public static final int FINAL_STATE_SUCCEEDED = 1; // 0x1
+ field public static final int TIME_SINCE_CREATED_UNKNOWN = -1; // 0xffffffff
}
@FlaggedApi("com.android.media.editing.flags.add_media_metrics_editing") public static final class EditingEndedEvent.Builder {
@@ -25797,7 +25823,7 @@
method @NonNull public android.media.metrics.EditingEndedEvent build();
method @NonNull public android.media.metrics.EditingEndedEvent.Builder setErrorCode(int);
method @NonNull public android.media.metrics.EditingEndedEvent.Builder setMetricsBundle(@NonNull android.os.Bundle);
- method @NonNull public android.media.metrics.EditingEndedEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=0xffffffff) long);
+ method @NonNull public android.media.metrics.EditingEndedEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=android.media.metrics.EditingEndedEvent.TIME_SINCE_CREATED_UNKNOWN) long);
}
public final class EditingSession implements java.lang.AutoCloseable {
@@ -44520,6 +44546,7 @@
method @Nullable public android.telephony.CellIdentity getCellIdentity();
method public int getDomain();
method @Nullable public String getRegisteredPlmn();
+ method @FlaggedApi("com.android.internal.telephony.flags.network_registration_info_reject_cause") public int getRejectCause();
method public int getTransportType();
method public boolean isNetworkRegistered();
method public boolean isNetworkRoaming();
@@ -50649,6 +50676,7 @@
field public static final int KEYCODE_DVR = 173; // 0xad
field public static final int KEYCODE_E = 33; // 0x21
field public static final int KEYCODE_EISU = 212; // 0xd4
+ field @FlaggedApi("com.android.hardware.input.emoji_and_screenshot_keycodes_available") public static final int KEYCODE_EMOJI_PICKER = 317; // 0x13d
field public static final int KEYCODE_ENDCALL = 6; // 0x6
field public static final int KEYCODE_ENTER = 66; // 0x42
field public static final int KEYCODE_ENVELOPE = 65; // 0x41
@@ -50781,6 +50809,7 @@
field public static final int KEYCODE_RIGHT_BRACKET = 72; // 0x48
field public static final int KEYCODE_RO = 217; // 0xd9
field public static final int KEYCODE_S = 47; // 0x2f
+ field @FlaggedApi("com.android.hardware.input.emoji_and_screenshot_keycodes_available") public static final int KEYCODE_SCREENSHOT = 318; // 0x13e
field public static final int KEYCODE_SCROLL_LOCK = 116; // 0x74
field public static final int KEYCODE_SEARCH = 84; // 0x54
field public static final int KEYCODE_SEMICOLON = 74; // 0x4a
@@ -53756,6 +53785,7 @@
method public default void addCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public default void addCrossWindowBlurEnabledListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method public default void addProposedRotationListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
+ method @FlaggedApi("com.android.window.flags.screen_recording_callbacks") @RequiresPermission(android.Manifest.permission.DETECT_SCREEN_RECORDING) public default int addScreenRecordingCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @NonNull public default android.view.WindowMetrics getCurrentWindowMetrics();
method @Deprecated public android.view.Display getDefaultDisplay();
method @NonNull public default android.view.WindowMetrics getMaximumWindowMetrics();
@@ -53765,6 +53795,7 @@
method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void registerUnbatchedSurfaceControlInputReceiver(int, @NonNull android.os.IBinder, @NonNull android.view.SurfaceControl, @NonNull android.os.Looper, @NonNull android.view.SurfaceControlInputReceiver);
method public default void removeCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public default void removeProposedRotationListener(@NonNull java.util.function.IntConsumer);
+ method @FlaggedApi("com.android.window.flags.screen_recording_callbacks") @RequiresPermission(android.Manifest.permission.DETECT_SCREEN_RECORDING) public default void removeScreenRecordingCallback(@NonNull java.util.function.Consumer<java.lang.Integer>);
method public void removeViewImmediate(android.view.View);
method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void unregisterSurfaceControlInputReceiver(@NonNull android.view.SurfaceControl);
method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void unregisterTrustedPresentationListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
@@ -53784,6 +53815,8 @@
field public static final String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
field public static final String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION = "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
field @FlaggedApi("com.android.window.flags.supports_multi_instance_system_ui") public static final String PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI = "android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI";
+ field @FlaggedApi("com.android.window.flags.screen_recording_callbacks") public static final int SCREEN_RECORDING_STATE_NOT_VISIBLE = 0; // 0x0
+ field @FlaggedApi("com.android.window.flags.screen_recording_callbacks") public static final int SCREEN_RECORDING_STATE_VISIBLE = 1; // 0x1
}
public static class WindowManager.BadTokenException extends java.lang.RuntimeException {
@@ -59542,7 +59575,6 @@
}
@FlaggedApi("android.appwidget.flags.draw_data_parcel") public static final class RemoteViews.DrawInstructions {
- method @FlaggedApi("android.appwidget.flags.draw_data_parcel") public void appendInstructions(@NonNull byte[]);
}
@FlaggedApi("android.appwidget.flags.draw_data_parcel") public static final class RemoteViews.DrawInstructions.Builder {
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 24b9233..55ed1f5 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -66,6 +66,11 @@
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setExpansionDisabledForSimNetworkLock(boolean);
}
+ public final class SystemServiceRegistry {
+ method @FlaggedApi("android.webkit.update_service_ipc_wrapper") @Nullable public static Object getSystemServiceWithNoContext(@NonNull String);
+ method @FlaggedApi("android.webkit.update_service_ipc_wrapper") public static <TServiceClass> void registerForeverStaticService(@NonNull String, @NonNull Class<TServiceClass>, @NonNull android.app.SystemServiceRegistry.StaticServiceProducerWithBinder<TServiceClass>);
+ }
+
}
package android.app.admin {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index f101161..e6040f8 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -414,6 +414,7 @@
field public static final String WRITE_OBB = "android.permission.WRITE_OBB";
field public static final String WRITE_SECURITY_LOG = "android.permission.WRITE_SECURITY_LOG";
field public static final String WRITE_SMS = "android.permission.WRITE_SMS";
+ field @FlaggedApi("android.provider.user_keys") public static final String WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS = "android.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS";
}
public static final class Manifest.permission_group {
@@ -822,6 +823,7 @@
public static interface AppOpsManager.OnOpNotedListener {
method public void onOpNoted(@NonNull String, int, @NonNull String, @Nullable String, int, int);
+ method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public default void onOpNoted(@NonNull String, int, @NonNull String, @Nullable String, int, int, int);
}
public static final class AppOpsManager.OpEntry implements android.os.Parcelable {
@@ -3156,6 +3158,7 @@
field public static final int STATUS_SUCCESS = 1; // 0x1
field public static final int STATUS_UNKNOWN = 0; // 0x0
field public static final int STATUS_UNSUPPORTED = 2; // 0x2
+ field @FlaggedApi("android.app.wearable.enable_unsupported_operation_status_code") public static final int STATUS_UNSUPPORTED_OPERATION = 6; // 0x6
field public static final int STATUS_WEARABLE_UNAVAILABLE = 4; // 0x4
}
@@ -3208,8 +3211,14 @@
package android.companion.virtual {
+ public final class VirtualDevice implements android.os.Parcelable {
+ method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") public boolean hasCustomAudioInputSupport();
+ method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") public boolean hasCustomCameraSupport();
+ }
+
public final class VirtualDeviceManager {
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.VirtualDeviceManager.VirtualDevice createVirtualDevice(int, @NonNull android.companion.virtual.VirtualDeviceParams);
+ method @FlaggedApi("android.companion.virtual.flags.persistent_device_id_api") @NonNull public java.util.Set<java.lang.String> getAllPersistentDeviceIds();
method @FlaggedApi("android.companion.virtual.flags.persistent_device_id_api") @Nullable public CharSequence getDisplayNameForPersistentDeviceId(@NonNull String);
field public static final int LAUNCH_FAILURE_NO_ACTIVITY = 2; // 0x2
field public static final int LAUNCH_FAILURE_PENDING_INTENT_CANCELED = 1; // 0x1
@@ -9657,6 +9666,7 @@
public final class DeviceWiphyCapabilities implements android.os.Parcelable {
ctor public DeviceWiphyCapabilities();
method public int describeContents();
+ method @FlaggedApi("android.net.wifi.flags.get_device_cross_akm_roaming_support") public int getMaxNumberAkms();
method public int getMaxNumberRxSpatialStreams();
method public int getMaxNumberTxSpatialStreams();
method public boolean isChannelWidthSupported(int);
@@ -11295,6 +11305,12 @@
field public static final int ERROR_UNKNOWN = 0; // 0x0
}
+ @FlaggedApi("android.provider.user_keys") public class ContactKeysManager {
+ method @RequiresPermission(allOf={android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateContactKeyLocalVerificationState(@NonNull String, @NonNull String, @NonNull String, @NonNull String, int);
+ method @RequiresPermission(allOf={android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateContactKeyRemoteVerificationState(@NonNull String, @NonNull String, @NonNull String, @NonNull String, int);
+ method @RequiresPermission(allOf={android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateSelfKeyRemoteVerificationState(@NonNull String, @NonNull String, @NonNull String, int);
+ }
+
@Deprecated public static final class ContactsContract.MetadataSync implements android.provider.BaseColumns android.provider.ContactsContract.MetadataSyncColumns {
field @Deprecated public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_metadata";
field @Deprecated public static final String CONTENT_TYPE = "vnd.android.cursor.dir/contact_metadata";
@@ -14120,7 +14136,6 @@
method @Nullable public android.telephony.DataSpecificRegistrationInfo getDataSpecificInfo();
method public int getNetworkRegistrationState();
method @Deprecated public int getRegistrationState();
- method public int getRejectCause();
method public int getRoamingType();
method public boolean isEmergencyEnabled();
method public void writeToParcel(android.os.Parcel, int);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index c546d7a..77add41 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3616,7 +3616,7 @@
method public final int getDisplayId();
method public final void setDisplayId(int);
field public static final int FLAG_IS_ACCESSIBILITY_EVENT = 2048; // 0x800
- field public static final int LAST_KEYCODE = 316; // 0x13c
+ field public static final int LAST_KEYCODE = 318; // 0x13e
}
public final class KeyboardShortcutGroup implements android.os.Parcelable {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 5d2a26e..4c54b03 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -303,7 +303,7 @@
public static final boolean DEBUG_MEMORY_TRIM = false;
private static final boolean DEBUG_PROVIDER = false;
public static final boolean DEBUG_ORDER = false;
- private static final boolean DEBUG_APP_INFO = true;
+ private static final boolean DEBUG_APP_INFO = false;
private static final long MIN_TIME_BETWEEN_GCS = 5*1000;
/**
* The delay to release the provider when it has no more references. It reduces the number of
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index bb666e6..0dbce97 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -36,6 +36,7 @@
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.app.usage.UsageStatsManager;
+import android.companion.virtual.VirtualDeviceManager;
import android.compat.Compatibility;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
@@ -1549,7 +1550,7 @@
AppProtoEnums.APP_OP_READ_SYSTEM_GRAMMATICAL_GENDER;
/**
- * Allows an app whose primary use case is to backup or sync content to run longer jobs.
+ * Allows an app with a major use case of backing-up or syncing content to run longer jobs.
*
* @hide
*/
@@ -7346,6 +7347,12 @@
* The default impl is to fallback onto {@link #onOpChanged(String, String)
*
+ * <p> Implement this method and not
+ * {@link #onOpChanged(String, String, int, String)} if
+ * callbacks are
+ * required only on op state changes for the default device
+ * {@link VirtualDeviceManager#PERSISTENT_DEVICE_ID_DEFAULT}.
+ *
* @param op The Op that changed.
* @param packageName Package of the app whose Op changed.
* @param userId User Space of the app whose Op changed.
@@ -7354,6 +7361,31 @@
default void onOpChanged(@NonNull String op, @NonNull String packageName, int userId) {
onOpChanged(op, packageName);
}
+
+ /**
+ * Similar to {@link #onOpChanged(String, String, int)} but includes the device for which
+ * the op mode has changed.
+ *
+ * <p> Implement this method if callbacks are required on all devices.
+ * If not implemented explicitly, the default implementation will notify for op changes
+ * on the default device {@link VirtualDeviceManager#PERSISTENT_DEVICE_ID_DEFAULT} only.
+ *
+ * <p> If implemented, {@link #onOpChanged(String, String, int)}
+ * will not be called automatically.
+ *
+ * @param op The Op that changed.
+ * @param packageName Package of the app whose Op changed.
+ * @param userId User id of the app whose Op changed.
+ * @param persistentDeviceId persistent device id whose Op changed.
+ */
+ @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
+ default void onOpChanged(@NonNull String op, @NonNull String packageName, int userId,
+ @NonNull String persistentDeviceId) {
+ if (Objects.equals(persistentDeviceId,
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)) {
+ onOpChanged(op, packageName, userId);
+ }
+ }
}
/**
@@ -7373,6 +7405,12 @@
/**
* Called when the active state of an app-op changes.
*
+ * <p> Implement this method and not
+ * {@link #onOpActiveChanged(String, int, String, String, int, boolean, int, int)} if
+ * callbacks are
+ * required only on op state changes for the default device
+ * {@link Context#DEVICE_ID_DEFAULT}.
+ *
* @param op The operation that changed.
* @param uid The UID performing the operation.
* @param packageName The package performing the operation.
@@ -7388,6 +7426,37 @@
int attributionFlags, int attributionChainId) {
onOpActiveChanged(op, uid, packageName, active);
}
+
+ /**
+ * Similar to {@link #onOpActiveChanged(String, int, String, String, boolean, int, int)},
+ * but also includes the virtual device id of the op is now active or inactive.
+ *
+ * <p> Implement this method if callbacks are required on all devices.
+ * If not implemented explicitly, the default implementation will notify for op state
+ * changes on the default device {@link Context#DEVICE_ID_DEFAULT} only.
+ *
+ * <p> If implemented,
+ * {@link #onOpActiveChanged(String, int, String, String, boolean, int, int)}
+ * will not be called automatically.
+ *
+ * @param op The operation that changed.
+ * @param uid The UID performing the operation.
+ * @param packageName The package performing the operation.
+ * @param attributionTag The operation's attribution tag.
+ * @param virtualDeviceId the virtual device id whose operation has changed
+ * @param active Whether the operation became active or inactive.
+ * @param attributionFlags the attribution flags for this operation.
+ * @param attributionChainId the unique id of the attribution chain this op is a part of.
+ */
+ @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
+ default void onOpActiveChanged(@NonNull String op, int uid, @NonNull String packageName,
+ @Nullable String attributionTag, int virtualDeviceId, boolean active,
+ @AttributionFlags int attributionFlags, int attributionChainId) {
+ if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
+ onOpActiveChanged(op, uid, packageName, attributionTag, active, attributionFlags,
+ attributionChainId);
+ }
+ }
}
/**
@@ -7400,6 +7469,10 @@
/**
* Called when an app-op is noted.
*
+ * <p> Implement this method and not
+ * {@link #onOpNoted(String, int, String, String, int, int, int)} if callbacks are
+ * required only on op notes for the default device {@link Context#DEVICE_ID_DEFAULT}.
+ *
* @param op The operation that was noted.
* @param uid The UID performing the operation.
* @param packageName The package performing the operation.
@@ -7409,6 +7482,34 @@
*/
void onOpNoted(@NonNull String op, int uid, @NonNull String packageName,
@Nullable String attributionTag, @OpFlags int flags, @Mode int result);
+
+ /**
+ * Similar to {@link #onOpNoted(String, int, String, String, int, int, int)},
+ * but also includes the virtual device id of the op is now active or inactive.
+ *
+ * <p> Implement this method if callbacks are required for op notes on all devices.
+ * If not implemented explicitly, the default implementation will notify for the
+ * default device {@link Context#DEVICE_ID_DEFAULT} only.
+ *
+ * <p> If implemented, {@link #onOpNoted(String, int, String, String, int, int)}
+ * will not be called automatically.
+ *
+ * @param op The operation that was noted.
+ * @param uid The UID performing the operation.
+ * @param packageName The package performing the operation.
+ * @param attributionTag The attribution tag performing the operation.
+ * @param virtualDeviceId the device that noted the operation
+ * @param flags The flags of this op
+ * @param result The result of the note.
+ */
+ @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
+ default void onOpNoted(@NonNull String op, int uid, @NonNull String packageName,
+ @Nullable String attributionTag, int virtualDeviceId, @OpFlags int flags,
+ @Mode int result) {
+ if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
+ onOpNoted(op, uid, packageName, attributionTag, flags, result);
+ }
+ }
}
/**
@@ -7447,6 +7548,9 @@
public static class OnOpChangedInternalListener implements OnOpChangedListener {
public void onOpChanged(String op, String packageName) { }
public void onOpChanged(int op, String packageName) { }
+ public void onOpChanged(int op, String packageName, String persistentDeviceId) {
+ onOpChanged(op, packageName);
+ }
}
/**
@@ -7457,6 +7561,8 @@
public interface OnOpActiveChangedInternalListener extends OnOpActiveChangedListener {
default void onOpActiveChanged(String op, int uid, String packageName, boolean active) { }
default void onOpActiveChanged(int op, int uid, String packageName, boolean active) { }
+ default void onOpActiveChanged(int op, int uid, String packageName, int virtualDeviceId,
+ boolean active) { }
}
/**
@@ -7510,6 +7616,12 @@
/**
* Called when an op was started.
*
+ * <p> Implement this method and not
+ * {@link #onOpStarted(int, int, String, String, int, int, int, int, int, int)} if
+ * callbacks are
+ * required only on op starts for the default device
+ * {@link Context#DEVICE_ID_DEFAULT}.
+ *
* Note: This is only for op starts. It is not called when an op is noted or stopped.
* By default, unless this method is overridden, no code will be executed for resume
* events.
@@ -7530,6 +7642,37 @@
onOpStarted(op, uid, packageName, attributionTag, flags, result);
}
}
+
+ /**
+ * Similar to {@link #onOpStarted(int, int, String, String, int, int)},
+ * but also includes the virtual device id that started the op.
+ *
+ * <p> Implement this method if callbacks are required on all devices.
+ * If not implemented explicitly, the default implementation will notify for op starts on
+ * the default device {@link Context#DEVICE_ID_DEFAULT} only.
+ *
+ * <p> If implemented, {@link #onOpStarted(int, int, String, String, int, int)}
+ * will not be called automatically.
+ *
+ * @param op The op code.
+ * @param uid The UID performing the operation.
+ * @param packageName The package performing the operation.
+ * @param attributionTag The attribution tag performing the operation.
+ * @param virtualDeviceId the device that started the operation
+ * @param flags The flags of this op.
+ * @param result The result of the start.
+ * @param startType The start type of this start event. Either failed, resumed, or started.
+ * @param attributionFlags The location of this started op in an attribution chain.
+ */
+ default void onOpStarted(int op, int uid, @NonNull String packageName,
+ @Nullable String attributionTag, int virtualDeviceId, @OpFlags int flags,
+ @Mode int result, @StartedType int startType,
+ @AttributionFlags int attributionFlags, int attributionChainId) {
+ if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
+ onOpStarted(op, uid, packageName, attributionTag, flags, result, startType,
+ attributionFlags, attributionChainId);
+ }
+ }
}
AppOpsManager(Context context, IAppOpsService service) {
@@ -8054,14 +8197,16 @@
IAppOpsCallback cb = mModeWatchers.get(callback);
if (cb == null) {
cb = new IAppOpsCallback.Stub() {
- public void opChanged(int op, int uid, String packageName) {
+ public void opChanged(int op, int uid, String packageName,
+ String persistentDeviceId) {
if (callback instanceof OnOpChangedInternalListener) {
- ((OnOpChangedInternalListener)callback).onOpChanged(op, packageName);
+ ((OnOpChangedInternalListener)callback).onOpChanged(op, packageName,
+ persistentDeviceId);
}
if (sAppOpInfos[op].name != null) {
callback.onOpChanged(sAppOpInfos[op].name, packageName,
- UserHandle.getUserId(uid));
+ UserHandle.getUserId(uid), persistentDeviceId);
}
}
};
@@ -8142,16 +8287,17 @@
cb = new IAppOpsActiveCallback.Stub() {
@Override
public void opActiveChanged(int op, int uid, String packageName,
- String attributionTag, boolean active, @AttributionFlags
- int attributionFlags, int attributionChainId) {
+ String attributionTag, int virtualDeviceId, boolean active,
+ @AttributionFlags int attributionFlags, int attributionChainId) {
executor.execute(() -> {
if (callback instanceof OnOpActiveChangedInternalListener) {
((OnOpActiveChangedInternalListener) callback).onOpActiveChanged(op,
- uid, packageName, active);
+ uid, packageName, virtualDeviceId, active);
}
if (sAppOpInfos[op].name != null) {
callback.onOpActiveChanged(sAppOpInfos[op].name, uid, packageName,
- attributionTag, active, attributionFlags, attributionChainId);
+ attributionTag, virtualDeviceId, active, attributionFlags,
+ attributionChainId);
}
});
}
@@ -8220,10 +8366,10 @@
cb = new IAppOpsStartedCallback.Stub() {
@Override
public void opStarted(int op, int uid, String packageName, String attributionTag,
- int flags, int mode, int startType, int attributionFlags,
- int attributionChainId) {
- callback.onOpStarted(op, uid, packageName, attributionTag, flags, mode,
- startType, attributionFlags, attributionChainId);
+ int virtualDeviceId, int flags, int mode, int startType,
+ int attributionFlags, int attributionChainId) {
+ callback.onOpStarted(op, uid, packageName, attributionTag, virtualDeviceId,
+ flags, mode, startType, attributionFlags, attributionChainId);
}
};
mStartedWatchers.put(callback, cb);
@@ -8391,13 +8537,13 @@
cb = new IAppOpsNotedCallback.Stub() {
@Override
public void opNoted(int op, int uid, String packageName, String attributionTag,
- int flags, int mode) {
+ int virtualDeviceId, int flags, int mode) {
final long identity = Binder.clearCallingIdentity();
try {
executor.execute(() -> {
if (sAppOpInfos[op].name != null) {
listener.onOpNoted(sAppOpInfos[op].name, uid, packageName,
- attributionTag,
+ attributionTag, virtualDeviceId,
flags, mode);
}
});
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index d540748..e2e2f1d 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -225,7 +225,7 @@
boolean focused, boolean newSessionId);
boolean requestAutofillData(in IAssistDataReceiver receiver, in Bundle receiverExtras,
in IBinder activityToken, int flags);
- boolean isAssistDataAllowedOnCurrentActivity();
+ boolean isAssistDataAllowed();
boolean requestAssistDataForTask(in IAssistDataReceiver receiver, int taskId,
in String callingPackageName, String callingAttributionTag);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index a81ad3c..d705eeb 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1061,6 +1061,12 @@
public static final String CATEGORY_MISSED_CALL = "missed_call";
/**
+ * Notification category: voicemail.
+ */
+ @FlaggedApi(Flags.FLAG_CATEGORY_VOICEMAIL)
+ public static final String CATEGORY_VOICEMAIL = "voicemail";
+
+ /**
* One of the predefined notification categories (see the <code>CATEGORY_*</code> constants)
* that best describes this Notification. May be used by the system for ranking and filtering.
*/
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 3b5bba2..729f92a 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -111,5 +111,9 @@
per-file ConfigurationController.java = file:/services/core/java/com/android/server/wm/OWNERS
per-file *ScreenCapture* = file:/services/core/java/com/android/server/wm/OWNERS
+# Multitasking
+per-file multitasking.aconfig = file:/services/core/java/com/android/server/wm/OWNERS
+per-file multitasking.aconfig = file:/libs/WindowManager/Shell/OWNERS
+
# Zygote
per-file *Zygote* = file:/ZYGOTE_OWNERS
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 397b63f..ba9c895 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -19,6 +19,7 @@
import android.accounts.AccountManager;
import android.accounts.IAccountManager;
import android.adservices.AdServicesFrameworkInitializer;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -1617,6 +1618,19 @@
return new ContactKeysManager(ctx);
}});
+ // DO NOT do a flag check like this unless the flag is read-only.
+ // (because this code is executed during preload in zygote.)
+ // If the flag is mutable, the check should be inside CachedServiceFetcher.
+ if (Flags.bicClient()) {
+ registerService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE,
+ BackgroundInstallControlManager.class,
+ new CachedServiceFetcher<BackgroundInstallControlManager>() {
+ @Override
+ public BackgroundInstallControlManager createService(ContextImpl ctx) {
+ return new BackgroundInstallControlManager(ctx);
+ }
+ });
+ }
sInitializing = true;
try {
// Note: the following functions need to be @SystemApis, once they become mainline
@@ -1668,11 +1682,7 @@
return new Object[sServiceCacheSize];
}
- /**
- * Gets a system service from a given context.
- * @hide
- */
- public static Object getSystemService(ContextImpl ctx, String name) {
+ private static ServiceFetcher<?> getSystemServiceFetcher(String name) {
if (name == null) {
return null;
}
@@ -1683,6 +1693,18 @@
}
return null;
}
+ return fetcher;
+ }
+
+ /**
+ * Gets a system service from a given context.
+ * @hide
+ */
+ public static Object getSystemService(@NonNull ContextImpl ctx, String name) {
+ final ServiceFetcher<?> fetcher = getSystemServiceFetcher(name);
+ if (fetcher == null) {
+ return null;
+ }
final Object ret = fetcher.getService(ctx);
if (sEnableServiceNotFoundWtf && ret == null) {
@@ -1710,6 +1732,26 @@
}
/**
+ * Gets a system service which has opted-in to being fetched without a context.
+ * @hide
+ */
+ @FlaggedApi(android.webkit.Flags.FLAG_UPDATE_SERVICE_IPC_WRAPPER)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static @Nullable Object getSystemServiceWithNoContext(@NonNull String name) {
+ final ServiceFetcher<?> fetcher = getSystemServiceFetcher(name);
+ if (fetcher == null) {
+ return null;
+ }
+
+ if (!fetcher.supportsFetchWithoutContext()) {
+ throw new IllegalArgumentException(
+ "Manager cannot be fetched without a context: " + name);
+ }
+
+ return fetcher.getService(null);
+ }
+
+ /**
* Gets the name of the system-level service that is represented by the specified class.
* @hide
*/
@@ -1863,6 +1905,50 @@
}
/**
+ * Used by apex modules to register a "service wrapper" that is not tied to any {@link Context}
+ * and will never require a context in the future.
+ *
+ * Services registered in this way can be fetched via
+ * {@link #getSystemServiceWithNoContext(String)}, so cannot require a context in future without
+ * a breaking change.
+ *
+ * <p>This can only be called from the methods called by the static initializer of
+ * {@link SystemServiceRegistry}. (Otherwise it throws a {@link IllegalStateException}.)
+ *
+ * @param serviceName the name of the binder object, such as
+ * {@link Context#JOB_SCHEDULER_SERVICE}.
+ * @param serviceWrapperClass the wrapper class, such as the class of
+ * {@link android.app.job.JobScheduler}.
+ * @param serviceProducer Callback that takes the service binder object with the name
+ * {@code serviceName} and returns an actual service wrapper instance.
+ *
+ * @hide
+ */
+ @FlaggedApi(android.webkit.Flags.FLAG_UPDATE_SERVICE_IPC_WRAPPER)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static <TServiceClass> void registerForeverStaticService(
+ @NonNull String serviceName, @NonNull Class<TServiceClass> serviceWrapperClass,
+ @NonNull StaticServiceProducerWithBinder<TServiceClass> serviceProducer) {
+ ensureInitializing("registerStaticService");
+ Preconditions.checkStringNotEmpty(serviceName);
+ Objects.requireNonNull(serviceWrapperClass);
+ Objects.requireNonNull(serviceProducer);
+
+ registerService(serviceName, serviceWrapperClass,
+ new StaticServiceFetcher<TServiceClass>() {
+ @Override
+ public TServiceClass createService() throws ServiceNotFoundException {
+ return serviceProducer.createService(
+ ServiceManager.getServiceOrThrow(serviceName));
+ }
+
+ @Override
+ public boolean supportsFetchWithoutContext() {
+ return true;
+ }});
+ }
+
+ /**
* Similar to {@link #registerStaticService(String, Class, StaticServiceProducerWithBinder)},
* but used for a "service wrapper" that doesn't take a service binder in its constructor.
*
@@ -1952,6 +2038,18 @@
*/
static abstract interface ServiceFetcher<T> {
T getService(ContextImpl ctx);
+
+ /**
+ * Should this service fetcher support being fetched via {@link #getSystemService(String)},
+ * without a Context?
+ *
+ * This means that the service cannot depend on a Context in future!
+ *
+ * @return true if this is supported for this service.
+ */
+ default boolean supportsFetchWithoutContext() {
+ return false;
+ }
}
/**
@@ -2059,6 +2157,11 @@
}
public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException;
+
+ // Services that explicitly use a Context can never be fetched without one.
+ public final boolean supportsFetchWithoutContext() {
+ return false;
+ }
}
/**
@@ -2083,6 +2186,13 @@
}
public abstract T createService() throws ServiceNotFoundException;
+
+ // Services that do not need a Context can potentially be fetched without one, but the
+ // default is false, so that the service can require one in future without this being a
+ // breaking change.
+ public boolean supportsFetchWithoutContext() {
+ return false;
+ }
}
/** @hide */
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index a271328..a045eae 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -16,6 +16,8 @@
package android.app;
+import static android.app.Flags.enableNightModeCache;
+
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
@@ -31,6 +33,7 @@
import android.content.Context;
import android.content.res.Configuration;
import android.os.Binder;
+import android.os.IpcDataCache;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
@@ -874,6 +877,51 @@
}
}
+ private Integer getNightModeFromServer() {
+ try {
+ if (sGlobals != null) {
+ return sGlobals.mService.getNightMode();
+ }
+ return -1;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+
+ /**
+ * Retrieve the night mode for the user.
+ */
+ private final IpcDataCache.QueryHandler<Void, Integer> mNightModeQuery =
+ new IpcDataCache.QueryHandler<>() {
+
+ @Override
+ @NonNull
+ public Integer apply(Void query) {
+ return getNightModeFromServer();
+ }
+ };
+
+ private static final String NIGHT_MODE_API = "getNightMode";
+
+ /**
+ * Cache the night mode for a user.
+ */
+ private final IpcDataCache<Void, Integer> mNightModeCache =
+ new IpcDataCache<>(1, IpcDataCache.MODULE_SYSTEM,
+ NIGHT_MODE_API, /* cacheName= */ "NightModeCache", mNightModeQuery);
+
+ /**
+ * Invalidate the night mode cache.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NIGHT_MODE_CACHE)
+ public static void invalidateNightModeCache() {
+ IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM,
+ NIGHT_MODE_API);
+ }
+
/**
* Returns the currently configured night mode.
* <p>
@@ -890,14 +938,11 @@
* @see #setNightMode(int)
*/
public @NightMode int getNightMode() {
- if (sGlobals != null) {
- try {
- return sGlobals.mService.getNightMode();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ if (enableNightModeCache()) {
+ return mNightModeCache.query(null);
+ } else {
+ return getNightModeFromServer();
}
- return -1;
}
/**
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index 6b558d0..ffbd80c 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -43,12 +43,14 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.infra.AndroidFuture;
+import com.android.server.backup.Flags;
import libcore.io.IoUtils;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.annotation.Retention;
@@ -1283,6 +1285,14 @@
// And bring live SharedPreferences instances up to date
reloadSharedPreferences();
+ // It's possible that onRestoreFile was overridden and that the agent did not
+ // consume all the data for this file from the pipe. We need to clear the pipe,
+ // otherwise the framework can get stuck trying to write to a full pipe or
+ // onRestoreFile could be called with the previous file's data left in the pipe.
+ if (Flags.enableClearPipeAfterRestoreFile()) {
+ clearUnconsumedDataFromPipe(data, size);
+ }
+
Binder.restoreCallingIdentity(ident);
try {
callbackBinder.opCompleteForUser(getBackupUserId(), token, 0);
@@ -1296,6 +1306,16 @@
}
}
+ private static void clearUnconsumedDataFromPipe(ParcelFileDescriptor data, long size) {
+ try (FileInputStream in = new FileInputStream(data.getFileDescriptor())) {
+ if (in.available() > 0) {
+ in.skip(size);
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to clear unconsumed data from pipe.", e);
+ }
+ }
+
@Override
public void doRestoreFinished(int token, IBackupManager callbackBinder) {
final long ident = Binder.clearCallingIdentity();
diff --git a/core/java/android/app/multitasking.aconfig b/core/java/android/app/multitasking.aconfig
new file mode 100644
index 0000000..ab00891
--- /dev/null
+++ b/core/java/android/app/multitasking.aconfig
@@ -0,0 +1,8 @@
+package: "android.app"
+
+flag {
+ name: "enable_pip_ui_state_callback_on_entering"
+ namespace: "multitasking"
+ description: "Enables PiP UI state callback on entering"
+ bug: "303718131"
+}
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index d11c6c5..a5d4a14 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -43,3 +43,10 @@
description: "Fixes the behavior of KeyguardManager#setPrivateNotificationsAllowed()"
bug: "309920145"
}
+
+flag {
+ name: "category_voicemail"
+ namespace: "wear_sysui"
+ description: "Adds a new voicemail category for notifications"
+ bug: "322806700"
+}
diff --git a/core/java/android/app/ui_mode_manager.aconfig b/core/java/android/app/ui_mode_manager.aconfig
new file mode 100644
index 0000000..1ae5264
--- /dev/null
+++ b/core/java/android/app/ui_mode_manager.aconfig
@@ -0,0 +1,8 @@
+package: "android.app"
+
+flag {
+ namespace: "system_performance"
+ name: "enable_night_mode_cache"
+ description: "Enables the use of binder caching for system night mode."
+ bug: "255999432"
+}
\ No newline at end of file
diff --git a/core/java/android/app/wearable/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java
index f1ca086..eca0039 100644
--- a/core/java/android/app/wearable/WearableSensingManager.java
+++ b/core/java/android/app/wearable/WearableSensingManager.java
@@ -18,6 +18,7 @@
import android.Manifest;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -99,6 +100,13 @@
*/
public static final int STATUS_ACCESS_DENIED = 5;
+ /**
+ * The value of the status code that indicates the method called is not supported by the
+ * implementation of {@link WearableSensingService}.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_UNSUPPORTED_OPERATION_STATUS_CODE)
+ public static final int STATUS_UNSUPPORTED_OPERATION = 6;
+
/** @hide */
@IntDef(prefix = { "STATUS_" }, value = {
STATUS_UNKNOWN,
@@ -106,7 +114,8 @@
STATUS_UNSUPPORTED,
STATUS_SERVICE_UNAVAILABLE,
STATUS_WEARABLE_UNAVAILABLE,
- STATUS_ACCESS_DENIED
+ STATUS_ACCESS_DENIED,
+ STATUS_UNSUPPORTED_OPERATION
})
@Retention(RetentionPolicy.SOURCE)
public @interface StatusCode {}
diff --git a/core/java/android/app/wearable/flags.aconfig b/core/java/android/app/wearable/flags.aconfig
new file mode 100644
index 0000000..074ce9b
--- /dev/null
+++ b/core/java/android/app/wearable/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.app.wearable"
+
+flag {
+ name: "enable_unsupported_operation_status_code"
+ namespace: "machine_learning"
+ description: "This flag enables the WearableSensingManager#STATUS_UNSUPPORTED_OPERATION status code API."
+ bug: "301427767"
+}
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/VirtualDevice.java b/core/java/android/companion/virtual/VirtualDevice.java
index d0c8be6..97fa2ba 100644
--- a/core/java/android/companion/virtual/VirtualDevice.java
+++ b/core/java/android/companion/virtual/VirtualDevice.java
@@ -17,11 +17,14 @@
package android.companion.virtual;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.companion.virtual.flags.Flags;
import android.content.Context;
import android.os.Parcel;
@@ -164,6 +167,44 @@
}
}
+ /**
+ * Returns whether this device may have custom audio input device.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS)
+ public boolean hasCustomAudioInputSupport() {
+ try {
+ return mVirtualDevice.getDevicePolicy(POLICY_TYPE_AUDIO) == DEVICE_POLICY_CUSTOM;
+ // TODO(b/291735254): also check for a custom audio injection mix for this device id.
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns whether this device may have custom cameras.
+ *
+ * <p>Returning {@code true} does not necessarily mean that this device has cameras, it only
+ * means that a {@link android.hardware.camera2.CameraManager} instance created from a
+ * {@link Context} associated with this device will return this device's cameras, if any.</p>
+ *
+ * @see Context#getDeviceId()
+ * @see Context#createDeviceContext(int)
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS)
+ public boolean hasCustomCameraSupport() {
+ try {
+ return mVirtualDevice.getDevicePolicy(POLICY_TYPE_CAMERA) == DEVICE_POLICY_CUSTOM;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 90d251b..a16e94a 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -63,6 +63,7 @@
import android.os.Binder;
import android.os.Looper;
import android.os.RemoteException;
+import android.util.ArraySet;
import android.util.Log;
import android.view.Surface;
import android.view.WindowManager;
@@ -74,9 +75,11 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.IntConsumer;
@@ -389,6 +392,28 @@
}
/**
+ * Returns all current persistent device IDs, including the ones for which no virtual device
+ * exists, as long as one may have existed or can be created.
+ *
+ * @hide
+ */
+ // TODO(b/315481938): Link @see VirtualDevice#getPersistentDeviceId()
+ @FlaggedApi(Flags.FLAG_PERSISTENT_DEVICE_ID_API)
+ @SystemApi
+ @NonNull
+ public Set<String> getAllPersistentDeviceIds() {
+ if (mService == null) {
+ Log.w(TAG, "Failed to retrieve persistent ids; no virtual device manager service.");
+ return Collections.emptySet();
+ }
+ try {
+ return new ArraySet<>(mService.getAllPersistentDeviceIds());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Checks whether the passed {@code deviceId} is a valid virtual device ID or not.
* {@link Context#DEVICE_ID_DEFAULT} is not valid as it is the ID of the default
* device which is not a virtual device. {@code deviceId} must correspond to a virtual device
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index bde562d..af13011 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -409,9 +409,10 @@
"packageName = " + mAttributionSourceState.packageName + ", " +
"attributionTag = " + mAttributionSourceState.attributionTag + ", " +
"token = " + mAttributionSourceState.token + ", " +
+ "deviceId = " + mAttributionSourceState.deviceId + ", " +
"next = " + (mAttributionSourceState.next != null
- && mAttributionSourceState.next.length > 0
- ? mAttributionSourceState.next[0] : null) +
+ && mAttributionSourceState.next.length > 0
+ ? new AttributionSource(mAttributionSourceState.next[0]).toString() : null) +
" }";
}
return super.toString();
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index caff457..19bce0b 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -183,3 +183,19 @@
description: "Feature flag to enable permission EMERGENCY_INSTALL_PACKAGES"
bug: "321080601"
}
+
+flag {
+ name: "asl_in_apk_app_metadata_source"
+ namespace: "package_manager_service"
+ description: "Feature flag to allow to know if the Android Safety Label (ASL) of an app is provided by the app's APK itself, or provided by an installer."
+ bug: "287487923"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "force_multi_arch_native_libs_match"
+ namespace: "package_manager_service"
+ description: "Feature flag to force an multiArch app's native libraries to match with the natively supported ABIs of the device"
+ bug: "282783453"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index efb8607..d7e64b6 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -108,3 +108,17 @@
bug: "316362775"
is_fixed_read_only: true
}
+
+flag {
+ name: "enable_permission_to_access_hidden_profiles"
+ namespace: "profile_experiences"
+ description: "Add permission to access API hidden users data via system APIs"
+ bug: "321988638"
+}
+
+flag {
+ name: "handle_interleaved_settings_for_private_space"
+ namespace: "profile_experiences"
+ description: "Handle listing of private space apps in settings pages with interleaved content"
+ bug: "323212460"
+}
diff --git a/core/java/android/content/pm/overlay/OverlayPaths.java b/core/java/android/content/pm/overlay/OverlayPaths.java
index bd74b0b..a4db733 100644
--- a/core/java/android/content/pm/overlay/OverlayPaths.java
+++ b/core/java/android/content/pm/overlay/OverlayPaths.java
@@ -49,13 +49,6 @@
public static class Builder {
final OverlayPaths mPaths = new OverlayPaths();
- public Builder() {}
-
- public Builder(@NonNull OverlayPaths base) {
- mPaths.mResourceDirs.addAll(base.getResourceDirs());
- mPaths.mOverlayPaths.addAll(base.getOverlayPaths());
- }
-
/**
* Adds a non-APK path to the contents of {@link OverlayPaths#getOverlayPaths()}.
*/
diff --git a/core/java/android/content/pm/verify/domain/OWNERS b/core/java/android/content/pm/verify/domain/OWNERS
index c669112..b451fe4 100644
--- a/core/java/android/content/pm/verify/domain/OWNERS
+++ b/core/java/android/content/pm/verify/domain/OWNERS
@@ -1,5 +1,4 @@
# Bug component: 36137
+include /PACKAGE_MANAGER_OWNERS
-chiuwinson@google.com
-patb@google.com
-toddke@google.com
\ No newline at end of file
+wloh@google.com
\ No newline at end of file
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index c7790bd..5e442b8 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -273,14 +273,27 @@
throw new NotFoundException("String resource name " + name);
}
+ private static boolean isIntLike(@NonNull String s) {
+ if (s.isEmpty() || s.length() > 10) return false;
+ for (int i = 0, size = s.length(); i < size; i++) {
+ final char c = s.charAt(i);
+ if (c < '0' || c > '9') {
+ return false;
+ }
+ }
+ return true;
+ }
+
int getIdentifier(String name, String defType, String defPackage) {
if (name == null) {
throw new NullPointerException("name is null");
}
- try {
- return Integer.parseInt(name);
- } catch (Exception e) {
- // Ignore
+ if (isIntLike(name)) {
+ try {
+ return Integer.parseInt(name);
+ } catch (Exception e) {
+ // Ignore
+ }
}
return mAssets.getResourceIdentifier(name, defType, defPackage);
}
diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig
index 90cd471..1ca11e6 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -47,4 +47,11 @@
name: "configurable_selector_ui_enabled"
description: "Enables OEM configurable Credential Selector UI"
bug: "319448437"
-}
\ No newline at end of file
+}
+
+flag {
+ namespace: "credential_manager"
+ name: "credman_biometric_api_enabled"
+ description: "Enables Credential Manager to work with the Biometric Authenticate API"
+ bug: "323211850"
+}
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 0396443..1c36a88 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -1575,7 +1575,7 @@
}
@Override
- public void opChanged(int op, int uid, String packageName) {
+ public void opChanged(int op, int uid, String packageName, String persistentDeviceId) {
if (op == AppOpsManager.OP_PLAY_AUDIO) {
final Camera camera = mWeakCamera.get();
if (camera != null) {
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 64a62a9..f18a0b7 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -202,8 +202,11 @@
/**
* Called by the window manager to perform traversals while holding a
* surface flinger transaction.
+ * @param t The default transaction.
+ * @param displayTransactions The transactions mapped by display id.
*/
- public abstract void performTraversal(Transaction t);
+ public abstract void performTraversal(Transaction t,
+ SparseArray<SurfaceControl.Transaction> displayTransactions);
/**
* Tells the display manager about properties of the display that depend on the windows on it.
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 17992aa..e070fe5 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -33,6 +33,13 @@
flag {
namespace: "input_native"
+ name: "emoji_and_screenshot_keycodes_available"
+ description: "Add new KeyEvent keycodes for opening Emoji Picker and Taking Screenshots"
+ bug: "315307777"
+}
+
+flag {
+ namespace: "input_native"
name: "keyboard_a11y_slow_keys_flag"
description: "Controls if the slow keys accessibility feature for physical keyboard is available to the user"
bug: "294546335"
diff --git a/core/java/android/net/flags.aconfig b/core/java/android/net/flags.aconfig
index 0ad1804..311dc09 100644
--- a/core/java/android/net/flags.aconfig
+++ b/core/java/android/net/flags.aconfig
@@ -1,50 +1,11 @@
-package: "com.android.net.flags"
+package: "android.net.platform.flags"
-flag {
- name: "track_multiple_network_activities"
- namespace: "android_core_networking"
- description: "NetworkActivityTracker tracks multiple networks including non default networks"
- bug: "267870186"
-}
-
-flag {
- name: "forbidden_capability"
- namespace: "android_core_networking"
- description: "This flag controls the forbidden capability API"
- bug: "302997505"
-}
-
-flag {
- name: "set_data_saver_via_cm"
- namespace: "android_core_networking"
- description: "Set data saver through ConnectivityManager API"
- bug: "297836825"
-}
-
-flag {
- name: "support_is_uid_networking_blocked"
- namespace: "android_core_networking"
- description: "This flag controls whether isUidNetworkingBlocked is supported"
- bug: "297836825"
-}
-
-flag {
- name: "basic_background_restrictions_enabled"
- namespace: "android_core_networking"
- description: "Block network access for apps in a low importance background state"
- bug: "304347838"
-}
-
-flag {
- name: "register_nsd_offload_engine"
- namespace: "android_core_networking"
- description: "The flag controls the access for registerOffloadEngine API in NsdManager"
- bug: "294777050"
-}
+# This file contains aconfig flags used from platform code
+# Flags used for module APIs must be in aconfig files under each modules
flag {
name: "ipsec_transform_state"
- namespace: "android_core_networking_ipsec"
+ namespace: "core_networking_ipsec"
description: "The flag controls the access for getIpSecTransformState and IpSecTransformState"
bug: "308011229"
}
diff --git a/core/java/android/os/incremental/OWNERS b/core/java/android/os/incremental/OWNERS
index 47eee64..a925564 100644
--- a/core/java/android/os/incremental/OWNERS
+++ b/core/java/android/os/incremental/OWNERS
@@ -1,6 +1,4 @@
# Bug component: 554432
-alexbuy@google.com
-schfan@google.com
-toddke@google.com
+include /PACKAGE_MANAGER_OWNERS
+
zyy@google.com
-patb@google.com
diff --git a/core/java/android/os/vibrator/VibrationConfig.java b/core/java/android/os/vibrator/VibrationConfig.java
index 92e4967..bcdb982 100644
--- a/core/java/android/os/vibrator/VibrationConfig.java
+++ b/core/java/android/os/vibrator/VibrationConfig.java
@@ -35,6 +35,7 @@
import android.util.IndentingPrintWriter;
import java.io.PrintWriter;
+import java.util.Arrays;
/**
* List of device-specific internal vibration configuration loaded from platform config.xml.
@@ -51,6 +52,8 @@
private final float mHapticChannelMaxVibrationAmplitude;
private final int mRampStepDurationMs;
private final int mRampDownDurationMs;
+ private final int mRequestVibrationParamsTimeoutMs;
+ private final int[] mRequestVibrationParamsForUsages;
private final boolean mIgnoreVibrationsOnWirelessCharger;
@@ -75,6 +78,10 @@
com.android.internal.R.integer.config_vibrationWaveformRampDownDuration, 0);
mRampStepDurationMs = loadInteger(resources,
com.android.internal.R.integer.config_vibrationWaveformRampStepDuration, 0);
+ mRequestVibrationParamsTimeoutMs = loadInteger(resources,
+ com.android.internal.R.integer.config_requestVibrationParamsTimeout, 0);
+ mRequestVibrationParamsForUsages = loadIntArray(resources,
+ com.android.internal.R.array.config_requestVibrationParamsForUsages);
mIgnoreVibrationsOnWirelessCharger = loadBoolean(resources,
com.android.internal.R.bool.config_ignoreVibrationsOnWirelessCharger, false);
@@ -115,6 +122,10 @@
return res != null ? res.getBoolean(resId) : defaultValue;
}
+ private static int[] loadIntArray(@Nullable Resources res, int resId) {
+ return res != null ? res.getIntArray(resId) : new int[0];
+ }
+
/**
* Return the maximum amplitude the vibrator can play using the audio haptic channels.
*
@@ -140,6 +151,23 @@
}
/**
+ * The duration, in milliseconds, that the vibrator control service will wait for new
+ * vibration params.
+ */
+ public int getRequestVibrationParamsTimeoutMs() {
+ return Math.max(mRequestVibrationParamsTimeoutMs, 0);
+ }
+
+ /**
+ * The list of usages that should request vibration params before they are played. These
+ * usages don't have strong latency requirements, e.g. ringtone and notification, and can be
+ * slightly delayed.
+ */
+ public int[] getRequestVibrationParamsForUsages() {
+ return mRequestVibrationParamsForUsages;
+ }
+
+ /**
* The duration, in milliseconds, that should be applied to convert vibration effect's
* {@link android.os.vibrator.RampSegment} to a {@link android.os.vibrator.StepSegment} on
* devices without PWLE support.
@@ -204,6 +232,9 @@
+ ", mDefaultMediaIntensity=" + mDefaultMediaVibrationIntensity
+ ", mDefaultNotificationIntensity=" + mDefaultNotificationVibrationIntensity
+ ", mDefaultRingIntensity=" + mDefaultRingVibrationIntensity
+ + ", mRequestVibrationParamsTimeoutMs=" + mRequestVibrationParamsTimeoutMs
+ + ", mRequestVibrationParamsForUsages=" + Arrays.toString(
+ getRequestVibrationParamsForUsagesNames())
+ "}";
}
@@ -220,4 +251,14 @@
pw.println("rampDownDurationMs = " + mRampDownDurationMs);
pw.decreaseIndent();
}
+
+ private String[] getRequestVibrationParamsForUsagesNames() {
+ int usagesCount = mRequestVibrationParamsForUsages.length;
+ String[] names = new String[usagesCount];
+ for (int i = 0; i < usagesCount; i++) {
+ names[i] = VibrationAttributes.usageToString(mRequestVibrationParamsForUsages[i]);
+ }
+
+ return names;
+ }
}
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 5d6dfc7..7d127ad 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -2102,7 +2102,7 @@
PhoneAccountHandle accountHandle) {
TelecomManager tm = null;
try {
- tm = TelecomManager.from(context);
+ tm = context.getSystemService(TelecomManager.class);
} catch (UnsupportedOperationException e) {
if (VERBOSE_LOG) {
Log.v(LOG_TAG, "No TelecomManager found to get account address.");
diff --git a/core/java/android/provider/ContactKeysManager.java b/core/java/android/provider/ContactKeysManager.java
index ecde699..bef6456 100644
--- a/core/java/android/provider/ContactKeysManager.java
+++ b/core/java/android/provider/ContactKeysManager.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
@@ -247,6 +248,44 @@
}
/**
+ * Updates a contact key entry's local verification state that belongs to the app identified
+ * by ownerPackageName.
+ *
+ * @param lookupKey the value that references the contact
+ * @param deviceId an app-specified identifier for the device
+ * @param accountId an app-specified identifier for the account
+ * @param ownerPackageName the package name of the app that owns the key
+ * @param localVerificationState the new local verification state
+ *
+ * @return true if the entry was updated, false otherwise.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS,
+ android.Manifest.permission.WRITE_CONTACTS})
+ public boolean updateContactKeyLocalVerificationState(@NonNull String lookupKey,
+ @NonNull String deviceId,
+ @NonNull String accountId,
+ @NonNull String ownerPackageName,
+ @VerificationState int localVerificationState) {
+ validateVerificationState(localVerificationState);
+
+ final Bundle extras = new Bundle();
+ extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+ extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+ extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+ extras.putString(ContactKeys.OWNER_PACKAGE_NAME, Objects.requireNonNull(ownerPackageName));
+ extras.putInt(ContactKeys.LOCAL_VERIFICATION_STATE, localVerificationState);
+
+ final Bundle response = nullSafeCall(mContentResolver,
+ ContactKeys.UPDATE_CONTACT_KEY_LOCAL_VERIFICATION_STATE_METHOD, extras);
+
+ return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+ }
+
+ /**
* Updates a contact key entry's remote verification state that belongs to the caller app.
*
* @param lookupKey the value that references the contact
@@ -275,6 +314,45 @@
return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
}
+ /**
+ * Updates a contact key entry's remote verification state that belongs to the app identified
+ * by ownerPackageName.
+ *
+ * @param lookupKey the value that references the contact
+ * @param deviceId an app-specified identifier for the device
+ * @param accountId an app-specified identifier for the account
+ * @param ownerPackageName the package name of the app that owns the key
+ * @param remoteVerificationState the new remote verification state
+ *
+ * @return true if the entry was updated, false otherwise.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS,
+ android.Manifest.permission.WRITE_CONTACTS})
+ public boolean updateContactKeyRemoteVerificationState(@NonNull String lookupKey,
+ @NonNull String deviceId,
+ @NonNull String accountId,
+ @NonNull String ownerPackageName,
+ @VerificationState int remoteVerificationState) {
+ validateVerificationState(remoteVerificationState);
+
+ final Bundle extras = new Bundle();
+ extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+ extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+ extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+ extras.putString(ContactKeys.OWNER_PACKAGE_NAME, Objects.requireNonNull(ownerPackageName));
+ extras.putInt(ContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState);
+
+ final Bundle response = nullSafeCall(mContentResolver,
+ ContactKeys.UPDATE_CONTACT_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras);
+
+ return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+ }
+
+
private static void validateVerificationState(int verificationState) {
if (verificationState != UNVERIFIED
&& verificationState != VERIFICATION_FAILED
@@ -297,12 +375,12 @@
public boolean removeContactKey(@NonNull String lookupKey,
@NonNull String deviceId,
@NonNull String accountId) {
- Bundle extras = new Bundle();
+ final Bundle extras = new Bundle();
extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
- Bundle response = nullSafeCall(mContentResolver,
+ final Bundle response = nullSafeCall(mContentResolver,
ContactKeys.REMOVE_CONTACT_KEY_METHOD, extras);
return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
@@ -369,6 +447,41 @@
}
/**
+ * Updates a self key entry's remote verification state that belongs to the app identified
+ * by ownerPackageName.
+ *
+ * @param deviceId an app-specified identifier for the device
+ * @param accountId an app-specified identifier for the account
+ * @param ownerPackageName the package name of the app that owns the key
+ * @param remoteVerificationState the new remote verification state
+ *
+ * @return true if the entry was updated, false otherwise.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS,
+ android.Manifest.permission.WRITE_CONTACTS})
+ public boolean updateSelfKeyRemoteVerificationState(@NonNull String deviceId,
+ @NonNull String accountId,
+ @NonNull String ownerPackageName,
+ @VerificationState int remoteVerificationState) {
+ validateVerificationState(remoteVerificationState);
+
+ Bundle extras = new Bundle();
+ extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+ extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+ extras.putString(ContactKeys.OWNER_PACKAGE_NAME, Objects.requireNonNull(ownerPackageName));
+ extras.putInt(ContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState);
+
+ Bundle response = nullSafeCall(mContentResolver,
+ ContactKeys.UPDATE_SELF_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras);
+
+ return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+ }
+
+ /**
* Maximum size of a contact key.
*/
public static int getMaxKeySizeBytes() {
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index af688e9..2841dc0 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -3202,7 +3202,7 @@
/**
* The infrastructure bitmask which the APN can be used on. For example, some APNs can only
* be used when the device is on cellular, on satellite, or both. The default value is
- * 1 (INFRASTRUCTURE_CELLULAR).
+ * 3 (INFRASTRUCTURE_CELLULAR | INFRASTRUCTURE_SATELLITE).
*
* <P>Type: INTEGER</P>
* @hide
diff --git a/core/java/android/security/OWNERS b/core/java/android/security/OWNERS
index 533d459..8bd6c85 100644
--- a/core/java/android/security/OWNERS
+++ b/core/java/android/security/OWNERS
@@ -1,7 +1,7 @@
# Bug component: 36824
brambonne@google.com
-brufino@google.com
+eranm@google.com
jeffv@google.com
per-file *NetworkSecurityPolicy.java = file:net/OWNERS
diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java
index 94c8eaf..783f3b7 100644
--- a/core/java/android/text/FontConfig.java
+++ b/core/java/android/text/FontConfig.java
@@ -26,7 +26,6 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
-import android.graphics.fonts.FontFamily.Builder.VariableFontFamilyType;
import android.graphics.fonts.FontStyle;
import android.graphics.fonts.FontVariationAxis;
import android.icu.util.ULocale;
@@ -240,6 +239,23 @@
private final @IntRange(from = 0) int mIndex;
private final @NonNull String mFontVariationSettings;
private final @Nullable String mFontFamilyName;
+ private final @VarTypeAxes int mVarTypeAxes;
+
+ /** @hide */
+ @Retention(SOURCE)
+ @IntDef(prefix = { "VAR_TYPE_AXES_" }, value = {
+ VAR_TYPE_AXES_NONE,
+ VAR_TYPE_AXES_WGHT,
+ VAR_TYPE_AXES_ITAL,
+ })
+ public @interface VarTypeAxes {}
+
+ /** @hide */
+ public static final int VAR_TYPE_AXES_NONE = 0;
+ /** @hide */
+ public static final int VAR_TYPE_AXES_WGHT = 1;
+ /** @hide */
+ public static final int VAR_TYPE_AXES_ITAL = 2;
/**
* Construct a Font instance.
@@ -248,7 +264,8 @@
*/
public Font(@NonNull File file, @Nullable File originalFile, @NonNull String postScriptName,
@NonNull FontStyle style, @IntRange(from = 0) int index,
- @NonNull String fontVariationSettings, @Nullable String fontFamilyName) {
+ @NonNull String fontVariationSettings, @Nullable String fontFamilyName,
+ @VarTypeAxes int varTypeAxes) {
mFile = file;
mOriginalFile = originalFile;
mPostScriptName = postScriptName;
@@ -256,6 +273,7 @@
mIndex = index;
mFontVariationSettings = fontVariationSettings;
mFontFamilyName = fontFamilyName;
+ mVarTypeAxes = varTypeAxes;
}
@Override
@@ -273,6 +291,7 @@
dest.writeInt(mIndex);
dest.writeString8(mFontVariationSettings);
dest.writeString8(mFontFamilyName);
+ dest.writeInt(mVarTypeAxes);
}
public static final @NonNull Creator<Font> CREATOR = new Creator<Font>() {
@@ -288,9 +307,10 @@
int index = source.readInt();
String varSettings = source.readString8();
String fallback = source.readString8();
+ int varTypeAxes = source.readInt();
return new Font(path, originalPath, postScriptName, new FontStyle(weight, slant),
- index, varSettings, fallback);
+ index, varSettings, fallback, varTypeAxes);
}
@Override
@@ -366,6 +386,15 @@
}
/**
+ * Returns the list of supported axes tags for variable family type resolution.
+ *
+ * @hide
+ */
+ public @VarTypeAxes int getVarTypeAxes() {
+ return mVarTypeAxes;
+ }
+
+ /**
* Returns the list of axes associated to this font.
* @deprecated Use getFontVariationSettings
* @hide
@@ -408,13 +437,14 @@
&& Objects.equals(mOriginalFile, font.mOriginalFile)
&& Objects.equals(mStyle, font.mStyle)
&& Objects.equals(mFontVariationSettings, font.mFontVariationSettings)
- && Objects.equals(mFontFamilyName, font.mFontFamilyName);
+ && Objects.equals(mFontFamilyName, font.mFontFamilyName)
+ && mVarTypeAxes == font.mVarTypeAxes;
}
@Override
public int hashCode() {
return Objects.hash(mFile, mOriginalFile, mStyle, mIndex, mFontVariationSettings,
- mFontFamilyName);
+ mFontFamilyName, mVarTypeAxes);
}
@Override
@@ -426,6 +456,7 @@
+ ", mIndex=" + mIndex
+ ", mFontVariationSettings='" + mFontVariationSettings + '\''
+ ", mFontFamilyName='" + mFontFamilyName + '\''
+ + ", mVarTypeAxes='" + mVarTypeAxes + '\''
+ '}';
}
}
@@ -549,7 +580,6 @@
private final @NonNull List<Font> mFonts;
private final @NonNull LocaleList mLocaleList;
private final @Variant int mVariant;
- private final int mVariableFontFamilyType;
/** @hide */
@Retention(SOURCE)
@@ -589,11 +619,10 @@
* @hide Only system server can create this instance and passed via IPC.
*/
public FontFamily(@NonNull List<Font> fonts, @NonNull LocaleList localeList,
- @Variant int variant, int variableFontFamilyType) {
+ @Variant int variant) {
mFonts = fonts;
mLocaleList = localeList;
mVariant = variant;
- mVariableFontFamilyType = variableFontFamilyType;
}
/**
@@ -644,20 +673,6 @@
return mVariant;
}
- /**
- * Returns the font family type.
- *
- * @see Builder#VARIABLE_FONT_FAMILY_TYPE_NONE
- * @see Builder#VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL
- * @see Builder#VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY
- * @see Builder#VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT
- * @hide
- * @return variable font family type.
- */
- public @VariableFontFamilyType int getVariableFontFamilyType() {
- return mVariableFontFamilyType;
- }
-
@Override
public int describeContents() {
return 0;
@@ -668,7 +683,6 @@
dest.writeTypedList(mFonts, flags);
dest.writeString8(mLocaleList.toLanguageTags());
dest.writeInt(mVariant);
- dest.writeInt(mVariableFontFamilyType);
}
public static final @NonNull Creator<FontFamily> CREATOR = new Creator<FontFamily>() {
@@ -679,10 +693,8 @@
source.readTypedList(fonts, Font.CREATOR);
String langTags = source.readString8();
int variant = source.readInt();
- int varFamilyType = source.readInt();
- return new FontFamily(fonts, LocaleList.forLanguageTags(langTags), variant,
- varFamilyType);
+ return new FontFamily(fonts, LocaleList.forLanguageTags(langTags), variant);
}
@Override
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 6e45fea..f3e0ea7 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -96,3 +96,10 @@
description: "A feature flag for fixing the crash while delete text in insert mode."
bug: "314254153"
}
+
+flag {
+ name: "insert_mode_not_update_selection"
+ namespace: "text"
+ description: "Fix that InputService#onUpdateSelection is not called when insert mode gesture is performed."
+ bug: "300850862"
+}
diff --git a/core/java/android/util/Slog.java b/core/java/android/util/Slog.java
index c0ceb9ea..9ecb4cb 100644
--- a/core/java/android/util/Slog.java
+++ b/core/java/android/util/Slog.java
@@ -217,7 +217,6 @@
* @see Log#wtf(String, String)
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- @android.ravenwood.annotation.RavenwoodThrow
public static int wtf(@Nullable String tag, @NonNull String msg) {
return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, null, false, true);
}
@@ -263,7 +262,6 @@
*
* @see Log#wtf(String, Throwable)
*/
- @android.ravenwood.annotation.RavenwoodThrow
public static int wtf(@Nullable String tag, @Nullable Throwable tr) {
return Log.wtf(Log.LOG_ID_SYSTEM, tag, tr.getMessage(), tr, false, true);
}
@@ -284,7 +282,6 @@
* @see Log#wtf(String, String, Throwable)
*/
@UnsupportedAppUsage
- @android.ravenwood.annotation.RavenwoodThrow
public static int wtf(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) {
return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, tr, false, true);
}
diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java
index 064bc69..35b137a 100644
--- a/core/java/android/view/FocusFinder.java
+++ b/core/java/android/view/FocusFinder.java
@@ -314,13 +314,20 @@
if (count < 2) {
return null;
}
+ View next = null;
+ final boolean[] looped = new boolean[1];
switch (direction) {
case View.FOCUS_FORWARD:
- return getNextFocusable(focused, focusables, count);
+ next = getNextFocusable(focused, focusables, count, looped);
+ break;
case View.FOCUS_BACKWARD:
- return getPreviousFocusable(focused, focusables, count);
+ next = getPreviousFocusable(focused, focusables, count, looped);
+ break;
}
- return focusables.get(count - 1);
+ if (root != null && root.mAttachInfo != null && root == root.getRootView()) {
+ root.mAttachInfo.mNextFocusLooped = looped[0];
+ }
+ return next != null ? next : focusables.get(count - 1);
}
private void setFocusBottomRight(ViewGroup root, Rect focusedRect) {
@@ -375,7 +382,8 @@
return closest;
}
- private static View getNextFocusable(View focused, ArrayList<View> focusables, int count) {
+ private static View getNextFocusable(View focused, ArrayList<View> focusables, int count,
+ boolean[] outLooped) {
if (count < 2) {
return null;
}
@@ -385,10 +393,12 @@
return focusables.get(position + 1);
}
}
+ outLooped[0] = true;
return focusables.get(0);
}
- private static View getPreviousFocusable(View focused, ArrayList<View> focusables, int count) {
+ private static View getPreviousFocusable(View focused, ArrayList<View> focusables, int count,
+ boolean[] outLooped) {
if (count < 2) {
return null;
}
@@ -398,6 +408,7 @@
return focusables.get(position - 1);
}
}
+ outLooped[0] = true;
return focusables.get(count - 1);
}
diff --git a/core/java/android/view/HdrRenderState.java b/core/java/android/view/HdrRenderState.java
index 2fbbf48..eadc507 100644
--- a/core/java/android/view/HdrRenderState.java
+++ b/core/java/android/view/HdrRenderState.java
@@ -31,9 +31,11 @@
private final ViewRootImpl mViewRoot;
+ private boolean mIsHdrEnabled = false;
private boolean mIsListenerRegistered = false;
private boolean mUpdateHdrSdrRatioInfo = false;
private float mDesiredHdrSdrRatio = 1f;
+ private float mTargetDesiredHdrSdrRatio = 1f;
private float mTargetHdrSdrRatio = 1f;
private float mRenderHdrSdrRatio = 1f;
private float mPreviousRenderRatio = 1f;
@@ -50,7 +52,7 @@
}
boolean isHdrEnabled() {
- return mDesiredHdrSdrRatio >= 1.01f;
+ return mIsHdrEnabled;
}
void stopListening() {
@@ -75,9 +77,7 @@
final float maxStep = timeDelta * TRANSITION_PER_MS;
mLastUpdateMillis = frameTimeMillis;
if (hasUpdate && FLAG_ANIMATE_ENABLED) {
- if (mTargetHdrSdrRatio == 1.0f) {
- mPreviousRenderRatio = mTargetHdrSdrRatio;
- } else {
+ if (isHdrEnabled()) {
float delta = mTargetHdrSdrRatio - mPreviousRenderRatio;
if (delta > maxStep) {
mRenderHdrSdrRatio = mPreviousRenderRatio + maxStep;
@@ -85,6 +85,19 @@
mViewRoot.invalidate();
}
mPreviousRenderRatio = mRenderHdrSdrRatio;
+
+ if (mTargetDesiredHdrSdrRatio < mDesiredHdrSdrRatio) {
+ mDesiredHdrSdrRatio = Math.max(mTargetDesiredHdrSdrRatio,
+ mDesiredHdrSdrRatio - maxStep);
+ if (mDesiredHdrSdrRatio != mTargetDesiredHdrSdrRatio) {
+ mUpdateHdrSdrRatioInfo = true;
+ mViewRoot.invalidate();
+ }
+ }
+
+ } else {
+ mPreviousRenderRatio = mTargetHdrSdrRatio;
+ mDesiredHdrSdrRatio = mTargetDesiredHdrSdrRatio;
}
}
return hasUpdate;
@@ -99,15 +112,23 @@
}
void forceUpdateHdrSdrRatio() {
- mTargetHdrSdrRatio = Math.min(mDesiredHdrSdrRatio, mViewRoot.mDisplay.getHdrSdrRatio());
+ if (isHdrEnabled()) {
+ mTargetHdrSdrRatio = Math.min(mDesiredHdrSdrRatio,
+ mViewRoot.mDisplay.getHdrSdrRatio());
+ } else {
+ mTargetHdrSdrRatio = 1.0f;
+ }
mUpdateHdrSdrRatioInfo = true;
}
- void setDesiredHdrSdrRatio(float desiredRatio) {
+ void setDesiredHdrSdrRatio(boolean isHdrEnabled, float desiredRatio) {
+ mIsHdrEnabled = isHdrEnabled;
mLastUpdateMillis = SystemClock.uptimeMillis();
- // TODO: When decreasing the desired ratio we need to animate it downwards
- if (desiredRatio != mDesiredHdrSdrRatio) {
- mDesiredHdrSdrRatio = desiredRatio;
+ if (desiredRatio != mTargetDesiredHdrSdrRatio) {
+ mTargetDesiredHdrSdrRatio = desiredRatio;
+ if (mTargetDesiredHdrSdrRatio > mDesiredHdrSdrRatio || !FLAG_ANIMATE_ENABLED) {
+ mDesiredHdrSdrRatio = mTargetDesiredHdrSdrRatio;
+ }
forceUpdateHdrSdrRatio();
mViewRoot.invalidate();
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 7903050..99863d0 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -1085,7 +1085,9 @@
void unregisterTrustedPresentationListener(in ITrustedPresentationListener listener, int id);
+ @EnforcePermission("DETECT_SCREEN_RECORDING")
boolean registerScreenRecordingCallback(IScreenRecordingCallback callback);
+ @EnforcePermission("DETECT_SCREEN_RECORDING")
void unregisterScreenRecordingCallback(IScreenRecordingCallback callback);
}
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index c6601e8d..1ee9509 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -19,6 +19,7 @@
import static android.os.IInputConstants.INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT;
import static android.view.Display.INVALID_DISPLAY;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
@@ -31,6 +32,8 @@
import android.util.SparseIntArray;
import android.view.KeyCharacterMap.KeyData;
+import com.android.hardware.input.Flags;
+
import java.util.concurrent.TimeUnit;
/**
@@ -384,7 +387,13 @@
public static final int KEYCODE_META_RIGHT = 118;
/** Key code constant: Function modifier key. */
public static final int KEYCODE_FUNCTION = 119;
- /** Key code constant: System Request / Print Screen key. */
+ /**
+ * Key code constant: System Request / Print Screen key.
+ *
+ * This key is sent to the app first and only if the app doesn't handle it, the framework
+ * handles it (to take a screenshot), unlike {@code KEYCODE_TAKE_SCREENSHOT} which is
+ * fully handled by the framework.
+ */
public static final int KEYCODE_SYSRQ = 120;
/** Key code constant: Break / Pause key. */
public static final int KEYCODE_BREAK = 121;
@@ -921,14 +930,25 @@
* User customizable key #4.
*/
public static final int KEYCODE_MACRO_4 = 316;
-
+ /** Key code constant: To open emoji picker */
+ @FlaggedApi(Flags.FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE)
+ public static final int KEYCODE_EMOJI_PICKER = 317;
+ /**
+ * Key code constant: To take a screenshot
+ *
+ * This key is fully handled by the framework and will not be sent to the foreground app,
+ * unlike {@code KEYCODE_SYSRQ} which is sent to the app first and only if the app
+ * doesn't handle it, the framework handles it (to take a screenshot).
+ */
+ @FlaggedApi(Flags.FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE)
+ public static final int KEYCODE_SCREENSHOT = 318;
/**
* Integer value of the last KEYCODE. Increases as new keycodes are added to KeyEvent.
* @hide
*/
@TestApi
- public static final int LAST_KEYCODE = KEYCODE_MACRO_4;
+ public static final int LAST_KEYCODE = KEYCODE_SCREENSHOT;
// NOTE: If you add a new keycode here you must also add it to:
// isSystem()
diff --git a/core/java/android/view/ScreenRecordingCallbacks.java b/core/java/android/view/ScreenRecordingCallbacks.java
new file mode 100644
index 0000000..ee55737
--- /dev/null
+++ b/core/java/android/view/ScreenRecordingCallbacks.java
@@ -0,0 +1,146 @@
+/*
+ * 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 android.view;
+
+import static android.Manifest.permission.DETECT_SCREEN_RECORDING;
+import static android.view.WindowManager.SCREEN_RECORDING_STATE_NOT_VISIBLE;
+import static android.view.WindowManager.SCREEN_RECORDING_STATE_VISIBLE;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.view.WindowManager.ScreenRecordingState;
+import android.window.IScreenRecordingCallback;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * This class is responsible for calling app-registered screen recording callbacks. This class
+ * registers a single screen recording callback with WindowManagerService and calls the
+ * app-registered callbacks whenever that WindowManagerService callback is called.
+ *
+ * @hide
+ */
+public final class ScreenRecordingCallbacks {
+
+ private static ScreenRecordingCallbacks sInstance;
+ private static final Object sLock = new Object();
+
+ private final ArrayMap<Consumer<@ScreenRecordingState Integer>, Executor> mCallbacks =
+ new ArrayMap<>();
+
+ private IScreenRecordingCallback mCallbackNotifier;
+ private @ScreenRecordingState int mState = SCREEN_RECORDING_STATE_NOT_VISIBLE;
+
+ private ScreenRecordingCallbacks() {}
+
+ private static @NonNull IWindowManager getWindowManagerService() {
+ return Objects.requireNonNull(WindowManagerGlobal.getWindowManagerService());
+ }
+
+ static ScreenRecordingCallbacks getInstance() {
+ synchronized (sLock) {
+ if (sInstance == null) {
+ sInstance = new ScreenRecordingCallbacks();
+ }
+ return sInstance;
+ }
+ }
+
+ @RequiresPermission(DETECT_SCREEN_RECORDING)
+ @ScreenRecordingState
+ int addCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<@ScreenRecordingState Integer> callback) {
+ synchronized (sLock) {
+ if (mCallbackNotifier == null) {
+ mCallbackNotifier =
+ new IScreenRecordingCallback.Stub() {
+ @Override
+ public void onScreenRecordingStateChanged(
+ boolean visibleInScreenRecording) {
+ int state =
+ visibleInScreenRecording
+ ? SCREEN_RECORDING_STATE_VISIBLE
+ : SCREEN_RECORDING_STATE_NOT_VISIBLE;
+ notifyCallbacks(state);
+ }
+ };
+ try {
+ boolean visibleInScreenRecording =
+ getWindowManagerService()
+ .registerScreenRecordingCallback(mCallbackNotifier);
+ mState =
+ visibleInScreenRecording
+ ? SCREEN_RECORDING_STATE_VISIBLE
+ : SCREEN_RECORDING_STATE_NOT_VISIBLE;
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ mCallbacks.put(callback, executor);
+ return mState;
+ }
+ }
+
+ @RequiresPermission(DETECT_SCREEN_RECORDING)
+ void removeCallback(@NonNull Consumer<@ScreenRecordingState Integer> callback) {
+ synchronized (sLock) {
+ mCallbacks.remove(callback);
+ if (mCallbacks.isEmpty()) {
+ try {
+ getWindowManagerService().unregisterScreenRecordingCallback(mCallbackNotifier);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ mCallbackNotifier = null;
+ }
+ }
+ }
+
+ private void notifyCallbacks(@ScreenRecordingState int state) {
+ List<Runnable> callbacks;
+ synchronized (sLock) {
+ mState = state;
+ if (mCallbacks.isEmpty()) {
+ return;
+ }
+
+ callbacks = new ArrayList<>();
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ Consumer<Integer> callback = mCallbacks.keyAt(i);
+ Executor executor = mCallbacks.valueAt(i);
+ callbacks.add(() -> executor.execute(() -> callback.accept(state)));
+ }
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ for (int i = 0; i < callbacks.size(); i++) {
+ callbacks.get(i).run();
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 1b22fda..2366ff7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5537,10 +5537,20 @@
*/
private static final float FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD = 0.07f;
+
+ private static final long INFREQUENT_UPDATE_INTERVAL_MILLIS = 100;
+ private static final int INFREQUENT_UPDATE_COUNTS = 2;
+
// The preferred frame rate of the view that is mainly used for
// touch boosting, view velocity handling, and TextureView.
private float mPreferredFrameRate = REQUESTED_FRAME_RATE_CATEGORY_DEFAULT;
+ private int mInfrequentUpdateCount = 0;
+ private long mLastUpdateTimeMillis = 0;
+ private long mMinusOneFrameIntervalMillis = 0;
+ private long mMinusTwoFrameIntervalMillis = 0;
+ private int mLastFrameRateCategory = FRAME_RATE_CATEGORY_HIGH;
+
@FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
public static final float REQUESTED_FRAME_RATE_CATEGORY_DEFAULT = 0;
@FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
@@ -20253,7 +20263,10 @@
}
// For VRR to vote the preferred frame rate
- votePreferredFrameRate();
+ if (sToolkitSetFrameRateReadOnlyFlagValue) {
+ updateInfrequentCount();
+ votePreferredFrameRate();
+ }
// Reset content capture caches
mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK;
@@ -20358,7 +20371,10 @@
protected void damageInParent() {
if (mParent != null && mAttachInfo != null) {
// For VRR to vote the preferred frame rate
- votePreferredFrameRate();
+ if (sToolkitSetFrameRateReadOnlyFlagValue) {
+ updateInfrequentCount();
+ votePreferredFrameRate();
+ }
mParent.onDescendantInvalidated(this, this);
}
}
@@ -31336,6 +31352,13 @@
final ArrayList<View> mTempArrayList = new ArrayList<View>(24);
/**
+ * Indicates if the next focus will be looped back to the first focusable view of the entire
+ * hierarchy when finding in the direction of {@link #FOCUS_FORWARD} or to the last
+ * focusable view when finding in the direction of {@link #FOCUS_BACKWARD}.
+ */
+ boolean mNextFocusLooped = false;
+
+ /**
* The id of the window for accessibility purposes.
*/
int mAccessibilityWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
@@ -33124,11 +33147,20 @@
}
private int calculateFrameRateCategory(float sizePercentage) {
- if (sizePercentage <= FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD) {
- return FRAME_RATE_CATEGORY_LOW;
- } else {
+ if (mMinusTwoFrameIntervalMillis + mMinusOneFrameIntervalMillis
+ < INFREQUENT_UPDATE_INTERVAL_MILLIS) {
+ if (sizePercentage <= FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD) {
+ return FRAME_RATE_CATEGORY_NORMAL;
+ } else {
+ return FRAME_RATE_CATEGORY_HIGH;
+ }
+ }
+
+ if (mInfrequentUpdateCount == INFREQUENT_UPDATE_COUNTS) {
return FRAME_RATE_CATEGORY_NORMAL;
}
+
+ return mLastFrameRateCategory;
}
private void votePreferredFrameRate() {
@@ -33137,22 +33169,22 @@
float sizePercentage = getSizePercentage();
int frameRateCateogry = calculateFrameRateCategory(sizePercentage);
if (viewRootImpl != null && sizePercentage > 0) {
- if (sToolkitSetFrameRateReadOnlyFlagValue) {
- if (mPreferredFrameRate < 0) {
- if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
- frameRateCateogry = FRAME_RATE_CATEGORY_NO_PREFERENCE;
- } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
- frameRateCateogry = FRAME_RATE_CATEGORY_LOW;
- } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
- frameRateCateogry = FRAME_RATE_CATEGORY_NORMAL;
- } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
- frameRateCateogry = FRAME_RATE_CATEGORY_HIGH;
- }
- } else {
- viewRootImpl.votePreferredFrameRate(mPreferredFrameRate);
+ if (mPreferredFrameRate < 0) {
+ if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
+ frameRateCateogry = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+ } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
+ frameRateCateogry = FRAME_RATE_CATEGORY_LOW;
+ } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
+ frameRateCateogry = FRAME_RATE_CATEGORY_NORMAL;
+ } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
+ frameRateCateogry = FRAME_RATE_CATEGORY_HIGH;
}
- viewRootImpl.votePreferredFrameRateCategory(frameRateCateogry);
+ } else {
+ viewRootImpl.votePreferredFrameRate(mPreferredFrameRate);
}
+ viewRootImpl.votePreferredFrameRateCategory(frameRateCateogry);
+ mLastFrameRateCategory = frameRateCateogry;
+
if (sToolkitMetricsForFrameRateDecisionFlagValue) {
viewRootImpl.recordViewPercentage(sizePercentage);
}
@@ -33231,4 +33263,27 @@
}
return 0;
}
+
+ /**
+ * This function is mainly used for migrating infrequent layer lagic
+ * from SurfaceFlinger to Toolkit.
+ * The infrequent layter logic includes:
+ * - NORMAL for infrequent update: FT2-FT1 > 100 && FT3-FT2 > 100.
+ * - HIGH/NORMAL based on size for frequent update: (FT3-FT2) + (FT2 - FT1) < 100.
+ * - otherwise, use the previous category value.
+ */
+ private void updateInfrequentCount() {
+ long currentTimeMillis = AnimationUtils.currentAnimationTimeMillis();
+ long timeIntervalMillis = currentTimeMillis - mLastUpdateTimeMillis;
+ mMinusTwoFrameIntervalMillis = mMinusOneFrameIntervalMillis;
+ mMinusOneFrameIntervalMillis = timeIntervalMillis;
+
+ mLastUpdateTimeMillis = currentTimeMillis;
+ if (timeIntervalMillis >= INFREQUENT_UPDATE_INTERVAL_MILLIS) {
+ mInfrequentUpdateCount = mInfrequentUpdateCount == INFREQUENT_UPDATE_COUNTS
+ ? mInfrequentUpdateCount : mInfrequentUpdateCount + 1;
+ } else {
+ mInfrequentUpdateCount = 0;
+ }
+ }
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 7bc832e..c27b2b1 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -399,6 +399,8 @@
@Nullable
private ContentObserver mForceInvertObserver;
+ private static final int INVALID_VALUE = Integer.MIN_VALUE;
+ private int mForceInvertEnabled = INVALID_VALUE;
/**
* Callback for notifying about global configuration changes.
*/
@@ -1023,6 +1025,13 @@
// time for revaluating the idle status before lowering the frame rate.
private static final int FRAME_RATE_IDLENESS_REEVALUATE_TIME = 500;
+ /*
+ * the variables below are used to determine whther a dVRR feature should be enabled
+ */
+
+ // Used to determine whether to suppress boost on typing
+ private boolean mShouldSuppressBoostOnTyping = false;
+
/**
* A temporary object used so relayoutWindow can return the latest SyncSeqId
* system. The SyncSeqId system was designed to work without synchronous relayout
@@ -1604,6 +1613,23 @@
}
}
+ private boolean isForceInvertEnabled() {
+ if (mForceInvertEnabled == INVALID_VALUE) {
+ reloadForceInvertEnabled();
+ }
+ return mForceInvertEnabled == 1;
+ }
+
+ private void reloadForceInvertEnabled() {
+ if (forceInvertColor()) {
+ mForceInvertEnabled = Settings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
+ /* def= */ 0,
+ UserHandle.myUserId());
+ }
+ }
+
/**
* Register any kind of listeners if setView was success.
*/
@@ -1630,6 +1656,7 @@
mForceInvertObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
+ reloadForceInvertEnabled();
updateForceDarkMode();
}
};
@@ -1850,16 +1877,11 @@
@VisibleForTesting
public @ForceDarkType.ForceDarkTypeDef int determineForceDarkType() {
if (forceInvertColor()) {
- boolean isForceInvertEnabled = Settings.Secure.getIntForUser(
- mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
- /* def= */ 0,
- UserHandle.myUserId()) == 1;
// Force invert ignores all developer opt-outs.
// We also ignore dark theme, since the app developer can override the user's preference
// for dark mode in configuration.uiMode. Instead, we assume that the force invert
// setting will be enabled at the same time dark theme is in the Settings app.
- if (isForceInvertEnabled) {
+ if (isForceInvertEnabled()) {
return ForceDarkType.FORCE_INVERT_COLOR_DARK;
}
}
@@ -5809,9 +5831,11 @@
if (mAttachInfo.mThreadedRenderer == null) {
return;
}
- if ((colorMode == ActivityInfo.COLOR_MODE_HDR || colorMode == ActivityInfo.COLOR_MODE_HDR10)
- && !mDisplay.isHdrSdrRatioAvailable()) {
+ boolean isHdr = colorMode == ActivityInfo.COLOR_MODE_HDR
+ || colorMode == ActivityInfo.COLOR_MODE_HDR10;
+ if (isHdr && !mDisplay.isHdrSdrRatioAvailable()) {
colorMode = ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT;
+ isHdr = false;
}
// TODO: Centralize this sanitization? Why do we let setting bad modes?
// Alternatively, can we just let HWUI figure it out? Do we need to care here?
@@ -5824,7 +5848,7 @@
desiredRatio = automaticRatio;
}
- mHdrRenderState.setDesiredHdrSdrRatio(desiredRatio);
+ mHdrRenderState.setDesiredHdrSdrRatio(isHdr, desiredRatio);
}
@Override
@@ -7266,8 +7290,18 @@
if (direction != 0) {
View focused = mView.findFocus();
if (focused != null) {
+ mAttachInfo.mNextFocusLooped = false;
View v = focused.focusSearch(direction);
if (v != null && v != focused) {
+ if (mAttachInfo.mNextFocusLooped) {
+ // The next focus is looped. Let's try to move the focus to the adjacent
+ // window. Note: we still need to move the focus in this window
+ // regardless of what moveFocusToAdjacentWindow returns, so the focus
+ // can be looped back from the focus in the adjacent window to next
+ // focus of this window.
+ moveFocusToAdjacentWindow(direction);
+ }
+
// do the math the get the interesting rect
// of previous focused into the coord system of
// newly focused view
@@ -12258,7 +12292,7 @@
boolean desiredAction = motionEventAction == MotionEvent.ACTION_DOWN
|| motionEventAction == MotionEvent.ACTION_MOVE
|| motionEventAction == MotionEvent.ACTION_UP;
- boolean undesiredType = windowType == TYPE_INPUT_METHOD;
+ boolean undesiredType = windowType == TYPE_INPUT_METHOD && mShouldSuppressBoostOnTyping;
// use toolkitSetFrameRate flag to gate the change
return desiredAction && !undesiredType && sToolkitSetFrameRateReadOnlyFlagValue
&& getFrameRateBoostOnTouchEnabled();
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index c788261..38cf490 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -128,8 +128,10 @@
import com.android.window.flags.Flags;
+import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -6117,4 +6119,65 @@
throw new UnsupportedOperationException(
"getDefaultToken is not implemented");
}
+
+ /** @hide */
+ @Target(ElementType.TYPE_USE)
+ @IntDef(
+ prefix = {"SCREEN_RECORDING_STATE"},
+ value = {SCREEN_RECORDING_STATE_NOT_VISIBLE, SCREEN_RECORDING_STATE_VISIBLE})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface ScreenRecordingState {}
+
+ /** Indicates the app that registered the callback is not visible in screen recording. */
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_SCREEN_RECORDING_CALLBACKS)
+ int SCREEN_RECORDING_STATE_NOT_VISIBLE = 0;
+
+ /** Indicates the app that registered the callback is visible in screen recording. */
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_SCREEN_RECORDING_CALLBACKS)
+ int SCREEN_RECORDING_STATE_VISIBLE = 1;
+
+ /**
+ * Adds a screen recording callback. The callback will be invoked whenever the app becomes
+ * visible in screen recording or was visible in screen recording and becomes invisible in
+ * screen recording.
+ *
+ * <p>An app is considered visible in screen recording if any activities owned by the
+ * registering process's UID are being recorded.
+ *
+ * <p>Example:
+ *
+ * <pre>
+ * windowManager.addScreenRecordingCallback(state -> {
+ * // handle change in screen recording state
+ * });
+ * </pre>
+ *
+ * @param executor The executor on which callback method will be invoked.
+ * @param callback The callback that will be invoked when screen recording visibility changes.
+ * @return the current screen recording state.
+ * @see #SCREEN_RECORDING_STATE_NOT_VISIBLE
+ * @see #SCREEN_RECORDING_STATE_VISIBLE
+ */
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ @RequiresPermission(permission.DETECT_SCREEN_RECORDING)
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_SCREEN_RECORDING_CALLBACKS)
+ default @ScreenRecordingState int addScreenRecordingCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<@ScreenRecordingState Integer> callback) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Removes a screen recording callback.
+ *
+ * @param callback The callback to remove.
+ * @see #addScreenRecordingCallback(Executor, Consumer)
+ */
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ @RequiresPermission(permission.DETECT_SCREEN_RECORDING)
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_SCREEN_RECORDING_CALLBACKS)
+ default void removeScreenRecordingCallback(
+ @NonNull Consumer<@ScreenRecordingState Integer> callback) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 5072ad7..eaf45c4 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -20,6 +20,8 @@
import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
import static android.window.WindowProviderService.isWindowProviderService;
+import static com.android.window.flags.Flags.screenRecordingCallbacks;
+
import android.annotation.CallbackExecutor;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -551,4 +553,25 @@
public IBinder getSurfaceControlInputClientToken(@NonNull SurfaceControl surfaceControl) {
return mGlobal.getSurfaceControlInputClientToken(surfaceControl);
}
+
+ @Override
+ public @ScreenRecordingState int addScreenRecordingCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<@ScreenRecordingState Integer> callback) {
+ if (screenRecordingCallbacks()) {
+ Objects.requireNonNull(executor, "executor must not be null");
+ Objects.requireNonNull(callback, "callback must not be null");
+ return ScreenRecordingCallbacks.getInstance().addCallback(executor, callback);
+ }
+ return SCREEN_RECORDING_STATE_NOT_VISIBLE;
+ }
+
+ @Override
+ public void removeScreenRecordingCallback(
+ @NonNull Consumer<@ScreenRecordingState Integer> callback) {
+ if (screenRecordingCallbacks()) {
+ Objects.requireNonNull(callback, "callback must not be null");
+ ScreenRecordingCallbacks.getInstance().removeCallback(callback);
+ }
+ }
}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index ef1bf5a..146b576 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -2424,7 +2424,7 @@
}
}
try {
- service.attachAccessibilityOverlayToDisplay_enforcePermission(
+ service.attachAccessibilityOverlayToDisplay(
displayId, surfaceControl);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 1c5d29e..eca1586 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -139,5 +139,5 @@
WindowTransformationSpec getWindowTransformationSpec(int windowId);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)")
- void attachAccessibilityOverlayToDisplay_enforcePermission(int displayId, in SurfaceControl surfaceControl);
+ void attachAccessibilityOverlayToDisplay(int displayId, in SurfaceControl surfaceControl);
}
diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig
index 0aa516e..9d613bc 100644
--- a/core/java/android/view/flags/refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/refresh_rate_flags.aconfig
@@ -50,4 +50,28 @@
description: "Feature flag for toolkit metrics collecting for frame rate decision"
bug: "301343249"
is_fixed_read_only: true
+}
+
+flag {
+ name: "toolkit_frame_rate_default_normal_read_only"
+ namespace: "toolkit"
+ description: "Feature flag for setting frame rate category as NORMAL for default"
+ bug: "239979904"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "toolkit_frame_rate_by_size_read_only"
+ namespace: "toolkit"
+ description: "Feature flag for setting frame rate category based on size"
+ bug: "239979904"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "toolkit_frame_rate_velocity_mapping_read_only"
+ namespace: "toolkit"
+ description: "Feature flag for setting frame rate based on velocity"
+ bug: "239979904"
+ is_fixed_read_only: true
}
\ No newline at end of file
diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java
index c6271d2..2f765ae 100644
--- a/core/java/android/webkit/URLUtil.java
+++ b/core/java/android/webkit/URLUtil.java
@@ -16,7 +16,6 @@
package android.webkit;
-import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.Compatibility;
@@ -47,8 +46,7 @@
*/
@ChangeId
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
- @FlaggedApi(android.os.Flags.FLAG_ANDROID_OS_BUILD_VANILLA_ICE_CREAM)
- public static final long PARSE_CONTENT_DISPOSITION_USING_RFC_6266 = 319400769L;
+ static final long PARSE_CONTENT_DISPOSITION_USING_RFC_6266 = 319400769L;
private static final String LOGTAG = "webkit";
private static final boolean TRACE = false;
diff --git a/core/java/android/webkit/flags.aconfig b/core/java/android/webkit/flags.aconfig
new file mode 100644
index 0000000..6938b29
--- /dev/null
+++ b/core/java/android/webkit/flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.webkit"
+
+flag {
+ name: "update_service_ipc_wrapper"
+ namespace: "webview"
+ description: "New API: proper wrapper for IWebViewUpdateService"
+ bug: "319292658"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 0654add..2433bd8 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -7597,14 +7597,6 @@
}
/**
- * Append additional instructions to this {@link DrawInstructions} object.
- */
- @FlaggedApi(FLAG_DRAW_DATA_PARCEL)
- public void appendInstructions(@NonNull final byte[] instructions) {
- mInstructions.add(instructions);
- }
-
- /**
* Builder class for {@link DrawInstructions} objects.
*/
@FlaggedApi(FLAG_DRAW_DATA_PARCEL)
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index e812f85..90d5140 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -243,6 +243,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastMath;
import com.android.internal.util.Preconditions;
+import com.android.text.flags.Flags;
import libcore.util.EmptyArray;
@@ -539,7 +540,6 @@
// System wide time for last cut, copy or text changed action.
static long sLastCutCopyOrTextChangedTime;
-
private ColorStateList mTextColor;
private ColorStateList mHintTextColor;
private ColorStateList mLinkTextColor;
@@ -2857,7 +2857,38 @@
}
if (updateText) {
- setText(mText);
+ if (Flags.insertModeNotUpdateSelection()) {
+ // Update the transformation text.
+ if (mTransformation == null) {
+ mTransformed = mText;
+ } else {
+ mTransformed = mTransformation.getTransformation(mText, this);
+ }
+ if (mTransformed == null) {
+ // Should not happen if the transformation method follows the non-null
+ // postcondition.
+ mTransformed = "";
+ }
+ final boolean isOffsetMapping = mTransformed instanceof OffsetMapping;
+
+ // If the mText is a Spannable and the new TransformationMethod needs to listen to
+ // its updates, apply the watcher on it.
+ if (mTransformation != null && mText instanceof Spannable
+ && (!mAllowTransformationLengthChange || isOffsetMapping)) {
+ Spannable sp = (Spannable) mText;
+ final int priority = isOffsetMapping ? OFFSET_MAPPING_SPAN_PRIORITY : 0;
+ sp.setSpan(mTransformation, 0, mText.length(),
+ Spanned.SPAN_INCLUSIVE_INCLUSIVE
+ | (priority << Spanned.SPAN_PRIORITY_SHIFT));
+ }
+ if (mLayout != null) {
+ nullLayouts();
+ requestLayout();
+ invalidate();
+ }
+ } else {
+ setText(mText);
+ }
}
if (hasPasswordTransformationMethod()) {
diff --git a/core/java/android/widget/flags/notification_widget_flags.aconfig b/core/java/android/widget/flags/notification_widget_flags.aconfig
new file mode 100644
index 0000000..9f0b7c3
--- /dev/null
+++ b/core/java/android/widget/flags/notification_widget_flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.widget.flags"
+
+flag {
+ name: "notif_linearlayout_optimized"
+ namespace: "systemui"
+ description: "Enables notification specific LinearLayout optimization"
+ bug: "316110233"
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/IAppOpsActiveCallback.aidl b/core/java/com/android/internal/app/IAppOpsActiveCallback.aidl
index ae6ad32..94bbd72 100644
--- a/core/java/com/android/internal/app/IAppOpsActiveCallback.aidl
+++ b/core/java/com/android/internal/app/IAppOpsActiveCallback.aidl
@@ -19,5 +19,5 @@
// Iterface to observe op active changes
oneway interface IAppOpsActiveCallback {
void opActiveChanged(int op, int uid, String packageName, String attributionTag,
- boolean active, int attributionFlags, int attributionChainId);
+ int virtualDeviceId, boolean active, int attributionFlags, int attributionChainId);
}
diff --git a/core/java/com/android/internal/app/IAppOpsCallback.aidl b/core/java/com/android/internal/app/IAppOpsCallback.aidl
index 024ff66..3a9525c 100644
--- a/core/java/com/android/internal/app/IAppOpsCallback.aidl
+++ b/core/java/com/android/internal/app/IAppOpsCallback.aidl
@@ -19,5 +19,5 @@
// This interface is also used by native code, so must
// be kept in sync with frameworks/native/libs/permission/include/binder/IAppOpsCallback.h
oneway interface IAppOpsCallback {
- void opChanged(int op, int uid, String packageName);
+ void opChanged(int op, int uid, String packageName, String persistentDeviceId);
}
diff --git a/core/java/com/android/internal/app/IAppOpsNotedCallback.aidl b/core/java/com/android/internal/app/IAppOpsNotedCallback.aidl
index f3759e0..123f4f4 100644
--- a/core/java/com/android/internal/app/IAppOpsNotedCallback.aidl
+++ b/core/java/com/android/internal/app/IAppOpsNotedCallback.aidl
@@ -18,5 +18,6 @@
// Iterface to observe op note/checks of ops
oneway interface IAppOpsNotedCallback {
- void opNoted(int op, int uid, String packageName, String attributionTag, int flags, int mode);
+ void opNoted(int op, int uid, String packageName, String attributionTag, int virtualDeviceId,
+ int flags, int mode);
}
diff --git a/core/java/com/android/internal/app/IAppOpsStartedCallback.aidl b/core/java/com/android/internal/app/IAppOpsStartedCallback.aidl
index 06640cb..fdae37a 100644
--- a/core/java/com/android/internal/app/IAppOpsStartedCallback.aidl
+++ b/core/java/com/android/internal/app/IAppOpsStartedCallback.aidl
@@ -18,6 +18,6 @@
// Iterface to observe op starts
oneway interface IAppOpsStartedCallback {
- void opStarted(int op, int uid, String packageName, String attributionTag, int flags, int mode,
- int startedType, int attributionFlags, int attributionChainId);
+ void opStarted(int op, int uid, String packageName, String attributionTag, int virtualDeviceId,
+ int flags, int mode, int startedType, int attributionFlags, int attributionChainId);
}
diff --git a/core/java/com/android/internal/os/AndroidPrintStream.java b/core/java/com/android/internal/os/AndroidPrintStream.java
index a6e41ff..bb388bb 100644
--- a/core/java/com/android/internal/os/AndroidPrintStream.java
+++ b/core/java/com/android/internal/os/AndroidPrintStream.java
@@ -24,6 +24,7 @@
*
* {@hide}
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
class AndroidPrintStream extends LoggingPrintStream {
private final int priority;
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 28b98d6..cdac097 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -39,6 +39,7 @@
import libcore.content.type.MimeMap;
+import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
@@ -50,6 +51,7 @@
* public consumption.
* @hide
*/
+@android.ravenwood.annotation.RavenwoodKeepPartialClass
public class RuntimeInit {
final static String TAG = "AndroidRuntime";
final static boolean DEBUG = false;
@@ -67,7 +69,15 @@
private static volatile ApplicationWtfHandler sDefaultApplicationWtfHandler;
+ /**
+ * Stored values of System.out and System.err before they've been replaced by
+ * redirectLogStreams(). Kept open here for other Ravenwood internals to use.
+ */
+ public static PrintStream sOut$ravenwood;
+ public static PrintStream sErr$ravenwood;
+
private static final native void nativeFinishInit();
+
private static final native void nativeSetExitWithoutCleanup(boolean exitWithoutCleanup);
private static int Clog_e(String tag, String msg, Throwable tr) {
@@ -385,6 +395,7 @@
/**
* Redirect System.out and System.err to the Android log.
*/
+ @android.ravenwood.annotation.RavenwoodReplace
public static void redirectLogStreams() {
System.out.close();
System.setOut(new AndroidPrintStream(Log.INFO, "System.out"));
@@ -392,6 +403,17 @@
System.setErr(new AndroidPrintStream(Log.WARN, "System.err"));
}
+ public static void redirectLogStreams$ravenwood() {
+ if (sOut$ravenwood == null) {
+ sOut$ravenwood = System.out;
+ System.setOut(new AndroidPrintStream(Log.INFO, "System.out"));
+ }
+ if (sErr$ravenwood == null) {
+ sErr$ravenwood = System.err;
+ System.setErr(new AndroidPrintStream(Log.WARN, "System.err"));
+ }
+ }
+
/**
* Report a serious error in the current process. May or may not cause
* the process to terminate (depends on system settings).
@@ -399,6 +421,7 @@
* @param tag to record with the error
* @param t exception describing the error site and conditions
*/
+ @android.ravenwood.annotation.RavenwoodReplace
public static void wtf(String tag, Throwable t, boolean system) {
try {
boolean exit = false;
@@ -436,6 +459,11 @@
}
}
+ public static void wtf$ravenwood(String tag, Throwable t, boolean system) {
+ // We've already emitted to logs, so there's nothing more to do here,
+ // as we don't have a DropBox pipeline configured
+ }
+
/**
* Set the default {@link ApplicationWtfHandler}, in case the ActivityManager is not ready yet.
*/
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 4e3b64c..b4f9ee3 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -2583,7 +2583,7 @@
mNavigationBarDividerColor = a.getColor(R.styleable.Window_navigationBarDividerColor,
Color.TRANSPARENT);
}
- if (!targetPreQ && !mEdgeToEdgeEnforced) {
+ if (!targetPreQ) {
mEnsureStatusBarContrastWhenTransparent = a.getBoolean(
R.styleable.Window_enforceStatusBarContrast, false);
mEnsureNavigationBarContrastWhenTransparent = a.getBoolean(
@@ -3966,9 +3966,6 @@
@Override
public void setStatusBarContrastEnforced(boolean ensureContrast) {
- if (mEdgeToEdgeEnforced) {
- return;
- }
mEnsureStatusBarContrastWhenTransparent = ensureContrast;
if (mDecor != null) {
mDecor.updateColorViews(null, false /* animate */);
@@ -3982,9 +3979,6 @@
@Override
public void setNavigationBarContrastEnforced(boolean enforceContrast) {
- if (mEdgeToEdgeEnforced) {
- return;
- }
mEnsureNavigationBarContrastWhenTransparent = enforceContrast;
if (mDecor != null) {
mDecor.updateColorViews(null, false /* animate */);
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 5351c6d..ed43b81 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -151,8 +151,17 @@
* @param hidesStatusBar whether it is being hidden
*/
void setTopAppHidesStatusBar(boolean hidesStatusBar);
-
+ /**
+ * Add a tile to the Quick Settings Panel to the first item in the QS Panel
+ * @param tile the ComponentName of the {@link android.service.quicksettings.TileService}
+ */
void addQsTile(in ComponentName tile);
+ /**
+ * Add a tile to the Quick Settings Panel
+ * @param tile the ComponentName of the {@link android.service.quicksettings.TileService}
+ * @param end if true, the tile will be added at the end. If false, at the beginning.
+ */
+ void addQsTileToFrontOrEnd(in ComponentName tile, boolean end);
void remQsTile(in ComponentName tile);
void setQsTiles(in String[] tiles);
void clickQsTile(in ComponentName tile);
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 656cc3e..3fc1683 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -284,7 +284,6 @@
"libscrypt_static",
"libstatssocket_lazy",
"libskia",
- "libperfetto_client_experimental",
],
shared_libs: [
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 3413ede..969e47b 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -25,6 +25,7 @@
#include <android_os_Parcel.h>
#include <audiomanager/AudioManager.h>
#include <jni.h>
+#include <media/AidlConversion.h>
#include <media/AudioContainers.h>
#include <media/AudioPolicy.h>
#include <media/AudioSystem.h>
@@ -66,6 +67,11 @@
jmethodID toArray;
} gArrayListMethods;
+static jclass gIntArrayClass;
+static struct {
+ jmethodID add;
+} gIntArrayMethods;
+
static jclass gBooleanClass;
static jmethodID gBooleanCstor;
@@ -1607,6 +1613,48 @@
return jStatus;
}
+// From AudioDeviceInfo
+static const int GET_DEVICES_INPUTS = 0x0001;
+static const int GET_DEVICES_OUTPUTS = 0x0002;
+
+static int android_media_AudioSystem_getSupportedDeviceTypes(JNIEnv *env, jobject clazz,
+ jint direction, jobject jDeviceTypes) {
+ if (jDeviceTypes == NULL) {
+ ALOGE("%s NULL Device Types IntArray", __func__);
+ return AUDIO_JAVA_BAD_VALUE;
+ }
+ if (!env->IsInstanceOf(jDeviceTypes, gIntArrayClass)) {
+ ALOGE("%s not an IntArray", __func__);
+ return AUDIO_JAVA_BAD_VALUE;
+ }
+
+ // Convert AudioManager.GET_DEVICES_ flags to AUDIO_PORT_ROLE_ constants
+ audio_port_role_t role;
+ if (direction == GET_DEVICES_INPUTS) {
+ role = AUDIO_PORT_ROLE_SOURCE;
+ } else if (direction == GET_DEVICES_OUTPUTS) {
+ role = AUDIO_PORT_ROLE_SINK;
+ } else {
+ ALOGE("%s invalid direction : 0x%X", __func__, direction);
+ return AUDIO_JAVA_BAD_VALUE;
+ }
+
+ std::vector<media::AudioPortFw> deviceList;
+ AudioSystem::listDeclaredDevicePorts(static_cast<media::AudioPortRole>(role), &deviceList);
+
+ // Walk the device list
+ for (const auto &device : deviceList) {
+ ConversionResult<audio_port_v7> result = aidl2legacy_AudioPortFw_audio_port_v7(device);
+
+ struct audio_port_v7 port = VALUE_OR_RETURN_STATUS(result);
+ assert(port.type == AUDIO_PORT_TYPE_DEVICE);
+
+ env->CallVoidMethod(jDeviceTypes, gIntArrayMethods.add, port.ext.device.type);
+ }
+
+ return AUDIO_JAVA_SUCCESS;
+}
+
static int
android_media_AudioSystem_createAudioPatch(JNIEnv *env, jobject clazz,
jobjectArray jPatches, jobjectArray jSources, jobjectArray jSinks)
@@ -3184,6 +3232,8 @@
android_media_AudioSystem_setAudioFlingerBinder),
MAKE_JNI_NATIVE_METHOD("listAudioPorts", "(Ljava/util/ArrayList;[I)I",
android_media_AudioSystem_listAudioPorts),
+ MAKE_JNI_NATIVE_METHOD("getSupportedDeviceTypes", "(ILandroid/util/IntArray;)I",
+ android_media_AudioSystem_getSupportedDeviceTypes),
MAKE_JNI_NATIVE_METHOD("createAudioPatch",
"([Landroid/media/AudioPatch;[Landroid/media/"
"AudioPortConfig;[Landroid/media/AudioPortConfig;)I",
@@ -3325,6 +3375,10 @@
gArrayListMethods.add = GetMethodIDOrDie(env, arrayListClass, "add", "(Ljava/lang/Object;)Z");
gArrayListMethods.toArray = GetMethodIDOrDie(env, arrayListClass, "toArray", "()[Ljava/lang/Object;");
+ jclass intArrayClass = FindClassOrDie(env, "android/util/IntArray");
+ gIntArrayClass = MakeGlobalRefOrDie(env, intArrayClass);
+ gIntArrayMethods.add = GetMethodIDOrDie(env, gIntArrayClass, "add", "(I)V");
+
jclass booleanClass = FindClassOrDie(env, "java/lang/Boolean");
gBooleanClass = MakeGlobalRefOrDie(env, booleanClass);
gBooleanCstor = GetMethodIDOrDie(env, booleanClass, "<init>", "(Z)V");
diff --git a/core/jni/android_tracing_PerfettoDataSource.cpp b/core/jni/android_tracing_PerfettoDataSource.cpp
index d710698..16c3ca9 100644
--- a/core/jni/android_tracing_PerfettoDataSource.cpp
+++ b/core/jni/android_tracing_PerfettoDataSource.cpp
@@ -25,7 +25,6 @@
#include <perfetto/public/producer.h>
#include <perfetto/public/protos/trace/test_event.pzc.h>
#include <perfetto/public/protos/trace/trace_packet.pzc.h>
-#include <perfetto/tracing.h>
#include <utils/Log.h>
#include <utils/RefBase.h>
diff --git a/core/jni/android_tracing_PerfettoDataSource.h b/core/jni/android_tracing_PerfettoDataSource.h
index 4ddf1d8..61a7654 100644
--- a/core/jni/android_tracing_PerfettoDataSource.h
+++ b/core/jni/android_tracing_PerfettoDataSource.h
@@ -23,10 +23,10 @@
#include <perfetto/public/producer.h>
#include <perfetto/public/protos/trace/test_event.pzc.h>
#include <perfetto/public/protos/trace/trace_packet.pzc.h>
-#include <perfetto/tracing.h>
#include <utils/Log.h>
#include <utils/RefBase.h>
+#include <map>
#include <sstream>
#include <thread>
diff --git a/core/jni/android_tracing_PerfettoDataSourceInstance.cpp b/core/jni/android_tracing_PerfettoDataSourceInstance.cpp
index e659bf1..3fbd5b3 100644
--- a/core/jni/android_tracing_PerfettoDataSourceInstance.cpp
+++ b/core/jni/android_tracing_PerfettoDataSourceInstance.cpp
@@ -25,7 +25,6 @@
#include <perfetto/public/producer.h>
#include <perfetto/public/protos/trace/test_event.pzc.h>
#include <perfetto/public/protos/trace/trace_packet.pzc.h>
-#include <perfetto/tracing.h>
#include <utils/Log.h>
#include <utils/RefBase.h>
diff --git a/core/jni/android_tracing_PerfettoDataSourceInstance.h b/core/jni/android_tracing_PerfettoDataSourceInstance.h
index d577655..ebb5259 100644
--- a/core/jni/android_tracing_PerfettoDataSourceInstance.h
+++ b/core/jni/android_tracing_PerfettoDataSourceInstance.h
@@ -23,7 +23,6 @@
#include <perfetto/public/producer.h>
#include <perfetto/public/protos/trace/test_event.pzc.h>
#include <perfetto/public/protos/trace/trace_packet.pzc.h>
-#include <perfetto/tracing.h>
#include <utils/Log.h>
#include <utils/RefBase.h>
diff --git a/core/jni/android_tracing_PerfettoProducer.cpp b/core/jni/android_tracing_PerfettoProducer.cpp
index ce72f58..f8c63c8 100644
--- a/core/jni/android_tracing_PerfettoProducer.cpp
+++ b/core/jni/android_tracing_PerfettoProducer.cpp
@@ -23,7 +23,6 @@
#include <perfetto/public/producer.h>
#include <perfetto/public/protos/trace/test_event.pzc.h>
#include <perfetto/public/protos/trace/trace_packet.pzc.h>
-#include <perfetto/tracing.h>
#include <utils/Log.h>
#include <utils/RefBase.h>
diff --git a/core/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp
index d4a7462..d11166f 100644
--- a/core/jni/android_view_InputChannel.cpp
+++ b/core/jni/android_view_InputChannel.cpp
@@ -203,8 +203,10 @@
if (parcel) {
bool isInitialized = parcel->readInt32();
if (isInitialized) {
- std::unique_ptr<InputChannel> inputChannel = std::make_unique<InputChannel>();
- inputChannel->readFromParcel(parcel);
+ android::os::InputChannelCore parcelableChannel;
+ parcelableChannel.readFromParcel(parcel);
+ std::unique_ptr<InputChannel> inputChannel =
+ InputChannel::create(std::move(parcelableChannel));
NativeInputChannel* nativeInputChannel =
new NativeInputChannel(std::move(inputChannel));
return reinterpret_cast<jlong>(nativeInputChannel);
@@ -228,7 +230,9 @@
return;
}
parcel->writeInt32(1); // initialized
- nativeInputChannel->getInputChannel()->writeToParcel(parcel);
+ android::os::InputChannelCore parcelableChannel;
+ nativeInputChannel->getInputChannel()->copyTo(parcelableChannel);
+ parcelableChannel.writeToParcel(parcel);
}
static jstring android_view_InputChannel_nativeGetName(JNIEnv* env, jobject obj, jlong channel) {
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index f7d8152..f93b306 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -189,11 +189,11 @@
void NativeInputEventReceiver::setFdEvents(int events) {
if (mFdEvents != events) {
mFdEvents = events;
- auto&& fd = mInputConsumer.getChannel()->getFd();
+ const int fd = mInputConsumer.getChannel()->getFd();
if (events) {
- mMessageQueue->getLooper()->addFd(fd.get(), 0, events, this, nullptr);
+ mMessageQueue->getLooper()->addFd(fd, 0, events, this, nullptr);
} else {
- mMessageQueue->getLooper()->removeFd(fd.get());
+ mMessageQueue->getLooper()->removeFd(fd);
}
}
}
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index 6bdf821..323f7b6 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -102,8 +102,8 @@
}
status_t NativeInputEventSender::initialize() {
- auto&& receiveFd = mInputPublisher.getChannel()->getFd();
- mMessageQueue->getLooper()->addFd(receiveFd.get(), 0, ALOOPER_EVENT_INPUT, this, NULL);
+ const int receiveFd = mInputPublisher.getChannel()->getFd();
+ mMessageQueue->getLooper()->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, this, NULL);
return OK;
}
@@ -112,7 +112,7 @@
LOG(DEBUG) << "channel '" << getInputChannelName() << "' ~ Disposing input event sender.";
}
- mMessageQueue->getLooper()->removeFd(mInputPublisher.getChannel()->getFd().get());
+ mMessageQueue->getLooper()->removeFd(mInputPublisher.getChannel()->getFd());
}
status_t NativeInputEventSender::sendKeyEvent(uint32_t seq, const KeyEvent* event) {
diff --git a/core/proto/OWNERS b/core/proto/OWNERS
index a854e36..381580b 100644
--- a/core/proto/OWNERS
+++ b/core/proto/OWNERS
@@ -13,7 +13,7 @@
jjaggi@google.com
kwekua@google.com
roosa@google.com
-per-file package_item_info.proto = toddke@google.com,patb@google.com
+per-file package_item_info.proto = file:/PACKAGE_MANAGER_OWNERS
per-file usagestatsservice.proto, usagestatsservice_v2.proto = file:/core/java/android/app/usage/OWNERS
per-file apphibernationservice.proto = file:/core/java/android/apphibernation/OWNERS
per-file android/hardware/sensorprivacy.proto = ntmyren@google.com,evanseverson@google.com
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 0171f58..6be1be4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -881,6 +881,19 @@
android:description="@string/permdesc_writeContacts"
android:protectionLevel="dangerous" />
+ <!-- Allows an app to update the verification status of E2EE contact keys owned by other apps.
+ <p>This permission is only granted to system apps.
+ <p>Protection level: signature|privileged
+ @SystemApi
+ @hide
+ @FlaggedApi("android.provider.user_keys")
+ -->
+ <permission android:name="android.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_writeVerificationStateE2eeContactKeys"
+ android:description="@string/permdesc_writeVerificationStateE2eeContactKeys"
+ android:protectionLevel="signature|privileged" />
+
<!-- Allows an application to set default account for new contacts.
<p> This permission is only granted to system applications fulfilling the Contacts app role.
<p>Protection level: internal|role
@@ -2632,6 +2645,13 @@
android:description="@string/permdesc_detectScreenCapture"
android:protectionLevel="normal" />
+ <!-- Allows an application to get notified when it is being recorded.
+ <p>Protection level: normal
+ @FlaggedApi("com.android.window.flags.screen_recording_callbacks")
+ -->
+ <permission android:name="android.permission.DETECT_SCREEN_RECORDING"
+ android:protectionLevel="normal" />
+
<!-- ======================================== -->
<!-- Permissions for factory reset protection -->
<!-- ======================================== -->
@@ -3188,6 +3208,14 @@
<permission android:name="android.permission.INTERACT_ACROSS_PROFILES"
android:protectionLevel="signature|appop" />
+ <!-- Allows applications to access profiles with ACCESS_HIDDEN_PROFILES user property
+ <p>Protection level: normal
+ @FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles") -->
+ <permission android:name="android.permission.ACCESS_HIDDEN_PROFILES"
+ android:label="@string/permlab_accessHiddenProfile"
+ android:description="@string/permdesc_accessHiddenProfile"
+ android:protectionLevel="normal" />
+
<!-- @SystemApi @hide Allows starting activities across profiles in the same profile group. -->
<permission android:name="android.permission.START_CROSS_PROFILE_ACTIVITIES"
android:protectionLevel="signature|role" />
@@ -7828,7 +7856,7 @@
android:protectionLevel="normal"/>
<!-- @FlaggedApi("android.app.job.backup_jobs_exemption")
- Gives applications whose <b>primary use case</b> is to backup or sync content increased
+ Gives applications with a <b>major use case</b> of backing-up or syncing content increased
job execution allowance in order to complete the related work. The jobs must have a valid
content URI trigger and network constraint set.
<p>This is a special access permission that can be revoked by the system or the user.
diff --git a/core/res/res/drawable-nodpi/platlogo.xml b/core/res/res/drawable-nodpi/platlogo.xml
index fe12f6e..f3acab0 100644
--- a/core/res/res/drawable-nodpi/platlogo.xml
+++ b/core/res/res/drawable-nodpi/platlogo.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2024 The Android Open Source Project
+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.
@@ -20,103 +20,179 @@
android:viewportWidth="512"
android:viewportHeight="512">
<path
- android:pathData="M256.22,437.7c-26.38,0 -43.81,-18.3 -61.2,-48.42 -17.39,-30.12 -103.26,-178.86 -120.65,-208.98s-24.52,-54.37 -11.33,-77.21c13.19,-22.84 37.75,-28.79 72.53,-28.79 34.78,0 206.53,0 241.31,0 34.78,0 59.35,5.95 72.53,28.79 13.19,22.84 6.06,47.09 -11.33,77.21s-103.26,178.86 -120.65,208.98c-17.39,30.12 -34.83,48.42 -61.2,48.42Z"
- android:strokeWidth="0">
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0">
<aapt:attr name="android:fillColor">
<gradient
- android:startX="56.22"
- android:startY="256"
- android:endX="456.22"
- android:endY="256"
+ android:startX="256"
+ android:startY="21.81"
+ android:endX="256"
+ android:endY="350.42"
android:type="linear">
<item android:offset="0" android:color="#FF073042"/>
<item android:offset="1" android:color="#FF073042"/>
</gradient>
</aapt:attr>
</path>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="m195.27,187.64l2.25,-6.69c13.91,78.13 50.84,284.39 50.84,50.33 0,-0.97 0.72,-1.81 1.62,-1.81h12.69c0.9,0 1.62,0.83 1.62,1.8 -0.2,409.91 -69.03,-43.64 -69.03,-43.64Z"
+ android:fillColor="#3ddc84"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="m158.77,180.68l-33.17,57.45c-1.9,3.3 -0.77,7.52 2.53,9.42 3.3,1.9 7.52,0.77 9.42,-2.53l33.59,-58.17c54.27,24.33 116.34,24.33 170.61,0l33.59,58.17c1.97,3.26 6.21,4.3 9.47,2.33 3.17,-1.91 4.26,-5.99 2.47,-9.23l-33.16,-57.45c56.95,-30.97 95.91,-88.64 101.61,-156.76H57.17c5.7,68.12 44.65,125.79 101.61,156.76Z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M250.26,187.66L262.17,187.66A2.13,2.13 0,0 1,264.3 189.78L264.3,217.85A2.13,2.13 0,0 1,262.17 219.98L250.26,219.98A2.13,2.13 0,0 1,248.14 217.85L248.14,189.78A2.13,2.13 0,0 1,250.26 187.66z"
+ android:fillColor="#3ddc84"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M250.12,170.29L262.32,170.29A1.98,1.98 0,0 1,264.3 172.26L264.3,176.39A1.98,1.98 0,0 1,262.32 178.37L250.12,178.37A1.98,1.98 0,0 1,248.14 176.39L248.14,172.26A1.98,1.98 0,0 1,250.12 170.29z"
+ android:fillColor="#3ddc84"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M171.92,216.82h4.04v4.04h-4.04z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M188.8,275.73h4.04v4.04h-4.04z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M369.04,337.63h4.04v4.04h-4.04z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M285.93,252.22h4.04v4.04h-4.04z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M318.96,218.84h4.04v4.04h-4.04z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M294.05,288.55h4.04v4.04h-4.04z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M330.82,273.31h8.08v8.08h-8.08z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M188.8,298.95h4.04v4.04h-4.04z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M220.14,238.94h8.08v8.08h-8.08z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M272.1,318.9h8.08v8.08h-8.08z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M293.34,349.25h8.08v8.08h-8.08z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M161.05,254.24h8.08v8.08h-8.08z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M378.92,192h8.08v8.08h-8.08z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M137.87,323.7h8.08v8.08h-8.08z"
+ android:fillColor="#fff"/>
+ </group>
<path
- android:pathData="M198.85,168.57l2.03,-6.03c12.55,70.48 45.87,256.56 45.87,45.41 0,-0.88 0.65,-1.63 1.46,-1.63h11.45c0.81,0 1.46,0.75 1.46,1.63 -0.18,369.8 -62.28,-39.37 -62.28,-39.37Z"
- android:strokeWidth="0"
- android:fillColor="#3ddc84"/>
- <path
- android:pathData="M186.69,167.97l-23.69,41.03c-1.36,2.36 -0.55,5.37 1.8,6.73 2.36,1.36 5.37,0.55 6.73,-1.8l23.99,-41.55c38.76,17.38 83.1,17.38 121.86,0l23.99,41.55c1.41,2.33 4.44,3.07 6.77,1.66 2.26,-1.37 3.04,-4.28 1.76,-6.59l-23.69,-41.03c40.68,-22.12 68.5,-63.31 72.57,-111.97H114.11c4.07,48.65 31.89,89.85 72.57,111.97Z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M248.46,168.59L259.2,168.59A1.92,1.92 0,0 1,261.12 170.5L261.12,195.83A1.92,1.92 0,0 1,259.2 197.75L248.46,197.75A1.92,1.92 0,0 1,246.54 195.83L246.54,170.5A1.92,1.92 0,0 1,248.46 168.59z"
- android:strokeWidth="0"
- android:fillColor="#3ddc84"/>
- <path
- android:pathData="M248.32,152.92L259.34,152.92A1.78,1.78 0,0 1,261.12 154.7L261.12,158.43A1.78,1.78 0,0 1,259.34 160.21L248.32,160.21A1.78,1.78 0,0 1,246.54 158.43L246.54,154.7A1.78,1.78 0,0 1,248.32 152.92z"
- android:strokeWidth="0"
- android:fillColor="#3ddc84"/>
- <path
- android:pathData="M159.03,176.91h4.04v4.04h-4.04z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M188.8,275.73h4.04v4.04h-4.04z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M373.41,158.93h4.04v4.04h-4.04z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M112.1,129.34h4.04v4.04h-4.04z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M285.93,252.22h4.04v4.04h-4.04z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M318.96,218.84h4.04v4.04h-4.04z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M294.05,288.55h4.04v4.04h-4.04z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M330.82,263.31h8.08v8.08h-8.08z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M188.8,298.95h4.04v4.04h-4.04z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M224.18,216.82h8.08v8.08h-8.08z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M272.1,318.9h8.08v8.08h-8.08z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M293.34,339.25h8.08v8.08h-8.08z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M165.09,236.28h8.08v8.08h-8.08z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M378.92,192h8.08v8.08h-8.08z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M204.28,314.86h8.08v8.08h-8.08z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M253.83,118.47c-6.04,0 -10.93,4.76 -10.93,10.62v13.1c0,1.13 0.92,2.05 2.05,2.05h0c1.13,0 2.05,-0.92 2.05,-2.05v-3.53c0,-2.27 1.84,-4.1 4.1,-4.1h5.47c2.27,0 4.1,1.84 4.1,4.1v3.53c0,1.13 0.92,2.05 2.05,2.05s2.05,-0.92 2.05,-2.05v-13.1c0,-5.86 -4.9,-10.62 -10.93,-10.62Z"
- android:strokeWidth="0"
- android:fillColor="#3ddc84"/>
- <path
- android:pathData="M256,437.7c-26.38,0 -43.81,-18.3 -61.2,-48.42 -17.39,-30.12 -103.26,-178.86 -120.65,-208.98 -17.39,-30.12 -24.52,-54.37 -11.33,-77.21 13.19,-22.84 37.75,-28.79 72.53,-28.79 34.78,0 206.53,0 241.31,0 34.78,0 59.35,5.95 72.53,28.79 13.19,22.84 6.06,47.09 -11.33,77.21 -17.39,30.12 -103.26,178.86 -120.65,208.98 -17.39,30.12 -34.83,48.42 -61.2,48.42Z"
- android:strokeWidth="55"
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"
+ android:strokeWidth="56.561"
android:fillColor="#00000000"
- android:strokeColor="#f86733"/>
+ android:strokeColor="#f86734"/>
+ <path
+ android:pathData="m256.22,126.57c-6.69,0 -12.12,5.27 -12.12,11.77v14.52c0,1.25 1.02,2.27 2.27,2.27h0c1.25,0 2.27,-1.02 2.27,-2.27v-3.91c0,-2.51 2.04,-4.55 4.55,-4.55h6.06c2.51,0 4.55,2.04 4.55,4.55v3.91c0,1.25 1.02,2.27 2.27,2.27s2.27,-1.02 2.27,-2.27v-14.52c0,-6.5 -5.43,-11.77 -12.12,-11.77Z"
+ android:fillColor="#3ddc84"/>
+ <path
+ android:pathData="m93.34,116.36l3.85,-4.36 29.64,9.76 -4.44,5.03 -6.23,-2.1 -7.86,8.91 2.86,5.92 -4.43,5.03 -13.39,-28.18ZM110.43,122.76l-8.86,-3.02 4.11,8.41 4.76,-5.39Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="m153.62,100.85l-21.71,-6.2 10.38,14.38 -5.21,3.76 -16.78,-23.26 4.49,-3.24 21.65,6.19 -10.35,-14.35 5.24,-3.78 16.78,23.26 -4.49,3.24Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="m161.46,63.15l8.99,-3.84c7.43,-3.18 15.96,0.12 19.09,7.44 3.13,7.32 -0.38,15.76 -7.81,18.94l-8.99,3.84 -11.28,-26.38ZM179.41,80.26c4.46,-1.91 5.96,-6.72 4.15,-10.96 -1.81,-4.24 -6.33,-6.48 -10.79,-4.57l-3.08,1.32 6.64,15.53 3.08,-1.32Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="m204.23,47.57l11.1,-2.2c5.31,-1.05 9.47,2.08 10.4,6.76 0.72,3.65 -0.76,6.37 -4.07,8.34l12.4,10.44 -7.57,1.5 -11.65,-9.76 -1.03,0.2 2.3,11.61 -6.3,1.25 -5.57,-28.14ZM216.78,56.7c1.86,-0.37 3,-1.71 2.68,-3.33 -0.34,-1.7 -1.88,-2.43 -3.74,-2.06l-4.04,0.8 1.07,5.39 4.04,-0.8Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="m244.29,55.6c0.13,-8.16 6.86,-14.72 15.06,-14.58 8.16,0.13 14.72,6.9 14.58,15.06s-6.91,14.72 -15.06,14.58c-8.2,-0.13 -14.71,-6.9 -14.58,-15.06ZM267.44,55.98c0.08,-4.64 -3.54,-8.66 -8.18,-8.74 -4.68,-0.08 -8.42,3.82 -8.5,8.47 -0.08,4.65 3.54,8.66 8.22,8.74 4.64,0.08 8.39,-3.82 8.46,-8.47Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="m294.39,44.84l6.31,1.23 -5.49,28.16 -6.31,-1.23 5.49,-28.16Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="m321.94,51.41l9.14,3.48c7.55,2.88 11.39,11.17 8.56,18.61 -2.83,7.44 -11.22,11.07 -18.77,8.19l-9.14,-3.48 10.22,-26.8ZM322.96,76.19c4.53,1.73 8.95,-0.69 10.6,-5 1.64,-4.3 -0.05,-9.06 -4.58,-10.78l-3.13,-1.19 -6.01,15.78 3.13,1.19Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="m381.41,89.24l-4.21,-3.21 3.65,-4.78 9.06,6.91 -17.4,22.81 -4.85,-3.7 13.75,-18.02Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="m397.96,126.37l-9.56,-10.26 3.61,-3.36 22.8,-1.25 3.74,4.02 -12.35,11.51 2.51,2.69 -4.08,3.8 -2.51,-2.69 -4.55,4.24 -4.16,-4.46 4.55,-4.24ZM407.83,117.17l-10.28,0.58 4.49,4.82 5.79,-5.4Z"
+ android:fillColor="#fff"/>
</vector>
+
diff --git a/core/res/res/drawable/ic_satellite_alt_24px.xml b/core/res/res/drawable/ic_satellite_alt_24px.xml
new file mode 100644
index 0000000..f9ca7dc
--- /dev/null
+++ b/core/res/res/drawable/ic_satellite_alt_24px.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M560,928L560,848Q677,848 758.5,766.5Q840,685 840,568L920,568Q920,643 891.5,708.5Q863,774 814.5,822.5Q766,871 700.5,899.5Q635,928 560,928ZM560,768L560,688Q610,688 645,653Q680,618 680,568L760,568Q760,651 701.5,709.5Q643,768 560,768ZM222,903Q207,903 192,897Q177,891 165,880L23,738Q12,726 6,711Q0,696 0,681Q0,665 6,650.5Q12,636 23,625L150,498Q173,475 207,474.5Q241,474 264,497L314,547L342,519L292,469Q269,446 269,413Q269,380 292,357L349,300Q372,277 405.5,277Q439,277 462,300L512,350L540,322L490,272Q467,249 467,215.5Q467,182 490,159L617,32Q629,20 644,14Q659,8 674,8Q689,8 703.5,14Q718,20 730,32L872,174Q884,185 889.5,199.5Q895,214 895,230Q895,245 889.5,260Q884,275 872,287L745,414Q722,437 688.5,437Q655,437 632,414L582,364L554,392L604,442Q627,465 626.5,498.5Q626,532 603,555L547,611Q524,634 490.5,634Q457,634 434,611L384,561L356,589L406,639Q429,662 428.5,696Q428,730 405,753L278,880Q267,891 252.5,897Q238,903 222,903ZM222,824Q222,824 222,824Q222,824 222,824L264,782L122,640L80,682Q80,682 80,682Q80,682 80,682L222,824ZM307,739L349,697Q349,697 349,697Q349,697 349,697L207,555Q207,555 207,555Q207,555 207,555L165,597L307,739ZM491,555Q491,555 491,555Q491,555 491,555L547,499Q547,499 547,499Q547,499 547,499L405,357Q405,357 405,357Q405,357 405,357L349,413Q349,413 349,413Q349,413 349,413L491,555ZM689,357Q689,357 689,357Q689,357 689,357L731,315L589,173L547,215Q547,215 547,215Q547,215 547,215L689,357ZM774,272L816,230Q816,230 816,230Q816,230 816,230L674,88Q674,88 674,88Q674,88 674,88L632,130L774,272ZM448,456L448,456Q448,456 448,456Q448,456 448,456L448,456Q448,456 448,456Q448,456 448,456L448,456Q448,456 448,456Q448,456 448,456L448,456Q448,456 448,456Q448,456 448,456Z"/>
+</vector>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ebb0d34..238f242 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -283,6 +283,20 @@
when there's no network connection. If the scan doesn't timeout, use zero -->
<integer name="config_radioScanningTimeout">0</integer>
+ <!-- The duration (in milliseconds) that the vibrator control service will wait for new
+ vibration params. -->
+ <integer name="config_requestVibrationParamsTimeout">50</integer>
+
+ <!-- Array containing the usages that should request vibration params before they are played.
+ These usages don't have strong latency requirements, e.g. ringtone and notification, and
+ can be slightly delayed. -->
+ <integer-array name="config_requestVibrationParamsForUsages">
+ <item>17</item> <!-- USAGE_ALARM -->
+ <item>33</item> <!-- USAGE_RINGTONE -->
+ <item>49</item> <!-- USAGE_NOTIFICATION -->
+ <item>65</item> <!-- USAGE_COMMUNICATION_REQUEST -->
+ </integer-array>
+
<!-- XXXXX NOTE THE FOLLOWING RESOURCES USE THE WRONG NAMING CONVENTION.
Please don't copy them, copy anything else. -->
@@ -3327,9 +3341,27 @@
<string name="config_carrierAppInstallDialogComponent" translatable="false"
>com.android.simappdialog/com.android.simappdialog.InstallCarrierAppActivity</string>
- <!-- Name of the dialog that is used to get or save an app credential -->
+ <!-- Name of the default framework dialog that is used to get or save an app credential.
+
+ This UI should be always launch-able and is used as a fallback when an oem replacement activity
+ (defined at config_oemCredentialManagerDialogComponent) is undefined / not found. -->
<string name="config_credentialManagerDialogComponent" translatable="false"
>com.android.credentialmanager/com.android.credentialmanager.CredentialSelectorActivity</string>
+ <!-- Whether to allow the credential selector activity to be replaced by an activity at
+ run-time (restricted to the privileged activity specified by
+ config_credentialSelectorActivityName).
+
+ When disabled, the fallback activity defined at
+ config_credentialManagerDialogComponent will be used instead. -->
+ <bool name="config_enableOemCredentialManagerDialogComponent" translatable="false">true</bool>
+ <!-- Fully qualified activity name providing the credential selector UI, that serves the
+ CredentialManager APIs.
+
+ Used only when config_enableOemCredentialManagerDialogComponent is true.
+
+ If the activity specified cannot be found or launched, then the fallback activity defined at
+ config_credentialManagerDialogComponent will be used instead. -->
+ <string name="config_oemCredentialManagerDialogComponent" translatable="false"></string>
<!-- Name of the broadcast receiver that is used to receive provider change events -->
<string name="config_credentialManagerReceiverComponent" translatable="false"
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 558bae7..be96cc2 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -396,6 +396,27 @@
<!-- Displayed when the call forwarding query was set but forwarding is not enabled. -->
<string name="cfTemplateRegisteredTime"><xliff:g id="bearer_service_code">{0}</xliff:g>: Not forwarded</string>
+ <!-- Title of the cellular network security safety center source's status. -->
+ <string name="scCellularNetworkSecurityTitle">Cellular network security</string>
+ <!-- Summary of the cellular network security safety center source's status. -->
+ <string name="scCellularNetworkSecuritySummary">Review settings</string>
+ <!-- Title of the safety center issue and notification when the phone's identifier is shared over the network. -->
+ <string name="scIdentifierDisclosureIssueTitle">Device identifier accessed</string>
+ <!-- Summary of the safety center issue and notification when the phone's identifier is shared over the network. -->
+ <string name="scIdentifierDisclosureIssueSummary">A network on the <xliff:g id="disclosure_network">%4$s</xliff:g> connection recorded your device\'s unique identifier (IMSI) <xliff:g id="disclosure_count">%1$d</xliff:g> times in the period between <xliff:g id="disclosure_window_start_time">%2$tr</xliff:g> and <xliff:g id="disclosure_window_end_time">%3$tr</xliff:g>.</string>
+ <!-- Title of the safety center issue and notification when the phone restores an encrypted connection to the network. -->
+ <string name="scNullCipherIssueEncryptedTitle">Encrypted connection to <xliff:g id="network_name">%1$s</xliff:g></string>
+ <!-- Summary of the safety center issue and notification when the phone restores an encrypted connection to the network. -->
+ <string name="scNullCipherIssueEncryptedSummary">You\'re now connected to a more secure cellular network.</string>
+ <!-- Title of the safety center issue and notification when a connected network is not using encryption. -->
+ <string name="scNullCipherIssueNonEncryptedTitle">Non-encrypted connection to <xliff:g id="network_name">%1$s</xliff:g></string>
+ <!-- Summary of the safety center issue and notification when a connected network is not using encryption. -->
+ <string name="scNullCipherIssueNonEncryptedSummary">You\'re connected to a non-encrypted cellular network. Your calls, messages, and data are vulnerable to interception.</string>
+ <!-- Label for the button that links to the cellular network security settings. -->
+ <string name="scNullCipherIssueActionSettings">Cellular security settings</string>
+ <!-- Label for the button that link to education resourcess about cellular network security settings. -->
+ <string name="scNullCipherIssueActionLearnMore">Learn more</string>
+
<!-- android.net.http Error strings --> <skip />
<!-- Displayed when a feature code (non-phone number) is dialed and completes successfully. -->
<string name="fcComplete">Feature code complete.</string>
@@ -1572,6 +1593,11 @@
<string name="permdesc_setWallpaper">Allows the app to set the system wallpaper.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_accessHiddenProfile">Access hidden profiles</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_accessHiddenProfile">Allows the app to access hidden profiles.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_setWallpaperHints">adjust your wallpaper size</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_setWallpaperHints">Allows the app to set the system wallpaper size hints.</string>
@@ -2222,6 +2248,11 @@
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this.[CHAR_LIMIT=NONE] -->
<string name="permdesc_updatePackagesWithoutUserAction">Allows the holder to update the app it previously installed without user action</string>
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
+ <string name="permlab_writeVerificationStateE2eeContactKeys">update the verification states of E2EE contact keys owned by other apps</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this.[CHAR_LIMIT=NONE] -->
+ <string name="permdesc_writeVerificationStateE2eeContactKeys">Allows the app to update the verification states of E2EE contact keys owned by other apps</string>
+
<!-- Policy administration -->
<!-- Title of policy access to limiting the user's password choices -->
@@ -6386,4 +6417,14 @@
<string name="redacted_notification_message"></string>
<!-- Notification action title used instead of a notification's normal title sensitive [CHAR_LIMIT=NOTIF_BODY] -->
<string name="redacted_notification_action_title"></string>
+
+ <!-- Satellite related messages -->
+ <!-- Notification title when satellite service is connected. -->
+ <string name="satellite_notification_title">Auto connected to satellite</string>
+ <!-- Notification summary when satellite service is connected. [CHAR LIMIT=NONE] -->
+ <string name="satellite_notification_summary">You can send and receive messages without a mobile or Wi-Fi network</string>
+ <!-- Invoke "What to expect" dialog of messaging application -->
+ <string name="satellite_notification_open_message">Open Messages</string>
+ <!-- Invoke Satellite setting activity of Settings -->
+ <string name="satellite_notification_how_it_works">How it works</string>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index dc2f74b..603b902 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -954,6 +954,16 @@
<java-symbol type="string" name="roamingText8" />
<java-symbol type="string" name="roamingText9" />
<java-symbol type="string" name="roamingTextSearching" />
+ <java-symbol type="string" name="scCellularNetworkSecuritySummary" />
+ <java-symbol type="string" name="scCellularNetworkSecurityTitle" />
+ <java-symbol type="string" name="scIdentifierDisclosureIssueSummary" />
+ <java-symbol type="string" name="scIdentifierDisclosureIssueTitle" />
+ <java-symbol type="string" name="scNullCipherIssueActionLearnMore" />
+ <java-symbol type="string" name="scNullCipherIssueActionSettings" />
+ <java-symbol type="string" name="scNullCipherIssueEncryptedSummary" />
+ <java-symbol type="string" name="scNullCipherIssueEncryptedTitle" />
+ <java-symbol type="string" name="scNullCipherIssueNonEncryptedSummary" />
+ <java-symbol type="string" name="scNullCipherIssueNonEncryptedTitle" />
<java-symbol type="string" name="selected" />
<java-symbol type="string" name="sendText" />
<java-symbol type="string" name="sending" />
@@ -2080,6 +2090,8 @@
<java-symbol type="bool" name="config_ignoreVibrationsOnWirelessCharger" />
<java-symbol type="integer" name="config_vibrationWaveformRampDownDuration" />
<java-symbol type="integer" name="config_radioScanningTimeout" />
+ <java-symbol type="integer" name="config_requestVibrationParamsTimeout" />
+ <java-symbol type="array" name="config_requestVibrationParamsForUsages" />
<java-symbol type="integer" name="config_screenBrightnessSettingMinimum" />
<java-symbol type="integer" name="config_screenBrightnessSettingMaximum" />
<java-symbol type="integer" name="config_screenBrightnessSettingDefault" />
@@ -2281,6 +2293,8 @@
<java-symbol type="string" name="config_platformVpnConfirmDialogComponent" />
<java-symbol type="string" name="config_carrierAppInstallDialogComponent" />
<java-symbol type="string" name="config_credentialManagerDialogComponent" />
+ <java-symbol type="bool" name="config_enableOemCredentialManagerDialogComponent" />
+ <java-symbol type="string" name="config_oemCredentialManagerDialogComponent" />
<java-symbol type="string" name="config_credentialManagerReceiverComponent" />
<java-symbol type="string" name="config_defaultNetworkScorerPackageName" />
<java-symbol type="string" name="config_persistentDataPackageName" />
@@ -5325,4 +5339,11 @@
<java-symbol type="bool" name="config_showPercentageTextDuringRebootToUpdate" />
<java-symbol type="integer" name="config_minMillisBetweenInputUserActivityEvents" />
+
+ <!-- System notification for satellite service -->
+ <java-symbol type="string" name="satellite_notification_title" />
+ <java-symbol type="string" name="satellite_notification_summary" />
+ <java-symbol type="string" name="satellite_notification_open_message" />
+ <java-symbol type="string" name="satellite_notification_how_it_works" />
+ <java-symbol type="drawable" name="ic_satellite_alt_24px" />
</resources>
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index f476799..513e022 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -203,7 +203,6 @@
"androidx.test.uiautomator_uiautomator",
"compatibility-device-util-axt",
"flag-junit",
- "mockito_ravenwood",
"platform-test-annotations",
"flag-junit",
"testng",
diff --git a/core/tests/coretests/src/android/app/backup/BackupAgentTest.java b/core/tests/coretests/src/android/app/backup/BackupAgentTest.java
index 9e8e2d6..cd5deb6 100644
--- a/core/tests/coretests/src/android/app/backup/BackupAgentTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupAgentTest.java
@@ -20,24 +20,33 @@
import static org.mockito.Mockito.when;
+import android.app.IBackupAgent;
import android.app.backup.BackupAgent.IncludeExcludeRules;
import android.app.backup.BackupAnnotations.BackupDestination;
import android.app.backup.BackupAnnotations.OperationType;
import android.app.backup.FullBackup.BackupScheme.PathWithRequiredFlags;
+import android.content.Context;
import android.os.ParcelFileDescriptor;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArraySet;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.backup.Flags;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
@@ -49,9 +58,12 @@
private static final UserHandle USER_HANDLE = new UserHandle(15);
private static final String DATA_TYPE_BACKED_UP = "test data type";
+ @Mock IBackupManager mIBackupManager;
@Mock FullBackup.BackupScheme mBackupScheme;
+ @Mock Context mContext;
- private BackupAgent mBackupAgent;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void setUp() {
@@ -67,11 +79,11 @@
excludePaths.add(path);
IncludeExcludeRules expectedRules = new IncludeExcludeRules(includePaths, excludePaths);
- mBackupAgent = getAgentForBackupDestination(BackupDestination.CLOUD);
+ BackupAgent backupAgent = getAgentForBackupDestination(BackupDestination.CLOUD);
when(mBackupScheme.maybeParseAndGetCanonicalExcludePaths()).thenReturn(excludePaths);
when(mBackupScheme.maybeParseAndGetCanonicalIncludePaths()).thenReturn(includePaths);
- IncludeExcludeRules rules = mBackupAgent.getIncludeExcludeRules(mBackupScheme);
+ IncludeExcludeRules rules = backupAgent.getIncludeExcludeRules(mBackupScheme);
assertThat(rules).isEqualTo(expectedRules);
}
@@ -137,6 +149,31 @@
0).getSuccessCount()).isEqualTo(1);
}
+ @Test
+ public void doRestoreFile_agentOverrideIgnoresFile_consumesAllBytesInBuffer() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_CLEAR_PIPE_AFTER_RESTORE_FILE);
+ BackupAgent agent = new TestRestoreIgnoringFullBackupAgent();
+ agent.attach(mContext);
+ agent.onCreate(USER_HANDLE, BackupDestination.CLOUD, OperationType.RESTORE);
+ IBackupAgent agentBinder = (IBackupAgent) agent.onBind();
+
+ ParcelFileDescriptor[] pipes = ParcelFileDescriptor.createPipe();
+ FileOutputStream writeSide = new FileOutputStream(
+ pipes[1].getFileDescriptor());
+ writeSide.write("Hello".getBytes(StandardCharsets.UTF_8));
+
+ agentBinder.doRestoreFile(pipes[0], /* length= */ 5, BackupAgent.TYPE_FILE,
+ FullBackup.FILES_TREE_TOKEN, /* path= */ "hello_file", /* mode= */
+ 0666, /* mtime= */ 12345, /* token= */ 6789, mIBackupManager);
+
+ try (FileInputStream in = new FileInputStream(pipes[0].getFileDescriptor())) {
+ assertThat(in.available()).isEqualTo(0);
+ } finally {
+ pipes[0].close();
+ pipes[1].close();
+ }
+ }
+
private BackupAgent getAgentForBackupDestination(@BackupDestination int backupDestination) {
BackupAgent agent = new TestFullBackupAgent();
agent.onCreate(USER_HANDLE, backupDestination);
@@ -144,7 +181,6 @@
}
private static class TestFullBackupAgent extends BackupAgent {
-
@Override
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) throws IOException {
@@ -162,4 +198,14 @@
// Left empty as this is a full backup agent.
}
}
+
+ private static class TestRestoreIgnoringFullBackupAgent extends TestFullBackupAgent {
+
+ @Override
+ protected void onRestoreFile(ParcelFileDescriptor data, long size,
+ int type, String domain, String path, long mode, long mtime)
+ throws IOException {
+ // Ignore the file and don't consume any data.
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/graphics/FontListParserTest.java b/core/tests/coretests/src/android/graphics/FontListParserTest.java
index 4dd5889..5f96c17 100644
--- a/core/tests/coretests/src/android/graphics/FontListParserTest.java
+++ b/core/tests/coretests/src/android/graphics/FontListParserTest.java
@@ -22,14 +22,18 @@
import static android.text.FontConfig.FontFamily.VARIANT_COMPACT;
import static android.text.FontConfig.FontFamily.VARIANT_DEFAULT;
import static android.text.FontConfig.FontFamily.VARIANT_ELEGANT;
+import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE;
+import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY;
+import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL;
+import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT;
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.fail;
import android.graphics.fonts.FontCustomizationParser;
-import android.graphics.fonts.FontFamily;
import android.graphics.fonts.FontStyle;
+import android.graphics.fonts.SystemFonts;
import android.os.LocaleList;
import android.text.FontConfig;
import android.util.Xml;
@@ -64,9 +68,9 @@
FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList(
Collections.singletonList(new FontConfig.FontFamily(
Arrays.asList(new FontConfig.Font(new File("test.ttf"), null, "test",
- new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null)),
- LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE)), "sans-serif");
+ new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null,
+ FontConfig.Font.VAR_TYPE_AXES_NONE)),
+ LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), "sans-serif");
FontConfig.NamedFamilyList family = readNamedFamily(xml);
assertThat(family).isEqualTo(expected);
}
@@ -82,12 +86,11 @@
Arrays.asList(
new FontConfig.Font(new File("test.ttf"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
- 0, "", null),
+ 0, "", null, FontConfig.Font.VAR_TYPE_AXES_NONE),
new FontConfig.Font(new File("test.ttf"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
- 0, "", "serif")),
- LocaleList.forLanguageTags("en"), VARIANT_DEFAULT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE);
+ 0, "", "serif", FontConfig.Font.VAR_TYPE_AXES_NONE)),
+ LocaleList.forLanguageTags("en"), VARIANT_DEFAULT);
FontConfig.FontFamily family = readFamily(xml);
assertThat(family).isEqualTo(expected);
@@ -103,9 +106,8 @@
Arrays.asList(
new FontConfig.Font(new File("test.ttf"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
- 0, "", null)),
- LocaleList.forLanguageTags("en"), VARIANT_COMPACT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE);
+ 0, "", null, FontConfig.Font.VAR_TYPE_AXES_NONE)),
+ LocaleList.forLanguageTags("en"), VARIANT_COMPACT);
FontConfig.FontFamily family = readFamily(xml);
assertThat(family).isEqualTo(expected);
@@ -121,9 +123,8 @@
Arrays.asList(
new FontConfig.Font(new File("test.ttf"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
- 0, "", null)),
- LocaleList.forLanguageTags("en"), VARIANT_ELEGANT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE);
+ 0, "", null, FontConfig.Font.VAR_TYPE_AXES_NONE)),
+ LocaleList.forLanguageTags("en"), VARIANT_ELEGANT);
FontConfig.FontFamily family = readFamily(xml);
assertThat(family).isEqualTo(expected);
@@ -140,13 +141,15 @@
FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList(
Collections.singletonList(new FontConfig.FontFamily(Arrays.asList(
new FontConfig.Font(new File("normal.ttf"), null, "test",
- new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null),
+ new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null,
+ FontConfig.Font.VAR_TYPE_AXES_NONE),
new FontConfig.Font(new File("weight.ttf"), null, "test",
- new FontStyle(100, FONT_SLANT_UPRIGHT), 0, "", null),
+ new FontStyle(100, FONT_SLANT_UPRIGHT), 0, "", null,
+ FontConfig.Font.VAR_TYPE_AXES_NONE),
new FontConfig.Font(new File("italic.ttf"), null, "test",
- new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_ITALIC), 0, "", null)),
- LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE)), "sans-serif");
+ new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_ITALIC), 0, "", null,
+ FontConfig.Font.VAR_TYPE_AXES_NONE)),
+ LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), "sans-serif");
FontConfig.NamedFamilyList family = readNamedFamily(xml);
assertThat(family).isEqualTo(expected);
}
@@ -168,12 +171,13 @@
Collections.singletonList(new FontConfig.FontFamily(Arrays.asList(
new FontConfig.Font(new File("test-VF.ttf"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
- 0, "'wdth' 100.0,'wght' 200.0", null),
+ 0, "'wdth' 100.0,'wght' 200.0", null,
+ FontConfig.Font.VAR_TYPE_AXES_NONE),
new FontConfig.Font(new File("test-VF.ttf"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
- 0, "'wdth' 400.0,'wght' 700.0", null)),
- LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE)),
+ 0, "'wdth' 400.0,'wght' 700.0", null,
+ FontConfig.Font.VAR_TYPE_AXES_NONE)),
+ LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)),
"sans-serif");
FontConfig.NamedFamilyList family = readNamedFamily(xml);
assertThat(family).isEqualTo(expected);
@@ -190,12 +194,11 @@
Collections.singletonList(new FontConfig.FontFamily(Arrays.asList(
new FontConfig.Font(new File("test.ttc"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
- 0, "", null),
+ 0, "", null, FontConfig.Font.VAR_TYPE_AXES_NONE),
new FontConfig.Font(new File("test.ttc"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
- 1, "", null)),
- LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE)),
+ 1, "", null, FontConfig.Font.VAR_TYPE_AXES_NONE)),
+ LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)),
"sans-serif");
FontConfig.NamedFamilyList family = readNamedFamily(xml);
assertThat(family).isEqualTo(expected);
@@ -211,11 +214,12 @@
FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList(
Collections.singletonList(new FontConfig.FontFamily(Arrays.asList(
new FontConfig.Font(new File("test.ttc"), null, "foo",
- new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null),
+ new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null,
+ FontConfig.Font.VAR_TYPE_AXES_NONE),
new FontConfig.Font(new File("test.ttc"), null, "test",
- new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 1, "", null)),
- LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE)), "sans-serif");
+ new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 1, "", null,
+ FontConfig.Font.VAR_TYPE_AXES_NONE)),
+ LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), "sans-serif");
FontConfig.NamedFamilyList family = readNamedFamily(xml);
assertThat(family).isEqualTo(expected);
}
@@ -385,14 +389,81 @@
public void varFamilyType() throws Exception {
String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ "<familyset>"
- + " <family name='sans-serif' varFamilyType='1'>"
- + " <font>test.ttf</font>"
+ + " <family name='sans-serif'>"
+ + " <font supportedAxes='wght'>test.ttf</font>"
+ + " <font supportedAxes='ital'>test.ttf</font>"
+ + " <font supportedAxes='wght,ital'>test.ttf</font>"
+ " </family>"
+ "</familyset>";
FontConfig config = readFamilies(xml, true /* include non-existing font files */);
List<FontConfig.FontFamily> families = config.getFontFamilies();
assertThat(families.size()).isEqualTo(1); // legacy one should be ignored.
- assertThat(families.get(0).getVariableFontFamilyType()).isEqualTo(1);
+ assertThat(families.get(0).getFontList().get(0).getVarTypeAxes())
+ .isEqualTo(FontConfig.Font.VAR_TYPE_AXES_WGHT);
+ assertThat(families.get(0).getFontList().get(1).getVarTypeAxes())
+ .isEqualTo(FontConfig.Font.VAR_TYPE_AXES_ITAL);
+ assertThat(families.get(0).getFontList().get(2).getVarTypeAxes())
+ .isEqualTo(FontConfig.Font.VAR_TYPE_AXES_WGHT | FontConfig.Font.VAR_TYPE_AXES_ITAL);
+ }
+
+ @Test
+ public void varFamilyTypeRsolve() throws Exception {
+ String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ + "<familyset>"
+ + " <family name='sans-serif'>"
+ + " <font style='normal' supportedAxes='wght'>test.ttf</font>"
+ + " </family>"
+ + " <family>"
+ + " <font style='normal' supportedAxes='wght'>test.ttf</font>"
+ + " <font style='italic' supportedAxes='wght'>test.ttf</font>"
+ + " </family>"
+ + " <family>"
+ + " <font supportedAxes='wght,ital'>test.ttf</font>"
+ + " </family>"
+ + " <family>"
+ + " <font supportedAxes='ital'>test.ttf</font>"
+ + " </family>"
+ + " <family>"
+ + " <font>test.ttf</font>"
+ + " </family>"
+ + "</familyset>";
+ FontConfig config = readFamilies(xml, true /* include non-existing font files */);
+ List<FontConfig.FontFamily> families = config.getFontFamilies();
+ assertThat(families.size()).isEqualTo(5);
+ assertThat(SystemFonts.resolveVarFamilyType(families.get(0), null))
+ .isEqualTo(VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY);
+ assertThat(SystemFonts.resolveVarFamilyType(families.get(1), null))
+ .isEqualTo(VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT);
+ assertThat(SystemFonts.resolveVarFamilyType(families.get(2), null))
+ .isEqualTo(VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL);
+ assertThat(SystemFonts.resolveVarFamilyType(families.get(3), null))
+ .isEqualTo(VARIABLE_FONT_FAMILY_TYPE_NONE);
+ assertThat(SystemFonts.resolveVarFamilyType(families.get(4), null))
+ .isEqualTo(VARIABLE_FONT_FAMILY_TYPE_NONE);
+ }
+
+ @Test
+ public void varFamilyTypeRsolve_ForName() throws Exception {
+ String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ + "<familyset>"
+ + " <family name='sans-serif'>"
+ + " <font style='normal' supportedAxes='wght'>test.ttf</font>"
+ + " </family>"
+ + " <family name='serif'>"
+ + " <font style='normal' supportedAxes='wght'>test.ttf</font>"
+ + " </family>"
+ + " <family>"
+ + " <font style='normal' supportedAxes='wght'>test.ttf</font>"
+ + " <font fallbackFor='serif' supportedAxes='wght,ital'>test.ttf</font>"
+ + " </family>"
+ + "</familyset>";
+ FontConfig config = readFamilies(xml, true /* include non-existing font files */);
+ List<FontConfig.FontFamily> families = config.getFontFamilies();
+ assertThat(families.size()).isEqualTo(2);
+ assertThat(SystemFonts.resolveVarFamilyType(families.get(1), null))
+ .isEqualTo(VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY);
+ assertThat(SystemFonts.resolveVarFamilyType(families.get(1), "serif"))
+ .isEqualTo(VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL);
}
private FontConfig readFamilies(String xml, boolean allowNonExisting)
diff --git a/core/tests/coretests/src/android/util/LogTest.java b/core/tests/coretests/src/android/util/LogTest.java
index f9966a1..15caac9 100644
--- a/core/tests/coretests/src/android/util/LogTest.java
+++ b/core/tests/coretests/src/android/util/LogTest.java
@@ -37,6 +37,13 @@
private static final String LOG_TAG = "LogTest";
@Test
+ public void testWtf() {
+ Log.wtf(LOG_TAG, "Message");
+ Log.wtf(LOG_TAG, "Message", new Throwable("Throwable"));
+ Log.wtf(LOG_TAG, new Throwable("Throwable"));
+ }
+
+ @Test
@Ignore
public void testIsLoggable() {
// First clear any SystemProperty setting for our test key.
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index cf3eb12..60769c7 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -18,6 +18,7 @@
import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR;
import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
+import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
@@ -475,8 +476,9 @@
* Also, mIsFrameRateBoosting should be true when the visibility becomes visible
*/
@Test
- @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
- public void votePreferredFrameRate_voteFrameRateCategory_visibility() {
+ @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+ FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY})
+ public void votePreferredFrameRate_voteFrameRateCategory_visibility_bySize() {
View view = new View(sContext);
attachViewToWindow(view);
ViewRootImpl viewRootImpl = view.getViewRootImpl();
@@ -507,8 +509,9 @@
* <7%: FRAME_RATE_CATEGORY_LOW
*/
@Test
- @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
- public void votePreferredFrameRate_voteFrameRateCategory_smallSize() {
+ @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+ FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY})
+ public void votePreferredFrameRate_voteFrameRateCategory_smallSize_bySize() {
View view = new View(sContext);
WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
@@ -534,8 +537,9 @@
* >=7% : FRAME_RATE_CATEGORY_NORMAL
*/
@Test
- @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
- public void votePreferredFrameRate_voteFrameRateCategory_normalSize() {
+ @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+ FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY})
+ public void votePreferredFrameRate_voteFrameRateCategory_normalSize_bySize() {
View view = new View(sContext);
WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
@@ -559,6 +563,96 @@
}
/**
+ * Test the value of the frame rate cateogry based on the visibility of a view
+ * Invsible: FRAME_RATE_CATEGORY_NO_PREFERENCE
+ * Visible: FRAME_RATE_CATEGORY_HIGH
+ * Also, mIsFrameRateBoosting should be true when the visibility becomes visible
+ */
+ @Test
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+ public void votePreferredFrameRate_voteFrameRateCategory_visibility_defaultHigh() {
+ View view = new View(sContext);
+ attachViewToWindow(view);
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+ sInstrumentation.runOnMainSync(() -> {
+ view.setVisibility(View.INVISIBLE);
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+ FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ });
+ sInstrumentation.waitForIdleSync();
+
+ sInstrumentation.runOnMainSync(() -> {
+ view.setVisibility(View.VISIBLE);
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+ FRAME_RATE_CATEGORY_HIGH);
+ });
+ sInstrumentation.waitForIdleSync();
+
+ sInstrumentation.runOnMainSync(() -> {
+ assertEquals(viewRootImpl.getIsFrameRateBoosting(), true);
+ });
+ }
+
+ /**
+ * Test the value of the frame rate cateogry based on the size of a view.
+ * The current threshold value is 7% of the screen size
+ * <7%: FRAME_RATE_CATEGORY_NORMAL
+ */
+ @Test
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+ public void votePreferredFrameRate_voteFrameRateCategory_smallSize_defaultHigh() {
+ View view = new View(sContext);
+ WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
+ wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+ wmlp.width = 1;
+ wmlp.height = 1;
+
+ sInstrumentation.runOnMainSync(() -> {
+ WindowManager wm = sContext.getSystemService(WindowManager.class);
+ wm.addView(view, wmlp);
+ });
+ sInstrumentation.waitForIdleSync();
+
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+ sInstrumentation.runOnMainSync(() -> {
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
+ });
+ }
+
+ /**
+ * Test the value of the frame rate cateogry based on the size of a view.
+ * The current threshold value is 7% of the screen size
+ * >=7% : FRAME_RATE_CATEGORY_HIGH
+ */
+ @Test
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+ public void votePreferredFrameRate_voteFrameRateCategory_normalSize_defaultHigh() {
+ View view = new View(sContext);
+ WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
+ wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+
+ sInstrumentation.runOnMainSync(() -> {
+ WindowManager wm = sContext.getSystemService(WindowManager.class);
+ Display display = wm.getDefaultDisplay();
+ DisplayMetrics metrics = new DisplayMetrics();
+ display.getMetrics(metrics);
+ wmlp.width = (int) (metrics.widthPixels * 0.9);
+ wmlp.height = (int) (metrics.heightPixels * 0.9);
+ wm.addView(view, wmlp);
+ });
+ sInstrumentation.waitForIdleSync();
+
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+ sInstrumentation.runOnMainSync(() -> {
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ });
+ }
+
+ /**
* Test how values of the frame rate cateogry are aggregated.
* It should take the max value among all of the voted categories per frame.
*/
@@ -701,6 +795,61 @@
});
}
+ /**
+ * Test the logic of infrequent layer:
+ * - NORMAL for infrequent update: FT2-FT1 > 100 && FT3-FT2 > 100.
+ * - HIGH/NORMAL based on size for frequent update: (FT3-FT2) + (FT2 - FT1) < 100.
+ * - otherwise, use the previous category value.
+ */
+ @Test
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+ public void votePreferredFrameRate_infrequentLayer_defaultHigh() throws InterruptedException {
+ final long delay = 200L;
+
+ View view = new View(sContext);
+ WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
+ wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+
+ sInstrumentation.runOnMainSync(() -> {
+ WindowManager wm = sContext.getSystemService(WindowManager.class);
+ Display display = wm.getDefaultDisplay();
+ DisplayMetrics metrics = new DisplayMetrics();
+ display.getMetrics(metrics);
+ wmlp.width = (int) (metrics.widthPixels * 0.9);
+ wmlp.height = (int) (metrics.heightPixels * 0.9);
+ wm.addView(view, wmlp);
+ });
+ sInstrumentation.waitForIdleSync();
+
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+
+ // Frequent update
+ sInstrumentation.runOnMainSync(() -> {
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+ FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ });
+
+ // In transistion from frequent update to infrequent update
+ Thread.sleep(delay);
+ sInstrumentation.runOnMainSync(() -> {
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ });
+
+ // Infrequent update
+ Thread.sleep(delay);
+ sInstrumentation.runOnMainSync(() -> {
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
+ });
+ }
+
@Test
public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() {
mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index 543d73b..5862711 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -450,13 +450,8 @@
}
private RemoteViews.DrawInstructions getDrawInstructions() {
- final byte[] first = new byte[] {'f', 'i', 'r', 's', 't'};
- final byte[] second = new byte[] {'s', 'e', 'c', 'o', 'n', 'd'};
- final RemoteViews.DrawInstructions drawInstructions =
- new RemoteViews.DrawInstructions.Builder(
- Collections.singletonList(first)).build();
- drawInstructions.appendInstructions(second);
- return drawInstructions;
+ final byte[] bytes = new byte[] {'h', 'e', 'l', 'l', 'o'};
+ return new RemoteViews.DrawInstructions.Builder(Collections.singletonList(bytes)).build();
}
private RemoteViews createViewChained(int depth, String... texts) {
diff --git a/core/tests/utiltests/Android.bp b/core/tests/utiltests/Android.bp
index 2ccee71..f5563a7 100644
--- a/core/tests/utiltests/Android.bp
+++ b/core/tests/utiltests/Android.bp
@@ -60,7 +60,6 @@
static_libs: [
"androidx.annotation_annotation",
"androidx.test.rules",
- "mockito_ravenwood",
"frameworks-base-testutils",
"servicestests-utils",
],
diff --git a/core/tests/utiltests/src/android/util/SlogTest.java b/core/tests/utiltests/src/android/util/SlogTest.java
index 6f761e3..738c668 100644
--- a/core/tests/utiltests/src/android/util/SlogTest.java
+++ b/core/tests/utiltests/src/android/util/SlogTest.java
@@ -44,4 +44,11 @@
Slog.w(TAG, MSG, THROWABLE);
Slog.e(TAG, MSG, THROWABLE);
}
+
+ @Test
+ public void testWtf() {
+ Slog.wtf(TAG, MSG);
+ Slog.wtf(TAG, MSG, THROWABLE);
+ Slog.wtf(TAG, THROWABLE);
+ }
}
diff --git a/data/etc/OWNERS b/data/etc/OWNERS
index ea23aba..245f216 100644
--- a/data/etc/OWNERS
+++ b/data/etc/OWNERS
@@ -1,3 +1,5 @@
+include /PACKAGE_MANAGER_OWNERS
+
alanstokes@google.com
cbrubaker@google.com
hackbod@android.com
@@ -6,11 +8,6 @@
jsharkey@android.com
jsharkey@google.com
lorenzo@google.com
-svetoslavganov@android.com
-svetoslavganov@google.com
-toddke@android.com
-toddke@google.com
-patb@google.com
yamasani@google.com
per-file preinstalled-packages* = file:/MULTIUSER_OWNERS
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 2873428..91e620c 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -185,6 +185,7 @@
<permission name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
<permission name="android.permission.UWB_PRIVILEGED"/>
<permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
+ <permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
</privapp-permissions>
<privapp-permissions package="com.android.providers.calendar">
@@ -571,6 +572,8 @@
<!-- Permission required for BinaryTransparencyService shell API and host test -->
<permission name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES" />
<permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
+ <!-- Permissions required for CTS test - CtsContactKeysProviderPrivilegedApp -->
+ <permission name="android.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS"/>
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/data/fonts/font_fallback.xml b/data/fonts/font_fallback.xml
index 1bd182f..15ea15a 100644
--- a/data/fonts/font_fallback.xml
+++ b/data/fonts/font_fallback.xml
@@ -11,12 +11,84 @@
effectively add 300 to the weight, this ensures that 900 is the bold
paired with the 500 weight, ensuring adequate contrast.
- TODO(rsheeter) update comment; ordering to match 800 to 900 is no longer required
+
+ The font_fallback.xml defines the list of font used by the system.
+
+ `familyset` node:
+ A `familyset` element must be a root node of the font_fallback.xml. No attributes are allowed
+ to `familyset` node.
+ The `familyset` node can contains `family` and `alias` nodes. Any other nodes will be ignored.
+
+ `family` node:
+ A `family` node defines a single font family definition.
+ A font family is a set of fonts for drawing text in various styles such as weight, slant.
+ There are three types of families, default family, named family and locale fallback family.
+
+ The default family is a special family node appeared the first node of the `familyset` node.
+ The default family is used as first priority fallback.
+ Only `name` attribute can be used for default family node. If the `name` attribute is
+ specified, This family will also works as named family.
+
+ The named family is a family that has name attribute. The named family defines a new fallback.
+ For example, if the name attribute is "serif", it creates serif fallback. Developers can
+ access the fallback by using Typeface#create API.
+ The named family can not have attribute other than `name` attribute. The `name` attribute
+ cannot be empty.
+
+ The locale fallback family is a font family that is used for fallback. The fallback family is
+ used when the named family or default family cannot be used. The locale fallback family can
+ have `lang` attribute and `variant` attribute. The `lang` attribute is an optional comma
+ separated BCP-47i language tag. The `variant` is an optional attribute that can be one one
+ `element`, `compact`. If a `variant` attribute is not specified, it is treated as default.
+
+ `alias` node:
+ An `alias` node defines a alias of named family with changing weight offset. An `alias` node
+ can have mandatory `name` and `to` attribute and optional `weight` attribute. This `alias`
+ defines new fallback that has the name of specified `name` attribute. The fallback list is
+ the same to the fallback that of the name specified with `to` attribute. If `weight` attribute
+ is specified, the base weight offset is shifted to the specified value. For example, if the
+ `weight` is 500, the output text is drawn with 500 of weight.
+
+ `font` node:
+ A `font` node defines a single font definition. There are two types of fonts, static font and
+ variable font.
+
+ A static font can have `weight`, `style`, `index` and `postScriptName` attributes. A `weight`
+ is a mandatory attribute that defines the weight of the font. Any number between 0 to 1000 is
+ valid. A `style` is a mandatory attribute that defines the style of the font. A 'style'
+ attribute can be `normal` or `italic`. An `index` is an optional attribute that defines the
+ index of the font collection. If this is not specified, it is treated as 0. If the font file
+ is not a font collection, this attribute is ignored. A `postScriptName` attribute is an
+ optional attribute. A PostScript name is used for identifying target of system font update.
+ If this is not specified, the system assumes the filename is same to PostScript name of the
+ font file. For example, if the font file is "Roboto-Regular.ttf", the system assume the
+ PostScript name of this font is "Roboto-Regular".
+
+ A variable font can be only defined for the variable font file. A variable font can have
+ `axis` child nodes for specifying axis values. A variable font can have all attribute of
+ static font and can have additional `supportedAxes` attribute. A `supportedAxes` attribute
+ is a comma separated supported axis tags. As of Android V, only `wght` and `ital` axes can
+ be specified.
+
+ If `supportedAxes` attribute is not specified, this `font` node works as static font of the
+ single instance of variable font specified with `axis` children.
+
+ If `supportedAxes` attribute is specified, the system dynamically create font instance for the
+ given weight and style value. If `wght` is specified in `supportedAxes` attribute the `weight`
+ attribute and `axis` child that has `wght` tag become optional and ignored because it is
+ determined by system at runtime. Similarly, if `ital` is specified in `supportedAxes`
+ attribute, the `style` attribute and `axis` child that has `ital` tag become optional and
+ ignored.
+
+ `axis` node:
+ An `axis` node defines a font variation value for a tag. An `axis` node can have two mandatory
+ attributes, `tag` and `value`. If the font is variable font and the same tag `axis` node is
+ specified in `supportedAxes` attribute, the style value works like a default instance.
-->
-<familyset version="23">
+<familyset>
<!-- first font is default -->
- <family name="sans-serif" varFamilyType="2">
- <font>Roboto-Regular.ttf
+ <family name="sans-serif">
+ <font supportedAxes="wght,ital">Roboto-Regular.ttf
<axis tag="wdth" stylevalue="100" />
</font>
</family>
@@ -32,8 +104,8 @@
<alias name="tahoma" to="sans-serif" />
<alias name="verdana" to="sans-serif" />
- <family name="sans-serif-condensed" varFamilyType="2">
- <font>Roboto-Regular.ttf
+ <family name="sans-serif-condensed">
+ <font supportedAxes="wght,ital">Roboto-Regular.ttf
<axis tag="wdth" stylevalue="75" />
</font>
</family>
@@ -72,8 +144,8 @@
<font weight="400" style="normal" postScriptName="ComingSoon-Regular">ComingSoon.ttf</font>
</family>
- <family name="cursive" varFamilyType="1">
- <font>DancingScript-Regular.ttf</font>
+ <family name="cursive">
+ <font supportedAxes="wght">DancingScript-Regular.ttf</font>
</family>
<family name="sans-serif-smallcaps">
@@ -90,8 +162,8 @@
</family>
<alias name="source-sans-pro-semi-bold" to="source-sans-pro" weight="600"/>
- <family name="roboto-flex" varFamilyType="2">
- <font>RobotoFlex-Regular.ttf
+ <family name="roboto-flex">
+ <font supportedAxes="wght">RobotoFlex-Regular.ttf
<axis tag="wdth" stylevalue="100" />
</font>
</family>
@@ -109,11 +181,11 @@
</font>
<font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
</family>
- <family lang="und-Ethi" varFamilyType="1">
- <font postScriptName="NotoSansEthiopic-Regular">
+ <family lang="und-Ethi">
+ <font postScriptName="NotoSansEthiopic-Regular" supportedAxes="wght">
NotoSansEthiopic-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifEthiopic-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifEthiopic-Regular" supportedAxes="wght">
NotoSerifEthiopic-VF.ttf
</font>
</family>
@@ -140,32 +212,32 @@
</font>
<font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font>
</family>
- <family lang="und-Armn" varFamilyType="1">
- <font postScriptName="NotoSansArmenian-Regular">
+ <family lang="und-Armn">
+ <font postScriptName="NotoSansArmenian-Regular" supportedAxes="wght">
NotoSansArmenian-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifArmenian-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifArmenian-Regular" supportedAxes="wght">
NotoSerifArmenian-VF.ttf
</font>
</family>
- <family lang="und-Geor,und-Geok" varFamilyType="1">
- <font postScriptName="NotoSansGeorgian-Regular">
+ <family lang="und-Geor,und-Geok">
+ <font postScriptName="NotoSansGeorgian-Regular" supportedAxes="wght">
NotoSansGeorgian-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifGeorgian-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifGeorgian-Regular" supportedAxes="wght">
NotoSerifGeorgian-VF.ttf
</font>
</family>
- <family lang="und-Deva" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansDevanagari-Regular">
+ <family lang="und-Deva" variant="elegant">
+ <font postScriptName="NotoSansDevanagari-Regular" supportedAxes="wght">
NotoSansDevanagari-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifDevanagari-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifDevanagari-Regular" supportedAxes="wght">
NotoSerifDevanagari-VF.ttf
</font>
</family>
- <family lang="und-Deva" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansDevanagariUI-Regular">
+ <family lang="und-Deva" variant="compact">
+ <font postScriptName="NotoSansDevanagariUI-Regular" supportedAxes="wght">
NotoSansDevanagariUI-VF.ttf
</font>
</family>
@@ -178,21 +250,9 @@
NotoSansGujarati-Regular.ttf
</font>
<font weight="700" style="normal">NotoSansGujarati-Bold.ttf</font>
- <font weight="400" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
- <axis tag="wght" stylevalue="700"/>
+ <font style="normal" fallbackFor="serif" postScriptName="NotoSerifGujarati-Regular"
+ supportedAxes="wght">
+ NotoSerifGujarati-VF.ttf
</font>
</family>
<family lang="und-Gujr" variant="compact">
@@ -201,81 +261,81 @@
</font>
<font weight="700" style="normal">NotoSansGujaratiUI-Bold.ttf</font>
</family>
- <family lang="und-Guru" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansGurmukhi-Regular">
+ <family lang="und-Guru" variant="elegant">
+ <font postScriptName="NotoSansGurmukhi-Regular" supportedAxes="wght">
NotoSansGurmukhi-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifGurmukhi-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifGurmukhi-Regular" supportedAxes="wght">
NotoSerifGurmukhi-VF.ttf
</font>
</family>
- <family lang="und-Guru" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansGurmukhiUI-Regular">
+ <family lang="und-Guru" variant="compact">
+ <font postScriptName="NotoSansGurmukhiUI-Regular" supportedAxes="wght">
NotoSansGurmukhiUI-VF.ttf
</font>
</family>
- <family lang="und-Taml" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansTamil-Regular">
+ <family lang="und-Taml" variant="elegant">
+ <font postScriptName="NotoSansTamil-Regular" supportedAxes="wght">
NotoSansTamil-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifTamil-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifTamil-Regular" supportedAxes="wght">
NotoSerifTamil-VF.ttf
</font>
</family>
- <family lang="und-Taml" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansTamilUI-Regular">
+ <family lang="und-Taml" variant="compact">
+ <font postScriptName="NotoSansTamilUI-Regular" supportedAxes="wght">
NotoSansTamilUI-VF.ttf
</font>
</family>
- <family lang="und-Mlym" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansMalayalam-Regular">
+ <family lang="und-Mlym" variant="elegant">
+ <font postScriptName="NotoSansMalayalam-Regular" supportedAxes="wght">
NotoSansMalayalam-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifMalayalam-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifMalayalam-Regular" supportedAxes="wght">
NotoSerifMalayalam-VF.ttf
</font>
</family>
- <family lang="und-Mlym" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansMalayalamUI-Regular">
+ <family lang="und-Mlym" variant="compact">
+ <font postScriptName="NotoSansMalayalamUI-Regular" supportedAxes="wght">
NotoSansMalayalamUI-VF.ttf
</font>
</family>
- <family lang="und-Beng" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansBengali-Regular">
+ <family lang="und-Beng" variant="elegant">
+ <font postScriptName="NotoSansBengali-Regular" supportedAxes="wght">
NotoSansBengali-VF.ttf
</font>
<font fallbackFor="serif" postScriptName="NotoSerifBengali-Regular">
NotoSerifBengali-VF.ttf
</font>
</family>
- <family lang="und-Beng" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansBengaliUI-Regular">
+ <family lang="und-Beng" variant="compact">
+ <font postScriptName="NotoSansBengaliUI-Regular" supportedAxes="wght">
NotoSansBengaliUI-VF.ttf
</font>
</family>
- <family lang="und-Telu" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansTelugu-Regular">
+ <family lang="und-Telu" variant="elegant">
+ <font postScriptName="NotoSansTelugu-Regular" supportedAxes="wght">
NotoSansTelugu-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifTelugu-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifTelugu-Regular" supportedAxes="wght">
NotoSerifTelugu-VF.ttf
</font>
</family>
- <family lang="und-Telu" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansTeluguUI-Regular">
+ <family lang="und-Telu" variant="compact">
+ <font postScriptName="NotoSansTeluguUI-Regular" supportedAxes="wght">
NotoSansTeluguUI-VF.ttf
</font>
</family>
- <family lang="und-Knda" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansKannada-Regular">
+ <family lang="und-Knda" variant="elegant">
+ <font postScriptName="NotoSansKannada-Regular" supportedAxes="wght">
NotoSansKannada-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifKannada-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifKannada-Regular" supportedAxes="wght">
NotoSerifKannada-VF.ttf
</font>
</family>
- <family lang="und-Knda" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansKannadaUI-Regular">
+ <family lang="und-Knda" variant="compact">
+ <font postScriptName="NotoSansKannadaUI-Regular" supportedAxes="wght">
NotoSansKannadaUI-VF.ttf
</font>
</family>
@@ -290,19 +350,20 @@
</font>
<font weight="700" style="normal">NotoSansOriyaUI-Bold.ttf</font>
</family>
- <family lang="und-Sinh" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansSinhala-Regular">
+ <family lang="und-Sinh" variant="elegant">
+ <font postScriptName="NotoSansSinhala-Regular" supportedAxes="wght">
NotoSansSinhala-VF.ttf
</font>
<font fallbackFor="serif" postScriptName="NotoSerifSinhala-Regular">
NotoSerifSinhala-VF.ttf
</font>
</family>
- <family lang="und-Sinh" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansSinhalaUI-Regular">
+ <family lang="und-Sinh" variant="compact">
+ <font postScriptName="NotoSansSinhalaUI-Regular" supportedAxes="wght">
NotoSansSinhalaUI-VF.ttf
</font>
</family>
+ <!-- TODO: NotoSansKhmer uses non-standard wght value, so cannot use auto-adjustment. -->
<family lang="und-Khmr" variant="elegant">
<font weight="100" style="normal" postScriptName="NotoSansKhmer-Regular">
NotoSansKhmer-VF.ttf
@@ -398,8 +459,8 @@
<family lang="und-Ahom">
<font weight="400" style="normal">NotoSansAhom-Regular.otf</font>
</family>
- <family lang="und-Adlm" varFamilyType="1">
- <font postScriptName="NotoSansAdlam-Regular">
+ <family lang="und-Adlm">
+ <font postScriptName="NotoSansAdlam-Regular" supportedAxes="wght">
NotoSansAdlam-VF.ttf
</font>
</family>
@@ -686,8 +747,8 @@
NotoSansTaiViet-Regular.ttf
</font>
</family>
- <family lang="und-Tibt" varFamilyType="1">
- <font postScriptName="NotoSerifTibetan-Regular">
+ <family lang="und-Tibt">
+ <font postScriptName="NotoSerifTibetan-Regular" supportedAxes="wght">
NotoSerifTibetan-VF.ttf
</font>
</family>
@@ -865,27 +926,27 @@
<family lang="und-Dogr">
<font weight="400" style="normal">NotoSerifDogra-Regular.ttf</font>
</family>
- <family lang="und-Medf" varFamilyType="1">
- <font postScriptName="NotoSansMedefaidrin-Regular">
+ <family lang="und-Medf">
+ <font postScriptName="NotoSansMedefaidrin-Regular" supportedAxes="wght">
NotoSansMedefaidrin-VF.ttf
</font>
</family>
- <family lang="und-Soyo" varFamilyType="1">
+ <family lang="und-Soyo" supportedAxes="wght">
<font postScriptName="NotoSansSoyombo-Regular">
NotoSansSoyombo-VF.ttf
</font>
</family>
- <family lang="und-Takr" varFamilyType="1">
+ <family lang="und-Takr" supportedAxes="wght">
<font postScriptName="NotoSansTakri-Regular">
NotoSansTakri-VF.ttf
</font>
</family>
- <family lang="und-Hmnp" varFamilyType="1">
+ <family lang="und-Hmnp" supportedAxes="wght">
<font postScriptName="NotoSerifHmongNyiakeng-Regular">
NotoSerifNyiakengPuachueHmong-VF.ttf
</font>
</family>
- <family lang="und-Yezi" varFamilyType="1">
+ <family lang="und-Yezi" supportedAxes="wght">
<font postScriptName="NotoSerifYezidi-Regular">
NotoSerifYezidi-VF.ttf
</font>
diff --git a/data/fonts/font_fallback_cjkvf.xml b/data/fonts/font_fallback_cjkvf.xml
index 75bc74e..c1ca67e 100644
--- a/data/fonts/font_fallback_cjkvf.xml
+++ b/data/fonts/font_fallback_cjkvf.xml
@@ -11,12 +11,84 @@
effectively add 300 to the weight, this ensures that 900 is the bold
paired with the 500 weight, ensuring adequate contrast.
- TODO(rsheeter) update comment; ordering to match 800 to 900 is no longer required
+
+ The font_fallback.xml defines the list of font used by the system.
+
+ `familyset` node:
+ A `familyset` element must be a root node of the font_fallback.xml. No attributes are allowed
+ to `familyset` node.
+ The `familyset` node can contains `family` and `alias` nodes. Any other nodes will be ignored.
+
+ `family` node:
+ A `family` node defines a single font family definition.
+ A font family is a set of fonts for drawing text in various styles such as weight, slant.
+ There are three types of families, default family, named family and locale fallback family.
+
+ The default family is a special family node appeared the first node of the `familyset` node.
+ The default family is used as first priority fallback.
+ Only `name` attribute can be used for default family node. If the `name` attribute is
+ specified, This family will also works as named family.
+
+ The named family is a family that has name attribute. The named family defines a new fallback.
+ For example, if the name attribute is "serif", it creates serif fallback. Developers can
+ access the fallback by using Typeface#create API.
+ The named family can not have attribute other than `name` attribute. The `name` attribute
+ cannot be empty.
+
+ The locale fallback family is a font family that is used for fallback. The fallback family is
+ used when the named family or default family cannot be used. The locale fallback family can
+ have `lang` attribute and `variant` attribute. The `lang` attribute is an optional comma
+ separated BCP-47i language tag. The `variant` is an optional attribute that can be one one
+ `element`, `compact`. If a `variant` attribute is not specified, it is treated as default.
+
+ `alias` node:
+ An `alias` node defines a alias of named family with changing weight offset. An `alias` node
+ can have mandatory `name` and `to` attribute and optional `weight` attribute. This `alias`
+ defines new fallback that has the name of specified `name` attribute. The fallback list is
+ the same to the fallback that of the name specified with `to` attribute. If `weight` attribute
+ is specified, the base weight offset is shifted to the specified value. For example, if the
+ `weight` is 500, the output text is drawn with 500 of weight.
+
+ `font` node:
+ A `font` node defines a single font definition. There are two types of fonts, static font and
+ variable font.
+
+ A static font can have `weight`, `style`, `index` and `postScriptName` attributes. A `weight`
+ is a mandatory attribute that defines the weight of the font. Any number between 0 to 1000 is
+ valid. A `style` is a mandatory attribute that defines the style of the font. A 'style'
+ attribute can be `normal` or `italic`. An `index` is an optional attribute that defines the
+ index of the font collection. If this is not specified, it is treated as 0. If the font file
+ is not a font collection, this attribute is ignored. A `postScriptName` attribute is an
+ optional attribute. A PostScript name is used for identifying target of system font update.
+ If this is not specified, the system assumes the filename is same to PostScript name of the
+ font file. For example, if the font file is "Roboto-Regular.ttf", the system assume the
+ PostScript name of this font is "Roboto-Regular".
+
+ A variable font can be only defined for the variable font file. A variable font can have
+ `axis` child nodes for specifying axis values. A variable font can have all attribute of
+ static font and can have additional `supportedAxes` attribute. A `supportedAxes` attribute
+ is a comma separated supported axis tags. As of Android V, only `wght` and `ital` axes can
+ be specified.
+
+ If `supportedAxes` attribute is not specified, this `font` node works as static font of the
+ single instance of variable font specified with `axis` children.
+
+ If `supportedAxes` attribute is specified, the system dynamically create font instance for the
+ given weight and style value. If `wght` is specified in `supportedAxes` attribute the `weight`
+ attribute and `axis` child that has `wght` tag become optional and ignored because it is
+ determined by system at runtime. Similarly, if `ital` is specified in `supportedAxes`
+ attribute, the `style` attribute and `axis` child that has `ital` tag become optional and
+ ignored.
+
+ `axis` node:
+ An `axis` node defines a font variation value for a tag. An `axis` node can have two mandatory
+ attributes, `tag` and `value`. If the font is variable font and the same tag `axis` node is
+ specified in `supportedAxes` attribute, the style value works like a default instance.
-->
-<familyset version="23">
+<familyset>
<!-- first font is default -->
- <family name="sans-serif" varFamilyType="2">
- <font>Roboto-Regular.ttf
+ <family name="sans-serif">
+ <font supportedAxes="wght,ital">Roboto-Regular.ttf
<axis tag="wdth" stylevalue="100" />
</font>
</family>
@@ -32,8 +104,8 @@
<alias name="tahoma" to="sans-serif" />
<alias name="verdana" to="sans-serif" />
- <family name="sans-serif-condensed" varFamilyType="2">
- <font>Roboto-Regular.ttf
+ <family name="sans-serif-condensed">
+ <font supportedAxes="wght,ital">Roboto-Regular.ttf
<axis tag="wdth" stylevalue="75" />
</font>
</family>
@@ -72,8 +144,8 @@
<font weight="400" style="normal" postScriptName="ComingSoon-Regular">ComingSoon.ttf</font>
</family>
- <family name="cursive" varFamilyType="1">
- <font>DancingScript-Regular.ttf</font>
+ <family name="cursive">
+ <font supportedAxes="wght">DancingScript-Regular.ttf</font>
</family>
<family name="sans-serif-smallcaps">
@@ -90,8 +162,8 @@
</family>
<alias name="source-sans-pro-semi-bold" to="source-sans-pro" weight="600"/>
- <family name="roboto-flex" varFamilyType="2">
- <font>RobotoFlex-Regular.ttf
+ <family name="roboto-flex">
+ <font supportedAxes="wght">RobotoFlex-Regular.ttf
<axis tag="wdth" stylevalue="100" />
</font>
</family>
@@ -109,11 +181,11 @@
</font>
<font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
</family>
- <family lang="und-Ethi" varFamilyType="1">
- <font postScriptName="NotoSansEthiopic-Regular">
+ <family lang="und-Ethi">
+ <font postScriptName="NotoSansEthiopic-Regular" supportedAxes="wght">
NotoSansEthiopic-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifEthiopic-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifEthiopic-Regular" supportedAxes="wght">
NotoSerifEthiopic-VF.ttf
</font>
</family>
@@ -140,32 +212,32 @@
</font>
<font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font>
</family>
- <family lang="und-Armn" varFamilyType="1">
- <font postScriptName="NotoSansArmenian-Regular">
+ <family lang="und-Armn">
+ <font postScriptName="NotoSansArmenian-Regular" supportedAxes="wght">
NotoSansArmenian-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifArmenian-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifArmenian-Regular" supportedAxes="wght">
NotoSerifArmenian-VF.ttf
</font>
</family>
- <family lang="und-Geor,und-Geok" varFamilyType="1">
- <font postScriptName="NotoSansGeorgian-Regular">
+ <family lang="und-Geor,und-Geok">
+ <font postScriptName="NotoSansGeorgian-Regular" supportedAxes="wght">
NotoSansGeorgian-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifGeorgian-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifGeorgian-Regular" supportedAxes="wght">
NotoSerifGeorgian-VF.ttf
</font>
</family>
- <family lang="und-Deva" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansDevanagari-Regular">
+ <family lang="und-Deva" variant="elegant">
+ <font postScriptName="NotoSansDevanagari-Regular" supportedAxes="wght">
NotoSansDevanagari-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifDevanagari-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifDevanagari-Regular" supportedAxes="wght">
NotoSerifDevanagari-VF.ttf
</font>
</family>
- <family lang="und-Deva" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansDevanagariUI-Regular">
+ <family lang="und-Deva" variant="compact">
+ <font postScriptName="NotoSansDevanagariUI-Regular" supportedAxes="wght">
NotoSansDevanagariUI-VF.ttf
</font>
</family>
@@ -178,21 +250,9 @@
NotoSansGujarati-Regular.ttf
</font>
<font weight="700" style="normal">NotoSansGujarati-Bold.ttf</font>
- <font weight="400" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
- <axis tag="wght" stylevalue="700"/>
+ <font style="normal" fallbackFor="serif" postScriptName="NotoSerifGujarati-Regular"
+ supportedAxes="wght">
+ NotoSerifGujarati-VF.ttf
</font>
</family>
<family lang="und-Gujr" variant="compact">
@@ -201,81 +261,81 @@
</font>
<font weight="700" style="normal">NotoSansGujaratiUI-Bold.ttf</font>
</family>
- <family lang="und-Guru" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansGurmukhi-Regular">
+ <family lang="und-Guru" variant="elegant">
+ <font postScriptName="NotoSansGurmukhi-Regular" supportedAxes="wght">
NotoSansGurmukhi-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifGurmukhi-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifGurmukhi-Regular" supportedAxes="wght">
NotoSerifGurmukhi-VF.ttf
</font>
</family>
- <family lang="und-Guru" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansGurmukhiUI-Regular">
+ <family lang="und-Guru" variant="compact">
+ <font postScriptName="NotoSansGurmukhiUI-Regular" supportedAxes="wght">
NotoSansGurmukhiUI-VF.ttf
</font>
</family>
- <family lang="und-Taml" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansTamil-Regular">
+ <family lang="und-Taml" variant="elegant">
+ <font postScriptName="NotoSansTamil-Regular" supportedAxes="wght">
NotoSansTamil-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifTamil-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifTamil-Regular" supportedAxes="wght">
NotoSerifTamil-VF.ttf
</font>
</family>
- <family lang="und-Taml" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansTamilUI-Regular">
+ <family lang="und-Taml" variant="compact">
+ <font postScriptName="NotoSansTamilUI-Regular" supportedAxes="wght">
NotoSansTamilUI-VF.ttf
</font>
</family>
- <family lang="und-Mlym" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansMalayalam-Regular">
+ <family lang="und-Mlym" variant="elegant">
+ <font postScriptName="NotoSansMalayalam-Regular" supportedAxes="wght">
NotoSansMalayalam-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifMalayalam-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifMalayalam-Regular" supportedAxes="wght">
NotoSerifMalayalam-VF.ttf
</font>
</family>
- <family lang="und-Mlym" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansMalayalamUI-Regular">
+ <family lang="und-Mlym" variant="compact">
+ <font postScriptName="NotoSansMalayalamUI-Regular" supportedAxes="wght">
NotoSansMalayalamUI-VF.ttf
</font>
</family>
- <family lang="und-Beng" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansBengali-Regular">
+ <family lang="und-Beng" variant="elegant">
+ <font postScriptName="NotoSansBengali-Regular" supportedAxes="wght">
NotoSansBengali-VF.ttf
</font>
<font fallbackFor="serif" postScriptName="NotoSerifBengali-Regular">
NotoSerifBengali-VF.ttf
</font>
</family>
- <family lang="und-Beng" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansBengaliUI-Regular">
+ <family lang="und-Beng" variant="compact">
+ <font postScriptName="NotoSansBengaliUI-Regular" supportedAxes="wght">
NotoSansBengaliUI-VF.ttf
</font>
</family>
- <family lang="und-Telu" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansTelugu-Regular">
+ <family lang="und-Telu" variant="elegant">
+ <font postScriptName="NotoSansTelugu-Regular" supportedAxes="wght">
NotoSansTelugu-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifTelugu-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifTelugu-Regular" supportedAxes="wght">
NotoSerifTelugu-VF.ttf
</font>
</family>
- <family lang="und-Telu" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansTeluguUI-Regular">
+ <family lang="und-Telu" variant="compact">
+ <font postScriptName="NotoSansTeluguUI-Regular" supportedAxes="wght">
NotoSansTeluguUI-VF.ttf
</font>
</family>
- <family lang="und-Knda" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansKannada-Regular">
+ <family lang="und-Knda" variant="elegant">
+ <font postScriptName="NotoSansKannada-Regular" supportedAxes="wght">
NotoSansKannada-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifKannada-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifKannada-Regular" supportedAxes="wght">
NotoSerifKannada-VF.ttf
</font>
</family>
- <family lang="und-Knda" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansKannadaUI-Regular">
+ <family lang="und-Knda" variant="compact">
+ <font postScriptName="NotoSansKannadaUI-Regular" supportedAxes="wght">
NotoSansKannadaUI-VF.ttf
</font>
</family>
@@ -290,19 +350,20 @@
</font>
<font weight="700" style="normal">NotoSansOriyaUI-Bold.ttf</font>
</family>
- <family lang="und-Sinh" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansSinhala-Regular">
+ <family lang="und-Sinh" variant="elegant">
+ <font postScriptName="NotoSansSinhala-Regular" supportedAxes="wght">
NotoSansSinhala-VF.ttf
</font>
<font fallbackFor="serif" postScriptName="NotoSerifSinhala-Regular">
NotoSerifSinhala-VF.ttf
</font>
</family>
- <family lang="und-Sinh" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansSinhalaUI-Regular">
+ <family lang="und-Sinh" variant="compact">
+ <font postScriptName="NotoSansSinhalaUI-Regular" supportedAxes="wght">
NotoSansSinhalaUI-VF.ttf
</font>
</family>
+ <!-- TODO: NotoSansKhmer uses non-standard wght value, so cannot use auto-adjustment. -->
<family lang="und-Khmr" variant="elegant">
<font weight="100" style="normal" postScriptName="NotoSansKhmer-Regular">
NotoSansKhmer-VF.ttf
@@ -398,8 +459,8 @@
<family lang="und-Ahom">
<font weight="400" style="normal">NotoSansAhom-Regular.otf</font>
</family>
- <family lang="und-Adlm" varFamilyType="1">
- <font postScriptName="NotoSansAdlam-Regular">
+ <family lang="und-Adlm">
+ <font postScriptName="NotoSansAdlam-Regular" supportedAxes="wght">
NotoSansAdlam-VF.ttf
</font>
</family>
@@ -686,8 +747,8 @@
NotoSansTaiViet-Regular.ttf
</font>
</family>
- <family lang="und-Tibt" varFamilyType="1">
- <font postScriptName="NotoSerifTibetan-Regular">
+ <family lang="und-Tibt">
+ <font postScriptName="NotoSerifTibetan-Regular" supportedAxes="wght">
NotoSerifTibetan-VF.ttf
</font>
</family>
@@ -707,123 +768,36 @@
<font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font>
</family>
<family lang="zh-Hans">
- <font weight="100" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"
+ supportedAxes="wght">
NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="100"/>
- </font>
- <font weight="200" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="200"/>
- </font>
- <font weight="300" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="300"/>
- </font>
- <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="700"/>
- </font>
- <font weight="800" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="800"/>
- </font>
- <font weight="900" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="900"/>
+ <!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400
+ for making regular style as default. -->
+ <axis tag="wght" stylevalue="400" />
</font>
<font weight="400" style="normal" index="2" fallbackFor="serif"
postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
</font>
</family>
<family lang="zh-Hant,zh-Bopo">
- <font weight="100" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"
+ supportedAxes="wght">
NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="100"/>
- </font>
- <font weight="200" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="200"/>
- </font>
- <font weight="300" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="300"/>
- </font>
- <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="700"/>
- </font>
- <font weight="800" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="800"/>
- </font>
- <font weight="900" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="900"/>
+ <!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400
+ for making regular style as default. -->
+ <axis tag="wght" stylevalue="400" />
</font>
<font weight="400" style="normal" index="3" fallbackFor="serif"
postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
</font>
</family>
<family lang="ja">
- <font weight="100" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"
+ supportedAxes="wght">
NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="100"/>
- </font>
- <font weight="200" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="200"/>
- </font>
- <font weight="300" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="300"/>
- </font>
- <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="700"/>
- </font>
- <font weight="800" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="800"/>
- </font>
- <font weight="900" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="900"/>
+ <!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400
+ for making regular style as default. -->
+ <axis tag="wght" stylevalue="400" />
</font>
<font weight="400" style="normal" index="0" fallbackFor="serif"
postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
@@ -840,41 +814,12 @@
</font>
</family>
<family lang="ko">
- <font weight="100" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"
+ supportedAxes="wght">
NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="100"/>
- </font>
- <font weight="200" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="200"/>
- </font>
- <font weight="300" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="300"/>
- </font>
- <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="700"/>
- </font>
- <font weight="800" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="800"/>
- </font>
- <font weight="900" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="900"/>
+ <!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400
+ for making regular style as default. -->
+ <axis tag="wght" stylevalue="400" />
</font>
<font weight="400" style="normal" index="1" fallbackFor="serif"
postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
@@ -997,27 +942,27 @@
<family lang="und-Dogr">
<font weight="400" style="normal">NotoSerifDogra-Regular.ttf</font>
</family>
- <family lang="und-Medf" varFamilyType="1">
- <font postScriptName="NotoSansMedefaidrin-Regular">
+ <family lang="und-Medf">
+ <font postScriptName="NotoSansMedefaidrin-Regular" supportedAxes="wght">
NotoSansMedefaidrin-VF.ttf
</font>
</family>
- <family lang="und-Soyo" varFamilyType="1">
+ <family lang="und-Soyo" supportedAxes="wght">
<font postScriptName="NotoSansSoyombo-Regular">
NotoSansSoyombo-VF.ttf
</font>
</family>
- <family lang="und-Takr" varFamilyType="1">
+ <family lang="und-Takr" supportedAxes="wght">
<font postScriptName="NotoSansTakri-Regular">
NotoSansTakri-VF.ttf
</font>
</family>
- <family lang="und-Hmnp" varFamilyType="1">
+ <family lang="und-Hmnp" supportedAxes="wght">
<font postScriptName="NotoSerifHmongNyiakeng-Regular">
NotoSerifNyiakengPuachueHmong-VF.ttf
</font>
</family>
- <family lang="und-Yezi" varFamilyType="1">
+ <family lang="und-Yezi" supportedAxes="wght">
<font postScriptName="NotoSerifYezidi-Regular">
NotoSerifYezidi-VF.ttf
</font>
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index d1aceaf..b33a5d2 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -18,6 +18,7 @@
import android.annotation.ColorInt;
import android.annotation.ColorLong;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -30,6 +31,8 @@
import android.os.Build;
import android.text.TextShaper;
+import com.android.graphics.hwui.flags.Flags;
+
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
@@ -766,6 +769,21 @@
}
/**
+ * Preconcat the current matrix with the specified matrix. If the specified
+ * matrix is null, this method does nothing. If the canvas's matrix is changed in the z-axis
+ * through this function, the deprecated {@link #getMatrix()} method will return a 3x3 with
+ * z-axis info stripped away.
+ *
+ * @param m The 4x4 matrix to preconcatenate with the current matrix
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public void concat44(@Nullable Matrix44 m) {
+ if (m != null) {
+ nConcat(mNativeCanvasWrapper, m.mBackingArray);
+ }
+ }
+
+ /**
* Completely replace the current matrix with the specified matrix. If the
* matrix parameter is null, then the current matrix is reset to identity.
*
@@ -1444,6 +1462,8 @@
private static native void nSkew(long canvasHandle, float sx, float sy);
@CriticalNative
private static native void nConcat(long nativeCanvas, long nativeMatrix);
+ @FastNative
+ private static native void nConcat(long nativeCanvas, float[] mat);
@CriticalNative
private static native void nSetMatrix(long nativeCanvas, long nativeMatrix);
@CriticalNative
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 52b0b95..17c2dd9 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -16,10 +16,6 @@
package android.graphics;
-import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE;
-import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL;
-import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY;
-import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT;
import static android.text.FontConfig.NamedFamilyList;
import android.annotation.NonNull;
@@ -32,7 +28,6 @@
import android.os.LocaleList;
import android.text.FontConfig;
import android.util.ArraySet;
-import android.util.Log;
import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
@@ -65,6 +60,7 @@
private static final String VARIANT_ELEGANT = "elegant";
// XML constants for Font.
+ public static final String ATTR_SUPPORTED_AXES = "supportedAxes";
public static final String ATTR_INDEX = "index";
public static final String ATTR_WEIGHT = "weight";
public static final String ATTR_POSTSCRIPT_NAME = "postScriptName";
@@ -78,6 +74,10 @@
public static final String ATTR_TAG = "tag";
public static final String ATTR_STYLEVALUE = "stylevalue";
+ // The tag string for variable font type resolution.
+ private static final String TAG_WGHT = "wght";
+ private static final String TAG_ITAL = "ital";
+
/* Parse fallback list (no names) */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static FontConfig parse(InputStream in) throws XmlPullParserException, IOException {
@@ -263,7 +263,6 @@
final String lang = parser.getAttributeValue("", "lang");
final String variant = parser.getAttributeValue(null, "variant");
final String ignore = parser.getAttributeValue(null, "ignore");
- final String varFamilyTypeStr = parser.getAttributeValue(null, "varFamilyType");
final List<FontConfig.Font> fonts = new ArrayList<>();
while (keepReading(parser)) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
@@ -286,45 +285,12 @@
intVariant = FontConfig.FontFamily.VARIANT_ELEGANT;
}
}
- int varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE;
- if (varFamilyTypeStr != null) {
- varFamilyType = Integer.parseInt(varFamilyTypeStr);
- if (varFamilyType <= -1 || varFamilyType > 3) {
- Log.e(TAG, "Error: unexpected varFamilyType value: " + varFamilyTypeStr);
- varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE;
- }
-
- // validation but don't read font content for performance reasons.
- switch (varFamilyType) {
- case VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY:
- if (fonts.size() != 1) {
- Log.e(TAG, "Error: Single font support wght axis, but two or more fonts are"
- + " included in the font family.");
- varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE;
- }
- break;
- case VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL:
- if (fonts.size() != 1) {
- Log.e(TAG, "Error: Single font support both ital and wght axes, but two or"
- + " more fonts are included in the font family.");
- varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE;
- }
- break;
- case VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT:
- if (fonts.size() != 2) {
- Log.e(TAG, "Error: two fonts that support wght axis, but one or three or"
- + " more fonts are included in the font family.");
- varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE;
- }
- }
- }
boolean skip = (ignore != null && (ignore.equals("true") || ignore.equals("1")));
if (skip || fonts.isEmpty()) {
return null;
}
- return new FontConfig.FontFamily(fonts, LocaleList.forLanguageTags(lang), intVariant,
- varFamilyType);
+ return new FontConfig.FontFamily(fonts, LocaleList.forLanguageTags(lang), intVariant);
}
private static void throwIfAttributeExists(String attrName, XmlPullParser parser) {
@@ -407,6 +373,7 @@
boolean isItalic = STYLE_ITALIC.equals(parser.getAttributeValue(null, ATTR_STYLE));
String fallbackFor = parser.getAttributeValue(null, ATTR_FALLBACK_FOR);
String postScriptName = parser.getAttributeValue(null, ATTR_POSTSCRIPT_NAME);
+ final String supportedAxes = parser.getAttributeValue(null, ATTR_SUPPORTED_AXES);
StringBuilder filename = new StringBuilder();
while (keepReading(parser)) {
if (parser.getEventType() == XmlPullParser.TEXT) {
@@ -422,6 +389,18 @@
}
String sanitizedName = FILENAME_WHITESPACE_PATTERN.matcher(filename).replaceAll("");
+ int varTypeAxes = 0;
+ if (supportedAxes != null) {
+ for (String tag : supportedAxes.split(",")) {
+ String strippedTag = tag.strip();
+ if (strippedTag.equals(TAG_WGHT)) {
+ varTypeAxes |= FontConfig.Font.VAR_TYPE_AXES_WGHT;
+ } else if (strippedTag.equals(TAG_ITAL)) {
+ varTypeAxes |= FontConfig.Font.VAR_TYPE_AXES_ITAL;
+ }
+ }
+ }
+
if (postScriptName == null) {
// If post script name was not provided, assume the file name is same to PostScript
// name.
@@ -462,7 +441,8 @@
),
index,
varSettings,
- fallbackFor);
+ fallbackFor,
+ varTypeAxes);
}
private static String findUpdatedFontFile(String psName,
diff --git a/graphics/java/android/graphics/Matrix44.java b/graphics/java/android/graphics/Matrix44.java
new file mode 100644
index 0000000..7cc0eb7
--- /dev/null
+++ b/graphics/java/android/graphics/Matrix44.java
@@ -0,0 +1,472 @@
+/*
+ * Copyright 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 android.graphics;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+
+import com.android.graphics.hwui.flags.Flags;
+
+import java.util.Arrays;
+
+/**
+ * The Matrix44 class holds a 4x4 matrix for transforming coordinates. It is similar to
+ * {@link Matrix}, and should be used when you want to manipulate the canvas in 3D. Values are kept
+ * in row-major order. The values and operations are treated as column vectors.
+ */
+@FlaggedApi(Flags.FLAG_MATRIX_44)
+public class Matrix44 {
+ final float[] mBackingArray;
+ /**
+ * The default Matrix44 constructor will instantiate an identity matrix.
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public Matrix44() {
+ mBackingArray = new float[]{1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f};
+ }
+
+ /**
+ * Creates and returns a Matrix44 by taking the 3x3 Matrix and placing it on the 0 of the z-axis
+ * by setting row {@code 2} and column {@code 2} to the identity as seen in the following
+ * operation:
+ * <pre class="prettyprint">
+ * [ a b c ] [ a b 0 c ]
+ * [ d e f ] -> [ d e 0 f ]
+ * [ g h i ] [ 0 0 1 0 ]
+ * [ g h 0 i ]
+ * </pre>
+ *
+ * @param mat A 3x3 Matrix to be converted (original Matrix will not be changed)
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public Matrix44(@NonNull Matrix mat) {
+ float[] m = new float[9];
+ mat.getValues(m);
+ mBackingArray = new float[]{m[0], m[1], 0.0f, m[2],
+ m[3], m[4], 0.0f, m[5],
+ 0.0f, 0.0f, 1.0f, 0.0f,
+ m[6], m[7], 0.0f, m[8]};
+ }
+
+ /**
+ * Copies matrix values into the provided array in row-major order.
+ *
+ * @param dst The float array where values will be copied, must be of length 16
+ * @throws IllegalArgumentException if the destination float array is not of length 16
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public void getValues(@NonNull float [] dst) {
+ if (dst.length == 16) {
+ System.arraycopy(mBackingArray, 0, dst, 0, mBackingArray.length);
+ } else {
+ throw new IllegalArgumentException("Dst array must be of length 16");
+ }
+ }
+
+ /**
+ * Replaces the Matrix's values with the values in the provided array.
+ *
+ * @param src A float array of length 16. Floats are treated in row-major order
+ * @throws IllegalArgumentException if the destination float array is not of length 16
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public void setValues(@NonNull float[] src) {
+ if (src.length == 16) {
+ System.arraycopy(src, 0, mBackingArray, 0, mBackingArray.length);
+ } else {
+ throw new IllegalArgumentException("Src array must be of length 16");
+ }
+ }
+
+ /**
+ * Gets the value at the matrix's row and column.
+ *
+ * @param row An integer from 0 to 4 indicating the row of the value to get
+ * @param col An integer from 0 to 4 indicating the column of the value to get
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public float get(int row, int col) {
+ if (row >= 0 && row < 4 && col >= 0 && col < 4) {
+ return mBackingArray[row * 4 + col];
+ }
+ throw new IllegalArgumentException("invalid row and column values");
+ }
+
+ /**
+ * Sets the value at the matrix's row and column to the provided value.
+ *
+ * @param row An integer from 0 to 4 indicating the row of the value to change
+ * @param col An integer from 0 to 4 indicating the column of the value to change
+ * @param val The value the element at the specified index will be set to
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public void set(int row, int col, float val) {
+ if (row >= 0 && row < 4 && col >= 0 && col < 4) {
+ mBackingArray[row * 4 + col] = val;
+ } else {
+ throw new IllegalArgumentException("invalid row and column values");
+ }
+ }
+
+ /**
+ * Sets the Matrix44 to the identity matrix.
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public void reset() {
+ for (int i = 0; i < mBackingArray.length; i++) {
+ mBackingArray[i] = (i % 4 == i / 4) ? 1.0f : 0.0f;
+ }
+ }
+
+ /**
+ * Inverts the Matrix44, then return true if successful, false if unable to invert.
+ *
+ * @return {@code true} on success, {@code false} otherwise
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public boolean invert() {
+ float a00 = mBackingArray[0];
+ float a01 = mBackingArray[1];
+ float a02 = mBackingArray[2];
+ float a03 = mBackingArray[3];
+ float a10 = mBackingArray[4];
+ float a11 = mBackingArray[5];
+ float a12 = mBackingArray[6];
+ float a13 = mBackingArray[7];
+ float a20 = mBackingArray[8];
+ float a21 = mBackingArray[9];
+ float a22 = mBackingArray[10];
+ float a23 = mBackingArray[11];
+ float a30 = mBackingArray[12];
+ float a31 = mBackingArray[13];
+ float a32 = mBackingArray[14];
+ float a33 = mBackingArray[15];
+ float b00 = a00 * a11 - a01 * a10;
+ float b01 = a00 * a12 - a02 * a10;
+ float b02 = a00 * a13 - a03 * a10;
+ float b03 = a01 * a12 - a02 * a11;
+ float b04 = a01 * a13 - a03 * a11;
+ float b05 = a02 * a13 - a03 * a12;
+ float b06 = a20 * a31 - a21 * a30;
+ float b07 = a20 * a32 - a22 * a30;
+ float b08 = a20 * a33 - a23 * a30;
+ float b09 = a21 * a32 - a22 * a31;
+ float b10 = a21 * a33 - a23 * a31;
+ float b11 = a22 * a33 - a23 * a32;
+ float det = (b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06);
+ if (det == 0.0f) {
+ return false;
+ }
+ float invDet = 1.0f / det;
+ mBackingArray[0] = ((a11 * b11 - a12 * b10 + a13 * b09) * invDet);
+ mBackingArray[1] = ((-a01 * b11 + a02 * b10 - a03 * b09) * invDet);
+ mBackingArray[2] = ((a31 * b05 - a32 * b04 + a33 * b03) * invDet);
+ mBackingArray[3] = ((-a21 * b05 + a22 * b04 - a23 * b03) * invDet);
+ mBackingArray[4] = ((-a10 * b11 + a12 * b08 - a13 * b07) * invDet);
+ mBackingArray[5] = ((a00 * b11 - a02 * b08 + a03 * b07) * invDet);
+ mBackingArray[6] = ((-a30 * b05 + a32 * b02 - a33 * b01) * invDet);
+ mBackingArray[7] = ((a20 * b05 - a22 * b02 + a23 * b01) * invDet);
+ mBackingArray[8] = ((a10 * b10 - a11 * b08 + a13 * b06) * invDet);
+ mBackingArray[9] = ((-a00 * b10 + a01 * b08 - a03 * b06) * invDet);
+ mBackingArray[10] = ((a30 * b04 - a31 * b02 + a33 * b00) * invDet);
+ mBackingArray[11] = ((-a20 * b04 + a21 * b02 - a23 * b00) * invDet);
+ mBackingArray[12] = ((-a10 * b09 + a11 * b07 - a12 * b06) * invDet);
+ mBackingArray[13] = ((a00 * b09 - a01 * b07 + a02 * b06) * invDet);
+ mBackingArray[14] = ((-a30 * b03 + a31 * b01 - a32 * b00) * invDet);
+ mBackingArray[15] = ((a20 * b03 - a21 * b01 + a22 * b00) * invDet);
+ return true;
+ }
+
+ /**
+ * Returns true if Matrix44 is equal to identity matrix.
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public boolean isIdentity() {
+ for (int i = 0; i < mBackingArray.length; i++) {
+ float expected = (i % 4 == i / 4) ? 1.0f : 0.0f;
+ if (expected != mBackingArray[i]) return false;
+ }
+ return true;
+ }
+
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ private static float dot(Matrix44 a, Matrix44 b, int row, int col) {
+ return (a.get(row, 0) * b.get(0, col))
+ + (a.get(row, 1) * b.get(1, col))
+ + (a.get(row, 2) * b.get(2, col))
+ + (a.get(row, 3) * b.get(3, col));
+ }
+
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ private static float dot(float r0, float r1, float r2, float r3,
+ float c0, float c1, float c2, float c3) {
+ return (r0 * c0) + (r1 * c1) + (r2 * c2) + (r3 * c3);
+ }
+
+ /**
+ * Multiplies (x, y, z, w) vector by the Matrix44, then returns the new (x, y, z, w). Users
+ * should set {@code w} to 1 to indicate the coordinates are normalized.
+ *
+ * @return An array of length 4 that represents the x, y, z, w (where w is perspective) value
+ * after multiplying x, y, z, 1 by the matrix
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public @NonNull float[] map(float x, float y, float z, float w) {
+ float[] dst = new float[4];
+ this.map(x, y, z, w, dst);
+ return dst;
+ }
+
+ /**
+ * Multiplies (x, y, z, w) vector by the Matrix44, then returns the new (x, y, z, w). Users
+ * should set {@code w} to 1 to indicate the coordinates are normalized.
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public void map(float x, float y, float z, float w, @NonNull float[] dst) {
+ if (dst.length != 4) {
+ throw new IllegalArgumentException("Dst array must be of length 4");
+ }
+ dst[0] = x * mBackingArray[0] + y * mBackingArray[1]
+ + z * mBackingArray[2] + w * mBackingArray[3];
+ dst[1] = x * mBackingArray[4] + y * mBackingArray[5]
+ + z * mBackingArray[6] + w * mBackingArray[7];
+ dst[2] = x * mBackingArray[8] + y * mBackingArray[9]
+ + z * mBackingArray[10] + w * mBackingArray[11];
+ dst[3] = x * mBackingArray[12] + y * mBackingArray[13]
+ + z * mBackingArray[14] + w * mBackingArray[15];
+ }
+
+ /**
+ * Multiplies `this` matrix (A) and provided Matrix (B) in the order of A * B.
+ * The result is saved in `this` Matrix.
+ *
+ * @param b The second Matrix in the concatenation operation
+ * @return A reference to this Matrix, which can be used to chain Matrix operations
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public @NonNull Matrix44 concat(@NonNull Matrix44 b) {
+ float val00 = dot(this, b, 0, 0);
+ float val01 = dot(this, b, 0, 1);
+ float val02 = dot(this, b, 0, 2);
+ float val03 = dot(this, b, 0, 3);
+ float val10 = dot(this, b, 1, 0);
+ float val11 = dot(this, b, 1, 1);
+ float val12 = dot(this, b, 1, 2);
+ float val13 = dot(this, b, 1, 3);
+ float val20 = dot(this, b, 2, 0);
+ float val21 = dot(this, b, 2, 1);
+ float val22 = dot(this, b, 2, 2);
+ float val23 = dot(this, b, 2, 3);
+ float val30 = dot(this, b, 3, 0);
+ float val31 = dot(this, b, 3, 1);
+ float val32 = dot(this, b, 3, 2);
+ float val33 = dot(this, b, 3, 3);
+
+ mBackingArray[0] = val00;
+ mBackingArray[1] = val01;
+ mBackingArray[2] = val02;
+ mBackingArray[3] = val03;
+ mBackingArray[4] = val10;
+ mBackingArray[5] = val11;
+ mBackingArray[6] = val12;
+ mBackingArray[7] = val13;
+ mBackingArray[8] = val20;
+ mBackingArray[9] = val21;
+ mBackingArray[10] = val22;
+ mBackingArray[11] = val23;
+ mBackingArray[12] = val30;
+ mBackingArray[13] = val31;
+ mBackingArray[14] = val32;
+ mBackingArray[15] = val33;
+
+ return this;
+ }
+
+ /**
+ * Applies a rotation around a given axis, then returns self.
+ * {@code x}, {@code y}, {@code z} represent the axis by which to rotate around.
+ * For example, pass in {@code 1, 0, 0} to rotate around the x-axis.
+ * The axis provided will be normalized.
+ *
+ * @param deg Amount in degrees to rotate the matrix about the x-axis
+ * @param xComp X component of the rotation axis
+ * @param yComp Y component of the rotation axis
+ * @param zComp Z component of the rotation axis
+ * @return A reference to this Matrix, which can be used to chain Matrix operations
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public @NonNull Matrix44 rotate(float deg, float xComp, float yComp, float zComp) {
+ float sum = xComp + yComp + zComp;
+ float x = xComp / sum;
+ float y = yComp / sum;
+ float z = zComp / sum;
+
+ float c = (float) Math.cos(deg * Math.PI / 180.0f);
+ float s = (float) Math.sin(deg * Math.PI / 180.0f);
+ float t = 1 - c;
+
+ float rotVals00 = t * x * x + c;
+ float rotVals01 = t * x * y - s * z;
+ float rotVals02 = t * x * z + s * y;
+ float rotVals10 = t * x * y + s * z;
+ float rotVals11 = t * y * y + c;
+ float rotVals12 = t * y * z - s * x;
+ float rotVals20 = t * x * z - s * y;
+ float rotVals21 = t * y * z + s * x;
+ float rotVals22 = t * z * z + c;
+
+ float v00 = dot(mBackingArray[0], mBackingArray[1], mBackingArray[2], mBackingArray[3],
+ rotVals00, rotVals10, rotVals20, 0);
+ float v01 = dot(mBackingArray[0], mBackingArray[1], mBackingArray[2], mBackingArray[3],
+ rotVals01, rotVals11, rotVals21, 0);
+ float v02 = dot(mBackingArray[0], mBackingArray[1], mBackingArray[2], mBackingArray[3],
+ rotVals02, rotVals12, rotVals22, 0);
+ float v03 = dot(mBackingArray[0], mBackingArray[1], mBackingArray[2], mBackingArray[3],
+ 0, 0, 0, 1);
+ float v10 = dot(mBackingArray[4], mBackingArray[5], mBackingArray[6], mBackingArray[7],
+ rotVals00, rotVals10, rotVals20, 0);
+ float v11 = dot(mBackingArray[4], mBackingArray[5], mBackingArray[6], mBackingArray[7],
+ rotVals01, rotVals11, rotVals21, 0);
+ float v12 = dot(mBackingArray[4], mBackingArray[5], mBackingArray[6], mBackingArray[7],
+ rotVals02, rotVals12, rotVals22, 0);
+ float v13 = dot(mBackingArray[4], mBackingArray[5], mBackingArray[6], mBackingArray[7],
+ 0, 0, 0, 1);
+ float v20 = dot(mBackingArray[8], mBackingArray[9], mBackingArray[10], mBackingArray[11],
+ rotVals00, rotVals10, rotVals20, 0);
+ float v21 = dot(mBackingArray[8], mBackingArray[9], mBackingArray[10], mBackingArray[11],
+ rotVals01, rotVals11, rotVals21, 0);
+ float v22 = dot(mBackingArray[8], mBackingArray[9], mBackingArray[10], mBackingArray[11],
+ rotVals02, rotVals12, rotVals22, 0);
+ float v23 = dot(mBackingArray[8], mBackingArray[9], mBackingArray[10], mBackingArray[11],
+ 0, 0, 0, 1);
+ float v30 = dot(mBackingArray[12], mBackingArray[13], mBackingArray[14], mBackingArray[15],
+ rotVals00, rotVals10, rotVals20, 0);
+ float v31 = dot(mBackingArray[12], mBackingArray[13], mBackingArray[14], mBackingArray[15],
+ rotVals01, rotVals11, rotVals21, 0);
+ float v32 = dot(mBackingArray[12], mBackingArray[13], mBackingArray[14], mBackingArray[15],
+ rotVals02, rotVals12, rotVals22, 0);
+ float v33 = dot(mBackingArray[12], mBackingArray[13], mBackingArray[14], mBackingArray[15],
+ 0, 0, 0, 1);
+
+ mBackingArray[0] = v00;
+ mBackingArray[1] = v01;
+ mBackingArray[2] = v02;
+ mBackingArray[3] = v03;
+ mBackingArray[4] = v10;
+ mBackingArray[5] = v11;
+ mBackingArray[6] = v12;
+ mBackingArray[7] = v13;
+ mBackingArray[8] = v20;
+ mBackingArray[9] = v21;
+ mBackingArray[10] = v22;
+ mBackingArray[11] = v23;
+ mBackingArray[12] = v30;
+ mBackingArray[13] = v31;
+ mBackingArray[14] = v32;
+ mBackingArray[15] = v33;
+
+ return this;
+ }
+
+ /**
+ * Applies scaling factors to `this` Matrix44, then returns self. Pass 1s for no change.
+ *
+ * @param x Scaling factor for the x-axis
+ * @param y Scaling factor for the y-axis
+ * @param z Scaling factor for the z-axis
+ * @return A reference to this Matrix, which can be used to chain Matrix operations
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public @NonNull Matrix44 scale(float x, float y, float z) {
+ mBackingArray[0] *= x;
+ mBackingArray[4] *= x;
+ mBackingArray[8] *= x;
+ mBackingArray[12] *= x;
+ mBackingArray[1] *= y;
+ mBackingArray[5] *= y;
+ mBackingArray[9] *= y;
+ mBackingArray[13] *= y;
+ mBackingArray[2] *= z;
+ mBackingArray[6] *= z;
+ mBackingArray[10] *= z;
+ mBackingArray[14] *= z;
+
+ return this;
+ }
+
+ /**
+ * Applies a translation to `this` Matrix44, then returns self.
+ *
+ * @param x Translation for the x-axis
+ * @param y Translation for the y-axis
+ * @param z Translation for the z-axis
+ * @return A reference to this Matrix, which can be used to chain Matrix operations
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public @NonNull Matrix44 translate(float x, float y, float z) {
+ float newX = x * mBackingArray[0] + y * mBackingArray[1]
+ + z * mBackingArray[2] + mBackingArray[3];
+ float newY = x * mBackingArray[4] + y * mBackingArray[5]
+ + z * mBackingArray[6] + mBackingArray[7];
+ float newZ = x * mBackingArray[8] + y * mBackingArray[9]
+ + z * mBackingArray[10] + mBackingArray[11];
+ float newW = x * mBackingArray[12] + y * mBackingArray[13]
+ + z * mBackingArray[14] + mBackingArray[15];
+
+ mBackingArray[3] = newX;
+ mBackingArray[7] = newY;
+ mBackingArray[11] = newZ;
+ mBackingArray[15] = newW;
+
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("""
+ | %f %f %f %f |
+ | %f %f %f %f |
+ | %f %f %f %f |
+ | %f %f %f %f |
+ """, mBackingArray[0], mBackingArray[1], mBackingArray[2], mBackingArray[3],
+ mBackingArray[4], mBackingArray[5], mBackingArray[6], mBackingArray[7],
+ mBackingArray[8], mBackingArray[9], mBackingArray[10], mBackingArray[11],
+ mBackingArray[12], mBackingArray[13], mBackingArray[14], mBackingArray[15]);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof Matrix44) {
+ return Arrays.equals(mBackingArray, ((Matrix44) obj).mBackingArray);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return (int) mBackingArray[0] + (int) mBackingArray[1] + (int) mBackingArray[2]
+ + (int) mBackingArray[3] + (int) mBackingArray[4] + (int) mBackingArray[5]
+ + (int) mBackingArray[6] + (int) mBackingArray[7] + (int) mBackingArray[8]
+ + (int) mBackingArray[9] + (int) mBackingArray[10] + (int) mBackingArray[11]
+ + (int) mBackingArray[12] + (int) mBackingArray[13] + (int) mBackingArray[14]
+ + (int) mBackingArray[15];
+ }
+
+}
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index 3ef714ed..a90961e 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -100,6 +100,71 @@
}
}
+ /** @hide */
+ @VisibleForTesting
+ public static @FontFamily.Builder.VariableFontFamilyType int resolveVarFamilyType(
+ @NonNull FontConfig.FontFamily xmlFamily,
+ @Nullable String familyName) {
+ int wghtCount = 0;
+ int italCount = 0;
+ int targetFonts = 0;
+ boolean hasItalicFont = false;
+
+ List<FontConfig.Font> fonts = xmlFamily.getFontList();
+ for (int i = 0; i < fonts.size(); ++i) {
+ FontConfig.Font font = fonts.get(i);
+
+ if (familyName == null) { // for default family
+ if (font.getFontFamilyName() != null) {
+ continue; // this font is not for the default family.
+ }
+ } else { // for the specific family
+ if (!familyName.equals(font.getFontFamilyName())) {
+ continue; // this font is not for given family.
+ }
+ }
+
+ final int varTypeAxes = font.getVarTypeAxes();
+ if (varTypeAxes == 0) {
+ // If we see static font, we can immediately return as VAR_TYPE_NONE.
+ return FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE;
+ }
+
+ if ((varTypeAxes & FontConfig.Font.VAR_TYPE_AXES_WGHT) != 0) {
+ wghtCount++;
+ }
+
+ if ((varTypeAxes & FontConfig.Font.VAR_TYPE_AXES_ITAL) != 0) {
+ italCount++;
+ }
+
+ if (font.getStyle().getSlant() == FontStyle.FONT_SLANT_ITALIC) {
+ hasItalicFont = true;
+ }
+ targetFonts++;
+ }
+
+ if (italCount == 0) { // No ital font.
+ if (targetFonts == 1 && wghtCount == 1) {
+ // If there is only single font that has wght, use it for regular style and
+ // use synthetic bolding for italic.
+ return FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY;
+ } else if (targetFonts == 2 && wghtCount == 2 && hasItalicFont) {
+ // If there are two fonts and italic font is available, use them for regular and
+ // italic separately. (It is impossible to have two italic fonts. It will end up
+ // with Typeface creation failure.)
+ return FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT;
+ }
+ } else if (italCount == 1) {
+ // If ital font is included, a single font should support both wght and ital.
+ if (wghtCount == 1 && targetFonts == 1) {
+ return FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL;
+ }
+ }
+ // Otherwise, unsupported.
+ return FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE;
+ }
+
private static void pushFamilyToFallback(@NonNull FontConfig.FontFamily xmlFamily,
@NonNull ArrayMap<String, NativeFamilyListSet> fallbackMap,
@NonNull Map<String, ByteBuffer> cache) {
@@ -126,7 +191,7 @@
}
final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily(
- defaultFonts, languageTags, variant, xmlFamily.getVariableFontFamilyType(), false,
+ defaultFonts, languageTags, variant, resolveVarFamilyType(xmlFamily, null), false,
cache);
// Insert family into fallback map.
for (int i = 0; i < fallbackMap.size(); i++) {
@@ -145,7 +210,7 @@
}
} else {
final FontFamily family = createFontFamily(fallback, languageTags, variant,
- xmlFamily.getVariableFontFamilyType(), false, cache);
+ resolveVarFamilyType(xmlFamily, name), false, cache);
if (family != null) {
familyListSet.familyList.add(family);
} else if (defaultFamily != null) {
@@ -217,7 +282,8 @@
final FontFamily family = createFontFamily(
xmlFamily.getFontList(),
xmlFamily.getLocaleList().toLanguageTags(), xmlFamily.getVariant(),
- xmlFamily.getVariableFontFamilyType(),
+ resolveVarFamilyType(xmlFamily,
+ null /* all fonts under named family should be treated as default */),
true, // named family is always default
bufferCache);
if (family == null) {
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 4cdc06a..a12fa5f 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -39,6 +39,7 @@
}
// Sources that have no dependencies that can be used directly downstream of this library
+// TODO(b/322791067): move these sources to WindowManager-Shell-shared
filegroup {
name: "wm_shell_util-sources",
srcs: [
@@ -137,6 +138,12 @@
},
}
+java_library {
+ name: "WindowManager-Shell-shared",
+
+ srcs: ["shared/**/*.java"],
+}
+
android_library {
name: "WindowManager-Shell",
srcs: [
@@ -162,6 +169,7 @@
"com_android_wm_shell_flags_lib",
"com.android.window.flags.window-aconfig-java",
"WindowManager-Shell-proto",
+ "WindowManager-Shell-shared",
"perfetto_trace_java_protos",
"dagger2",
"jsr330",
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 0e04658..9a66c0f 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -30,13 +30,6 @@
}
flag {
- name: "enable_pip_ui_state_on_entering"
- namespace: "multitasking"
- description: "Enables PiP UI state callback on entering"
- bug: "303718131"
-}
-
-flag {
name: "enable_pip2_implementation"
namespace: "multitasking"
description: "Enables the new implementation of PiP (PiP2)"
@@ -57,3 +50,17 @@
description: "Enables new animations for expand and collapse for bubbles"
bug: "311450609"
}
+
+flag {
+ name: "enable_pip_umo_experience"
+ namespace: "multitasking"
+ description: "Enables new UMO experience for PiP menu"
+ bug: "307998712"
+}
+
+flag {
+ name: "enable_bubble_bar"
+ namespace: "multitasking"
+ description: "Enables the new bubble bar UI for tablets"
+ bug: "286246694"
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/CounterRotator.java
similarity index 98%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/CounterRotator.java
index 7e95814..fd3a749 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/CounterRotator.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.util;
+package com.android.wm.shell.shared;
import android.graphics.Point;
import android.util.RotationUtils;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
similarity index 98%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
index 936faa3..dcd4062 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.util;
+package com.android.wm.shell.shared;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.view.RemoteAnimationTarget.MODE_CHANGING;
@@ -28,14 +28,13 @@
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -53,6 +52,8 @@
/** Various utility functions for transitions. */
public class TransitionUtil {
+ /** Flag applied to a transition change to identify it as a divider bar for animation. */
+ public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM;
/** @return true if the transition was triggered by opening something vs closing something */
public static boolean isOpeningType(@WindowManager.TransitionType int type) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
index 8241e1a..8d30db6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
@@ -29,7 +29,7 @@
import androidx.annotation.NonNull;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
/**
* Wrapper to handle the ActivityEmbedding animation update in one
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index 44ee561..539832e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -46,7 +46,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationAdapter.SnapshotAdapter;
import com.android.wm.shell.common.ScreenshotUtils;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
import java.util.ArrayList;
import java.util.List;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index efa5a1a..0272f1c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -38,7 +38,7 @@
import android.window.TransitionInfo;
import com.android.internal.policy.TransitionAnimation;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
/** Animation spec for ActivityEmbedding transition. */
// TODO(b/206557124): provide an easier way to customize animation
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index b4e852c..1f9358e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -38,9 +38,9 @@
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.List;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index f32f030..50a58da 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -465,7 +465,7 @@
/**
* Call when all the views should be removed/cleaned up.
*/
- void cleanupViews() {
+ public void cleanupViews() {
cleanupViews(true);
}
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 945b8ac..d0db708 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
@@ -173,7 +173,7 @@
private final Context mContext;
private final BubblesImpl mImpl = new BubblesImpl();
private Bubbles.BubbleExpandListener mExpandListener;
- @Nullable private BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer;
+ @Nullable private final BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer;
private final FloatingContentCoordinator mFloatingContentCoordinator;
private final BubbleDataRepository mDataRepository;
private final WindowManagerShellWrapper mWindowManagerShellWrapper;
@@ -197,12 +197,12 @@
private final Handler mMainHandler;
private final ShellExecutor mBackgroundExecutor;
- private BubbleLogger mLogger;
- private BubbleData mBubbleData;
+ private final BubbleLogger mLogger;
+ private final BubbleData mBubbleData;
@Nullable private BubbleStackView mStackView;
@Nullable private BubbleBarLayerView mLayerView;
private BubbleIconFactory mBubbleIconFactory;
- private BubblePositioner mBubblePositioner;
+ private final BubblePositioner mBubblePositioner;
private Bubbles.SysuiProxy mSysuiProxy;
// Tracks the id of the current (foreground) user.
@@ -232,13 +232,17 @@
/** Whether or not the BubbleStackView has been added to the WindowManager. */
private boolean mAddedToWindowManager = false;
- /** Saved screen density, used to detect display size changes in {@link #onConfigChanged}. */
+ /**
+ * Saved screen density, used to detect display size changes in {@link #onConfigurationChanged}.
+ */
private int mDensityDpi = Configuration.DENSITY_DPI_UNDEFINED;
- /** Saved screen bounds, used to detect screen size changes in {@link #onConfigChanged}. **/
- private Rect mScreenBounds = new Rect();
+ /**
+ * Saved screen bounds, used to detect screen size changes in {@link #onConfigurationChanged}.
+ */
+ private final Rect mScreenBounds = new Rect();
- /** Saved font scale, used to detect font size changes in {@link #onConfigChanged}. */
+ /** Saved font scale, used to detect font size changes in {@link #onConfigurationChanged}. */
private float mFontScale = 0;
/** Saved direction, used to detect layout direction changes @link #onConfigChanged}. */
@@ -253,9 +257,9 @@
private boolean mIsStatusBarShade = true;
/** One handed mode controller to register transition listener. */
- private Optional<OneHandedController> mOneHandedOptional;
+ private final Optional<OneHandedController> mOneHandedOptional;
/** Drag and drop controller to register listener for onDragStarted. */
- private Optional<DragAndDropController> mDragAndDropController;
+ private final Optional<DragAndDropController> mDragAndDropController;
/** Used to send bubble events to launcher. */
private Bubbles.BubbleStateListener mBubbleStateListener;
@@ -726,14 +730,16 @@
// window to show this in, but we use a separate code path.
// TODO(b/273312602): consider foldables where we do need a stack view when folded
if (mLayerView == null) {
- mLayerView = new BubbleBarLayerView(mContext, this);
+ mLayerView = new BubbleBarLayerView(mContext, this, mBubbleData);
mLayerView.setUnBubbleConversationCallback(mSysuiProxy::onUnbubbleConversation);
}
} else {
if (mStackView == null) {
+ BubbleStackViewManager bubbleStackViewManager =
+ BubbleStackViewManager.fromBubbleController(this);
mStackView = new BubbleStackView(
- mContext, this, mBubbleData, mSurfaceSynchronizer,
- mFloatingContentCoordinator, this, mMainExecutor);
+ mContext, bubbleStackViewManager, mBubblePositioner, mBubbleData,
+ mSurfaceSynchronizer, mFloatingContentCoordinator, this, mMainExecutor);
mStackView.onOrientationChanged();
if (mExpandListener != null) {
mStackView.setExpandListener(mExpandListener);
@@ -893,7 +899,6 @@
* Called by the BubbleStackView and whenever all bubbles have animated out, and none have been
* added in the meantime.
*/
- @VisibleForTesting
public void onAllBubblesAnimatedOut() {
if (mStackView != null) {
mStackView.setVisibility(INVISIBLE);
@@ -1047,7 +1052,6 @@
return mBubbleData.hasBubbles() || mBubbleData.isShowingOverflow();
}
- @VisibleForTesting
public boolean isStackExpanded() {
return mBubbleData.isExpanded();
}
@@ -1105,9 +1109,8 @@
* <p>This is used by external callers (launcher).
*/
@VisibleForTesting
- public void expandStackAndSelectBubbleFromLauncher(String key, int bubbleBarOffsetX,
- int bubbleBarOffsetY) {
- mBubblePositioner.setBubbleBarPosition(bubbleBarOffsetX, bubbleBarOffsetY);
+ public void expandStackAndSelectBubbleFromLauncher(String key, Rect bubbleBarBounds) {
+ mBubblePositioner.setBubbleBarPosition(bubbleBarBounds);
if (BubbleOverflow.KEY.equals(key)) {
mBubbleData.setSelectedBubbleFromLauncher(mBubbleData.getOverflow());
@@ -1273,9 +1276,17 @@
mBubbleData.setExpanded(true);
}
} else {
- // App bubble does not exist, lets add and expand it
- Log.i(TAG, " showOrHideAppBubble, creating and expanding app bubble");
- Bubble b = Bubble.createAppBubble(intent, user, icon, mMainExecutor);
+ // Check if it exists in the overflow
+ Bubble b = mBubbleData.getOverflowBubbleWithKey(appBubbleKey);
+ if (b != null) {
+ // It's in the overflow, so remove it & reinflate
+ Log.i(TAG, " showOrHideAppBubble, expanding app bubble from overflow");
+ mBubbleData.removeOverflowBubble(b);
+ } else {
+ // App bubble does not exist, lets add and expand it
+ Log.i(TAG, " showOrHideAppBubble, creating and expanding app bubble");
+ b = Bubble.createAppBubble(intent, user, icon, mMainExecutor);
+ }
b.setShouldAutoExpand(true);
inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
}
@@ -1703,8 +1714,7 @@
@Override
public void removeBubble(Bubble removedBubble) {
if (mLayerView != null) {
- // TODO: need to check if there's something that needs to happen here, e.g. if
- // the currently selected & expanded bubble is removed?
+ mLayerView.removeBubble(removedBubble);
}
}
@@ -2168,10 +2178,10 @@
}
@Override
- public void showBubble(String key, int bubbleBarOffsetX, int bubbleBarOffsetY) {
+ public void showBubble(String key, Rect bubbleBarBounds) {
mMainExecutor.execute(
() -> mController.expandStackAndSelectBubbleFromLauncher(
- key, bubbleBarOffsetX, bubbleBarOffsetY));
+ key, bubbleBarBounds));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 6e0c804..dbfa260 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -15,7 +15,6 @@
*/
package com.android.wm.shell.bubbles;
-import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_DATA;
@@ -41,6 +40,7 @@
import com.android.launcher3.icons.BubbleIconFactory;
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.Bubbles.DismissReason;
+import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
import com.android.wm.shell.common.bubbles.RemovedBubble;
@@ -427,7 +427,7 @@
/**
* When this method is called it is expected that all info in the bubble has completed loading.
* @see Bubble#inflate(BubbleViewInfoTask.Callback, Context, BubbleController, BubbleStackView,
- * BubbleIconFactory, boolean)
+ * BubbleBarLayerView, BubbleIconFactory, boolean)
*/
void notificationEntryUpdated(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
if (DEBUG_BUBBLE_DATA) {
@@ -491,6 +491,19 @@
}
/**
+ * Explicitly removes a bubble from the overflow, if it exists.
+ *
+ * @param bubble the bubble to remove.
+ */
+ public void removeOverflowBubble(Bubble bubble) {
+ if (bubble == null) return;
+ if (mOverflowBubbles.remove(bubble)) {
+ mStateChange.removedOverflowBubble = bubble;
+ dispatchPendingChanges();
+ }
+ }
+
+ /**
* Adds a group key indicating that the summary for this group should be suppressed.
*
* @param groupKey the group key of the group whose summary should be suppressed.
@@ -1056,7 +1069,6 @@
/**
* The set of bubbles in row.
*/
- @VisibleForTesting(visibility = PACKAGE)
public List<Bubble> getBubbles() {
return Collections.unmodifiableList(mBubbles);
}
@@ -1145,7 +1157,6 @@
return null;
}
- @VisibleForTesting(visibility = PRIVATE)
public Bubble getOverflowBubbleWithKey(String key) {
for (int i = 0; i < mOverflowBubbles.size(); i++) {
Bubble bubble = mOverflowBubbles.get(i);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index a76bd26..d62c86c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -19,7 +19,6 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Insets;
-import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -97,7 +96,7 @@
private PointF mRestingStackPosition;
private boolean mShowingInBubbleBar;
- private final Point mBubbleBarPosition = new Point();
+ private final Rect mBubbleBarBounds = new Rect();
public BubblePositioner(Context context, WindowManager windowManager) {
mContext = context;
@@ -791,15 +790,10 @@
}
/**
- * Sets the position of the bubble bar in screen coordinates.
- *
- * @param offsetX the offset of the bubble bar from the edge of the screen on the X axis
- * @param offsetY the offset of the bubble bar from the edge of the screen on the Y axis
+ * Sets the position of the bubble bar in display coordinates.
*/
- public void setBubbleBarPosition(int offsetX, int offsetY) {
- mBubbleBarPosition.set(
- getAvailableRect().width() - offsetX,
- getAvailableRect().height() + mInsets.top + mInsets.bottom - offsetY);
+ public void setBubbleBarPosition(Rect bubbleBarBounds) {
+ mBubbleBarBounds.set(bubbleBarBounds);
}
/**
@@ -820,7 +814,7 @@
/** The bottom position of the expanded view when showing above the bubble bar. */
public int getExpandedViewBottomForBubbleBar() {
- return mBubbleBarPosition.y - mExpandedViewPadding;
+ return mBubbleBarBounds.top - mExpandedViewPadding;
}
/**
@@ -831,9 +825,9 @@
}
/**
- * Returns the on screen co-ordinates of the bubble bar.
+ * Returns the display coordinates of the bubble bar.
*/
- public Point getBubbleBarPosition() {
- return mBubbleBarPosition;
+ public Rect getBubbleBarBounds() {
+ return mBubbleBarBounds;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index a619401..9facef3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -204,7 +204,7 @@
Choreographer.getInstance().postFrameCallback(frameCallback);
}
};
- private final BubbleController mBubbleController;
+ private final BubbleStackViewManager mManager;
private final BubbleData mBubbleData;
private final Bubbles.SysuiProxy.Provider mSysuiProxyProvider;
private StackViewState mStackViewState = new StackViewState();
@@ -858,6 +858,7 @@
private BubbleOverflow mBubbleOverflow;
private StackEducationView mStackEduView;
+ private StackEducationView.Manager mStackEducationViewManager;
private ManageEducationView mManageEduView;
private DismissView mDismissView;
@@ -873,15 +874,16 @@
private BubblePositioner mPositioner;
@SuppressLint("ClickableViewAccessibility")
- public BubbleStackView(Context context, BubbleController bubbleController,
- BubbleData data, @Nullable SurfaceSynchronizer synchronizer,
+ public BubbleStackView(Context context, BubbleStackViewManager bubbleStackViewManager,
+ BubblePositioner bubblePositioner, BubbleData data,
+ @Nullable SurfaceSynchronizer synchronizer,
FloatingContentCoordinator floatingContentCoordinator,
Bubbles.SysuiProxy.Provider sysuiProxyProvider,
ShellExecutor mainExecutor) {
super(context);
mMainExecutor = mainExecutor;
- mBubbleController = bubbleController;
+ mManager = bubbleStackViewManager;
mBubbleData = data;
mSysuiProxyProvider = sysuiProxyProvider;
@@ -893,7 +895,7 @@
mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
- mPositioner = mBubbleController.getPositioner();
+ mPositioner = bubblePositioner;
final TypedArray ta = mContext.obtainStyledAttributes(
new int[]{android.R.attr.dialogCornerRadius});
@@ -903,7 +905,7 @@
final Runnable onBubbleAnimatedOut = () -> {
if (getBubbleCount() == 0) {
mExpandedViewTemporarilyHidden = false;
- mBubbleController.onAllBubblesAnimatedOut();
+ mManager.onAllBubblesAnimatedOut();
}
};
mStackAnimationController = new StackAnimationController(
@@ -1383,7 +1385,9 @@
return false;
}
if (mStackEduView == null) {
- mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController);
+ mStackEducationViewManager = mManager::updateWindowFlagsForBackpress;
+ mStackEduView =
+ new StackEducationView(mContext, mPositioner, mStackEducationViewManager);
addView(mStackEduView);
}
return showStackEdu();
@@ -1412,7 +1416,9 @@
private void updateUserEdu() {
if (isStackEduVisible() && !mStackEduView.isHiding()) {
removeView(mStackEduView);
- mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController);
+ mStackEducationViewManager = mManager::updateWindowFlagsForBackpress;
+ mStackEduView =
+ new StackEducationView(mContext, mPositioner, mStackEducationViewManager);
addView(mStackEduView);
showStackEdu();
}
@@ -2106,7 +2112,7 @@
logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
logBubbleEvent(mExpandedBubble,
FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED);
- mBubbleController.isNotificationPanelExpanded(notifPanelExpanded -> {
+ mManager.checkNotificationPanelExpandedState(notifPanelExpanded -> {
if (!notifPanelExpanded && mIsExpanded) {
startMonitoringSwipeUpGesture();
}
@@ -2227,7 +2233,7 @@
*/
void hideCurrentInputMethod() {
mPositioner.setImeVisible(false, 0);
- mBubbleController.hideCurrentInputMethod();
+ mManager.hideCurrentInputMethod();
}
/** Set the stack position to whatever the positioner says. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackViewManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackViewManager.kt
new file mode 100644
index 0000000..fb597a0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackViewManager.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+import java.util.function.Consumer
+
+/** Defines callbacks from [BubbleStackView] to its manager. */
+interface BubbleStackViewManager {
+
+ /** Notifies that all bubbles animated out. */
+ fun onAllBubblesAnimatedOut()
+
+ /** Notifies whether backpress should be intercepted. */
+ fun updateWindowFlagsForBackpress(interceptBack: Boolean)
+
+ /**
+ * Checks the current expansion state of the notification panel, and invokes [callback] with the
+ * result.
+ */
+ fun checkNotificationPanelExpandedState(callback: Consumer<Boolean>)
+
+ /** Requests to hide the current input method. */
+ fun hideCurrentInputMethod()
+
+ companion object {
+
+ @JvmStatic
+ fun fromBubbleController(controller: BubbleController) = object : BubbleStackViewManager {
+ override fun onAllBubblesAnimatedOut() {
+ controller.onAllBubblesAnimatedOut()
+ }
+
+ override fun updateWindowFlagsForBackpress(interceptBack: Boolean) {
+ controller.updateWindowFlagsForBackpress(interceptBack)
+ }
+
+ override fun checkNotificationPanelExpandedState(callback: Consumer<Boolean>) {
+ controller.isNotificationPanelExpanded(callback)
+ }
+
+ override fun hideCurrentInputMethod() {
+ controller.hideCurrentInputMethod()
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
index 5fc67d7..dc27133 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
@@ -31,6 +31,7 @@
import android.graphics.Rect;
import android.util.Log;
import android.view.View;
+import android.view.ViewGroup;
import androidx.annotation.Nullable;
@@ -186,6 +187,7 @@
}
if (mTaskView != null) {
mTaskView.release();
+ ((ViewGroup) mParentView).removeView(mTaskView);
mTaskView = null;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java
index 9e8a385..c1f704a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java
@@ -24,8 +24,8 @@
import androidx.annotation.NonNull;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
/**
* Observer used to identify tasks that are opening or moving to front. If a bubble activity is
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 5776ad1..7a5afec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -17,6 +17,7 @@
package com.android.wm.shell.bubbles;
import android.content.Intent;
+import android.graphics.Rect;
import com.android.wm.shell.bubbles.IBubblesListener;
/**
@@ -29,7 +30,7 @@
oneway void unregisterBubbleListener(in IBubblesListener listener) = 2;
- oneway void showBubble(in String key, in int bubbleBarOffsetX, in int bubbleBarOffsetY) = 3;
+ oneway void showBubble(in String key, in Rect bubbleBarBounds) = 3;
oneway void removeBubble(in String key) = 4;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
index 95f1017..c4108c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
@@ -35,7 +35,7 @@
class StackEducationView(
context: Context,
private val positioner: BubblePositioner,
- private val controller: BubbleController
+ private val manager: Manager
) : LinearLayout(context) {
companion object {
@@ -44,6 +44,12 @@
private const val ANIMATE_DURATION_SHORT: Long = 40
}
+ /** Callbacks to notify managers of [StackEducationView] about events. */
+ interface Manager {
+ /** Notifies whether backpress should be intercepted. */
+ fun updateWindowFlagsForBackpress(interceptBack: Boolean)
+ }
+
private val view by lazy { requireViewById<View>(R.id.stack_education_layout) }
private val titleTextView by lazy { requireViewById<TextView>(R.id.stack_education_title) }
private val descTextView by lazy { requireViewById<TextView>(R.id.stack_education_description) }
@@ -93,7 +99,7 @@
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
setOnKeyListener(null)
- controller.updateWindowFlagsForBackpress(false /* interceptBack */)
+ manager.updateWindowFlagsForBackpress(false /* interceptBack */)
}
private fun setTextColor() {
@@ -124,7 +130,7 @@
isHiding = false
if (visibility == VISIBLE) return false
- controller.updateWindowFlagsForBackpress(true /* interceptBack */)
+ manager.updateWindowFlagsForBackpress(true /* interceptBack */)
layoutParams.width =
if (positioner.isLargeScreen || positioner.isLandscape)
context.resources.getDimensionPixelSize(R.dimen.bubbles_user_education_width)
@@ -185,7 +191,7 @@
if (visibility != VISIBLE || isHiding) return
isHiding = true
- controller.updateWindowFlagsForBackpress(false /* interceptBack */)
+ manager.updateWindowFlagsForBackpress(false /* interceptBack */)
animate()
.alpha(0f)
.setDuration(if (isExpanding) ANIMATE_DURATION_SHORT else ANIMATE_DURATION)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index 893a87f..84a616f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -22,6 +22,7 @@
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Point;
+import android.graphics.Rect;
import android.util.Log;
import android.util.Size;
import android.view.View;
@@ -149,12 +150,12 @@
bbev.setVisibility(VISIBLE);
// Set the pivot point for the scale, so the view animates out from the bubble bar.
- Point bubbleBarPosition = mPositioner.getBubbleBarPosition();
+ Rect bubbleBarBounds = mPositioner.getBubbleBarBounds();
mExpandedViewContainerMatrix.setScale(
1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
- bubbleBarPosition.x,
- bubbleBarPosition.y);
+ bubbleBarBounds.centerX(),
+ bubbleBarBounds.top);
bbev.setAnimationMatrix(mExpandedViewContainerMatrix);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index 00d683e..73a9cf4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -266,13 +266,8 @@
mListener.onBackPressed();
}
- /** Cleans up task view, should be called when the bubble is no longer active. */
+ /** Cleans up the expanded view, should be called when the bubble is no longer active. */
public void cleanUpExpandedState() {
- if (mBubbleTaskViewHelper != null) {
- if (mTaskView != null) {
- removeView(mTaskView);
- }
- }
mMenuViewController.hideMenu(false /* animated */);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index bd8ce80..b95d258 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -34,7 +34,9 @@
import android.widget.FrameLayout;
import com.android.wm.shell.R;
+import com.android.wm.shell.bubbles.Bubble;
import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.bubbles.BubbleData;
import com.android.wm.shell.bubbles.BubbleOverflow;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleViewProvider;
@@ -61,6 +63,7 @@
private static final float SCRIM_ALPHA = 0.2f;
private final BubbleController mBubbleController;
+ private final BubbleData mBubbleData;
private final BubblePositioner mPositioner;
private final BubbleBarAnimationHelper mAnimationHelper;
private final BubbleEducationViewController mEducationViewController;
@@ -85,9 +88,10 @@
private TouchDelegate mHandleTouchDelegate;
private final Rect mHandleTouchBounds = new Rect();
- public BubbleBarLayerView(Context context, BubbleController controller) {
+ public BubbleBarLayerView(Context context, BubbleController controller, BubbleData bubbleData) {
super(context);
mBubbleController = controller;
+ mBubbleData = bubbleData;
mPositioner = mBubbleController.getPositioner();
mAnimationHelper = new BubbleBarAnimationHelper(context,
@@ -236,15 +240,44 @@
showScrim(true);
}
+ /** Removes the given {@code bubble}. */
+ public void removeBubble(Bubble bubble) {
+ if (mBubbleData.getBubbles().isEmpty()) {
+ // we're removing the last bubble. collapse the expanded view and cleanup bubble views
+ // at the end.
+ collapse(bubble::cleanupViews);
+ } else {
+ bubble.cleanupViews();
+ }
+ }
+
/** Collapses any showing expanded view */
public void collapse() {
+ collapse(/* endAction= */ null);
+ }
+
+ /**
+ * Collapses any showing expanded view.
+ *
+ * @param endAction an action to run and the end of the collapse animation.
+ */
+ public void collapse(@Nullable Runnable endAction) {
+ if (!mIsExpanded) {
+ return;
+ }
mIsExpanded = false;
final BubbleBarExpandedView viewToRemove = mExpandedView;
mEducationViewController.hideEducation(/* animated = */ true);
+ Runnable runnable = () -> {
+ removeView(viewToRemove);
+ if (endAction != null) {
+ endAction.run();
+ }
+ };
if (mDragController != null && mDragController.isStuckToDismiss()) {
- mAnimationHelper.animateDismiss(() -> removeView(viewToRemove));
+ mAnimationHelper.animateDismiss(runnable);
} else {
- mAnimationHelper.animateCollapse(() -> removeView(viewToRemove));
+ mAnimationHelper.animateCollapse(runnable);
}
mBubbleController.getSysuiProxy().onStackExpandChanged(false);
mExpandedView = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
index 49db8d9..e8c809e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
@@ -20,10 +20,11 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;
import android.annotation.IntDef;
+import com.android.wm.shell.shared.TransitionUtil;
+
/** Helper utility class of methods and constants that are available to be imported in Launcher. */
public class SplitScreenConstants {
/** Duration used for every split fade-in or fade-out. */
@@ -126,7 +127,7 @@
WINDOWING_MODE_FREEFORM};
/** Flag applied to a transition change to identify it as a divider bar for animation. */
- public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM;
+ public static final int FLAG_IS_DIVIDER_BAR = TransitionUtil.FLAG_IS_DIVIDER_BAR;
public static final String splitPositionToString(@SplitPosition int pos) {
switch (pos) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
new file mode 100644
index 0000000..fd91ac0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.window.WindowContainerTransaction
+import com.android.wm.shell.sysui.ShellCommandHandler
+import java.io.PrintWriter
+
+/**
+ * Handles the shell commands for the DesktopTasksController.
+ */
+class DesktopModeShellCommandHandler(private val controller: DesktopTasksController) :
+ ShellCommandHandler.ShellCommandActionHandler {
+
+ override fun onShellCommand(args: Array<String>, pw: PrintWriter): Boolean {
+ return when (args[0]) {
+ "moveToDesktop" -> {
+ if (!runMoveToDesktop(args, pw)) {
+ pw.println("Task not found. Please enter a valid taskId.")
+ false
+ } else {
+ true
+ }
+ }
+
+ else -> {
+ pw.println("Invalid command: ${args[0]}")
+ false
+ }
+ }
+ }
+
+ private fun runMoveToDesktop(args: Array<String>, pw: PrintWriter): Boolean {
+ if (args.size < 2) {
+ // First argument is the action name.
+ pw.println("Error: task id should be provided as arguments")
+ return false
+ }
+
+ val taskId = try {
+ args[1].toInt()
+ } catch (e: NumberFormatException) {
+ pw.println("Error: task id should be an integer")
+ return false
+ }
+
+ return controller.moveToDesktopWithoutDecor(taskId, WindowContainerTransaction())
+ }
+
+ override fun printShellCommandHelp(pw: PrintWriter, prefix: String) {
+ pw.println("$prefix moveToDesktop <taskId> ")
+ pw.println("$prefix Move a task with given id to desktop mode.")
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index c0fc02fa..f82212d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -86,10 +86,10 @@
) {
visibleTasksListeners[visibleTasksListener] = executor
displayData.keyIterator().forEach { displayId ->
- val visibleTasks = getVisibleTaskCount(displayId)
+ val visibleTasksCount = getVisibleTaskCount(displayId)
val stashed = isStashed(displayId)
executor.execute {
- visibleTasksListener.onVisibilityChanged(displayId, visibleTasks > 0)
+ visibleTasksListener.onTasksVisibilityChanged(displayId, visibleTasksCount)
visibleTasksListener.onStashedChanged(displayId, stashed)
}
}
@@ -222,10 +222,8 @@
val otherDisplays = displayData.keyIterator().asSequence().filter { it != displayId }
for (otherDisplayId in otherDisplays) {
if (displayData[otherDisplayId].visibleTasks.remove(taskId)) {
- // Task removed from other display, check if we should notify listeners
- if (displayData[otherDisplayId].visibleTasks.isEmpty()) {
- notifyVisibleTaskListeners(otherDisplayId, hasVisibleFreeformTasks = false)
- }
+ notifyVisibleTaskListeners(otherDisplayId,
+ displayData[otherDisplayId].visibleTasks.size)
}
}
}
@@ -248,15 +246,15 @@
)
}
- // Check if count changed and if there was no tasks or this is the first task
- if (prevCount != newCount && (prevCount == 0 || newCount == 0)) {
- notifyVisibleTaskListeners(displayId, newCount > 0)
+ // Check if count changed
+ if (prevCount != newCount) {
+ notifyVisibleTaskListeners(displayId, newCount)
}
}
- private fun notifyVisibleTaskListeners(displayId: Int, hasVisibleFreeformTasks: Boolean) {
+ private fun notifyVisibleTaskListeners(displayId: Int, visibleTasksCount: Int) {
visibleTasksListeners.forEach { (listener, executor) ->
- executor.execute { listener.onVisibilityChanged(displayId, hasVisibleFreeformTasks) }
+ executor.execute { listener.onTasksVisibilityChanged(displayId, visibleTasksCount) }
}
}
@@ -379,9 +377,9 @@
*/
interface VisibleTasksListener {
/**
- * Called when the desktop starts or stops showing freeform tasks.
+ * Called when the desktop changes the number of visible freeform tasks.
*/
- fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {}
+ fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {}
/**
* Called when the desktop stashed status changes.
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 28c06a4..e872849 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
@@ -100,14 +100,17 @@
private val desktopMode: DesktopModeImpl
private var visualIndicator: DesktopModeVisualIndicator? = null
+ private val desktopModeShellCommandHandler: DesktopModeShellCommandHandler =
+ DesktopModeShellCommandHandler(this)
+
private val mOnAnimationFinishedCallback = Consumer<SurfaceControl.Transaction> {
t: SurfaceControl.Transaction ->
visualIndicator?.releaseVisualIndicator(t)
visualIndicator = null
}
private val taskVisibilityListener = object : VisibleTasksListener {
- override fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {
- launchAdjacentController.launchAdjacentEnabled = !hasVisibleFreeformTasks
+ override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {
+ launchAdjacentController.launchAdjacentEnabled = visibleTasksCount == 0
}
}
private val dragToDesktopStateListener = object : DragToDesktopStateListener {
@@ -148,6 +151,8 @@
private fun onInit() {
KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController")
shellCommandHandler.addDumpCallback(this::dump, this)
+ shellCommandHandler.addCommandCallback("desktopmode", desktopModeShellCommandHandler,
+ this)
shellController.addExternalInterface(
ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE,
{ createExternalInterface() },
@@ -240,6 +245,40 @@
}
}
+ /** Move a task with given `taskId` to desktop without decor */
+ fun moveToDesktopWithoutDecor(
+ taskId: Int,
+ wct: WindowContainerTransaction
+ ): Boolean {
+ val task = shellTaskOrganizer.getRunningTaskInfo(taskId) ?: return false
+ moveToDesktopWithoutDecor(task, wct)
+ return true
+ }
+
+ /**
+ * Move a task to desktop without decor
+ */
+ private fun moveToDesktopWithoutDecor(
+ task: RunningTaskInfo,
+ wct: WindowContainerTransaction
+ ) {
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: moveToDesktopWithoutDecor taskId=%d",
+ task.taskId
+ )
+ exitSplitIfApplicable(wct, task)
+ // Bring other apps to front first
+ bringDesktopAppsToFront(task.displayId, wct)
+ addMoveToDesktopChanges(wct, task)
+
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
+ } else {
+ shellTaskOrganizer.applyTransaction(wct)
+ }
+ }
+
/**
* Move a task to desktop
*/
@@ -1033,14 +1072,16 @@
SingleInstanceRemoteListener<DesktopTasksController, IDesktopTaskListener>
private val listener: VisibleTasksListener = object : VisibleTasksListener {
- override fun onVisibilityChanged(displayId: Int, visible: Boolean) {
+ override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
- "IDesktopModeImpl: onVisibilityChanged display=%d visible=%b",
+ "IDesktopModeImpl: onVisibilityChanged display=%d visible=%d",
displayId,
- visible
+ visibleTasksCount
)
- remoteListener.call { l -> l.onVisibilityChanged(displayId, visible) }
+ remoteListener.call {
+ l -> l.onTasksVisibilityChanged(displayId, visibleTasksCount)
+ }
}
override fun onStashedChanged(displayId: Int, stashed: Boolean) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 731fec7..39610e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -26,6 +26,7 @@
import android.window.WindowContainerTransaction
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup
+import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP
@@ -33,7 +34,6 @@
import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
import com.android.wm.shell.transition.Transitions.TransitionHandler
import com.android.wm.shell.util.KtProtoLog
-import com.android.wm.shell.util.TransitionUtil
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator.Companion.DRAG_FREEFORM_SCALE
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
index 39128a8..8ed87f2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
@@ -22,8 +22,8 @@
*/
interface IDesktopTaskListener {
- /** Desktop task visibility has change. Visible if at least 1 task is visible. */
- oneway void onVisibilityChanged(int displayId, boolean visible);
+ /** Desktop tasks visibility has changed. Visible if at least 1 task is visible. */
+ oneway void onTasksVisibilityChanged(int displayId, int visibleTasksCount);
/** Desktop task stashed status has changed. */
oneway void onStashedChanged(int displayId, boolean stashed);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md
index 73a7348..3fad28a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md
@@ -15,4 +15,4 @@
Todo
- Per-feature docs
- Feature flagging
-- Best practices
\ No newline at end of file
+- Best practices & patterns
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
index fbf326e..9aa5f4f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
@@ -102,5 +102,5 @@
Launcher uses.
If the new code doesn't fall into those categories, they can be added explicitly in the Shell's
-[Android.bp](frameworks/base/libs/WindowManager/Shell/Android.bp) file under the
+[Android.bp](/libs/WindowManager/Shell/Android.bp) file under the
`wm_shell_util-sources` filegroup.
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md
index 6c01d96..7070dea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md
@@ -21,16 +21,16 @@
(especially as products override components).
The module dependency tree looks a bit like:
-- [WMShellConcurrencyModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java)
+- [WMShellConcurrencyModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java)
(provides threading-related components)
- - [WMShellBaseModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java)
+ - [WMShellBaseModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java)
(provides components that are likely common to all products, ie. DisplayController,
Transactions, etc.)
- - [WMShellModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java)
+ - [WMShellModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java)
(phone/tablet specific components only)
- - [TvPipModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java)
+ - [TvPipModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java)
(PIP specific components for TV)
- - [TvWMShellModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java)
+ - [TvWMShellModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java)
(TV specific components only)
- etc.
@@ -43,7 +43,7 @@
product it runs on. If there are hooks that can be added to the component, that is the
preferable approach.
-The alternative is to use the [@DynamicOverride](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java)
+The alternative is to use the [@DynamicOverride](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java)
annotation to allow the product module to provide an implementation that the base module can
reference. This is most useful if the existence of the entire component is controlled by the
product and the override implementation is optional (there is a default implementation). More
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
index f9ea1d4..438aa76 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
@@ -29,30 +29,78 @@
### Kotlin
Protolog tool does not yet have support for Kotlin code (see [b/168581922](https://b.corp.google.com/issues/168581922)).
-For logging in Kotlin, use the [KtProtoLog](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt)
+For logging in Kotlin, use the [KtProtoLog](/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt)
class which has a similar API to the Java ProtoLog class.
### Enabling ProtoLog command line logging
-Run these commands to enable protologs for both WM Core and WM Shell to print to logcat.
+Run these commands to enable protologs (in logcat) for WM Core ([list of all core tags](/core/java/com/android/internal/protolog/ProtoLogGroup.java)):
```shell
-adb shell wm logging enable-text NEW_FEATURE
-adb shell wm logging disable-text NEW_FEATURE
+adb shell wm logging enable-text TAG
+adb shell wm logging disable-text TAG
+```
+
+And these commands to enable protologs (in logcat) for WM Shell ([list of all shell tags](/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java)):
+```shell
+adb shell dumpsys activity service SystemUIService WMShell protolog enable-text TAG
+adb shell dumpsys activity service SystemUIService WMShell protolog enable-text TAG
```
## Winscope Tracing
The Winscope tool is extremely useful in determining what is happening on-screen in both
WindowManager and SurfaceFlinger. Follow [go/winscope](http://go/winscope-help) to learn how to
-use the tool.
+use the tool. This trace will contain all the information about the windows/activities/surfaces on
+screen.
-In addition, there is limited preliminary support for Winscope tracing componetns in the Shell,
-which involves adding trace fields to [wm_shell_trace.proto](frameworks/base/libs/WindowManager/Shell/proto/wm_shell_trace.proto)
-file and ensure it is updated as a part of `WMShell#writeToProto`.
+## WindowManager/SurfaceFlinger hierarchy dump
-Tracing can be started via the shell command (to be added to the Winscope tool as needed):
+A quick way to view the WindowManager hierarchy without a winscope trace is via the wm dumps:
```shell
-adb shell cmd statusbar tracing start
-adb shell cmd statusbar tracing stop
+adb shell dumpsys activity containers
+```
+
+Likewise, the SurfaceFlinger hierarchy can be dumped for inspection by running:
+```shell
+adb shell dumpsys SurfaceFlinger
+# Search output for "Layer Hierarchy"
+```
+
+## Tracing global SurfaceControl transaction updates
+
+While Winscope traces are very useful, it sometimes doesn't give you enough information about which
+part of the code is initiating the transaction updates. In such cases, it can be helpful to get
+stack traces when specific surface transaction calls are made, which is possible by enabling the
+following system properties for example:
+```shell
+# Enabling
+adb shell setprop persist.wm.debug.sc.tx.log_match_call setAlpha # matches the name of the SurfaceControlTransaction method
+adb shell setprop persist.wm.debug.sc.tx.log_match_name com.android.systemui # matches the name of the surface
+adb reboot
+adb logcat -s "SurfaceControlRegistry"
+
+# Disabling logging
+adb shell setprop persist.wm.debug.sc.tx.log_match_call \"\"
+adb shell setprop persist.wm.debug.sc.tx.log_match_name \"\"
+adb reboot
+```
+
+It is not necessary to set both `log_match_call` and `log_match_name`, but note logs can be quite
+noisy if unfiltered.
+
+## Tracing activity starts in the app process
+
+It's sometimes useful to know when to see a stack trace of when an activity starts in the app code
+(ie. if you are repro'ing a bug related to activity starts). You can enable this system property to
+get this trace:
+```shell
+# Enabling
+adb shell setprop persist.wm.debug.start_activity true
+adb reboot
+adb logcat -s "Instrumentation"
+
+# Disabling
+adb shell setprop persist.wm.debug.start_activity \"\"
+adb reboot
```
## Dumps
@@ -69,6 +117,21 @@
- Update `WMShell` if you are dumping SysUI state
- Inject `ShellCommandHandler` into your Shell class, and add a dump callback
+## Shell commands
+
+It can be useful to add additional shell commands to drive and test specific interactions.
+
+To add a new command for your feature, inject a `ShellCommandHandler` into your class and add a
+shell command handler in your controller.
+
+```shell
+# List all available commands
+adb shell dumpsys activity service SystemUIService WMShell help
+
+# Run a specific command
+adb shell dumpsys activity service SystemUIService WMShell <cmd> <args> ...
+```
+
## Debugging in Android Studio
If you are using the [go/sysui-studio](http://go/sysui-studio) project, then you can debug Shell
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md
index a88ef6a..b489fe8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md
@@ -19,25 +19,24 @@
## Where does the code live
-The core WMShell library code is currently located in the [frameworks/base/libs/WindowManager/Shell](frameworks/base/libs/WindowManager/Shell)
+The core WMShell library code is currently located in the [frameworks/base/libs/WindowManager/Shell](/libs/WindowManager/Shell)
directory and is included as a part dependency of the host SystemUI apk.
## How do I build the Shell library
-The library can be built directly by running (using [go/makepush](http://go/makepush)):
+The library can be built directly by running:
```shell
-mp :WindowManager-Shell
+m WindowManager-Shell
```
But this is mainly useful for inspecting the contents of the library or verifying it builds. The
-various targets can be found in the Shell library's [Android.bp](frameworks/base/libs/WindowManager/Shell/Android.bp)
+various targets can be found in the Shell library's [Android.bp](/libs/WindowManager/Shell/Android.bp)
file.
Normally, you would build it as a part of the host SystemUI, for example via commandline:
```shell
# Phone SystemUI variant
-mp sysuig
-# Building Shell & SysUI changes along w/ framework changes
-mp core services sysuig
+m SystemUI
+adevice update
```
Or preferably, if you are making WMShell/SysUI only changes (no other framework changes), then
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/patterns/TEMPLATE.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/patterns/TEMPLATE.md
new file mode 100644
index 0000000..0e20a8e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/patterns/TEMPLATE.md
@@ -0,0 +1,30 @@
+# Pattern (one line description)
+
+### What this pattern solves
+
+Give detailed information about the problem that this pattern addresses, include when it
+should/should not be used.
+
+### How it works
+
+Give a high level overview of how this pattern works technically with sufficient links for the
+relevant dependencies for folks to read more.
+
+### Code examples
+
+Explain how this pattern is used in code.
+
+File.kt:
+```kotlin
+fun someFunction() {
+ // Use this code
+}
+```
+
+### Relevant links
+
+Add relevant links to other files that implement this pattern, design docs, or other important
+info.
+
+Link 1: [More info](some_example_link) \
+Link 2: [More info](some_example_link)
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
index d6302e6..30ff669 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
@@ -80,4 +80,6 @@
# Run a specific command
adb shell dumpsys activity service SystemUIService WMShell help
adb shell dumpsys activity service SystemUIService WMShell <cmd> <args> ...
-```
\ No newline at end of file
+```
+
+More detail can be found in [Debugging in the Shell](debugging.md) section.
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md
index 8a80333..98af930 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md
@@ -5,7 +5,7 @@
## Unit tests
New WM Shell unit tests can be added to the
-[Shell/tests/unittest](frameworks/base/libs/WindowManager/Shell/tests/unittest) directory, and can
+[Shell/tests/unittest](/libs/WindowManager/Shell/tests/unittest) directory, and can
be run via command line using `atest`:
```shell
atest WMShellUnitTests
@@ -25,10 +25,24 @@
and SurfaceFlinger traces captured during the run.
New WM Shell Flicker tests can be added to the
-[Shell/tests/flicker](frameworks/base/libs/WindowManager/Shell/tests/flicker) directory, and can
-be run via command line using `atest`:
+[Shell/tests/flicker](/libs/WindowManager/Shell/tests/flicker) directory, and can be run via command line using `atest`:
```shell
-atest WMShellFlickerTests
+# Bubbles
+atest WMShellFlickerTestsBubbles
+
+# PIP
+atest WMShellFlickerTestsPip1
+atest WMShellFlickerTestsPip2
+atest WMShellFlickerTestsPip3
+atest WMShellFlickerTestsPipApps
+atest WMShellFlickerTestsPipAppsCSuite
+
+# Splitscreen
+atest WMShellFlickerTestsSplitScreenGroup1
+atest WMShellFlickerTestsSplitScreenGroup2
+
+# Other
+atest WMShellFlickerTestsOther
```
**Note**: Currently Flicker tests can only be run from the commandline and not via SysUI Studio
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md
index eac74889..9d01535 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md
@@ -43,7 +43,7 @@
## Dagger setup
-The threading-related components are provided by the [WMShellConcurrencyModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java),
+The threading-related components are provided by the [WMShellConcurrencyModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java),
for example, the Executors and Handlers for the various threads that are used. You can request
an executor of the necessary type by using the appropriate annotation for each of the threads (ie.
`@ShellMainThread Executor`) when injecting into your Shell component.
@@ -76,7 +76,7 @@
want to dedupe multiple messages
- In such cases inject `@ShellMainThread Handler` or use view.getHandler() which should be OK
assuming that the view root was initialized on the main Shell thread
-- **Never use Looper.getMainLooper()**
+- <u>**Never</u> use Looper.getMainLooper()**
- It's likely going to be wrong, you can inject `@Main ShellExecutor` to get the SysUI main thread
### Testing
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index e63bbc0..73de231 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -27,7 +27,7 @@
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
import static android.view.WindowManager.TRANSIT_SLEEP;
-import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
+import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index e739266..896ca96 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -71,11 +71,11 @@
import com.android.wm.shell.common.pip.PipMenuController;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.CounterRotatorHelper;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 238e6b5..c5a0102 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -797,14 +797,20 @@
mPipBoundsAlgorithm.getMovementBounds(postChangeBounds),
mPipBoundsState.getStashedState());
- updateDisplayLayout.run();
+ // Scale PiP on density dpi change, so it appears to be the same size physically.
+ final boolean densityDpiChanged =
+ mPipDisplayLayoutState.getDisplayLayout().densityDpi() != 0
+ && (mPipDisplayLayoutState.getDisplayLayout().densityDpi()
+ != layout.densityDpi());
+ if (densityDpiChanged) {
+ final float scale = (float) layout.densityDpi()
+ / mPipDisplayLayoutState.getDisplayLayout().densityDpi();
+ postChangeBounds.set(0, 0,
+ (int) (postChangeBounds.width() * scale),
+ (int) (postChangeBounds.height() * scale));
+ }
- // Resize the PiP bounds to be at the same scale relative to the new size spec. For
- // example, if PiP was resized to 90% of the maximum size on the previous layout,
- // make sure it is 90% of the new maximum size spec.
- postChangeBounds.set(0, 0,
- (int) (mPipBoundsState.getMaxSize().x * mPipBoundsState.getBoundsScale()),
- (int) (mPipBoundsState.getMaxSize().y * mPipBoundsState.getBoundsScale()));
+ updateDisplayLayout.run();
// Calculate the PiP bounds in the new orientation based on same fraction along the
// rotated movement bounds.
@@ -821,10 +827,6 @@
mPipBoundsState.setHasUserResizedPip(true);
mTouchHandler.setUserResizeBounds(postChangeBounds);
- final boolean densityDpiChanged =
- mPipDisplayLayoutState.getDisplayLayout().densityDpi() != 0
- && (mPipDisplayLayoutState.getDisplayLayout().densityDpi()
- != layout.densityDpi());
if (densityDpiChanged) {
// Using PipMotionHelper#movePip directly here may cause race condition since
// the app content in PiP mode may or may not be updated for the new density dpi.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
index d16a692..c2f4d72a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
@@ -71,9 +71,9 @@
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.List;
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 1232baa..97d3457 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
@@ -58,10 +58,10 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.HomeTransitionObserver;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.function.Consumer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 5de8a9b..e8894a83 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -48,9 +48,9 @@
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.split.SplitDecorManager;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.transition.OneShotRemoteHandler;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index af05aa2..e5045ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -44,6 +44,8 @@
import static com.android.wm.shell.common.split.SplitScreenConstants.splitPositionToString;
import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
+import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
+import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
@@ -64,8 +66,6 @@
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
-import static com.android.wm.shell.util.TransitionUtil.isClosingType;
-import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -135,13 +135,13 @@
import com.android.wm.shell.common.split.SplitWindowManager;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason;
import com.android.wm.shell.transition.DefaultMixedHandler;
import com.android.wm.shell.transition.LegacyTransitions;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.util.SplitBounds;
-import com.android.wm.shell.util.TransitionUtil;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
import dalvik.annotation.optimization.NeverCompile;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index 84f21f6..198ec82 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -37,8 +37,8 @@
import androidx.annotation.VisibleForTesting;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.Objects;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java
index 628ce27..b03daaa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java
@@ -27,8 +27,8 @@
import androidx.annotation.NonNull;
-import com.android.wm.shell.util.CounterRotator;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.CounterRotator;
+import com.android.wm.shell.shared.TransitionUtil;
import java.util.List;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 8c2203e..8746b8c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -24,7 +24,7 @@
import static android.view.WindowManager.TRANSIT_PIP;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
-import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
+import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -48,11 +48,11 @@
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentsTransitionHandler;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.splitscreen.StageCoordinator;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.unfold.UnfoldTransitionHandler;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.Map;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 193a4fb..c70a821 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -109,8 +109,8 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.List;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
index af31f5f..cb2944c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
@@ -32,7 +32,7 @@
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
/**
* The {@link TransitionObserver} that observes for transitions involving the home
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index 293b660..4c4c580 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -39,7 +39,7 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index b012d35..1be85d0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -56,7 +56,7 @@
import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
/** The helper class that provides methods for adding styles to transition animations. */
public class TransitionAnimationHelper {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 67fc7e2..5e79681 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -37,9 +37,9 @@
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
+import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
-import static com.android.wm.shell.util.TransitionUtil.isClosingType;
-import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -80,13 +80,13 @@
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.tracing.LegacyTransitionTracer;
import com.android.wm.shell.transition.tracing.PerfettoTransitionTracer;
import com.android.wm.shell.transition.tracing.TransitionTracer;
-import com.android.wm.shell.util.TransitionUtil;
import java.io.PrintWriter;
import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
index 99df6a3..0c25f27 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
@@ -18,6 +18,7 @@
import android.internal.perfetto.protos.PerfettoTrace;
import android.os.SystemClock;
+import android.os.Trace;
import android.tracing.perfetto.DataSourceInstance;
import android.tracing.perfetto.DataSourceParams;
import android.tracing.perfetto.InitArguments;
@@ -58,6 +59,15 @@
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logDispatched");
+ try {
+ doLogDispatched(transitionId, handler);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogDispatched(int transitionId, Transitions.TransitionHandler handler) {
mDataSource.trace(ctx -> {
final int handlerId = getHandlerId(handler, ctx);
@@ -97,6 +107,15 @@
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logMergeRequested");
+ try {
+ doLogMergeRequested(mergeRequestedTransitionId, playingTransitionId);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogMergeRequested(int mergeRequestedTransitionId, int playingTransitionId) {
mDataSource.trace(ctx -> {
final ProtoOutputStream os = ctx.newTracePacket();
final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
@@ -120,10 +139,19 @@
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logMerged");
+ try {
+ doLogMerged(mergedTransitionId, playingTransitionId);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogMerged(int mergeRequestedTransitionId, int playingTransitionId) {
mDataSource.trace(ctx -> {
final ProtoOutputStream os = ctx.newTracePacket();
final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
- os.write(PerfettoTrace.ShellTransition.ID, mergedTransitionId);
+ os.write(PerfettoTrace.ShellTransition.ID, mergeRequestedTransitionId);
os.write(PerfettoTrace.ShellTransition.MERGE_TIME_NS,
SystemClock.elapsedRealtimeNanos());
os.write(PerfettoTrace.ShellTransition.MERGE_TARGET, playingTransitionId);
@@ -142,6 +170,15 @@
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logAborted");
+ try {
+ doLogAborted(transitionId);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogAborted(int transitionId) {
mDataSource.trace(ctx -> {
final ProtoOutputStream os = ctx.newTracePacket();
final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
@@ -157,6 +194,15 @@
}
private void onFlush() {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "onFlush");
+ try {
+ doOnFlush();
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doOnFlush() {
mDataSource.trace(ctx -> {
final ProtoOutputStream os = ctx.newTracePacket();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
index 98d343b..c26604a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
@@ -35,6 +35,7 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.transition.Transitions.TransitionFinishCallback;
@@ -43,7 +44,6 @@
import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator;
import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator;
import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.List;
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 a8b39c41..891eea0 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
@@ -888,7 +888,10 @@
private DesktopModeWindowDecoration getFocusedDecor() {
final int size = mWindowDecorByTaskId.size();
DesktopModeWindowDecoration focusedDecor = null;
- for (int i = 0; i < size; i++) {
+ // TODO(b/323251951): We need to iterate this in reverse to avoid potentially getting
+ // a decor for a closed task. This is a short term fix while the core issue is addressed,
+ // which involves refactoring the window decor lifecycle to be visibility based.
+ for (int i = size - 1; i >= 0; i--) {
final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
if (decor != null && decor.isFocused()) {
focusedDecor = decor;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index dab762f..fa0aba5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -1190,6 +1190,23 @@
assertThat(mBubbleData.getBubbleInStackWithKey(appBubbleKey)).isNull();
}
+ @Test
+ public void test_removeOverflowBubble() {
+ sendUpdatedEntryAtTime(mEntryA1, 2000);
+ mBubbleData.setListener(mListener);
+
+ mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_USER_GESTURE);
+ verifyUpdateReceived();
+ assertOverflowChangedTo(ImmutableList.of(mBubbleA1));
+
+ mBubbleData.removeOverflowBubble(mBubbleA1);
+ verifyUpdateReceived();
+
+ BubbleData.Update update = mUpdateCaptor.getValue();
+ assertThat(update.removedOverflowBubble).isEqualTo(mBubbleA1);
+ assertOverflowChangedTo(ImmutableList.of());
+ }
+
private void verifyUpdateReceived() {
verify(mListener).applyUpdate(mUpdateCaptor.capture());
reset(mListener);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
index 4878df8..1668e37 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
@@ -56,9 +56,7 @@
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
-/**
- * Tests for loading / inflating views & icons for a bubble.
- */
+/** Tests for loading / inflating views & icons for a bubble. */
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper(setAsMainLooper = true)
@@ -76,25 +74,33 @@
@Before
fun setup() {
metadataFlagListener = Bubbles.BubbleMetadataFlagListener {}
- iconFactory = BubbleIconFactory(context,
+ iconFactory =
+ BubbleIconFactory(
+ context,
60,
30,
Color.RED,
- mContext.resources.getDimensionPixelSize(
- R.dimen.importance_ring_stroke_width))
+ mContext.resources.getDimensionPixelSize(R.dimen.importance_ring_stroke_width)
+ )
mainExecutor = TestShellExecutor()
val windowManager = context.getSystemService(WindowManager::class.java)
val shellInit = ShellInit(mainExecutor)
val shellCommandHandler = ShellCommandHandler()
- val shellController = ShellController(context, shellInit, shellCommandHandler,
- mainExecutor)
+ val shellController = ShellController(context, shellInit, shellCommandHandler, mainExecutor)
val bubblePositioner = BubblePositioner(context, windowManager)
- val bubbleData = BubbleData(context, mock<BubbleLogger>(), bubblePositioner,
- BubbleEducationController(context), mainExecutor)
+ val bubbleData =
+ BubbleData(
+ context,
+ mock<BubbleLogger>(),
+ bubblePositioner,
+ BubbleEducationController(context),
+ mainExecutor
+ )
val surfaceSynchronizer = { obj: Runnable -> obj.run() }
- bubbleController = BubbleController(
+ bubbleController =
+ BubbleController(
context,
shellInit,
shellCommandHandler,
@@ -122,18 +128,36 @@
mock<Transitions>(),
mock<SyncTransactionQueue>(),
mock<IWindowManager>(),
- mock<BubbleProperties>())
+ mock<BubbleProperties>()
+ )
- bubbleStackView = BubbleStackView(context, bubbleController, bubbleData,
- surfaceSynchronizer, FloatingContentCoordinator(), bubbleController, mainExecutor)
- bubbleBarLayerView = BubbleBarLayerView(context, bubbleController)
+ val bubbleStackViewManager = BubbleStackViewManager.fromBubbleController(bubbleController)
+ bubbleStackView =
+ BubbleStackView(
+ context,
+ bubbleStackViewManager,
+ bubblePositioner,
+ bubbleData,
+ surfaceSynchronizer,
+ FloatingContentCoordinator(),
+ bubbleController,
+ mainExecutor
+ )
+ bubbleBarLayerView = BubbleBarLayerView(context, bubbleController, bubbleData)
}
@Test
fun testPopulate() {
bubble = createBubbleWithShortcut()
- val info = BubbleViewInfoTask.BubbleViewInfo.populate(context,
- bubbleController, bubbleStackView, iconFactory, bubble, false /* skipInflation */)
+ val info =
+ BubbleViewInfoTask.BubbleViewInfo.populate(
+ context,
+ bubbleController,
+ bubbleStackView,
+ iconFactory,
+ bubble,
+ false /* skipInflation */
+ )
assertThat(info!!).isNotNull()
assertThat(info.imageView).isNotNull()
@@ -151,9 +175,15 @@
@Test
fun testPopulateForBubbleBar() {
bubble = createBubbleWithShortcut()
- val info = BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(context,
- bubbleController, bubbleBarLayerView, iconFactory, bubble,
- false /* skipInflation */)
+ val info =
+ BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(
+ context,
+ bubbleController,
+ bubbleBarLayerView,
+ iconFactory,
+ bubble,
+ false /* skipInflation */
+ )
assertThat(info!!).isNotNull()
assertThat(info.imageView).isNull()
@@ -176,12 +206,18 @@
// exception here if the app has an issue loading the shortcut icon; we default to
// the app icon in that case / none of the icons will be null.
val mockIconFactory = mock<BubbleIconFactory>()
- whenever(mockIconFactory.getBubbleDrawable(eq(context), eq(bubble.shortcutInfo),
- any())).doThrow(RuntimeException())
+ whenever(mockIconFactory.getBubbleDrawable(eq(context), eq(bubble.shortcutInfo), any()))
+ .doThrow(RuntimeException())
- val info = BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(context,
- bubbleController, bubbleBarLayerView, iconFactory, bubble,
- true /* skipInflation */)
+ val info =
+ BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(
+ context,
+ bubbleController,
+ bubbleBarLayerView,
+ iconFactory,
+ bubble,
+ true /* skipInflation */
+ )
assertThat(info).isNotNull()
assertThat(info?.shortcutInfo).isNotNull()
@@ -194,8 +230,17 @@
private fun createBubbleWithShortcut(): Bubble {
val shortcutInfo = ShortcutInfo.Builder(mContext, "mockShortcutId").build()
- return Bubble("mockKey", shortcutInfo, 1000, Resources.ID_NULL,
- "mockTitle", 0 /* taskId */, "mockLocus", true /* isDismissible */,
- mainExecutor, metadataFlagListener)
+ return Bubble(
+ "mockKey",
+ shortcutInfo,
+ 1000,
+ Resources.ID_NULL,
+ "mockTitle",
+ 0 /* taskId */,
+ "mockLocus",
+ true /* isDismissible */,
+ mainExecutor,
+ metadataFlagListener
+ )
}
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index 3fe78ef..445f74a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -124,7 +124,7 @@
repo.addVisibleTasksListener(listener, executor)
executor.flushAll()
- assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue()
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(1)
assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
}
@@ -148,7 +148,7 @@
repo.addVisibleTasksListener(listener, executor)
executor.flushAll()
- assertThat(listener.hasVisibleTasksOnDefaultDisplay).isFalse()
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(0)
// One call as adding listener notifies it
assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(0)
}
@@ -162,8 +162,8 @@
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true)
executor.flushAll()
- assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue()
- assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(2)
+ assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(2)
}
@Test
@@ -175,16 +175,16 @@
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
executor.flushAll()
- assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue()
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(1)
assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
- assertThat(listener.hasVisibleTasksOnSecondaryDisplay).isFalse()
+ assertThat(listener.visibleTasksCountOnSecondaryDisplay).isEqualTo(0)
assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(0)
repo.updateVisibleFreeformTasks(displayId = 1, taskId = 2, visible = true)
executor.flushAll()
// Listener for secondary display is notified
- assertThat(listener.hasVisibleTasksOnSecondaryDisplay).isTrue()
+ assertThat(listener.visibleTasksCountOnSecondaryDisplay).isEqualTo(1)
assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(1)
// No changes to listener for default display
assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
@@ -198,7 +198,7 @@
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
executor.flushAll()
- assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue()
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(1)
// Mark task 1 visible on secondary display
repo.updateVisibleFreeformTasks(displayId = 1, taskId = 1, visible = true)
@@ -208,11 +208,11 @@
// 1 - visible task added
// 2 - visible task removed
assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(2)
- assertThat(listener.hasVisibleTasksOnDefaultDisplay).isFalse()
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(0)
// Secondary display should have 1 call for visible task added
assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(1)
- assertThat(listener.hasVisibleTasksOnSecondaryDisplay).isTrue()
+ assertThat(listener.visibleTasksCountOnSecondaryDisplay).isEqualTo(1)
}
@Test
@@ -224,17 +224,17 @@
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true)
executor.flushAll()
- assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue()
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(2)
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false)
executor.flushAll()
- assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
+ assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(3)
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = false)
executor.flushAll()
- assertThat(listener.hasVisibleTasksOnDefaultDisplay).isFalse()
- assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(2)
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(0)
+ assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(4)
}
@Test
@@ -397,8 +397,8 @@
}
class TestVisibilityListener : DesktopModeTaskRepository.VisibleTasksListener {
- var hasVisibleTasksOnDefaultDisplay = false
- var hasVisibleTasksOnSecondaryDisplay = false
+ var visibleTasksCountOnDefaultDisplay = 0
+ var visibleTasksCountOnSecondaryDisplay = 0
var visibleChangesOnDefaultDisplay = 0
var visibleChangesOnSecondaryDisplay = 0
@@ -409,14 +409,14 @@
var stashedChangesOnDefaultDisplay = 0
var stashedChangesOnSecondaryDisplay = 0
- override fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {
+ override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {
when (displayId) {
DEFAULT_DISPLAY -> {
- hasVisibleTasksOnDefaultDisplay = hasVisibleFreeformTasks
+ visibleTasksCountOnDefaultDisplay = visibleTasksCount
visibleChangesOnDefaultDisplay++
}
SECOND_DISPLAY -> {
- hasVisibleTasksOnSecondaryDisplay = hasVisibleFreeformTasks
+ visibleTasksCountOnSecondaryDisplay = visibleTasksCount
visibleChangesOnSecondaryDisplay++
}
else -> fail("Visible task listener received unexpected display id: $displayId")
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 008ea3a..14b8d8d 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -341,6 +341,10 @@
mCanvas->concat(matrix);
}
+void SkiaCanvas::concat(const SkM44& matrix) {
+ mCanvas->concat(matrix);
+}
+
void SkiaCanvas::rotate(float degrees) {
mCanvas->rotate(degrees);
}
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 4bf1790..5e3553b 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -86,6 +86,7 @@
virtual void getMatrix(SkMatrix* outMatrix) const override;
virtual void setMatrix(const SkMatrix& matrix) override;
virtual void concat(const SkMatrix& matrix) override;
+ virtual void concat(const SkM44& matrix) override;
virtual void rotate(float degrees) override;
virtual void scale(float sx, float sy) override;
virtual void skew(float sx, float sy) override;
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 9ec023b..20e3ad2 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -175,6 +175,7 @@
virtual void setMatrix(const SkMatrix& matrix) = 0;
virtual void concat(const SkMatrix& matrix) = 0;
+ virtual void concat(const SkM44& matrix) = 0;
virtual void rotate(float degrees) = 0;
virtual void scale(float sx, float sy) = 0;
virtual void skew(float sx, float sy) = 0;
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index d572593..295f4dc 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -158,6 +158,13 @@
get_canvas(canvasHandle)->concat(*matrix);
}
+static void concat44(JNIEnv* env, jobject obj, jlong canvasHandle, jfloatArray arr) {
+ jfloat* matVals = env->GetFloatArrayElements(arr, 0);
+ const SkM44 matrix = SkM44::RowMajor(matVals);
+ get_canvas(canvasHandle)->concat(matrix);
+ env->ReleaseFloatArrayElements(arr, matVals, 0);
+}
+
static void rotate(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, jfloat degrees) {
get_canvas(canvasHandle)->rotate(degrees);
}
@@ -781,6 +788,7 @@
{"nGetMatrix", "(JJ)V", (void*)CanvasJNI::getMatrix},
{"nSetMatrix","(JJ)V", (void*) CanvasJNI::setMatrix},
{"nConcat","(JJ)V", (void*) CanvasJNI::concat},
+ {"nConcat","(J[F)V", (void*) CanvasJNI::concat44},
{"nRotate","(JF)V", (void*) CanvasJNI::rotate},
{"nScale","(JFF)V", (void*) CanvasJNI::scale},
{"nSkew","(JFF)V", (void*) CanvasJNI::skew},
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index d55d28d..b5f7caa 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -31,6 +31,8 @@
#include <vk/GrVkExtensions.h>
#include <vk/GrVkTypes.h>
+#include <sstream>
+
#include "Properties.h"
#include "RenderThread.h"
#include "pipeline/skia/ShaderCache.h"
@@ -40,7 +42,8 @@
namespace uirenderer {
namespace renderthread {
-static std::array<std::string_view, 20> sEnableExtensions{
+// Not all of these are strictly required, but are all enabled if present.
+static std::array<std::string_view, 21> sEnableExtensions{
VK_KHR_BIND_MEMORY_2_EXTENSION_NAME,
VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,
VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME,
@@ -61,6 +64,7 @@
VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME,
VK_KHR_ANDROID_SURFACE_EXTENSION_NAME,
VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME,
+ VK_EXT_DEVICE_FAULT_EXTENSION_NAME,
};
static bool shouldEnableExtension(const std::string_view& extension) {
@@ -303,6 +307,15 @@
*tailPNext = ycbcrFeature;
tailPNext = &ycbcrFeature->pNext;
+ if (grExtensions.hasExtension(VK_EXT_DEVICE_FAULT_EXTENSION_NAME, 1)) {
+ VkPhysicalDeviceFaultFeaturesEXT* deviceFaultFeatures =
+ new VkPhysicalDeviceFaultFeaturesEXT;
+ deviceFaultFeatures->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FAULT_FEATURES_EXT;
+ deviceFaultFeatures->pNext = nullptr;
+ *tailPNext = deviceFaultFeatures;
+ tailPNext = &deviceFaultFeatures->pNext;
+ }
+
// query to get the physical device features
mGetPhysicalDeviceFeatures2(mPhysicalDevice, &features);
// this looks like it would slow things down,
@@ -405,6 +418,79 @@
});
}
+namespace {
+void onVkDeviceFault(const std::string& contextLabel, const std::string& description,
+ const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos,
+ const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
+ const std::vector<std::byte>& vendorBinaryData) {
+ // The final crash string should contain as much differentiating info as possible, up to 1024
+ // bytes. As this final message is constructed, the same information is also dumped to the logs
+ // but in a more verbose format. Building the crash string is unsightly, so the clearer logging
+ // statement is always placed first to give context.
+ ALOGE("VK_ERROR_DEVICE_LOST (%s context): %s", contextLabel.c_str(), description.c_str());
+ std::stringstream crashMsg;
+ crashMsg << "VK_ERROR_DEVICE_LOST (" << contextLabel;
+
+ if (!addressInfos.empty()) {
+ ALOGE("%zu VkDeviceFaultAddressInfoEXT:", addressInfos.size());
+ crashMsg << ", " << addressInfos.size() << " address info (";
+ for (VkDeviceFaultAddressInfoEXT addressInfo : addressInfos) {
+ ALOGE(" addressType: %d", (int)addressInfo.addressType);
+ ALOGE(" reportedAddress: %" PRIu64, addressInfo.reportedAddress);
+ ALOGE(" addressPrecision: %" PRIu64, addressInfo.addressPrecision);
+ crashMsg << addressInfo.addressType << ":"
+ << addressInfo.reportedAddress << ":"
+ << addressInfo.addressPrecision << ", ";
+ }
+ crashMsg.seekp(-2, crashMsg.cur); // Move back to overwrite trailing ", "
+ crashMsg << ")";
+ }
+
+ if (!vendorInfos.empty()) {
+ ALOGE("%zu VkDeviceFaultVendorInfoEXT:", vendorInfos.size());
+ crashMsg << ", " << vendorInfos.size() << " vendor info (";
+ for (VkDeviceFaultVendorInfoEXT vendorInfo : vendorInfos) {
+ ALOGE(" description: %s", vendorInfo.description);
+ ALOGE(" vendorFaultCode: %" PRIu64, vendorInfo.vendorFaultCode);
+ ALOGE(" vendorFaultData: %" PRIu64, vendorInfo.vendorFaultData);
+ // Omit descriptions for individual vendor info structs in the crash string, as the
+ // fault code and fault data fields should be enough for clustering, and the verbosity
+ // isn't worth it. Additionally, vendors may just set the general description field of
+ // the overall fault to the description of the first element in this list, and that
+ // overall description will be placed at the end of the crash string.
+ crashMsg << vendorInfo.vendorFaultCode << ":"
+ << vendorInfo.vendorFaultData << ", ";
+ }
+ crashMsg.seekp(-2, crashMsg.cur); // Move back to overwrite trailing ", "
+ crashMsg << ")";
+ }
+
+ if (!vendorBinaryData.empty()) {
+ // TODO: b/322830575 - Log in base64, or dump directly to a file that gets put in bugreports
+ ALOGE("%zu bytes of vendor-specific binary data (please notify Android's Core Graphics"
+ " Stack team if you observe this message).",
+ vendorBinaryData.size());
+ crashMsg << ", " << vendorBinaryData.size() << " bytes binary";
+ }
+
+ crashMsg << "): " << description;
+ LOG_ALWAYS_FATAL("%s", crashMsg.str().c_str());
+}
+
+void deviceLostProcRenderThread(void* callbackContext, const std::string& description,
+ const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos,
+ const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
+ const std::vector<std::byte>& vendorBinaryData) {
+ onVkDeviceFault("RenderThread", description, addressInfos, vendorInfos, vendorBinaryData);
+}
+void deviceLostProcUploadThread(void* callbackContext, const std::string& description,
+ const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos,
+ const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
+ const std::vector<std::byte>& vendorBinaryData) {
+ onVkDeviceFault("UploadThread", description, addressInfos, vendorInfos, vendorBinaryData);
+}
+} // anonymous namespace
+
static void onGrContextReleased(void* context) {
VulkanManager* manager = (VulkanManager*)context;
manager->decStrong((void*)onGrContextReleased);
@@ -430,6 +516,10 @@
backendContext.fVkExtensions = &mExtensions;
backendContext.fDeviceFeatures2 = &mPhysicalDeviceFeatures2;
backendContext.fGetProc = std::move(getProc);
+ backendContext.fDeviceLostContext = nullptr;
+ backendContext.fDeviceLostProc = (contextType == ContextType::kRenderThread)
+ ? deviceLostProcRenderThread
+ : deviceLostProcUploadThread;
LOG_ALWAYS_FATAL_IF(options.fContextDeleteProc != nullptr, "Conflicting fContextDeleteProcs!");
this->incStrong((void*)onGrContextReleased);
diff --git a/location/java/android/location/altitude/AltitudeConverter.java b/location/java/android/location/altitude/AltitudeConverter.java
index 461dafb..ec1edb8 100644
--- a/location/java/android/location/altitude/AltitudeConverter.java
+++ b/location/java/android/location/altitude/AltitudeConverter.java
@@ -219,6 +219,27 @@
* called on the main thread as data will not be loaded from raw assets. Returns true if a Mean
* Sea Level altitude is added to the {@code location}; otherwise, returns false and leaves the
* {@code location} unchanged.
+ *
+ * <p>Prior calls to {@link #addMslAltitudeToLocation(Context, Location)} off the main thread
+ * are necessary to load data from raw assets. Example code on the main thread is as follows:
+ *
+ * <pre>{@code
+ * if (!mAltitudeConverter.addMslAltitudeToLocation(location)) {
+ * // Queue up only one call off the main thread.
+ * if (mIsAltitudeConverterIdle) {
+ * mIsAltitudeConverterIdle = false;
+ * executeOffMainThread(() -> {
+ * try {
+ * // Load raw assets for next call attempt on main thread.
+ * mAltitudeConverter.addMslAltitudeToLocation(mContext, location);
+ * } catch (IOException e) {
+ * Log.e(TAG, "Not loading raw assets: " + e);
+ * }
+ * mIsAltitudeConverterIdle = true;
+ * });
+ * }
+ * }
+ * }</pre>
*/
@FlaggedApi(Flags.FLAG_GEOID_HEIGHTS_VIA_ALTITUDE_HAL)
public boolean addMslAltitudeToLocation(@NonNull Location location) {
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 4918289..69708ec 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -23,6 +23,7 @@
import static android.media.audio.Flags.automaticBtDeviceType;
import static android.media.audio.Flags.FLAG_FOCUS_EXCLUSIVE_WITH_RECORDING;
import static android.media.audio.Flags.FLAG_FOCUS_FREEZE_TEST_API;
+import static android.media.audio.Flags.FLAG_SUPPORTED_DEVICE_TYPES_API;
import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
import android.Manifest;
@@ -80,6 +81,7 @@
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.IntArray;
import android.util.Log;
import android.util.Pair;
import android.view.KeyEvent;
@@ -100,6 +102,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
@@ -7838,6 +7841,51 @@
}
/**
+ * Returns a Set of unique Integers corresponding to audio device type identifiers that can
+ * <i>potentially</i> be connected to the system and meeting the criteria specified in the
+ * <code>direction</code> parameter.
+ * Note that this set contains {@link AudioDeviceInfo} device type identifiers for both devices
+ * currently available <i>and</i> those that can be available if the user connects an audio
+ * peripheral. Examples include TYPE_WIRED_HEADSET if the Android device supports an analog
+ * headset jack or TYPE_USB_DEVICE if the Android device supports a USB host-mode port.
+ * These are generally a superset of device type identifiers associated with the
+ * AudioDeviceInfo objects returned from AudioManager.getDevices().
+ * @param direction The constant specifying whether input or output devices are queried.
+ * @see #GET_DEVICES_OUTPUTS
+ * @see #GET_DEVICES_INPUTS
+ * @return A (possibly zero-length) Set of Integer objects corresponding to the audio
+ * device types of devices supported by the implementation.
+ * @throws IllegalArgumentException If an invalid direction constant is specified.
+ */
+ @FlaggedApi(FLAG_SUPPORTED_DEVICE_TYPES_API)
+ public @NonNull Set<Integer>
+ getSupportedDeviceTypes(int direction) {
+ if (direction != GET_DEVICES_OUTPUTS && direction != GET_DEVICES_INPUTS) {
+ throw new IllegalArgumentException("AudioManager.getSupportedDeviceTypes("
+ + Integer.toHexString(direction) + ") - Invalid.");
+ }
+
+ IntArray internalDeviceTypes = new IntArray();
+ int status = AudioSystem.getSupportedDeviceTypes(direction, internalDeviceTypes);
+ if (status != AudioManager.SUCCESS) {
+ Log.e(TAG, "AudioManager.getSupportedDeviceTypes(" + direction + ") failed. status:"
+ + status);
+ }
+
+ // convert to external (AudioDeviceInfo.getType()) device IDs
+ HashSet<Integer> externalDeviceTypes = new HashSet<Integer>();
+ for (int index = 0; index < internalDeviceTypes.size(); index++) {
+ // Set will eliminate any duplicates which AudioSystem.getSupportedDeviceTypes()
+ // returns
+ externalDeviceTypes.add(
+ AudioDeviceInfo.convertInternalDeviceToDeviceType(
+ internalDeviceTypes.get(index)));
+ }
+
+ return externalDeviceTypes;
+ }
+
+ /**
* Returns an array of {@link AudioDeviceInfo} objects corresponding to the audio devices
* currently connected to the system and meeting the criteria specified in the
* <code>flags</code> parameter.
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 0f6cbff..f73be35f 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -36,6 +36,7 @@
import android.os.Parcel;
import android.os.Vibrator;
import android.telephony.TelephonyManager;
+import android.util.IntArray;
import android.util.Log;
import android.util.Pair;
@@ -1947,6 +1948,8 @@
/** @hide */
public static native int listAudioPorts(ArrayList<AudioPort> ports, int[] generation);
/** @hide */
+ public static native int getSupportedDeviceTypes(int flags, IntArray internalDeviceTypes);
+ /** @hide */
public static native int createAudioPatch(AudioPatch[] patch,
AudioPortConfig[] sources, AudioPortConfig[] sinks);
/** @hide */
diff --git a/media/java/android/media/metrics/EditingEndedEvent.java b/media/java/android/media/metrics/EditingEndedEvent.java
index 72e6db8..5ed8d40 100644
--- a/media/java/android/media/metrics/EditingEndedEvent.java
+++ b/media/java/android/media/metrics/EditingEndedEvent.java
@@ -86,7 +86,10 @@
*/
public static final int ERROR_CODE_IO_NO_PERMISSION = 8;
- /** */
+ /**
+ * Caused by failing to load data via cleartext HTTP, when the app's network security
+ * configuration does not permit it.
+ */
public static final int ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED = 9;
/** Caused by reading data out of the data bounds. */
@@ -146,6 +149,9 @@
@Retention(java.lang.annotation.RetentionPolicy.SOURCE)
public @interface ErrorCode {}
+ /** Special value for unknown {@linkplain #getTimeSinceCreatedMillis() time since creation}. */
+ public static final int TIME_SINCE_CREATED_UNKNOWN = -1;
+
private final @ErrorCode int mErrorCode;
@SuppressWarnings("HidingField") // Hiding field from superclass as for playback events.
private final long mTimeSinceCreatedMillis;
@@ -174,16 +180,16 @@
}
/**
- * Gets the elapsed time since creating of the editing session, in milliseconds, or -1 if
- * unknown.
+ * Gets the elapsed time since creating of the editing session, in milliseconds, or {@link
+ * #TIME_SINCE_CREATED_UNKNOWN} if unknown.
*
- * @return The elapsed time since creating the editing session, in milliseconds, or -1 if
- * unknown.
+ * @return The elapsed time since creating the editing session, in milliseconds, or {@link
+ * #TIME_SINCE_CREATED_UNKNOWN} if unknown.
* @see LogSessionId
* @see EditingSession
*/
@Override
- @IntRange(from = -1)
+ @IntRange(from = TIME_SINCE_CREATED_UNKNOWN)
public long getTimeSinceCreatedMillis() {
return mTimeSinceCreatedMillis;
}
@@ -283,7 +289,7 @@
public Builder(@FinalState int finalState) {
mFinalState = finalState;
mErrorCode = ERROR_CODE_NONE;
- mTimeSinceCreatedMillis = -1;
+ mTimeSinceCreatedMillis = TIME_SINCE_CREATED_UNKNOWN;
mMetricsBundle = new Bundle();
}
@@ -291,11 +297,11 @@
* Sets the elapsed time since creating the editing session, in milliseconds.
*
* @param timeSinceCreatedMillis The elapsed time since creating the editing session, in
- * milliseconds, or -1 if the value is unknown.
+ * milliseconds, or {@link #TIME_SINCE_CREATED_UNKNOWN} if unknown.
* @see #getTimeSinceCreatedMillis()
*/
public @NonNull Builder setTimeSinceCreatedMillis(
- @IntRange(from = -1) long timeSinceCreatedMillis) {
+ @IntRange(from = TIME_SINCE_CREATED_UNKNOWN) long timeSinceCreatedMillis) {
mTimeSinceCreatedMillis = timeSinceCreatedMillis;
return this;
}
diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl
index 8978277..b644621 100644
--- a/media/java/android/media/tv/ITvInputClient.aidl
+++ b/media/java/android/media/tv/ITvInputClient.aidl
@@ -69,4 +69,6 @@
// For ad response
void onAdResponse(in AdResponse response, int seq);
void onAdBufferConsumed(in AdBuffer buffer, int seq);
+
+ void onTvInputSessionData(in String type, in Bundle data, int seq);
}
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index 2f6575e..84c197d 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -152,4 +152,6 @@
// For freezing video playback
void setVideoFrozen(in IBinder sessionToken, boolean isFrozen, int userId);
+
+ void notifyTvAdSessionData(in IBinder sessionToken, in String type, in Bundle data, int userId);
}
diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl
index a93f18d..7b20c39 100644
--- a/media/java/android/media/tv/ITvInputSession.aidl
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -86,4 +86,6 @@
// For freezing video
void setVideoFrozen(boolean isFrozen);
+
+ void notifyTvAdSessionData(in String type, in Bundle data);
}
diff --git a/media/java/android/media/tv/ITvInputSessionCallback.aidl b/media/java/android/media/tv/ITvInputSessionCallback.aidl
index 8e2702a..76e079f 100644
--- a/media/java/android/media/tv/ITvInputSessionCallback.aidl
+++ b/media/java/android/media/tv/ITvInputSessionCallback.aidl
@@ -68,4 +68,6 @@
// For messages sent from the TV input
void onTvMessage(int type, in Bundle data);
+
+ void onTvInputSessionData(in String type, in Bundle data);
}
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index 921104d..999e2cf 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -82,6 +82,7 @@
private static final int DO_STOP_PLAYBACK = 33;
private static final int DO_START_PLAYBACK = 34;
private static final int DO_SET_VIDEO_FROZEN = 35;
+ private static final int DO_NOTIFY_AD_SESSION_DATA = 36;
private final boolean mIsRecordingSession;
private final HandlerCaller mCaller;
@@ -287,6 +288,7 @@
case DO_NOTIFY_TV_MESSAGE: {
SomeArgs args = (SomeArgs) msg.obj;
mTvInputSessionImpl.onTvMessageReceived((Integer) args.arg1, (Bundle) args.arg2);
+ args.recycle();
break;
}
case DO_STOP_PLAYBACK: {
@@ -301,6 +303,12 @@
mTvInputSessionImpl.setVideoFrozen((Boolean) msg.obj);
break;
}
+ case DO_NOTIFY_AD_SESSION_DATA: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ mTvInputSessionImpl.notifyTvAdSessionData((String) args.arg1, (Bundle) args.arg2);
+ args.recycle();
+ break;
+ }
default: {
Log.w(TAG, "Unhandled message code: " + msg.what);
break;
@@ -488,6 +496,12 @@
}
@Override
+ public void notifyTvAdSessionData(String type, Bundle data) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageOO(DO_NOTIFY_AD_SESSION_DATA, type, data));
+ }
+
+ @Override
public void setVideoFrozen(boolean isFrozen) {
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_VIDEO_FROZEN, isFrozen));
}
diff --git a/media/java/android/media/tv/SignalingDataInfo.aidl b/media/java/android/media/tv/SignalingDataInfo.aidl
new file mode 100644
index 0000000..7108f36
--- /dev/null
+++ b/media/java/android/media/tv/SignalingDataInfo.aidl
@@ -0,0 +1,3 @@
+package android.media.tv;
+
+parcelable SignalingDataInfo;
diff --git a/media/java/android/media/tv/SignalingDataInfo.java b/media/java/android/media/tv/SignalingDataInfo.java
new file mode 100644
index 0000000..b29ea5c
--- /dev/null
+++ b/media/java/android/media/tv/SignalingDataInfo.java
@@ -0,0 +1,117 @@
+/*
+ * 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.NonNull;
+import android.os.Parcelable;
+
+/** @hide */
+public class SignalingDataInfo implements Parcelable {
+ public static final @NonNull Parcelable.Creator<SignalingDataInfo> CREATOR =
+ new Parcelable.Creator<SignalingDataInfo>() {
+ @Override
+ public SignalingDataInfo[] newArray(int size) {
+ return new SignalingDataInfo[size];
+ }
+
+ @Override
+ public SignalingDataInfo createFromParcel(@NonNull android.os.Parcel in) {
+ return new SignalingDataInfo(in);
+ }
+ };
+
+ private int mTableId;
+ private @NonNull String mTable;
+ private int mMetadataType;
+ private int mVersion;
+ private int mGroup;
+ private @NonNull String mEncoding;
+
+ public SignalingDataInfo(
+ int tableId,
+ @NonNull String table,
+ int metadataType,
+ int version,
+ int group,
+ @NonNull String encoding) {
+ this.mTableId = tableId;
+ this.mTable = table;
+ com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, mTable);
+ this.mMetadataType = metadataType;
+ this.mVersion = version;
+ this.mGroup = group;
+ this.mEncoding = encoding;
+ com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, mEncoding);
+ }
+
+ public int getTableId() {
+ return mTableId;
+ }
+
+ public @NonNull String getTable() {
+ return mTable;
+ }
+
+ public int getMetadataType() {
+ return mMetadataType;
+ }
+
+ public int getVersion() {
+ return mVersion;
+ }
+
+ public int getGroup() {
+ return mGroup;
+ }
+
+ public @NonNull String getEncoding() {
+ return mEncoding;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ dest.writeInt(mTableId);
+ dest.writeString(mTable);
+ dest.writeInt(mMetadataType);
+ dest.writeInt(mVersion);
+ dest.writeInt(mGroup);
+ dest.writeString(mEncoding);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ SignalingDataInfo(@NonNull android.os.Parcel in) {
+ int tableId = in.readInt();
+ String table = in.readString();
+ int metadataType = in.readInt();
+ int version = in.readInt();
+ int group = in.readInt();
+ String encoding = in.readString();
+
+ this.mTableId = tableId;
+ this.mTable = table;
+ com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, mTable);
+ this.mMetadataType = metadataType;
+ this.mVersion = version;
+ this.mGroup = group;
+ this.mEncoding = encoding;
+ com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, mEncoding);
+ }
+}
diff --git a/media/java/android/media/tv/SignalingDataResponse.aidl b/media/java/android/media/tv/SignalingDataResponse.aidl
new file mode 100644
index 0000000..a548e4e
--- /dev/null
+++ b/media/java/android/media/tv/SignalingDataResponse.aidl
@@ -0,0 +1,3 @@
+package android.media.tv;
+
+parcelable SignalingDataResponse;
diff --git a/media/java/android/media/tv/SignalingDataResponse.java b/media/java/android/media/tv/SignalingDataResponse.java
new file mode 100644
index 0000000..3e4c790
--- /dev/null
+++ b/media/java/android/media/tv/SignalingDataResponse.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.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcelable;
+
+import java.util.List;
+
+/** @hide */
+public class SignalingDataResponse extends BroadcastInfoResponse implements Parcelable {
+ public static final @NonNull Parcelable.Creator<SignalingDataResponse> CREATOR =
+ new Parcelable.Creator<SignalingDataResponse>() {
+ @Override
+ public SignalingDataResponse[] newArray(int size) {
+ return new SignalingDataResponse[size];
+ }
+
+ @Override
+ public SignalingDataResponse createFromParcel(@NonNull android.os.Parcel in) {
+ return new SignalingDataResponse(in);
+ }
+ };
+ private static final @TvInputManager.BroadcastInfoType int RESPONSE_TYPE =
+ TvInputManager.BROADCAST_INFO_TYPE_SIGNALING_DATA;
+ private final @NonNull int[] mTableIds;
+ private final int mMetadataTypes;
+ private final @NonNull List<SignalingDataInfo> mSignalingDataInfoList;
+
+ public SignalingDataResponse(
+ int requestId,
+ int sequence,
+ @ResponseResult int responseResult,
+ @NonNull int[] tableIds,
+ int metadataTypes,
+ @NonNull List<SignalingDataInfo> signalingDataInfoList) {
+ super(RESPONSE_TYPE, requestId, sequence, responseResult);
+ this.mTableIds = tableIds;
+ com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, mTableIds);
+ this.mMetadataTypes = metadataTypes;
+ this.mSignalingDataInfoList = signalingDataInfoList;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mSignalingDataInfoList);
+ }
+
+ public @NonNull int[] getTableIds() {
+ return mTableIds;
+ }
+
+ public int getMetadataTypes() {
+ return mMetadataTypes;
+ }
+
+ public @NonNull List<SignalingDataInfo> getSignalingDataInfoList() {
+ return mSignalingDataInfoList;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeIntArray(mTableIds);
+ dest.writeInt(mMetadataTypes);
+ dest.writeParcelableList(mSignalingDataInfoList, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ SignalingDataResponse(@NonNull android.os.Parcel in) {
+ super(RESPONSE_TYPE, in);
+
+ int[] tableIds = in.createIntArray();
+ int metadataTypes = in.readInt();
+ List<SignalingDataInfo> signalingDataInfoList = new java.util.ArrayList<>();
+ in.readParcelableList(signalingDataInfoList, SignalingDataInfo.class.getClassLoader());
+
+ this.mTableIds = tableIds;
+ com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, mTableIds);
+ this.mMetadataTypes = metadataTypes;
+ this.mSignalingDataInfoList = signalingDataInfoList;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mSignalingDataInfoList);
+ }
+}
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index be1b675..8720bfe 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -22,6 +22,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.StringDef;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
@@ -535,6 +536,220 @@
*/
public static final int SIGNAL_STRENGTH_STRONG = 3;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef(prefix = "SESSION_DATA_TYPE_", value = {
+ SESSION_DATA_TYPE_TUNED,
+ SESSION_DATA_TYPE_TRACK_SELECTED,
+ SESSION_DATA_TYPE_TRACKS_CHANGED,
+ SESSION_DATA_TYPE_VIDEO_AVAILABLE,
+ SESSION_DATA_TYPE_VIDEO_UNAVAILABLE,
+ SESSION_DATA_TYPE_BROADCAST_INFO_RESPONSE,
+ SESSION_DATA_TYPE_AD_RESPONSE,
+ SESSION_DATA_TYPE_AD_BUFFER_CONSUMED,
+ SESSION_DATA_TYPE_TV_MESSAGE})
+ public @interface SessionDataType {}
+
+ /**
+ * Informs the application that the session has been tuned to the given channel.
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_CHANNEL_URI
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_TUNED = "tuned";
+
+ /**
+ * Sends the type and ID of a selected track. This is used to inform the application that a
+ * specific track is selected.
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_TRACK_TYPE
+ * @see SESSION_DATA_KEY_TRACK_ID
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_TRACK_SELECTED = "track_selected";
+
+ /**
+ * Sends the list of all audio/video/subtitle tracks.
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_TRACKS
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_TRACKS_CHANGED = "tracks_changed";
+
+ /**
+ * Informs the application that the video is now available for watching.
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_VIDEO_AVAILABLE = "video_available";
+
+ /**
+ * Informs the application that the video became unavailable for some reason.
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_VIDEO_UNAVAILABLE_REASON
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_VIDEO_UNAVAILABLE = "video_unavailable";
+
+ /**
+ * Notifies response for broadcast info.
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_BROADCAST_INFO_RESPONSE
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_BROADCAST_INFO_RESPONSE =
+ "broadcast_info_response";
+
+ /**
+ * Notifies response for advertisement.
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_AD_RESPONSE
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_AD_RESPONSE = "ad_response";
+
+ /**
+ * Notifies the advertisement buffer is consumed.
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_AD_BUFFER
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_AD_BUFFER_CONSUMED = "ad_buffer_consumed";
+
+ /**
+ * Sends the TV message.
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see TvInputService.Session#notifyTvMessage(int, Bundle)
+ * @see SESSION_DATA_KEY_TV_MESSAGE_TYPE
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_TV_MESSAGE = "tv_message";
+
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef(prefix = "SESSION_DATA_KEY_", value = {
+ SESSION_DATA_KEY_CHANNEL_URI,
+ SESSION_DATA_KEY_TRACK_TYPE,
+ SESSION_DATA_KEY_TRACK_ID,
+ SESSION_DATA_KEY_TRACKS,
+ SESSION_DATA_KEY_VIDEO_UNAVAILABLE_REASON,
+ SESSION_DATA_KEY_BROADCAST_INFO_RESPONSE,
+ SESSION_DATA_KEY_AD_RESPONSE,
+ SESSION_DATA_KEY_AD_BUFFER,
+ SESSION_DATA_KEY_TV_MESSAGE_TYPE})
+ public @interface SessionDataKey {}
+
+ /**
+ * The URI of a channel.
+ *
+ * <p> Type: android.net.Uri
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_CHANNEL_URI = "channel_uri";
+
+ /**
+ * The type of the track.
+ *
+ * <p>One of {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO},
+ * {@link TvTrackInfo#TYPE_SUBTITLE}.
+ *
+ * <p> Type: Integer
+ *
+ * @see TvTrackInfo#getType()
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_TRACK_TYPE = "track_type";
+
+ /**
+ * The ID of the track.
+ *
+ * <p> Type: String
+ *
+ * @see TvTrackInfo#getId()
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_TRACK_ID = "track_id";
+
+ /**
+ * A list which includes track information.
+ *
+ * <p> Type: {@code java.util.List<android.media.tv.TvTrackInfo> }
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_TRACKS = "tracks";
+
+ /**
+ * The reason why the video became unavailable.
+ * <p>The value can be {@link VIDEO_UNAVAILABLE_REASON_BUFFERING},
+ * {@link VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY}, etc.
+ *
+ * <p> Type: Integer
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_VIDEO_UNAVAILABLE_REASON =
+ "video_unavailable_reason";
+
+ /**
+ * An object of {@link BroadcastInfoResponse}.
+ *
+ * <p> Type: android.media.tv.BroadcastInfoResponse
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_BROADCAST_INFO_RESPONSE = "broadcast_info_response";
+
+ /**
+ * An object of {@link AdResponse}.
+ *
+ * <p> Type: android.media.tv.AdResponse
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_AD_RESPONSE = "ad_response";
+
+ /**
+ * An object of {@link AdBuffer}.
+ *
+ * <p> Type: android.media.tv.AdBuffer
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_AD_BUFFER = "ad_buffer";
+
+ /**
+ * The type of TV message.
+ * <p>It can be one of {@link TV_MESSAGE_TYPE_WATERMARK},
+ * {@link TV_MESSAGE_TYPE_CLOSED_CAPTION}, {@link TV_MESSAGE_TYPE_OTHER}
+ *
+ * <p> Type: Integer
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_TV_MESSAGE_TYPE = "tv_message_type";
+
+
/**
* An unknown state of the client pid gets from the TvInputManager. Client gets this value when
* query through {@link getClientPid(String sessionId)} fails.
@@ -1271,6 +1486,17 @@
});
}
}
+
+ void postTvInputSessionData(String type, Bundle data) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mSession.getAdSession() != null) {
+ mSession.getAdSession().notifyTvInputSessionData(type, data);
+ }
+ }
+ });
+ }
}
/**
@@ -1820,6 +2046,18 @@
record.postAdBufferConsumed(buffer);
}
}
+
+ @Override
+ public void onTvInputSessionData(String type, Bundle data, int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postTvInputSessionData(type, data);
+ }
+ }
};
ITvInputManagerCallback managerCallback = new ITvInputManagerCallback.Stub() {
@Override
@@ -3844,6 +4082,21 @@
}
}
+ /**
+ * Notifies data from session of linked TvAdService.
+ */
+ public void notifyTvAdSessionData(String type, Bundle data) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.notifyTvAdSessionData(mToken, type, data, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private final class InputEventHandler extends Handler {
public static final int MSG_SEND_INPUT_EVENT = 1;
public static final int MSG_TIMEOUT_INPUT_EVENT = 2;
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 6301a27..a022b1c 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -34,6 +34,7 @@
import android.hardware.hdmi.HdmiDeviceInfo;
import android.media.AudioPresentation;
import android.media.PlaybackParams;
+import android.media.tv.ad.TvAdManager;
import android.media.tv.interactive.TvInteractiveAppService;
import android.net.Uri;
import android.os.AsyncTask;
@@ -1259,6 +1260,37 @@
}
/**
+ * Notifies data related to this session to corresponding linked
+ * {@link android.media.tv.ad.TvAdService} object via TvAdView.
+ *
+ * <p>Methods like {@link #notifyBroadcastInfoResponse(BroadcastInfoResponse)} sends the
+ * related data to linked {@link android.media.tv.interactive.TvInteractiveAppService}, but
+ * don't work for {@link android.media.tv.ad.TvAdService}. The method is used specifically
+ * for {@link android.media.tv.ad.TvAdService} use cases.
+ *
+ * @param type data type
+ * @param data the related data values
+ * @hide
+ */
+ public void notifyTvInputSessionData(
+ @NonNull @TvInputManager.SessionDataType String type, @NonNull Bundle data) {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) Log.d(TAG, "notifyTvInputSessionData");
+ if (mSessionCallback != null) {
+ mSessionCallback.onTvInputSessionData(type, data);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in notifyTvInputSessionData", e);
+ }
+ }
+ });
+ }
+
+ /**
* Assigns a size and position to the surface passed in {@link #onSetSurface}. The position
* is relative to the overlay view that sits on top of this surface.
*
@@ -1401,6 +1433,20 @@
public void onAdBufferReady(@NonNull AdBuffer buffer) {
}
+
+ /**
+ * Called when data from the linked {@link android.media.tv.ad.TvAdService} is received.
+ *
+ * @param type the type of the data
+ * @param data a bundle contains the data received
+ * @see android.media.tv.ad.TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @see android.media.tv.ad.TvAdView#setTvView(TvView)
+ * @hide
+ */
+ public void onTvAdSessionData(
+ @NonNull @TvAdManager.SessionDataType String type, @NonNull Bundle data) {
+ }
+
/**
* Tunes to a given channel.
*
@@ -2166,6 +2212,10 @@
onAdBufferReady(buffer);
}
+ void notifyTvAdSessionData(String type, Bundle data) {
+ onTvAdSessionData(type, data);
+ }
+
void onTvMessageReceived(int type, Bundle data) {
onTvMessage(type, data);
}
diff --git a/media/java/android/media/tv/ad/ITvAdClient.aidl b/media/java/android/media/tv/ad/ITvAdClient.aidl
index 34d96b3..49046d0 100644
--- a/media/java/android/media/tv/ad/ITvAdClient.aidl
+++ b/media/java/android/media/tv/ad/ITvAdClient.aidl
@@ -16,6 +16,7 @@
package android.media.tv.ad;
+import android.os.Bundle;
import android.view.InputChannel;
/**
@@ -27,4 +28,11 @@
void onSessionCreated(in String serviceId, IBinder token, in InputChannel channel, int seq);
void onSessionReleased(int seq);
void onLayoutSurface(int left, int top, int right, int bottom, int seq);
+ void onRequestCurrentVideoBounds(int seq);
+ void onRequestCurrentChannelUri(int seq);
+ void onRequestTrackInfoList(int seq);
+ void onRequestCurrentTvInputId(int seq);
+ void onRequestSigning(
+ in String id, in String algorithm, in String alias, in byte[] data, int seq);
+ void onTvAdSessionData(in String type, in Bundle data, int seq);
}
\ No newline at end of file
diff --git a/media/java/android/media/tv/ad/ITvAdManager.aidl b/media/java/android/media/tv/ad/ITvAdManager.aidl
index 9620065..5c5f095 100644
--- a/media/java/android/media/tv/ad/ITvAdManager.aidl
+++ b/media/java/android/media/tv/ad/ITvAdManager.aidl
@@ -31,6 +31,7 @@
*/
interface ITvAdManager {
List<TvAdServiceInfo> getTvAdServiceList(int userId);
+ void sendAppLinkCommand(String serviceId, in Bundle command, int userId);
void createSession(
in ITvAdClient client, in String serviceId, in String type, int seq, int userId);
void releaseSession(in IBinder sessionToken, int userId);
@@ -58,4 +59,7 @@
int userId);
void relayoutMediaView(in IBinder sessionToken, in Rect frame, int userId);
void removeMediaView(in IBinder sessionToken, int userId);
+
+ void notifyTvInputSessionData(
+ in IBinder sessionToken, in String type, in Bundle data, int userId);
}
diff --git a/media/java/android/media/tv/ad/ITvAdSession.aidl b/media/java/android/media/tv/ad/ITvAdSession.aidl
index 69afb17..eba0bc8 100644
--- a/media/java/android/media/tv/ad/ITvAdSession.aidl
+++ b/media/java/android/media/tv/ad/ITvAdSession.aidl
@@ -46,4 +46,6 @@
void createMediaView(in IBinder windowToken, in Rect frame);
void relayoutMediaView(in Rect frame);
void removeMediaView();
+
+ void notifyTvInputSessionData(in String type, in Bundle data);
}
diff --git a/media/java/android/media/tv/ad/ITvAdSessionCallback.aidl b/media/java/android/media/tv/ad/ITvAdSessionCallback.aidl
index f21ef19..e4e5cbb 100644
--- a/media/java/android/media/tv/ad/ITvAdSessionCallback.aidl
+++ b/media/java/android/media/tv/ad/ITvAdSessionCallback.aidl
@@ -17,6 +17,7 @@
package android.media.tv.ad;
import android.media.tv.ad.ITvAdSession;
+import android.os.Bundle;
/**
* Helper interface for ITvAdSession to allow TvAdService to notify the system service when there is
@@ -26,4 +27,10 @@
oneway interface ITvAdSessionCallback {
void onSessionCreated(in ITvAdSession session);
void onLayoutSurface(int left, int top, int right, int bottom);
+ void onRequestCurrentVideoBounds();
+ void onRequestCurrentChannelUri();
+ void onRequestTrackInfoList();
+ void onRequestCurrentTvInputId();
+ void onRequestSigning(in String id, in String algorithm, in String alias, in byte[] data);
+ void onTvAdSessionData(in String type, in Bundle data);
}
\ No newline at end of file
diff --git a/media/java/android/media/tv/ad/ITvAdSessionWrapper.java b/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
index 251351d..e10a20e 100644
--- a/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
+++ b/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
@@ -65,6 +65,7 @@
private static final int DO_SEND_SIGNING_RESULT = 14;
private static final int DO_NOTIFY_ERROR = 15;
private static final int DO_NOTIFY_TV_MESSAGE = 16;
+ private static final int DO_NOTIFY_INPUT_SESSION_DATA = 17;
private final HandlerCaller mCaller;
private TvAdService.Session mSessionImpl;
@@ -180,6 +181,12 @@
args.recycle();
break;
}
+ case DO_NOTIFY_INPUT_SESSION_DATA: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ mSessionImpl.notifyTvInputSessionData((String) args.arg1, (Bundle) args.arg2);
+ args.recycle();
+ break;
+ }
default: {
Log.w(TAG, "Unhandled message code: " + msg.what);
break;
@@ -280,6 +287,12 @@
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_MEDIA_VIEW));
}
+ @Override
+ public void notifyTvInputSessionData(String type, Bundle data) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageOO(DO_NOTIFY_INPUT_SESSION_DATA, type, data));
+ }
+
private final class TvAdEventReceiver extends InputEventReceiver {
TvAdEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java
index 4dce72f..02c0d75 100644
--- a/media/java/android/media/tv/ad/TvAdManager.java
+++ b/media/java/android/media/tv/ad/TvAdManager.java
@@ -16,12 +16,15 @@
package android.media.tv.ad;
+import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.StringDef;
import android.annotation.SystemService;
import android.content.Context;
import android.graphics.Rect;
+import android.media.tv.AdBuffer;
import android.media.tv.TvInputManager;
import android.media.tv.TvTrackInfo;
import android.media.tv.flags.Flags;
@@ -43,7 +46,10 @@
import com.android.internal.util.Preconditions;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Executor;
@@ -57,6 +63,198 @@
// TODO: implement more methods and unhide APIs.
private static final String TAG = "TvAdManager";
+ /**
+ * Key for package name in app link.
+ * <p>Type: String
+ *
+ * @see #sendAppLinkCommand(String, Bundle)
+ * @hide
+ */
+ public static final String APP_LINK_KEY_PACKAGE_NAME = "package_name";
+
+ /**
+ * Key for class name in app link.
+ * <p>Type: String
+ *
+ * @see #sendAppLinkCommand(String, Bundle)
+ * @hide
+ */
+ public static final String APP_LINK_KEY_CLASS_NAME = "class_name";
+
+ /**
+ * Key for command type in app link command.
+ * <p>Type: String
+ *
+ * @see #sendAppLinkCommand(String, Bundle)
+ * @hide
+ */
+ public static final String APP_LINK_KEY_COMMAND_TYPE = "command_type";
+
+ /**
+ * Key for service ID in app link command.
+ * <p>Type: String
+ *
+ * @see #sendAppLinkCommand(String, Bundle)
+ * @hide
+ */
+ public static final String APP_LINK_KEY_SERVICE_ID = "service_id";
+
+ /**
+ * Key for back URI in app link command.
+ * <p>Type: String
+ *
+ * @see #sendAppLinkCommand(String, Bundle)
+ * @hide
+ */
+ public static final String APP_LINK_KEY_BACK_URI = "back_uri";
+
+ /**
+ * Broadcast intent action to send app command to TV app.
+ *
+ * @see #sendAppLinkCommand(String, Bundle)
+ * @hide
+ */
+ public static final String ACTION_APP_LINK_COMMAND =
+ "android.media.tv.ad.action.APP_LINK_COMMAND";
+
+ /**
+ * Intent key for TV input ID. It's used to send app command to TV app.
+ * <p>Type: String
+ *
+ * @see #sendAppLinkCommand(String, Bundle)
+ * @see #ACTION_APP_LINK_COMMAND
+ * @hide
+ */
+ public static final String INTENT_KEY_TV_INPUT_ID = "tv_input_id";
+
+ /**
+ * Intent key for TV AD service ID. It's used to send app command to TV app.
+ * <p>Type: String
+ *
+ * @see #sendAppLinkCommand(String, Bundle)
+ * @see #ACTION_APP_LINK_COMMAND
+ * @see TvAdServiceInfo#getId()
+ * @hide
+ */
+ public static final String INTENT_KEY_AD_SERVICE_ID = "ad_service_id";
+
+ /**
+ * Intent key for TV channel URI. It's used to send app command to TV app.
+ * <p>Type: android.net.Uri
+ *
+ * @see #sendAppLinkCommand(String, Bundle)
+ * @see #ACTION_APP_LINK_COMMAND
+ * @hide
+ */
+ public static final String INTENT_KEY_CHANNEL_URI = "channel_uri";
+
+ /**
+ * Intent key for command type. It's used to send app command to TV app. The value of this key
+ * could vary according to TV apps.
+ * <p>Type: String
+ *
+ * @see #sendAppLinkCommand(String, Bundle)
+ * @see #ACTION_APP_LINK_COMMAND
+ * @hide
+ */
+ public static final String INTENT_KEY_COMMAND_TYPE = "command_type";
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef(prefix = "SESSION_DATA_TYPE_", value = {
+ SESSION_DATA_TYPE_AD_REQUEST,
+ SESSION_DATA_TYPE_AD_BUFFER_READY,
+ SESSION_DATA_TYPE_BROADCAST_INFO_REQUEST,
+ SESSION_DATA_TYPE_REMOVE_BROADCAST_INFO_REQUEST})
+ public @interface SessionDataType {}
+
+ /**
+ * Sends an advertisement request to be processed by the related TV input.
+ *
+ * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_AD_REQUEST
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_AD_REQUEST = "ad_request";
+
+ /**
+ * Notifies the advertisement buffer is ready.
+ *
+ * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_AD_BUFFER
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_AD_BUFFER_READY = "ad_buffer_ready";
+
+ /**
+ * Sends request for broadcast info.
+ *
+ * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_BROADCAST_INFO_RESQUEST
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_BROADCAST_INFO_REQUEST = "broadcast_info_request";
+
+ /**
+ * Removes request for broadcast info.
+ *
+ * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_BROADCAST_INFO_REQUEST_ID
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_REMOVE_BROADCAST_INFO_REQUEST =
+ "remove_broadcast_info_request";
+
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef(prefix = "SESSION_DATA_KEY_", value = {
+ SESSION_DATA_KEY_AD_REQUEST,
+ SESSION_DATA_KEY_AD_BUFFER,
+ SESSION_DATA_KEY_BROADCAST_INFO_REQUEST,
+ SESSION_DATA_KEY_REQUEST_ID})
+ public @interface SessionDataKey {}
+
+ /**
+ * An object of {@link android.media.tv.AdRequest}.
+ *
+ * <p> Type: android.media.tv.AdRequest
+ *
+ * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_AD_REQUEST = "ad_request";
+
+ /**
+ * An object of {@link AdBuffer}.
+ *
+ * <p> Type: android.media.tv.AdBuffer
+ *
+ * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_AD_BUFFER = "ad_buffer";
+
+ /**
+ * An object of {@link android.media.tv.BroadcastInfoRequest}.
+ *
+ * <p> Type: android.media.tv.BroadcastInfoRequest
+ *
+ * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_BROADCAST_INFO_REQUEST = "broadcast_info_request";
+
+ /**
+ * The ID of {@link android.media.tv.BroadcastInfoRequest}.
+ *
+ * <p> Type: Integer
+ *
+ * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_REQUEST_ID = "request_id";
+
private final ITvAdManager mService;
private final int mUserId;
@@ -125,6 +323,79 @@
}
}
+ @Override
+ public void onRequestCurrentVideoBounds(int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postRequestCurrentVideoBounds();
+ }
+ }
+
+ @Override
+ public void onRequestCurrentChannelUri(int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postRequestCurrentChannelUri();
+ }
+ }
+
+ @Override
+ public void onRequestTrackInfoList(int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postRequestTrackInfoList();
+ }
+ }
+
+ @Override
+ public void onRequestCurrentTvInputId(int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postRequestCurrentTvInputId();
+ }
+ }
+
+ @Override
+ public void onRequestSigning(
+ String id, String algorithm, String alias, byte[] data, int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postRequestSigning(id, algorithm, alias, data);
+ }
+ }
+
+ @Override
+ public void onTvAdSessionData(String type, Bundle data, int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postTvAdSessionData(type, data);
+ }
+ }
+
};
ITvAdManagerCallback managerCallback =
@@ -220,6 +491,59 @@
}
/**
+ * Sends app link command.
+ *
+ * @param serviceId The ID of TV AD service which the command to be sent to. The ID can be found
+ * in {@link TvAdServiceInfo#getId()}.
+ * @param command The command to be sent.
+ * @hide
+ */
+ public void sendAppLinkCommand(@NonNull String serviceId, @NonNull Bundle command) {
+ try {
+ mService.sendAppLinkCommand(serviceId, command, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Registers a {@link TvAdServiceCallback}.
+ *
+ * @param callback A callback used to monitor status of the TV AD services.
+ * @param executor A {@link Executor} that the status change will be delivered to.
+ * @hide
+ */
+ public void registerCallback(
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull TvAdServiceCallback callback) {
+ Preconditions.checkNotNull(callback);
+ Preconditions.checkNotNull(executor);
+ synchronized (mLock) {
+ mCallbackRecords.add(new TvAdServiceCallbackRecord(callback, executor));
+ }
+ }
+
+ /**
+ * Unregisters the existing {@link TvAdServiceCallback}.
+ *
+ * @param callback The existing callback to remove.
+ * @hide
+ */
+ public void unregisterCallback(@NonNull final TvAdServiceCallback callback) {
+ Preconditions.checkNotNull(callback);
+ synchronized (mLock) {
+ for (Iterator<TvAdServiceCallbackRecord> it = mCallbackRecords.iterator();
+ it.hasNext(); ) {
+ TvAdServiceCallbackRecord record = it.next();
+ if (record.getCallback() == callback) {
+ it.remove();
+ break;
+ }
+ }
+ }
+ }
+
+ /**
* The Session provides the per-session functionality of AD service.
* @hide
*/
@@ -533,6 +857,88 @@
}
}
+ /**
+ * Notifies data from session of linked TvInputService.
+ */
+ public void notifyTvInputSessionData(String type, Bundle data) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.notifyTvInputSessionData(mToken, type, data, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Dispatches an input event to this session.
+ *
+ * @param event An {@link InputEvent} to dispatch. Cannot be {@code null}.
+ * @param token A token used to identify the input event later in the callback.
+ * @param callback A callback used to receive the dispatch result. Cannot be {@code null}.
+ * @param handler A {@link Handler} that the dispatch result will be delivered to. Cannot be
+ * {@code null}.
+ * @return Returns {@link #DISPATCH_HANDLED} if the event was handled. Returns
+ * {@link #DISPATCH_NOT_HANDLED} if the event was not handled. Returns
+ * {@link #DISPATCH_IN_PROGRESS} if the event is in progress and the callback will
+ * be invoked later.
+ * @hide
+ */
+ public int dispatchInputEvent(@NonNull InputEvent event, Object token,
+ @NonNull FinishedInputEventCallback callback, @NonNull Handler handler) {
+ Preconditions.checkNotNull(event);
+ Preconditions.checkNotNull(callback);
+ Preconditions.checkNotNull(handler);
+ synchronized (mHandler) {
+ if (mInputChannel == null) {
+ return DISPATCH_NOT_HANDLED;
+ }
+ PendingEvent p = obtainPendingEventLocked(event, token, callback, handler);
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ // Already running on the main thread so we can send the event immediately.
+ return sendInputEventOnMainLooperLocked(p);
+ }
+
+ // Post the event to the main thread.
+ Message msg = mHandler.obtainMessage(InputEventHandler.MSG_SEND_INPUT_EVENT, p);
+ msg.setAsynchronous(true);
+ mHandler.sendMessage(msg);
+ return DISPATCH_IN_PROGRESS;
+ }
+ }
+
+ private PendingEvent obtainPendingEventLocked(InputEvent event, Object token,
+ FinishedInputEventCallback callback, Handler handler) {
+ PendingEvent p = mPendingEventPool.acquire();
+ if (p == null) {
+ p = new PendingEvent();
+ }
+ p.mEvent = event;
+ p.mEventToken = token;
+ p.mCallback = callback;
+ p.mEventHandler = handler;
+ return p;
+ }
+
+ /**
+ * Callback that is invoked when an input event that was dispatched to this session has been
+ * finished.
+ *
+ * @hide
+ */
+ public interface FinishedInputEventCallback {
+ /**
+ * Called when the dispatched input event is finished.
+ *
+ * @param token A token passed to {@link #dispatchInputEvent}.
+ * @param handled {@code true} if the dispatched input event was handled properly.
+ * {@code false} otherwise.
+ */
+ void onFinishedInputEvent(Object token, boolean handled);
+ }
+
private final class InputEventHandler extends Handler {
public static final int MSG_SEND_INPUT_EVENT = 1;
public static final int MSG_TIMEOUT_INPUT_EVENT = 2;
@@ -639,23 +1045,6 @@
mPendingEventPool.release(p);
}
- /**
- * Callback that is invoked when an input event that was dispatched to this session has been
- * finished.
- *
- * @hide
- */
- public interface FinishedInputEventCallback {
- /**
- * Called when the dispatched input event is finished.
- *
- * @param token A token passed to {@link #dispatchInputEvent}.
- * @param handled {@code true} if the dispatched input event was handled properly.
- * {@code false} otherwise.
- */
- void onFinishedInputEvent(Object token, boolean handled);
- }
-
private final class TvInputEventSender extends InputEventSender {
TvInputEventSender(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
@@ -730,6 +1119,58 @@
public void onLayoutSurface(Session session, int left, int top, int right, int bottom) {
}
+ /**
+ * This is called when {@link TvAdService.Session#requestCurrentVideoBounds} is
+ * called.
+ *
+ * @param session A {@link TvAdService.Session} associated with this callback.
+ */
+ public void onRequestCurrentVideoBounds(Session session) {
+ }
+
+ /**
+ * This is called when {@link TvAdService.Session#requestCurrentChannelUri} is
+ * called.
+ *
+ * @param session A {@link TvAdService.Session} associated with this callback.
+ */
+ public void onRequestCurrentChannelUri(Session session) {
+ }
+
+ /**
+ * This is called when {@link TvAdService.Session#requestTrackInfoList} is
+ * called.
+ *
+ * @param session A {@link TvAdService.Session} associated with this callback.
+ */
+ public void onRequestTrackInfoList(Session session) {
+ }
+
+ /**
+ * This is called when {@link TvAdService.Session#requestCurrentTvInputId} is
+ * called.
+ *
+ * @param session A {@link TvAdService.Session} associated with this callback.
+ */
+ public void onRequestCurrentTvInputId(Session session) {
+ }
+
+ /**
+ * This is called when
+ * {@link TvAdService.Session#requestSigning(String, String, String, byte[])} is
+ * called.
+ *
+ * @param session A {@link TvAdService.Session} associated with this callback.
+ * @param signingId the ID to identify the request.
+ * @param algorithm the standard name of the signature algorithm requested, such as
+ * MD5withRSA, SHA256withDSA, etc.
+ * @param alias the alias of the corresponding {@link java.security.KeyStore}.
+ * @param data the original bytes to be signed.
+ */
+ public void onRequestSigning(
+ Session session, String signingId, String algorithm, String alias, byte[] data) {
+ }
+
}
/**
@@ -810,6 +1251,62 @@
}
});
}
+
+ void postRequestCurrentVideoBounds() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onRequestCurrentVideoBounds(mSession);
+ }
+ });
+ }
+
+ void postRequestCurrentChannelUri() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onRequestCurrentChannelUri(mSession);
+ }
+ });
+ }
+
+ void postRequestTrackInfoList() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onRequestTrackInfoList(mSession);
+ }
+ });
+ }
+
+ void postRequestCurrentTvInputId() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onRequestCurrentTvInputId(mSession);
+ }
+ });
+ }
+
+ void postRequestSigning(String id, String algorithm, String alias, byte[] data) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onRequestSigning(mSession, id, algorithm, alias, data);
+ }
+ });
+ }
+
+ void postTvAdSessionData(String type, Bundle data) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mSession.getInputSession() != null) {
+ mSession.getInputSession().notifyTvAdSessionData(type, data);
+ }
+ }
+ });
+ }
}
private static final class TvAdServiceCallbackRecord {
diff --git a/media/java/android/media/tv/ad/TvAdService.java b/media/java/android/media/tv/ad/TvAdService.java
index 5d81837..4d8f5c8b 100644
--- a/media/java/android/media/tv/ad/TvAdService.java
+++ b/media/java/android/media/tv/ad/TvAdService.java
@@ -31,6 +31,7 @@
import android.graphics.Rect;
import android.media.tv.TvInputManager;
import android.media.tv.TvTrackInfo;
+import android.media.tv.TvView;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
@@ -132,6 +133,9 @@
/**
* Called when app link command is received.
+ *
+ * @see TvAdManager#sendAppLinkCommand(String, Bundle)
+ * @hide
*/
public void onAppLinkCommand(@NonNull Bundle command) {
}
@@ -279,6 +283,143 @@
onResetAdService();
}
+ /**
+ * Requests the bounds of the current video.
+ * @hide
+ */
+ @CallSuper
+ public void requestCurrentVideoBounds() {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "requestCurrentVideoBounds");
+ }
+ if (mSessionCallback != null) {
+ mSessionCallback.onRequestCurrentVideoBounds();
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in requestCurrentVideoBounds", e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Requests the URI of the current channel.
+ * @hide
+ */
+ @CallSuper
+ public void requestCurrentChannelUri() {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "requestCurrentChannelUri");
+ }
+ if (mSessionCallback != null) {
+ mSessionCallback.onRequestCurrentChannelUri();
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in requestCurrentChannelUri", e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Requests the list of {@link TvTrackInfo}.
+ * @hide
+ */
+ @CallSuper
+ public void requestTrackInfoList() {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "requestTrackInfoList");
+ }
+ if (mSessionCallback != null) {
+ mSessionCallback.onRequestTrackInfoList();
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in requestTrackInfoList", e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Requests current TV input ID.
+ *
+ * @see android.media.tv.TvInputInfo
+ * @hide
+ */
+ @CallSuper
+ public void requestCurrentTvInputId() {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "requestCurrentTvInputId");
+ }
+ if (mSessionCallback != null) {
+ mSessionCallback.onRequestCurrentTvInputId();
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in requestCurrentTvInputId", e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Requests signing of the given data.
+ *
+ * <p>This is used when the corresponding server of the AD service app requires signing
+ * during handshaking, and the service doesn't have the built-in private key. The private
+ * key is provided by the content providers and pre-built in the related app, such as TV
+ * app.
+ *
+ * @param signingId the ID to identify the request. When a result is received, this ID can
+ * be used to correlate the result with the request.
+ * @param algorithm the standard name of the signature algorithm requested, such as
+ * MD5withRSA, SHA256withDSA, etc. The name is from standards like
+ * FIPS PUB 186-4 and PKCS #1.
+ * @param alias the alias of the corresponding {@link java.security.KeyStore}.
+ * @param data the original bytes to be signed.
+ *
+ * @see #onSigningResult(String, byte[])
+ */
+ @CallSuper
+ public void requestSigning(@NonNull String signingId, @NonNull String algorithm,
+ @NonNull String alias, @NonNull byte[] data) {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "requestSigning");
+ }
+ if (mSessionCallback != null) {
+ mSessionCallback.onRequestSigning(signingId, algorithm, alias, data);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in requestSigning", e);
+ }
+ }
+ });
+ }
+
@Override
public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
return false;
@@ -470,6 +611,19 @@
}
/**
+ * Called when data from the linked {@link android.media.tv.TvInputService} is received.
+ *
+ * @param type the type of the data
+ * @param data a bundle contains the data received
+ * @see android.media.tv.TvInputService.Session#notifyTvAdSessionData(String, Bundle)
+ * @see android.media.tv.ad.TvAdView#setTvView(TvView)
+ * @hide
+ */
+ public void onTvInputSessionData(
+ @NonNull @TvInputManager.SessionDataType String type, @NonNull Bundle data) {
+ }
+
+ /**
* Called when the size of the media view is changed by the application.
*
* <p>This is always called at least once when the session is created regardless of whether
@@ -497,6 +651,33 @@
}
/**
+ * Notifies data related to this session to corresponding linked
+ * {@link android.media.tv.TvInputService} object via TvView.
+ *
+ * @param type data type
+ * @param data the related data values
+ * @see TvAdView#setTvView(TvView)
+ * @hide
+ */
+ public void notifyTvAdSessionData(
+ @NonNull @TvAdManager.SessionDataType String type, @NonNull Bundle data) {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) Log.d(TAG, "notifyTvAdSessionData");
+ if (mSessionCallback != null) {
+ mSessionCallback.onTvAdSessionData(type, data);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in notifyTvAdSessionData", e);
+ }
+ }
+ });
+ }
+
+ /**
* Takes care of dispatching incoming input events and tells whether the event was handled.
*/
int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
@@ -594,6 +775,10 @@
onTvMessage(type, data);
}
+ void notifyTvInputSessionData(String type, Bundle data) {
+ onTvInputSessionData(type, data);
+ }
+
private void executeOrPostRunnableOnMainThread(Runnable action) {
synchronized (mLock) {
if (mSessionCallback == null) {
diff --git a/media/java/android/media/tv/ad/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java
index ec23b7c..604dbd5 100644
--- a/media/java/android/media/tv/ad/TvAdView.java
+++ b/media/java/android/media/tv/ad/TvAdView.java
@@ -28,17 +28,21 @@
import android.media.tv.TvInputManager;
import android.media.tv.TvTrackInfo;
import android.media.tv.TvView;
+import android.media.tv.ad.TvAdManager.Session.FinishedInputEventCallback;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Xml;
+import android.view.InputEvent;
+import android.view.KeyEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewRootImpl;
import java.util.List;
import java.util.concurrent.Executor;
@@ -88,6 +92,7 @@
private boolean mMediaViewCreated;
private Rect mMediaViewFrame;
+ private OnUnhandledInputEventListener mOnUnhandledInputEventListener;
@@ -327,6 +332,109 @@
mSession.dispatchSurfaceChanged(format, width, height);
}
+ private final FinishedInputEventCallback mFinishedInputEventCallback =
+ new FinishedInputEventCallback() {
+ @Override
+ public void onFinishedInputEvent(Object token, boolean handled) {
+ if (DEBUG) {
+ Log.d(TAG, "onFinishedInputEvent(token=" + token + ", handled="
+ + handled + ")");
+ }
+ if (handled) {
+ return;
+ }
+ // TODO: Re-order unhandled events.
+ InputEvent event = (InputEvent) token;
+ if (dispatchUnhandledInputEvent(event)) {
+ return;
+ }
+ ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl != null) {
+ viewRootImpl.dispatchUnhandledInputEvent(event);
+ }
+ }
+ };
+
+ /**
+ * Dispatches an unhandled input event to the next receiver.
+ *
+ * It gives the host application a chance to dispatch the unhandled input events.
+ *
+ * @param event The input event.
+ * @return {@code true} if the event was handled by the view, {@code false} otherwise.
+ * @hide
+ */
+ public boolean dispatchUnhandledInputEvent(@NonNull InputEvent event) {
+ if (mOnUnhandledInputEventListener != null) {
+ if (mOnUnhandledInputEventListener.onUnhandledInputEvent(event)) {
+ return true;
+ }
+ }
+ return onUnhandledInputEvent(event);
+ }
+
+ /**
+ * Called when an unhandled input event also has not been handled by the user provided
+ * callback. This is the last chance to handle the unhandled input event in the
+ * TvAdView.
+ *
+ * @param event The input event.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to be
+ * handled by the next receiver, return {@code false}.
+ */
+ public boolean onUnhandledInputEvent(@NonNull InputEvent event) {
+ return false;
+ }
+
+ /**
+ * Sets a listener to be invoked when an input event is not handled
+ * by the TV AD service.
+ *
+ * @param listener The callback to be invoked when the unhandled input event is received.
+ * @hide
+ */
+ public void setOnUnhandledInputEventListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnUnhandledInputEventListener listener) {
+ mOnUnhandledInputEventListener = listener;
+ // TODO: handle CallbackExecutor
+ }
+
+ /**
+ * Gets the {@link OnUnhandledInputEventListener}.
+ * <p>Returns {@code null} if the listener is not set or is cleared.
+ *
+ * @see #setOnUnhandledInputEventListener(Executor, OnUnhandledInputEventListener)
+ * @see #clearOnUnhandledInputEventListener()
+ * @hide
+ */
+ @Nullable
+ public OnUnhandledInputEventListener getOnUnhandledInputEventListener() {
+ return mOnUnhandledInputEventListener;
+ }
+
+ /**
+ * Clears the {@link OnUnhandledInputEventListener}.
+ * @hide
+ */
+ public void clearOnUnhandledInputEventListener() {
+ mOnUnhandledInputEventListener = null;
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(@NonNull KeyEvent event) {
+ if (super.dispatchKeyEvent(event)) {
+ return true;
+ }
+ if (mSession == null) {
+ return false;
+ }
+ InputEvent copiedEvent = event.copy();
+ int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback,
+ mHandler);
+ return ret != TvAdManager.Session.DISPATCH_NOT_HANDLED;
+ }
+
/**
* Prepares the AD service of corresponding {@link TvAdService}.
*
@@ -504,6 +612,24 @@
}
/**
+ * Interface definition for a callback to be invoked when the unhandled input event is received.
+ * @hide
+ */
+ public interface OnUnhandledInputEventListener {
+ /**
+ * Called when an input event was not handled by the TV AD service.
+ *
+ * <p>This is called asynchronously from where the event is dispatched. It gives the host
+ * application a chance to handle the unhandled input events.
+ *
+ * @param event The input event.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ */
+ boolean onUnhandledInputEvent(@NonNull InputEvent event);
+ }
+
+ /**
* Sets the callback to be invoked when an event is dispatched to this TvAdView.
*
* @param callback the callback to receive events. MUST NOT be {@code null}.
@@ -606,6 +732,117 @@
mUseRequestedSurfaceLayout = true;
requestLayout();
}
+
+ @Override
+ public void onRequestCurrentVideoBounds(TvAdManager.Session session) {
+ if (DEBUG) {
+ Log.d(TAG, "onRequestCurrentVideoBounds");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onRequestCurrentVideoBounds - session not created");
+ return;
+ }
+ synchronized (mCallbackLock) {
+ if (mCallbackExecutor != null) {
+ mCallbackExecutor.execute(() -> {
+ synchronized (mCallbackLock) {
+ if (mCallback != null) {
+ mCallback.onRequestCurrentVideoBounds(mServiceId);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ @Override
+ public void onRequestCurrentChannelUri(TvAdManager.Session session) {
+ if (DEBUG) {
+ Log.d(TAG, "onRequestCurrentChannelUri");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onRequestCurrentChannelUri - session not created");
+ return;
+ }
+ synchronized (mCallbackLock) {
+ if (mCallbackExecutor != null) {
+ mCallbackExecutor.execute(() -> {
+ synchronized (mCallbackLock) {
+ if (mCallback != null) {
+ mCallback.onRequestCurrentChannelUri(mServiceId);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ @Override
+ public void onRequestTrackInfoList(TvAdManager.Session session) {
+ if (DEBUG) {
+ Log.d(TAG, "onRequestTrackInfoList");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onRequestTrackInfoList - session not created");
+ return;
+ }
+ synchronized (mCallbackLock) {
+ if (mCallbackExecutor != null) {
+ mCallbackExecutor.execute(() -> {
+ synchronized (mCallbackLock) {
+ if (mCallback != null) {
+ mCallback.onRequestTrackInfoList(mServiceId);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ @Override
+ public void onRequestCurrentTvInputId(TvAdManager.Session session) {
+ if (DEBUG) {
+ Log.d(TAG, "onRequestCurrentTvInputId");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onRequestCurrentTvInputId - session not created");
+ return;
+ }
+ synchronized (mCallbackLock) {
+ if (mCallbackExecutor != null) {
+ mCallbackExecutor.execute(() -> {
+ synchronized (mCallbackLock) {
+ if (mCallback != null) {
+ mCallback.onRequestCurrentTvInputId(mServiceId);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ @Override
+ public void onRequestSigning(TvAdManager.Session session, String id, String algorithm,
+ String alias, byte[] data) {
+ if (DEBUG) {
+ Log.d(TAG, "onRequestSigning");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onRequestSigning - session not created");
+ return;
+ }
+ synchronized (mCallbackLock) {
+ if (mCallbackExecutor != null) {
+ mCallbackExecutor.execute(() -> {
+ synchronized (mCallbackLock) {
+ if (mCallback != null) {
+ mCallback.onRequestSigning(mServiceId, id, algorithm, alias, data);
+ }
+ }
+ });
+ }
+ }
+ }
}
/**
@@ -613,5 +850,55 @@
* @hide
*/
public abstract static class TvAdCallback {
+
+ /**
+ * This is called when {@link TvAdService.Session#requestCurrentVideoBounds()}
+ * is called.
+ *
+ * @param serviceId The ID of the TV AD service bound to this view.
+ */
+ public void onRequestCurrentVideoBounds(@NonNull String serviceId) {
+ }
+
+ /**
+ * This is called when {@link TvAdService.Session#requestCurrentChannelUri()} is
+ * called.
+ *
+ * @param serviceId The ID of the AD service bound to this view.
+ */
+ public void onRequestCurrentChannelUri(@NonNull String serviceId) {
+ }
+
+ /**
+ * This is called when {@link TvAdService.Session#requestTrackInfoList()} is called.
+ *
+ * @param serviceId The ID of the AD service bound to this view.
+ */
+ public void onRequestTrackInfoList(@NonNull String serviceId) {
+ }
+
+ /**
+ * This is called when {@link TvAdService.Session#requestCurrentTvInputId()} is called.
+ *
+ * @param serviceId The ID of the AD service bound to this view.
+ */
+ public void onRequestCurrentTvInputId(@NonNull String serviceId) {
+ }
+
+ /**
+ * This is called when
+ * {@link TvAdService.Session#requestSigning(String, String, String, byte[])} is called.
+ *
+ * @param serviceId The ID of the AD service bound to this view.
+ * @param signingId the ID to identify the request.
+ * @param algorithm the standard name of the signature algorithm requested, such as
+ * MD5withRSA, SHA256withDSA, etc.
+ * @param alias the alias of the corresponding {@link java.security.KeyStore}.
+ * @param data the original bytes to be signed.
+ *
+ */
+ public void onRequestSigning(@NonNull String serviceId, @NonNull String signingId,
+ @NonNull String algorithm, @NonNull String alias, @NonNull byte[] data) {
+ }
}
}
diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig
index 018eaf6..f110705 100644
--- a/media/java/android/media/tv/flags/media_tv.aconfig
+++ b/media/java/android/media/tv/flags/media_tv.aconfig
@@ -12,4 +12,11 @@
namespace: "media_tv"
description: "Enable the TV client-side AD framework."
bug: "303506816"
+}
+
+flag {
+ name: "tiaf_v_apis"
+ namespace: "media_tv"
+ description: "TIAF V3.0 APIs for Android V"
+ bug: "303323657"
}
\ No newline at end of file
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 3524f8c..f8b640c7 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -17,7 +17,7 @@
method @FlaggedApi("android.nfc.enable_nfc_charging") public void registerWlcStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.WlcStateListener);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean);
- method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setReaderMode(boolean);
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setReaderModePollingEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int setTagIntentAppPreferenceForUser(int, @NonNull String, boolean);
method @FlaggedApi("android.nfc.enable_nfc_charging") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setWlcEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 11eb97b..4d56c11 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -1764,7 +1764,9 @@
private static final int ENABLE_POLLING_FLAGS = 0x0000;
/**
- * Privileged API to enable disable reader polling.
+ * Privileged API to enable or disable reader polling.
+ * Unlike {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}, this API does not
+ * need a foreground activity to control reader mode parameters
* Note: Use with caution! The app is responsible for ensuring that the polling state is
* returned to normal.
*
@@ -1778,14 +1780,14 @@
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
@FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
@SuppressLint("VisiblySynchronized")
- public void setReaderMode(boolean enablePolling) {
+ public void setReaderModePollingEnabled(boolean enable) {
synchronized (sLock) {
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
}
Binder token = new Binder();
- int flags = enablePolling ? ENABLE_POLLING_FLAGS : DISABLE_POLLING_FLAGS;
+ int flags = enable ? ENABLE_POLLING_FLAGS : DISABLE_POLLING_FLAGS;
try {
NfcAdapter.sService.setReaderMode(token, null, flags, null);
} catch (RemoteException e) {
diff --git a/packages/CredentialManager/res/drawable/more_horiz_24px.xml b/packages/CredentialManager/res/drawable/more_horiz_24px.xml
index 7b235f8..0100718 100644
--- a/packages/CredentialManager/res/drawable/more_horiz_24px.xml
+++ b/packages/CredentialManager/res/drawable/more_horiz_24px.xml
@@ -3,6 +3,7 @@
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
+ android:contentDescription="@string/more_options_content_description"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
index 929756c..fdda9ea 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
@@ -17,6 +17,7 @@
android:id="@android:id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:minWidth="@dimen/dropdown_touch_target_min_width"
android:orientation="horizontal"
android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
android:elevation="3dp">
@@ -27,7 +28,7 @@
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
- android:contentDescription="@string/provider_icon_content_description"
+ android:contentDescription="@string/more_options_content_description"
android:background="@null"/>
<TextView
android:id="@android:id/text1"
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
index 1fe5e0e..c7c2fda 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
@@ -17,6 +17,7 @@
android:id="@android:id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:minWidth="@dimen/dropdown_touch_target_min_width"
android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
android:elevation="3dp">
@@ -24,7 +25,6 @@
android:id="@android:id/icon1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:contentDescription="@string/provider_icon_content_description"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
android:background="@null"/>
diff --git a/packages/CredentialManager/res/values/dimens.xml b/packages/CredentialManager/res/values/dimens.xml
index 3a8c78f..53852cb 100644
--- a/packages/CredentialManager/res/values/dimens.xml
+++ b/packages/CredentialManager/res/values/dimens.xml
@@ -27,4 +27,5 @@
<dimen name="autofill_dropdown_textview_max_width">230dp</dimen>
<dimen name="dropdown_layout_horizontal_margin">24dp</dimen>
<integer name="autofill_max_visible_datasets">3</integer>
+ <dimen name="dropdown_touch_target_min_width">48dp</dimen>
</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index f98164b..82b47a9 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -172,5 +172,5 @@
<!-- Strings for dropdown presentation. -->
<!-- Text shown in the dropdown presentation to select more sign in options. [CHAR LIMIT=120] -->
<string name="dropdown_presentation_more_sign_in_options_text">Sign-in options</string>
- <string name="provider_icon_content_description">Credential provider icon</string>
+ <string name="more_options_content_description">More</string>
</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
index 68f1c86..02afc54 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
@@ -74,6 +74,8 @@
setMaxHeightMethodName,
context.resources.getDimensionPixelSize(
com.android.credentialmanager.R.dimen.autofill_icon_size));
+ remoteViews.setContentDescription(android.R.id.icon1, credentialEntryInfo
+ .providerDisplayName);
val drawableId =
com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one
remoteViews.setInt(
diff --git a/packages/CtsShim/OWNERS b/packages/CtsShim/OWNERS
index 9419771..f5741a0 100644
--- a/packages/CtsShim/OWNERS
+++ b/packages/CtsShim/OWNERS
@@ -1,3 +1,2 @@
-ioffe@google.com
-toddke@google.com
-patb@google.com
\ No newline at end of file
+include /PACKAGE_MANAGER_OWNERS
+ioffe@google.com
\ No newline at end of file
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index 5a21d59..09e0d61 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -75,19 +75,15 @@
</intent-filter>
</activity>
- <!-- NOTE: the workaround to fix the screen flash problem. Remember to check the problem
- is resolved for new implementation -->
<activity android:name=".InstallStaging"
- android:theme="@style/Theme.AlertDialogActivity.NoDim"
- android:exported="false" />
+ android:exported="false" />
<activity android:name=".DeleteStagedFileOnResult"
android:theme="@style/Theme.AlertDialogActivity.NoActionBar"
android:exported="false" />
<activity android:name=".PackageInstallerActivity"
- android:theme="@style/Theme.AlertDialogActivity.NoAnimation"
- android:exported="false" />
+ android:exported="false" />
<activity android:name=".InstallInstalling"
android:theme="@style/Theme.AlertDialogActivity.NoAnimation"
diff --git a/packages/PackageInstaller/OWNERS b/packages/PackageInstaller/OWNERS
index 2736870..acbdff8 100644
--- a/packages/PackageInstaller/OWNERS
+++ b/packages/PackageInstaller/OWNERS
@@ -1,5 +1,4 @@
-svetoslavganov@google.com
+# Bug component: 1002434
include /PACKAGE_MANAGER_OWNERS
-# For automotive related changes
-rogerxue@google.com
+sumedhsen@google.com
\ No newline at end of file
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index f4641b9..f425f52 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -272,7 +272,7 @@
<!-- The title of a dialog which asks the user to restore (i.e. re-install, re-download) an app
after parts of the app have been previously moved into the cloud for temporary storage.
"installername" is the app that will facilitate the download of the app. [CHAR LIMIT=50] -->
- <string name="unarchive_application_title">Restore <xliff:g id="appname" example="Bird Game">%1$s</xliff:g> from <xliff:g id="installername" example="App Store">%1$s</xliff:g>?</string>
+ <string name="unarchive_application_title">Restore <xliff:g id="appname" example="Bird Game">%1$s</xliff:g> from <xliff:g id="installername" example="App Store">%2$s</xliff:g>?</string>
<!-- After the user confirms the dialog, a download will start. [CHAR LIMIT=none] -->
<string name="unarchive_body_text">This app will begin to download in the background</string>
<!-- The action to restore (i.e. re-install, re-download) an app after parts of the app have been previously moved
diff --git a/packages/PackageInstaller/res/values/themes.xml b/packages/PackageInstaller/res/values/themes.xml
index 811fa73..9a06229 100644
--- a/packages/PackageInstaller/res/values/themes.xml
+++ b/packages/PackageInstaller/res/values/themes.xml
@@ -32,9 +32,4 @@
<item name="android:windowNoTitle">true</item>
</style>
- <style name="Theme.AlertDialogActivity.NoDim"
- parent="@style/Theme.AlertDialogActivity.NoActionBar">
- <item name="android:backgroundDimAmount">0</item>
- </style>
-
</resources>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index ceb580d..7dc157f 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -308,7 +308,6 @@
}
private void initiateInstall() {
- bindUi();
String pkgName = mPkgInfo.packageName;
// Check if there is already a package on the device with this name
// but it has been renamed to something else.
@@ -448,6 +447,7 @@
if (mAppSnippet != null) {
// load placeholder layout with OK button disabled until we override this layout in
// startInstallConfirm
+ bindUi();
checkIfAllowedAndInitiateInstall();
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java
index 9af799c..b20117d 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java
@@ -25,10 +25,12 @@
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.content.IntentSender;
+import android.content.pm.InstallSourceInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Process;
+import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -97,14 +99,30 @@
String appTitle = pm.getApplicationInfo(mPackageName,
PackageManager.ApplicationInfoFlags.of(
MATCH_ARCHIVED_PACKAGES)).loadLabel(pm).toString();
- // TODO(ag/25387215) Get the real installer title here after fixing getInstallSource for
- // archived apps.
- showDialogFragment(appTitle, "installerTitle");
+ String installerTitle = getResponsibleInstallerTitle(pm,
+ pm.getInstallSourceInfo(mPackageName));
+ showDialogFragment(appTitle, installerTitle);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Invalid packageName: " + e.getMessage());
}
}
+ private String getResponsibleInstallerTitle(PackageManager pm,
+ InstallSourceInfo installSource)
+ throws PackageManager.NameNotFoundException {
+ String packageName = TextUtils.isEmpty(installSource.getUpdateOwnerPackageName())
+ ? installSource.getInstallingPackageName()
+ : installSource.getUpdateOwnerPackageName();
+ if (packageName == null) {
+ // Should be unreachable.
+ Log.e(TAG, "Installer not found.");
+ setResult(Activity.RESULT_FIRST_USER);
+ finish();
+ return "";
+ }
+ return pm.getApplicationInfo(packageName, /* flags= */ 0).loadLabel(pm).toString();
+ }
+
@NonNull
private String[] getRequestedPermissions(String callingPackage) {
String[] requestedPermissions = null;
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt
index b2a8b87..960ebcc 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt
@@ -21,6 +21,7 @@
data class CardButton(
val text: String,
+ val contentDescription: String? = null,
val onClick: () -> Unit,
)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
index c7845fa..700fa48 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
@@ -45,6 +45,8 @@
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import com.android.settingslib.spa.debug.UiModePreviews
import com.android.settingslib.spa.framework.theme.SettingsDimension
@@ -182,7 +184,11 @@
@Composable
private fun Button(button: CardButton, color: Color) {
- TextButton(onClick = button.onClick) {
+ TextButton(
+ onClick = button.onClick,
+ modifier =
+ Modifier.semantics { button.contentDescription?.let { this.contentDescription = it } }
+ ) {
Text(text = button.text, color = color)
}
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt
index beb9433..b5b2525 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt
@@ -36,8 +36,7 @@
@RunWith(AndroidJUnit4::class)
class SettingsCardTest {
- @get:Rule
- val composeTestRule = createComposeRule()
+ @get:Rule val composeTestRule = createComposeRule()
private val context: Context = ApplicationProvider.getApplicationContext()
@@ -76,9 +75,7 @@
CardModel(
title = "",
text = "",
- buttons = listOf(
- CardButton(text = TEXT) {}
- ),
+ buttons = listOf(CardButton(text = TEXT) {}),
)
)
}
@@ -94,9 +91,7 @@
CardModel(
title = "",
text = "",
- buttons = listOf(
- CardButton(text = TEXT) { buttonClicked = true }
- ),
+ buttons = listOf(CardButton(text = TEXT) { buttonClicked = true }),
)
)
}
@@ -107,6 +102,25 @@
}
@Test
+ fun settingsCard_buttonHaveContentDescription() {
+ composeTestRule.setContent {
+ SettingsCard(
+ CardModel(
+ title = "",
+ text = "",
+ buttons = listOf(CardButton(
+ text = TEXT,
+ contentDescription = CONTENT_DESCRIPTION,
+ ) {}
+ ),
+ )
+ )
+ }
+
+ composeTestRule.onNodeWithContentDescription(CONTENT_DESCRIPTION).assertIsDisplayed()
+ }
+
+ @Test
fun settingsCard_dismiss() {
composeTestRule.setContent {
var isVisible by remember { mutableStateOf(true) }
@@ -130,5 +144,6 @@
private companion object {
const val TITLE = "Title"
const val TEXT = "Text"
+ const val CONTENT_DESCRIPTION = "content-description"
}
}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index a4b3af9..2e64212 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1340,11 +1340,11 @@
<string name="notice_header" translatable="false"></string>
<!-- Name of the phone device. [CHAR LIMIT=30] -->
- <string name="media_transfer_this_device_name" product="default">This phone</string>
+ <string name="media_transfer_this_device_name">This phone</string>
<!-- Name of the tablet device. [CHAR LIMIT=30] -->
- <string name="media_transfer_this_device_name" product="tablet">This tablet</string>
+ <string name="media_transfer_this_device_name_tablet">This tablet</string>
<!-- Name of the default media output of the TV. [CHAR LIMIT=30] -->
- <string name="media_transfer_this_device_name" product="tv">@string/tv_media_transfer_default</string>
+ <string name="media_transfer_this_device_name_tv">@string/tv_media_transfer_default</string>
<!-- Name of the dock device. [CHAR LIMIT=30] -->
<string name="media_transfer_dock_speaker_device_name">Dock speaker</string>
<!-- Default name of the external device. [CHAR LIMIT=30] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index 15f33d2..ba9180d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -37,6 +37,7 @@
import android.hardware.hdmi.HdmiPortInfo;
import android.media.MediaRoute2Info;
import android.media.RouteListingPreference;
+import android.os.SystemProperties;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -45,6 +46,7 @@
import com.android.settingslib.R;
import com.android.settingslib.media.flags.Flags;
+import java.util.Arrays;
import java.util.List;
/**
@@ -63,6 +65,17 @@
private final DeviceIconUtil mDeviceIconUtil;
+ /** Returns this device name for media transfer. */
+ public static @NonNull String getMediaTransferThisDeviceName(@NonNull Context context) {
+ if (isTv(context)) {
+ return context.getString(R.string.media_transfer_this_device_name_tv);
+ } else if (isTablet()) {
+ return context.getString(R.string.media_transfer_this_device_name_tablet);
+ } else {
+ return context.getString(R.string.media_transfer_this_device_name);
+ }
+ }
+
/** Returns the device name for the given {@code routeInfo}. */
public static String getSystemRouteNameFromType(
@NonNull Context context, @NonNull MediaRoute2Info routeInfo) {
@@ -80,7 +93,7 @@
name = context.getString(R.string.media_transfer_dock_speaker_device_name);
break;
case TYPE_BUILTIN_SPEAKER:
- name = context.getString(R.string.media_transfer_this_device_name);
+ name = getMediaTransferThisDeviceName(context);
break;
case TYPE_HDMI:
name = context.getString(isTv ? R.string.tv_media_transfer_default :
@@ -135,6 +148,11 @@
&& Flags.enableTvMediaOutputDialog();
}
+ static boolean isTablet() {
+ return Arrays.asList(SystemProperties.get("ro.build.characteristics").split(","))
+ .contains("tablet");
+ }
+
// MediaRoute2Info.getType was made public on API 34, but exists since API 30.
@SuppressWarnings("NewApi")
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index 3355fb3..6761aa7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -16,29 +16,71 @@
package com.android.settingslib.volume.data.repository
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.media.AudioDeviceInfo
import android.media.AudioManager
+import android.media.AudioManager.OnCommunicationDeviceChangedListener
+import androidx.concurrent.futures.DirectExecutor
import com.android.internal.util.ConcurrentUtils
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.settingslib.volume.shared.model.AudioStreamModel
+import com.android.settingslib.volume.shared.model.RingerMode
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
-/** Provides audio managing functionality and data. */
+/** Provides audio streams state and managing functionality. */
interface AudioRepository {
/** Current [AudioManager.getMode]. */
val mode: StateFlow<Int>
+
+ /**
+ * Ringtone mode.
+ *
+ * @see AudioManager.getRingerModeInternal
+ */
+ val ringerMode: StateFlow<RingerMode>
+
+ /**
+ * Communication device. Emits null when there is no communication device available.
+ *
+ * @see AudioDeviceInfo.getType
+ */
+ val communicationDevice: StateFlow<AudioDeviceInfo?>
+
+ /** State of the [AudioStream]. */
+ suspend fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel>
+
+ /** Current state of the [AudioStream]. */
+ suspend fun getCurrentAudioStream(audioStream: AudioStream): AudioStreamModel
+
+ suspend fun setVolume(audioStream: AudioStream, volume: Int)
+
+ suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean)
}
class AudioRepositoryImpl(
+ private val context: Context,
private val audioManager: AudioManager,
- backgroundCoroutineContext: CoroutineContext,
- coroutineScope: CoroutineScope,
+ private val backgroundCoroutineContext: CoroutineContext,
+ private val coroutineScope: CoroutineScope,
) : AudioRepository {
override val mode: StateFlow<Int> =
@@ -50,4 +92,117 @@
}
.flowOn(backgroundCoroutineContext)
.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), audioManager.mode)
+
+ private val audioManagerIntents: SharedFlow<String> =
+ callbackFlow {
+ val receiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent) {
+ intent.action?.let { action -> launch { send(action) } }
+ }
+ }
+ context.registerReceiver(
+ receiver,
+ IntentFilter().apply {
+ for (action in allActions) {
+ addAction(action)
+ }
+ }
+ )
+
+ awaitClose { context.unregisterReceiver(receiver) }
+ }
+ .shareIn(coroutineScope, SharingStarted.WhileSubscribed())
+
+ override val ringerMode: StateFlow<RingerMode> =
+ audioManagerIntents
+ .filter { ringerActions.contains(it) }
+ .map { RingerMode(audioManager.ringerModeInternal) }
+ .flowOn(backgroundCoroutineContext)
+ .stateIn(
+ coroutineScope,
+ SharingStarted.WhileSubscribed(),
+ RingerMode(audioManager.ringerModeInternal),
+ )
+
+ override val communicationDevice: StateFlow<AudioDeviceInfo?>
+ get() =
+ callbackFlow {
+ val listener = OnCommunicationDeviceChangedListener { trySend(Unit) }
+ audioManager.addOnCommunicationDeviceChangedListener(
+ DirectExecutor.INSTANCE,
+ listener
+ )
+
+ awaitClose { audioManager.removeOnCommunicationDeviceChangedListener(listener) }
+ }
+ .filterNotNull()
+ .map { audioManager.communicationDevice }
+ .flowOn(backgroundCoroutineContext)
+ .stateIn(
+ coroutineScope,
+ SharingStarted.WhileSubscribed(),
+ audioManager.communicationDevice,
+ )
+
+ override suspend fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> {
+ return audioManagerIntents
+ .filter { modelActions.contains(it) }
+ .map { getCurrentAudioStream(audioStream) }
+ .flowOn(backgroundCoroutineContext)
+ }
+
+ override suspend fun getCurrentAudioStream(audioStream: AudioStream): AudioStreamModel {
+ return withContext(backgroundCoroutineContext) {
+ AudioStreamModel(
+ audioStream = audioStream,
+ minVolume = getMinVolume(audioStream),
+ maxVolume = audioManager.getStreamMaxVolume(audioStream.value),
+ volume = audioManager.getStreamVolume(audioStream.value),
+ isAffectedByRingerMode =
+ audioManager.isStreamAffectedByRingerMode(audioStream.value),
+ isMuted = audioManager.isStreamMute(audioStream.value)
+ )
+ }
+ }
+
+ override suspend fun setVolume(audioStream: AudioStream, volume: Int) =
+ withContext(backgroundCoroutineContext) {
+ audioManager.setStreamVolume(audioStream.value, volume, 0)
+ }
+
+ override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) =
+ withContext(backgroundCoroutineContext) {
+ if (isMuted) {
+ audioManager.adjustStreamVolume(audioStream.value, 0, AudioManager.ADJUST_MUTE)
+ } else {
+ audioManager.adjustStreamVolume(audioStream.value, 0, AudioManager.ADJUST_UNMUTE)
+ }
+ }
+
+ private fun getMinVolume(stream: AudioStream): Int =
+ try {
+ audioManager.getStreamMinVolume(stream.value)
+ } catch (e: IllegalArgumentException) {
+ // Fallback to STREAM_VOICE_CALL because
+ // CallVolumePreferenceController.java default
+ // return STREAM_VOICE_CALL in getAudioStream
+ audioManager.getStreamMinVolume(AudioManager.STREAM_VOICE_CALL)
+ }
+
+ private companion object {
+ val modelActions =
+ setOf(
+ AudioManager.STREAM_MUTE_CHANGED_ACTION,
+ AudioManager.MASTER_MUTE_CHANGED_ACTION,
+ AudioManager.VOLUME_CHANGED_ACTION,
+ AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
+ AudioManager.STREAM_DEVICES_CHANGED_ACTION,
+ )
+ val ringerActions =
+ setOf(
+ AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
+ )
+ val allActions = ringerActions + modelActions
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSystemRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSystemRepository.kt
new file mode 100644
index 0000000..2b12936
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSystemRepository.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.settingslib.volume.data.repository
+
+import android.content.Context
+import android.media.AudioSystem
+
+/** Provides the current state of the audio system. */
+interface AudioSystemRepository {
+
+ val isSingleVolume: Boolean
+}
+
+class AudioSystemRepositoryImpl(private val context: Context) : AudioSystemRepository {
+
+ override val isSingleVolume: Boolean
+ get() = AudioSystem.isSingleVolume(context)
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt
new file mode 100644
index 0000000..58f3c2d
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume.shared.model
+
+import android.media.AudioManager
+
+/** Type-safe wrapper for [AudioManager] audio stream. */
+@JvmInline
+value class AudioStream(val value: Int) {
+ init {
+ require(value in supportedStreamTypes) { "Unsupported stream=$value" }
+ }
+
+ private companion object {
+ val supportedStreamTypes =
+ setOf(
+ AudioManager.STREAM_VOICE_CALL,
+ AudioManager.STREAM_SYSTEM,
+ AudioManager.STREAM_RING,
+ AudioManager.STREAM_MUSIC,
+ AudioManager.STREAM_ALARM,
+ AudioManager.STREAM_NOTIFICATION,
+ AudioManager.STREAM_BLUETOOTH_SCO,
+ AudioManager.STREAM_SYSTEM_ENFORCED,
+ AudioManager.STREAM_DTMF,
+ AudioManager.STREAM_TTS,
+ AudioManager.STREAM_ACCESSIBILITY,
+ AudioManager.STREAM_ASSISTANT,
+ )
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStreamModel.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStreamModel.kt
new file mode 100644
index 0000000..c1be1ee
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStreamModel.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.volume.shared.model
+
+/** Current state of the audio stream. */
+data class AudioStreamModel(
+ val audioStream: AudioStream,
+ val volume: Int,
+ val minVolume: Int,
+ val maxVolume: Int,
+ val isAffectedByRingerMode: Boolean,
+ val isMuted: Boolean,
+)
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/RingerMode.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/RingerMode.kt
new file mode 100644
index 0000000..9f03927
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/RingerMode.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.settingslib.volume.shared.model
+
+import android.media.AudioManager
+
+/** Type-safe wrapper for [AudioManager] ringer mode. */
+@JvmInline
+value class RingerMode(val value: Int) {
+
+ init {
+ require(value in supportedRingerModes) { "Unsupported stream=$value" }
+ }
+
+ private companion object {
+ val supportedRingerModes =
+ setOf(
+ AudioManager.RINGER_MODE_SILENT,
+ AudioManager.RINGER_MODE_VIBRATE,
+ AudioManager.RINGER_MODE_NORMAL,
+ AudioManager.RINGER_MODE_MAX,
+ )
+ }
+}
diff --git a/packages/SettingsLib/tests/integ/Android.bp b/packages/SettingsLib/tests/integ/Android.bp
index ce3a7ba..f303ab5 100644
--- a/packages/SettingsLib/tests/integ/Android.bp
+++ b/packages/SettingsLib/tests/integ/Android.bp
@@ -40,6 +40,8 @@
"android.test.runner",
"telephony-common",
"android.test.base",
+ "android.test.mock",
+ "truth",
],
platform_apis: true,
@@ -49,16 +51,23 @@
"androidx.test.core",
"androidx.test.rules",
"androidx.test.espresso.core",
+ "androidx.test.ext.junit",
"flag-junit",
- "mockito-target-minus-junit4",
+ "kotlinx_coroutines_test",
+ "mockito-target-extended-minus-junit4",
"platform-test-annotations",
"truth",
"SettingsLibDeviceStateRotationLock",
"SettingsLibSettingsSpinner",
"SettingsLibUsageProgressBarPreference",
"settingslib_media_flags_lib",
- "kotlinx_coroutines_test",
],
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libmultiplejvmtiagentsinterferenceagent",
+ "libstaticjvmtiagent",
+ ],
dxflags: ["--multi-dex"],
+ manifest: "AndroidManifest.xml",
}
diff --git a/packages/SettingsLib/tests/integ/AndroidManifest.xml b/packages/SettingsLib/tests/integ/AndroidManifest.xml
index 32048ca..9fb1c1f 100644
--- a/packages/SettingsLib/tests/integ/AndroidManifest.xml
+++ b/packages/SettingsLib/tests/integ/AndroidManifest.xml
@@ -25,7 +25,7 @@
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
- <application>
+ <application android:debuggable="true" android:testOnly="true">
<uses-library android:name="android.test.runner" />
<activity android:name=".drawer.SettingsDrawerActivityTest$TestActivity"/>
diff --git a/packages/SettingsLib/tests/integ/AndroidTest.xml b/packages/SettingsLib/tests/integ/AndroidTest.xml
index d0aee88..9de6019 100644
--- a/packages/SettingsLib/tests/integ/AndroidTest.xml
+++ b/packages/SettingsLib/tests/integ/AndroidTest.xml
@@ -16,6 +16,7 @@
<configuration description="Runs Tests for SettingsLib.">
<target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
<option name="test-file-name" value="SettingsLibTests.apk" />
+ <option name="install-arg" value="-t" />
</target_preparer>
<option name="test-suite-tag" value="apct" />
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
new file mode 100644
index 0000000..7b70c64
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
@@ -0,0 +1,281 @@
+/*
+ * 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.volume.data.repository
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.media.AudioDeviceInfo
+import android.media.AudioManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.settingslib.volume.shared.model.AudioStreamModel
+import com.android.settingslib.volume.shared.model.RingerMode
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+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.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@Suppress("UnspecifiedRegisterReceiverFlag")
+@RunWith(AndroidJUnit4::class)
+class AudioRepositoryTest {
+
+ @Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
+ @Captor
+ private lateinit var modeListenerCaptor: ArgumentCaptor<AudioManager.OnModeChangedListener>
+ @Captor
+ private lateinit var communicationDeviceListenerCaptor:
+ ArgumentCaptor<AudioManager.OnCommunicationDeviceChangedListener>
+
+ @Mock private lateinit var context: Context
+ @Mock private lateinit var audioManager: AudioManager
+ @Mock private lateinit var communicationDevice: AudioDeviceInfo
+
+ private val volumeByStream: MutableMap<Int, Int> = mutableMapOf()
+ private val isAffectedByRingerModeByStream: MutableMap<Int, Boolean> = mutableMapOf()
+ private val isMuteByStream: MutableMap<Int, Boolean> = mutableMapOf()
+ private val testScope = TestScope()
+
+ private lateinit var underTest: AudioRepository
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ `when`(audioManager.mode).thenReturn(AudioManager.MODE_RINGTONE)
+ `when`(audioManager.communicationDevice).thenReturn(communicationDevice)
+ `when`(audioManager.getStreamMinVolume(anyInt())).thenReturn(MIN_VOLUME)
+ `when`(audioManager.getStreamMaxVolume(anyInt())).thenReturn(MAX_VOLUME)
+ `when`(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL)
+ `when`(audioManager.setStreamVolume(anyInt(), anyInt(), anyInt())).then {
+ volumeByStream[it.arguments[0] as Int] = it.arguments[1] as Int
+ triggerIntent(AudioManager.ACTION_VOLUME_CHANGED)
+ }
+ `when`(audioManager.adjustStreamVolume(anyInt(), anyInt(), anyInt())).then {
+ isMuteByStream[it.arguments[0] as Int] = it.arguments[2] == AudioManager.ADJUST_MUTE
+ triggerIntent(AudioManager.STREAM_MUTE_CHANGED_ACTION)
+ }
+ `when`(audioManager.getStreamVolume(anyInt())).thenAnswer {
+ volumeByStream.getOrDefault(it.arguments[0] as Int, 0)
+ }
+ `when`(audioManager.isStreamAffectedByRingerMode(anyInt())).thenAnswer {
+ isAffectedByRingerModeByStream.getOrDefault(it.arguments[0] as Int, false)
+ }
+ `when`(audioManager.isStreamMute(anyInt())).thenAnswer {
+ isMuteByStream.getOrDefault(it.arguments[0] as Int, false)
+ }
+
+ underTest =
+ AudioRepositoryImpl(
+ context,
+ audioManager,
+ testScope.testScheduler,
+ testScope.backgroundScope,
+ )
+ }
+
+ @Test
+ fun audioModeChanges_repositoryEmits() {
+ testScope.runTest {
+ val modes = mutableListOf<Int>()
+ underTest.mode.onEach { modes.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+
+ triggerModeChange(AudioManager.MODE_IN_CALL)
+ runCurrent()
+
+ assertThat(modes).containsExactly(AudioManager.MODE_RINGTONE, AudioManager.MODE_IN_CALL)
+ }
+ }
+
+ @Test
+ fun ringerModeChanges_repositoryEmits() {
+ testScope.runTest {
+ val modes = mutableListOf<RingerMode>()
+ underTest.ringerMode.onEach { modes.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+
+ `when`(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_SILENT)
+ triggerIntent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)
+ runCurrent()
+
+ assertThat(modes)
+ .containsExactly(
+ RingerMode(AudioManager.RINGER_MODE_NORMAL),
+ RingerMode(AudioManager.RINGER_MODE_SILENT),
+ )
+ }
+ }
+
+ @Test
+ fun communicationDeviceChanges_repositoryEmits() {
+ testScope.runTest {
+ var device: AudioDeviceInfo? = null
+ underTest.communicationDevice.onEach { device = it }.launchIn(backgroundScope)
+ runCurrent()
+
+ triggerConnectedDeviceChange(communicationDevice)
+ runCurrent()
+
+ assertThat(device).isSameInstanceAs(communicationDevice)
+ }
+ }
+
+ @Test
+ fun adjustingVolume_changesTheStream() {
+ testScope.runTest {
+ val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
+ var streamModel: AudioStreamModel? = null
+ underTest
+ .getAudioStream(audioStream)
+ .onEach { streamModel = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ underTest.setVolume(audioStream, 50)
+ runCurrent()
+
+ assertThat(streamModel)
+ .isEqualTo(
+ AudioStreamModel(
+ audioStream = audioStream,
+ volume = 50,
+ minVolume = MIN_VOLUME,
+ maxVolume = MAX_VOLUME,
+ isAffectedByRingerMode = false,
+ isMuted = false,
+ )
+ )
+ }
+ }
+
+ @Test
+ fun adjustingVolume_currentModeIsUpToDate() {
+ testScope.runTest {
+ val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
+ var streamModel: AudioStreamModel? = null
+ underTest
+ .getAudioStream(audioStream)
+ .onEach { streamModel = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ underTest.setVolume(audioStream, 50)
+ runCurrent()
+
+ assertThat(underTest.getCurrentAudioStream(audioStream)).isEqualTo(streamModel)
+ }
+ }
+
+ @Test
+ fun muteStream_mutesTheStream() {
+ testScope.runTest {
+ val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
+ var streamModel: AudioStreamModel? = null
+ underTest
+ .getAudioStream(audioStream)
+ .onEach { streamModel = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ underTest.setMuted(audioStream, true)
+ runCurrent()
+
+ assertThat(streamModel)
+ .isEqualTo(
+ AudioStreamModel(
+ audioStream = audioStream,
+ volume = 0,
+ minVolume = MIN_VOLUME,
+ maxVolume = MAX_VOLUME,
+ isAffectedByRingerMode = false,
+ isMuted = true,
+ )
+ )
+ }
+ }
+
+ @Test
+ fun unmuteStream_unmutesTheStream() {
+ testScope.runTest {
+ val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
+ isMuteByStream[audioStream.value] = true
+ var streamModel: AudioStreamModel? = null
+ underTest
+ .getAudioStream(audioStream)
+ .onEach { streamModel = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ underTest.setMuted(audioStream, false)
+ runCurrent()
+
+ assertThat(streamModel)
+ .isEqualTo(
+ AudioStreamModel(
+ audioStream = audioStream,
+ volume = 0,
+ minVolume = MIN_VOLUME,
+ maxVolume = MAX_VOLUME,
+ isAffectedByRingerMode = false,
+ isMuted = false,
+ )
+ )
+ }
+ }
+
+ private fun triggerConnectedDeviceChange(communicationDevice: AudioDeviceInfo?) {
+ verify(audioManager)
+ .addOnCommunicationDeviceChangedListener(
+ any(),
+ communicationDeviceListenerCaptor.capture(),
+ )
+ communicationDeviceListenerCaptor.value.onCommunicationDeviceChanged(communicationDevice)
+ }
+
+ private fun triggerModeChange(mode: Int) {
+ verify(audioManager).addOnModeChangedListener(any(), modeListenerCaptor.capture())
+ modeListenerCaptor.value.onModeChanged(mode)
+ }
+
+ private fun triggerIntent(action: String) {
+ verify(context).registerReceiver(receiverCaptor.capture(), any())
+ receiverCaptor.value.onReceive(context, Intent(action))
+ }
+
+ private companion object {
+ const val MIN_VOLUME = 0
+ const val MAX_VOLUME = 100
+ }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt
index 686362f..dddf8e82 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt
@@ -16,9 +16,15 @@
package com.android.settingslib.volume.data.repository
+import android.media.AudioDeviceInfo
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.settingslib.volume.shared.model.AudioStreamModel
+import com.android.settingslib.volume.shared.model.RingerMode
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
class FakeAudioRepository : AudioRepository {
@@ -26,7 +32,59 @@
override val mode: StateFlow<Int>
get() = mutableMode.asStateFlow()
+ private val mutableRingerMode = MutableStateFlow(RingerMode(0))
+ override val ringerMode: StateFlow<RingerMode>
+ get() = mutableRingerMode.asStateFlow()
+
+ private val mutableCommunicationDevice = MutableStateFlow<AudioDeviceInfo?>(null)
+ override val communicationDevice: StateFlow<AudioDeviceInfo?>
+ get() = mutableCommunicationDevice.asStateFlow()
+
+ private val models: MutableMap<AudioStream, MutableStateFlow<AudioStreamModel>> = mutableMapOf()
+
+ private fun getAudioStreamModelState(
+ audioStream: AudioStream
+ ): MutableStateFlow<AudioStreamModel> =
+ models.getOrPut(audioStream) {
+ MutableStateFlow(
+ AudioStreamModel(
+ audioStream = audioStream,
+ volume = 0,
+ minVolume = 0,
+ maxVolume = 0,
+ isAffectedByRingerMode = false,
+ isMuted = false,
+ )
+ )
+ }
+
+ override suspend fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> =
+ getAudioStreamModelState(audioStream).asStateFlow()
+
+ override suspend fun getCurrentAudioStream(audioStream: AudioStream): AudioStreamModel =
+ getAudioStreamModelState(audioStream).value
+
+ override suspend fun setVolume(audioStream: AudioStream, volume: Int) {
+ getAudioStreamModelState(audioStream).update { it.copy(volume = volume) }
+ }
+
+ override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) {
+ getAudioStreamModelState(audioStream).update { it.copy(isMuted = isMuted) }
+ }
+
fun setMode(newMode: Int) {
mutableMode.value = newMode
}
+
+ fun setRingerMode(newRingerMode: RingerMode) {
+ mutableRingerMode.value = newRingerMode
+ }
+
+ fun setCommunicationDevice(device: AudioDeviceInfo?) {
+ mutableCommunicationDevice.value = device
+ }
+
+ fun setAudioStreamModel(model: AudioStreamModel) {
+ getAudioStreamModelState(model.audioStream).update { model }
+ }
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt
index 3bc1edc..4dbf865 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt
@@ -17,8 +17,9 @@
package com.android.settingslib.volume.domain.interactor
import android.media.AudioManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import androidx.test.runner.AndroidJUnit4
+import com.android.settingslib.BaseTest
import com.android.settingslib.volume.data.repository.FakeAudioRepository
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -33,7 +34,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
@SmallTest
-class AudioModeInteractorTest {
+class AudioModeInteractorTest : BaseTest() {
private val testScope = TestScope()
private val fakeAudioRepository = FakeAudioRepository()
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
index ceba9be..e2d58d6 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
@@ -24,6 +24,7 @@
import static com.android.settingslib.media.PhoneMediaDevice.PHONE_ID;
import static com.android.settingslib.media.PhoneMediaDevice.USB_HEADSET_ID;
import static com.android.settingslib.media.PhoneMediaDevice.WIRED_HEADSET_ID;
+import static com.android.settingslib.media.PhoneMediaDevice.getMediaTransferThisDeviceName;
import static com.google.common.truth.Truth.assertThat;
@@ -114,7 +115,7 @@
when(mInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER);
assertThat(mPhoneMediaDevice.getName())
- .isEqualTo(mContext.getString(R.string.media_transfer_this_device_name));
+ .isEqualTo(getMediaTransferThisDeviceName(mContext));
}
@EnableFlags(Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
diff --git a/packages/SettingsLib/tests/unit/Android.bp b/packages/SettingsLib/tests/unit/Android.bp
index 6d6e2ff..e2eda4f 100644
--- a/packages/SettingsLib/tests/unit/Android.bp
+++ b/packages/SettingsLib/tests/unit/Android.bp
@@ -32,5 +32,7 @@
"androidx.test.ext.junit",
"androidx.test.runner",
"truth",
+ "kotlinx_coroutines_test",
+ "mockito-target-minus-junit4",
],
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 95e0e1b..e99fcc9 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -908,6 +908,12 @@
<!-- Permissions required for CTS test - CtsPermissionUiTestCases -->
<uses-permission android:name="android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES" />
+ <!-- Permissions required for CTS test - CtsContactKeysProviderPrivilegedApp -->
+ <uses-permission android:name="android.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS"/>
+
+ <!-- Permission required for Cts test ScreenRecordingCallbackTests -->
+ <uses-permission android:name="android.permission.DETECT_SCREEN_RECORDING" />
+
<application
android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SystemUI/aconfig/cross_device_control.aconfig b/packages/SystemUI/aconfig/cross_device_control.aconfig
new file mode 100644
index 0000000..d3f14c1
--- /dev/null
+++ b/packages/SystemUI/aconfig/cross_device_control.aconfig
@@ -0,0 +1,15 @@
+package: "com.android.systemui"
+
+flag {
+ name: "legacy_le_audio_sharing"
+ namespace: "pixel_cross_device_control"
+ description: "Gates the legacy le audio sharing UI."
+ bug: "322295262"
+}
+
+flag {
+ name: "enable_personal_le_audio_sharing"
+ namespace: "pixel_cross_device_control"
+ description: "Gates the personal le audio sharing UI in UMO."
+ bug: "322295480"
+}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 0ea2b1f..854f6b9 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -323,13 +323,6 @@
}
flag {
- name: "screenshare_notification_hiding"
- namespace: "systemui"
- description: "Enable hiding of notifications during screenshare"
- bug: "312784809"
-}
-
-flag {
name: "run_fingerprint_detect_on_dismissible_keyguard"
namespace: "systemui"
description: "Run fingerprint detect instead of authenticate if the keyguard is dismissible."
@@ -370,3 +363,17 @@
description: "Enables the compose version of keyguard."
bug: "301968149"
}
+
+flag {
+ name: "enable_contextual_tip_for_power_off"
+ namespace: "systemui"
+ description: "Enables on-screen contextual tip about how to power off or restart phone"
+ bug: "322891421"
+}
+
+flag {
+ name: "enable_contextual_tip_for_take_screenshot"
+ namespace: "systemui"
+ description: "Enables on-screen contextual tip about how to take screenshot."
+ bug: "322891421"
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
index f9b91cf..bd539a7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
@@ -20,6 +20,7 @@
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TextField
@@ -36,16 +37,21 @@
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onInterceptKeyBeforeSoftKeyboard
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
+import com.android.compose.PlatformIconButton
import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
+import com.android.systemui.res.R
/** UI for the input part of a password-requiring version of the bouncer. */
@Composable
@@ -64,6 +70,7 @@
val password: String by viewModel.password.collectAsState()
val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState()
val animateFailure: Boolean by viewModel.animateFailure.collectAsState()
+ val isImeSwitcherButtonVisible by viewModel.isImeSwitcherButtonVisible.collectAsState()
DisposableEffect(Unit) {
viewModel.onShown()
@@ -116,5 +123,28 @@
false
}
},
+ trailingIcon =
+ if (isImeSwitcherButtonVisible) {
+ { ImeSwitcherButton(viewModel, color) }
+ } else null
+ )
+}
+
+/** Button for changing the password input method (IME). */
+@Composable
+private fun ImeSwitcherButton(
+ viewModel: PasswordBouncerViewModel,
+ color: Color,
+) {
+ val context = LocalContext.current
+ PlatformIconButton(
+ onClick = { viewModel.onImeSwitcherButtonClicked(context.displayId) },
+ iconResource = R.drawable.ic_lockscreen_ime,
+ contentDescription = stringResource(R.string.accessibility_ime_switch_button),
+ colors =
+ IconButtonDefaults.filledIconButtonColors(
+ contentColor = color,
+ containerColor = Color.Transparent,
+ )
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 576596f..4e72dfe 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -160,14 +160,23 @@
gridCoordinates
) {
detectLongPressGesture { offset ->
- isButtonToEditWidgetsShowing = true
-
// Deduct both grid offset relative to its container and content offset.
val adjustedOffset =
gridCoordinates?.let {
offset - it.positionInWindow() - contentOffset
}
val index = adjustedOffset?.let { firstIndexAtOffset(gridState, it) }
+ // Display the button only when the gesture initiates from widgets,
+ // the CTA tile, or an empty area on the screen. UMO/smartspace have
+ // their own long-press handlers. To prevent user confusion, we should
+ // not display this button.
+ if (
+ index == null ||
+ communalContent[index].isWidget() ||
+ communalContent[index] is CommunalContentModel.CtaTileInViewMode
+ ) {
+ isButtonToEditWidgetsShowing = true
+ }
val key = index?.let { keyAtIndexIfEditable(communalContent, index) }
viewModel.setSelectedKey(key)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt
new file mode 100644
index 0000000..2ba78cf
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.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.notifications.ui.composable
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import com.android.compose.nestedscroll.PriorityNestedScrollConnection
+
+/**
+ * A [NestedScrollConnection] that listens for all vertical scroll events and responds in the
+ * following way:
+ * - If you **scroll up**, it **first brings the [scrimOffset]** back to the [minScrimOffset] and
+ * then allows scrolling of the children (usually the content).
+ * - If you **scroll down**, it **first allows scrolling of the children** (usually the content) and
+ * then resets the [scrimOffset] to [maxScrimOffset].
+ */
+fun NotificationScrimNestedScrollConnection(
+ scrimOffset: () -> Float,
+ onScrimOffsetChanged: (Float) -> Unit,
+ minScrimOffset: () -> Float,
+ maxScrimOffset: Float,
+ contentHeight: () -> Float,
+ minVisibleScrimHeight: () -> Float,
+): PriorityNestedScrollConnection {
+ return PriorityNestedScrollConnection(
+ orientation = Orientation.Vertical,
+ // scrolling up and inner content is taller than the scrim, so scrim needs to
+ // expand; content can scroll once scrim is at the minScrimOffset.
+ canStartPreScroll = { offsetAvailable, offsetBeforeStart ->
+ offsetAvailable < 0 &&
+ offsetBeforeStart == 0f &&
+ contentHeight() > minVisibleScrimHeight() &&
+ scrimOffset() > minScrimOffset()
+ },
+ // scrolling down and content is done scrolling to top. After that, the scrim
+ // needs to collapse; collapse the scrim until it is at the maxScrimOffset.
+ canStartPostScroll = { offsetAvailable, _ ->
+ offsetAvailable > 0 && scrimOffset() < maxScrimOffset
+ },
+ canStartPostFling = { false },
+ canContinueScroll = {
+ val currentHeight = scrimOffset()
+ minScrimOffset() < currentHeight && currentHeight < maxScrimOffset
+ },
+ canScrollOnFling = true,
+ onStart = { /* do nothing */},
+ onScroll = { offsetAvailable ->
+ val currentHeight = scrimOffset()
+ val amountConsumed =
+ if (offsetAvailable > 0) {
+ val amountLeft = maxScrimOffset - currentHeight
+ offsetAvailable.coerceAtMost(amountLeft)
+ } else {
+ val amountLeft = minScrimOffset() - currentHeight
+ offsetAvailable.coerceAtLeast(amountLeft)
+ }
+ onScrimOffsetChanged(currentHeight + amountConsumed)
+ amountConsumed
+ },
+ // Don't consume the velocity on pre/post fling
+ onStop = { 0f },
+ )
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index e835d3e..0e08a19 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -20,32 +20,53 @@
import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.asPaddingValues
+import androidx.compose.foundation.layout.displayCutout
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.systemBars
+import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInWindow
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.NestedScrollBehavior
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.height
import com.android.systemui.notifications.ui.composable.Notifications.Form
+import com.android.systemui.scene.ui.composable.Gone
+import com.android.systemui.scene.ui.composable.Shade
+import com.android.systemui.shade.ui.composable.ShadeHeader
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import kotlin.math.roundToInt
@@ -100,33 +121,109 @@
@Composable
fun SceneScope.NotificationScrollingStack(
viewModel: NotificationsPlaceholderViewModel,
+ maxScrimTop: () -> Float,
modifier: Modifier = Modifier,
) {
+ val density = LocalDensity.current
val cornerRadius by viewModel.cornerRadiusDp.collectAsState()
-
- val contentHeight by viewModel.intrinsicContentHeight.collectAsState()
-
val expansionFraction by viewModel.expandFraction.collectAsState(0f)
- Box(
- modifier =
- modifier
- .verticalNestedScrollToScene()
- .fillMaxWidth()
- .element(Notifications.Elements.NotificationScrim)
- .graphicsLayer {
- shape = RoundedCornerShape(cornerRadius.dp)
- clip = true
- alpha = expansionFraction
- }
- .background(MaterialTheme.colorScheme.surface)
- .debugBackground(viewModel, Color(0.5f, 0.5f, 0f, 0.2f))
- ) {
- NotificationPlaceholder(
- viewModel = viewModel,
- form = Form.Stack,
- modifier = Modifier.fillMaxWidth().height { contentHeight.roundToInt() }
+ val navBarHeight =
+ with(density) { WindowInsets.systemBars.asPaddingValues().calculateBottomPadding().toPx() }
+ val statusBarHeight =
+ with(density) { WindowInsets.systemBars.asPaddingValues().calculateTopPadding().toPx() }
+ val displayCutoutHeight =
+ with(density) { WindowInsets.displayCutout.asPaddingValues().calculateTopPadding().toPx() }
+ val screenHeight =
+ with(density) { LocalConfiguration.current.screenHeightDp.dp.toPx() } +
+ navBarHeight +
+ maxOf(statusBarHeight, displayCutoutHeight)
+
+ val contentHeight = viewModel.intrinsicContentHeight.collectAsState()
+
+ // the offset for the notifications scrim. Its upper bound is 0, and its lower bound is
+ // calculated in minScrimOffset. The scrim is the same height as the screen minus the
+ // height of the Shade Header, and at rest (scrimOffset = 0) its top bound is at maxScrimStartY.
+ // When fully expanded (scrimOffset = minScrimOffset), its top bound is at minScrimStartY,
+ // which is equal to the height of the Shade Header. Thus, when the scrim is fully expanded, the
+ // entire height of the scrim is visible on screen.
+ val scrimOffset = remember { mutableStateOf(0f) }
+
+ val minScrimTop = with(density) { ShadeHeader.Dimensions.CollapsedHeight.toPx() }
+
+ // The minimum offset for the scrim. The scrim is considered fully expanded when it
+ // is at this offset.
+ val minScrimOffset: () -> Float = { minScrimTop - maxScrimTop() }
+
+ // The height of the scrim visible on screen when it is in its resting (collapsed) state.
+ val minVisibleScrimHeight: () -> Float = { screenHeight - maxScrimTop() }
+
+ // we are not scrolled to the top unless the scrim is at its maximum offset.
+ LaunchedEffect(viewModel, scrimOffset) {
+ snapshotFlow { scrimOffset.value >= 0f }
+ .collect { isScrolledToTop -> viewModel.setScrolledToTop(isScrolledToTop) }
+ }
+
+ // if contentHeight drops below minimum visible scrim height while scrim is
+ // expanded, reset scrim offset.
+ LaunchedEffect(contentHeight, screenHeight, maxScrimTop, scrimOffset) {
+ snapshotFlow { contentHeight.value < minVisibleScrimHeight() && scrimOffset.value < 0f }
+ .collect { shouldCollapse -> if (shouldCollapse) scrimOffset.value = 0f }
+ }
+
+ Box(modifier = modifier.element(Notifications.Elements.NotificationScrim)) {
+ Spacer(
+ modifier =
+ Modifier.fillMaxSize()
+ .graphicsLayer {
+ shape = RoundedCornerShape(cornerRadius.dp)
+ clip = true
+ }
+ .drawBehind { drawRect(Color.Black, blendMode = BlendMode.DstOut) }
)
+ Box(
+ modifier =
+ Modifier.fillMaxSize()
+ .offset { IntOffset(0, scrimOffset.value.roundToInt()) }
+ .graphicsLayer {
+ shape = RoundedCornerShape(cornerRadius.dp)
+ clip = true
+ alpha =
+ if (layoutState.isTransitioningBetween(Gone, Shade)) {
+ (expansionFraction / 0.3f).coerceAtMost(1f)
+ } else 1f
+ }
+ .background(MaterialTheme.colorScheme.surface)
+ .debugBackground(viewModel, Color(0.5f, 0.5f, 0f, 0.2f))
+ ) {
+ NotificationPlaceholder(
+ viewModel = viewModel,
+ form = Form.Stack,
+ modifier =
+ Modifier.verticalNestedScrollToScene(
+ topBehavior = NestedScrollBehavior.EdgeWithPreview,
+ )
+ .nestedScroll(
+ remember(
+ scrimOffset,
+ maxScrimTop,
+ minScrimTop,
+ ) {
+ NotificationScrimNestedScrollConnection(
+ scrimOffset = { scrimOffset.value },
+ onScrimOffsetChanged = { scrimOffset.value = it },
+ minScrimOffset = minScrimOffset,
+ maxScrimOffset = 0f,
+ contentHeight = { contentHeight.value },
+ minVisibleScrimHeight = minVisibleScrimHeight,
+ )
+ }
+ )
+ .verticalScroll(rememberScrollState())
+ .fillMaxWidth()
+ .height { (contentHeight.value + navBarHeight).roundToInt() },
+ )
+ }
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index bbfe0fd..5531f9c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -36,6 +36,7 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
@@ -46,6 +47,11 @@
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.boundsInWindow
+import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.unit.dp
@@ -56,7 +62,7 @@
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace
+import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.qs.footer.ui.compose.FooterActions
import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
import com.android.systemui.scene.shared.model.SceneKey
@@ -116,6 +122,8 @@
statusBarIconController: StatusBarIconController,
modifier: Modifier = Modifier,
) {
+ val cornerRadius by viewModel.notifications.cornerRadiusDp.collectAsState()
+
// TODO(b/280887232): implement the real UI.
Box(modifier = modifier.fillMaxSize()) {
val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
@@ -234,10 +242,32 @@
}
}
}
- HeadsUpNotificationSpace(
- viewModel = viewModel.notifications,
- isPeekFromBottom = true,
- modifier = Modifier.padding(16.dp).fillMaxSize(),
+ // Scrim with height 0 aligned to bottom of the screen to facilitate shared element
+ // transition from Shade scene.
+ Box(
+ modifier =
+ Modifier.element(Notifications.Elements.NotificationScrim)
+ .fillMaxWidth()
+ .height(0.dp)
+ .graphicsLayer {
+ shape = RoundedCornerShape(cornerRadius.dp)
+ clip = true
+ alpha = 1f
+ }
+ .background(MaterialTheme.colorScheme.surface)
+ .align(Alignment.BottomCenter)
+ .onPlaced { coordinates: LayoutCoordinates ->
+ viewModel.notifications.onContentTopChanged(
+ coordinates.positionInWindow().y
+ )
+ val boundsInWindow = coordinates.boundsInWindow()
+ viewModel.notifications.onBoundsChanged(
+ left = boundsInWindow.left,
+ top = boundsInWindow.top,
+ right = boundsInWindow.right,
+ bottom = boundsInWindow.bottom,
+ )
+ }
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index 747faab..770d654 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -18,13 +18,10 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace
import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.Edge
@@ -66,12 +63,6 @@
override fun SceneScope.Content(
modifier: Modifier,
) {
- Box(modifier = modifier) {
- Box(modifier = Modifier.fillMaxSize().element(Notifications.Elements.NotificationScrim))
- HeadsUpNotificationSpace(
- viewModel = notificationsViewModel,
- modifier = Modifier.padding(16.dp).fillMaxSize(),
- )
- }
+ Box(modifier = Modifier.fillMaxSize().element(Notifications.Elements.NotificationScrim))
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 1545372..497fe87 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -32,6 +32,7 @@
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.dimensionResource
@@ -62,6 +63,7 @@
import com.android.systemui.util.animation.MeasurementInput
import javax.inject.Inject
import javax.inject.Named
+import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -154,62 +156,104 @@
mediaHost: MediaHost,
modifier: Modifier = Modifier,
) {
- val localDensity = LocalDensity.current
+ val density = LocalDensity.current
val layoutWidth = remember { mutableStateOf(0) }
+ val maxNotifScrimTop = remember { mutableStateOf(0f) }
Box(
modifier =
modifier.element(Shade.Elements.Scrim).background(MaterialTheme.colorScheme.scrim),
)
Box {
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- modifier = Modifier.fillMaxWidth().clickable(onClick = { viewModel.onContentClicked() })
- ) {
- CollapsedShadeHeader(
- viewModel = viewModel.shadeHeaderViewModel,
- createTintedIconManager = createTintedIconManager,
- createBatteryMeterViewController = createBatteryMeterViewController,
- statusBarIconController = statusBarIconController,
- modifier = Modifier.padding(horizontal = Shade.Dimensions.HorizontalPadding)
- )
- QuickSettings(
- modifier = Modifier.height(130.dp),
- viewModel.qsSceneAdapter,
- )
-
- if (viewModel.isMediaVisible()) {
- val mediaHeight = dimensionResource(R.dimen.qs_media_session_height_expanded)
- MediaCarousel(
- modifier =
- Modifier.height(mediaHeight).fillMaxWidth().layout { measurable, constraints
- ->
- val placeable = measurable.measure(constraints)
-
- // Notify controller to size the carousel for the current space
- mediaHost.measurementInput =
- MeasurementInput(placeable.width, placeable.height)
- mediaCarouselController.setSceneContainerSize(
- placeable.width,
- placeable.height
+ Layout(
+ contents =
+ listOf(
+ {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier =
+ Modifier.fillMaxWidth()
+ .clickable(onClick = { viewModel.onContentClicked() })
+ ) {
+ CollapsedShadeHeader(
+ viewModel = viewModel.shadeHeaderViewModel,
+ createTintedIconManager = createTintedIconManager,
+ createBatteryMeterViewController = createBatteryMeterViewController,
+ statusBarIconController = statusBarIconController,
+ modifier =
+ Modifier.padding(
+ horizontal = Shade.Dimensions.HorizontalPadding
+ )
+ )
+ QuickSettings(
+ modifier = Modifier.height(130.dp),
+ viewModel.qsSceneAdapter,
)
- layout(placeable.width, placeable.height) {
- placeable.placeRelative(0, 0)
- }
- },
- mediaHost = mediaHost,
- layoutWidth = layoutWidth.value,
- layoutHeight = with(localDensity) { mediaHeight.toPx() }.toInt(),
- carouselController = mediaCarouselController,
- )
- }
+ if (viewModel.isMediaVisible()) {
+ val mediaHeight =
+ dimensionResource(R.dimen.qs_media_session_height_expanded)
+ MediaCarousel(
+ modifier =
+ Modifier.height(mediaHeight).fillMaxWidth().layout {
+ measurable,
+ constraints ->
+ val placeable = measurable.measure(constraints)
- Spacer(modifier = Modifier.height(16.dp))
- NotificationScrollingStack(
- viewModel = viewModel.notifications,
- modifier = Modifier.fillMaxWidth().weight(1f),
- )
+ // Notify controller to size the carousel for the
+ // current space
+ mediaHost.measurementInput =
+ MeasurementInput(placeable.width, placeable.height)
+ mediaCarouselController.setSceneContainerSize(
+ placeable.width,
+ placeable.height
+ )
+
+ layout(placeable.width, placeable.height) {
+ placeable.placeRelative(0, 0)
+ }
+ },
+ mediaHost = mediaHost,
+ layoutWidth = layoutWidth.value,
+ layoutHeight = with(density) { mediaHeight.toPx() }.toInt(),
+ carouselController = mediaCarouselController,
+ )
+ }
+
+ Spacer(modifier = Modifier.height(16.dp))
+ }
+ },
+ {
+ NotificationScrollingStack(
+ viewModel = viewModel.notifications,
+ maxScrimTop = { maxNotifScrimTop.value },
+ )
+ },
+ )
+ ) { measurables, constraints ->
+ check(measurables.size == 2)
+ check(measurables[0].size == 1)
+ check(measurables[1].size == 1)
+
+ val quickSettingsPlaceable = measurables[0][0].measure(constraints)
+
+ val notificationsMeasurable = measurables[1][0]
+ val notificationsScrimMaxHeight =
+ constraints.maxHeight - ShadeHeader.Dimensions.CollapsedHeight.roundToPx()
+ val notificationsPlaceable =
+ notificationsMeasurable.measure(
+ constraints.copy(
+ minHeight = notificationsScrimMaxHeight,
+ maxHeight = notificationsScrimMaxHeight
+ )
+ )
+
+ maxNotifScrimTop.value = quickSettingsPlaceable.height.toFloat()
+
+ layout(constraints.maxWidth, constraints.maxHeight) {
+ quickSettingsPlaceable.placeRelative(x = 0, y = 0)
+ notificationsPlaceable.placeRelative(x = 0, y = maxNotifScrimTop.value.roundToInt())
+ }
}
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
index 7057545..b9b472f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
@@ -30,6 +30,7 @@
internal fun CoroutineScope.animateToScene(
layoutState: BaseSceneTransitionLayoutState,
target: SceneKey,
+ transitionKey: TransitionKey?,
): TransitionState.Transition? {
val transitionState = layoutState.transitionState
if (transitionState.currentScene == target) {
@@ -45,7 +46,7 @@
}
return when (transitionState) {
- is TransitionState.Idle -> animate(layoutState, target)
+ is TransitionState.Idle -> animate(layoutState, target, transitionKey)
is TransitionState.Transition -> {
// A transition is currently running: first check whether `transition.toScene` or
// `transition.fromScene` is the same as our target scene, in which case the transition
@@ -67,7 +68,7 @@
// The transition is in progress: start the canned animation at the same
// progress as it was in.
// TODO(b/290184746): Also take the current velocity into account.
- animate(layoutState, target, startProgress = progress)
+ animate(layoutState, target, transitionKey, startProgress = progress)
}
} else if (transitionState.fromScene == target) {
// There is a transition from [target] to another scene: simply animate the same
@@ -82,12 +83,18 @@
null
} else {
// TODO(b/290184746): Also take the current velocity into account.
- animate(layoutState, target, startProgress = progress, reversed = true)
+ animate(
+ layoutState,
+ target,
+ transitionKey,
+ startProgress = progress,
+ reversed = true,
+ )
}
} else {
// Generic interruption; the current transition is neither from or to [target].
// TODO(b/290930950): Better handle interruptions here.
- animate(layoutState, target)
+ animate(layoutState, target, transitionKey)
}
}
}
@@ -96,6 +103,7 @@
private fun CoroutineScope.animate(
layoutState: BaseSceneTransitionLayoutState,
target: SceneKey,
+ transitionKey: TransitionKey?,
startProgress: Float = 0f,
reversed: Boolean = false,
): TransitionState.Transition {
@@ -127,7 +135,7 @@
// Change the current layout state to start this new transition. This will compute the
// TransformationSpec associated to this transition, which we need to initialize the Animatable
// that will actually animate it.
- layoutState.startTransition(transition)
+ layoutState.startTransition(transition, transitionKey)
// The transformation now contains the spec that we should use to instantiate the Animatable.
val animationSpec = layoutState.transformationSpec.progressSpec
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
index c6851954..2596d4a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
@@ -55,6 +55,7 @@
// Implementation of [UserActionResult].
override val toScene: SceneKey = this
+ override val transitionKey: TransitionKey? = null
override val distance: UserActionDistance? = null
override fun toString(): String {
@@ -104,3 +105,13 @@
return "ValueKey(debugName=$debugName)"
}
}
+
+/**
+ * Key for a transition. This can be used to specify which transition spec should be used when
+ * starting the transition between two scenes.
+ */
+class TransitionKey(debugName: String, identity: Any = Object()) : Key(debugName, identity) {
+ override fun toString(): String {
+ return "TransitionKey(debugName=$debugName)"
+ }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
index 35754d6..23af5ac 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
@@ -54,7 +54,7 @@
private fun updateTransition(newTransition: SwipeTransition, force: Boolean = false) {
if (isDrivingTransition || force) {
- layoutState.startTransition(newTransition)
+ layoutState.startTransition(newTransition, newTransition.key)
// Initialize SwipeTransition.swipeSpec. Note that this must be called right after
// layoutState.startTransition() is called, because it computes the
@@ -237,7 +237,11 @@
}
swipeTransition.dragOffset += acceleratedOffset
- if (isNewFromScene || result.toScene != swipeTransition.toScene) {
+ if (
+ isNewFromScene ||
+ result.toScene != swipeTransition.toScene ||
+ result.transitionKey != swipeTransition.key
+ ) {
updateTransition(
SwipeTransition(fromScene, result).apply {
this.dragOffset = swipeTransition.dragOffset
@@ -484,6 +488,7 @@
private fun SwipeTransition(fromScene: Scene, result: UserActionResult): SwipeTransition {
return SwipeTransition(
+ result.transitionKey,
fromScene,
layoutImpl.scene(result.toScene),
computeAbsoluteDistance(fromScene, result),
@@ -491,6 +496,7 @@
}
internal class SwipeTransition(
+ val key: TransitionKey?,
val _fromScene: Scene,
val _toScene: Scene,
/**
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 7e0aa9c3..d904c8b 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
@@ -388,8 +388,9 @@
/**
* The result of performing a [UserAction].
*
- * Note: [UserActionResult] is implemented by [SceneKey], and you can also use [withDistance] to
- * easily create a [UserActionResult] with a fixed distance:
+ * Note: [UserActionResult] is implemented by [SceneKey], so you can also use scene keys directly
+ * when defining your [UserActionResult]s.
+ *
* ```
* SceneTransitionLayout(...) {
* scene(
@@ -397,7 +398,7 @@
* userActions =
* mapOf(
* Swipe.Right to Scene.Bar,
- * Swipe.Down to Scene.Doe withDistance 100.dp,
+ * Swipe.Down to Scene.Doe,
* )
* )
* ) { ... }
@@ -408,6 +409,9 @@
/** The scene we should be transitioning to during the [UserAction]. */
val toScene: SceneKey
+ /** The key of the transition that should be used. */
+ val transitionKey: TransitionKey?
+
/**
* The distance the action takes to animate from 0% to 100%.
*
@@ -416,6 +420,32 @@
val distance: UserActionDistance?
}
+/** Create a [UserActionResult] to [toScene] with the given [distance] and [transitionKey]. */
+fun UserActionResult(
+ toScene: SceneKey,
+ distance: UserActionDistance? = null,
+ transitionKey: TransitionKey? = null,
+): UserActionResult {
+ return object : UserActionResult {
+ override val toScene: SceneKey = toScene
+ override val transitionKey: TransitionKey? = transitionKey
+ override val distance: UserActionDistance? = distance
+ }
+}
+
+/** Create a [UserActionResult] to [toScene] with the given fixed [distance] and [transitionKey]. */
+fun UserActionResult(
+ toScene: SceneKey,
+ distance: Dp,
+ transitionKey: TransitionKey? = null,
+): UserActionResult {
+ return UserActionResult(
+ toScene = toScene,
+ distance = FixedDistance(distance),
+ transitionKey = transitionKey,
+ )
+}
+
interface UserActionDistance {
/**
* Return the **absolute** distance of the user action given the size of the scene we are
@@ -424,22 +454,6 @@
fun Density.absoluteDistance(fromSceneSize: IntSize, orientation: Orientation): Float
}
-/**
- * A utility function to make it possible to define user actions with a distance using the syntax
- * `Swipe.Up to Scene.foo withDistance 100.dp`
- */
-infix fun Pair<UserAction, SceneKey>.withDistance(
- distance: Dp
-): Pair<UserAction, UserActionResult> {
- val scene = second
- val distance = FixedDistance(distance)
- return first to
- object : UserActionResult {
- override val toScene: SceneKey = scene
- override val distance: UserActionDistance = distance
- }
-}
-
/** The user action has a fixed [absoluteDistance]. */
private class FixedDistance(private val distance: Dp) : UserActionDistance {
override fun Density.absoluteDistance(fromSceneSize: IntSize, orientation: Orientation): Float {
@@ -490,6 +504,7 @@
layoutImpl.density = density
layoutImpl.swipeSourceDetector = swipeSourceDetector
+ layoutImpl.transitionInterceptionThreshold = transitionInterceptionThreshold
}
layoutImpl.Content(modifier)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 956e326..aee6f9e 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -92,6 +92,7 @@
fun setTargetScene(
targetScene: SceneKey,
coroutineScope: CoroutineScope,
+ transitionKey: TransitionKey? = null,
): TransitionState.Transition?
}
@@ -213,11 +214,14 @@
}
/** Start a new [transition], instantly interrupting any ongoing transition if there was one. */
- internal fun startTransition(transition: TransitionState.Transition) {
+ internal fun startTransition(
+ transition: TransitionState.Transition,
+ transitionKey: TransitionKey?,
+ ) {
// Compute the [TransformationSpec] when the transition starts.
transformationSpec =
transitions
- .transitionSpec(transition.fromScene, transition.toScene)
+ .transitionSpec(transition.fromScene, transition.toScene, key = transitionKey)
.transformationSpec()
transitionState = transition
@@ -265,7 +269,11 @@
// Inspired by AnimateAsState.kt: let's poll the last value to avoid being one frame
// late.
val newKey = targetSceneChannel.tryReceive().getOrNull() ?: newKey
- animateToScene(layoutState = this@HoistedSceneTransitionLayoutScene, newKey)
+ animateToScene(
+ layoutState = this@HoistedSceneTransitionLayoutScene,
+ target = newKey,
+ transitionKey = null,
+ )
}
}
}
@@ -278,14 +286,14 @@
) : MutableSceneTransitionLayoutState, BaseSceneTransitionLayoutState(initialScene) {
override fun setTargetScene(
targetScene: SceneKey,
- coroutineScope: CoroutineScope
+ coroutineScope: CoroutineScope,
+ transitionKey: TransitionKey?,
): TransitionState.Transition? {
- return with(this) {
- coroutineScope.animateToScene(
- layoutState = this@MutableSceneTransitionLayoutStateImpl,
- target = targetScene,
- )
- }
+ return coroutineScope.animateToScene(
+ layoutState = this@MutableSceneTransitionLayoutStateImpl,
+ target = targetScene,
+ transitionKey = transitionKey,
+ )
}
override fun CoroutineScope.onChangeScene(scene: SceneKey) {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index ac423b7..b8f9359 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -42,32 +42,42 @@
internal val defaultSwipeSpec: SpringSpec<Float>,
internal val transitionSpecs: List<TransitionSpecImpl>,
) {
- private val cache = mutableMapOf<SceneKey, MutableMap<SceneKey, TransitionSpecImpl>>()
+ private val cache =
+ mutableMapOf<
+ SceneKey, MutableMap<SceneKey, MutableMap<TransitionKey?, TransitionSpecImpl>>
+ >()
- internal fun transitionSpec(from: SceneKey, to: SceneKey): TransitionSpecImpl {
- return cache.getOrPut(from) { mutableMapOf() }.getOrPut(to) { findSpec(from, to) }
+ internal fun transitionSpec(
+ from: SceneKey,
+ to: SceneKey,
+ key: TransitionKey?,
+ ): TransitionSpecImpl {
+ return cache
+ .getOrPut(from) { mutableMapOf() }
+ .getOrPut(to) { mutableMapOf() }
+ .getOrPut(key) { findSpec(from, to, key) }
}
- private fun findSpec(from: SceneKey, to: SceneKey): TransitionSpecImpl {
- val spec = transition(from, to) { it.from == from && it.to == to }
+ private fun findSpec(from: SceneKey, to: SceneKey, key: TransitionKey?): TransitionSpecImpl {
+ val spec = transition(from, to, key) { it.from == from && it.to == to }
if (spec != null) {
return spec
}
- val reversed = transition(from, to) { it.from == to && it.to == from }
+ val reversed = transition(from, to, key) { it.from == to && it.to == from }
if (reversed != null) {
return reversed.reversed()
}
val relaxedSpec =
- transition(from, to) {
+ transition(from, to, key) {
(it.from == from && it.to == null) || (it.to == to && it.from == null)
}
if (relaxedSpec != null) {
return relaxedSpec
}
- return transition(from, to) {
+ return transition(from, to, key) {
(it.from == to && it.to == null) || (it.to == from && it.from == null)
}
?.reversed()
@@ -77,11 +87,12 @@
private fun transition(
from: SceneKey,
to: SceneKey,
+ key: TransitionKey?,
filter: (TransitionSpecImpl) -> Boolean,
): TransitionSpecImpl? {
var match: TransitionSpecImpl? = null
transitionSpecs.fastForEach { spec ->
- if (filter(spec)) {
+ if (spec.key == key && filter(spec)) {
if (match != null) {
error("Found multiple transition specs for transition $from => $to")
}
@@ -92,7 +103,7 @@
}
private fun defaultTransition(from: SceneKey, to: SceneKey) =
- TransitionSpecImpl(from, to, TransformationSpec.EmptyProvider)
+ TransitionSpecImpl(key = null, from, to, TransformationSpec.EmptyProvider)
companion object {
internal val DefaultSwipeSpec =
@@ -107,6 +118,9 @@
/** The definition of a transition between [from] and [to]. */
interface TransitionSpec {
+ /** The key of this [TransitionSpec]. */
+ val key: TransitionKey?
+
/**
* The scene we are transitioning from. If `null`, this spec can be used to animate from any
* scene.
@@ -164,12 +178,14 @@
}
internal class TransitionSpecImpl(
+ override val key: TransitionKey?,
override val from: SceneKey?,
override val to: SceneKey?,
private val transformationSpec: () -> TransformationSpecImpl,
) : TransitionSpec {
override fun reversed(): TransitionSpecImpl {
return TransitionSpecImpl(
+ key = key,
from = to,
to = from,
transformationSpec = {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index 04e0937..d93911d 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -42,10 +42,14 @@
* any scene. For the animation specification to apply only when transitioning between two
* specific scenes, use [from] instead.
*
+ * If [key] is not `null`, then this transition will only be used if the same key is specified
+ * when triggering the transition.
+ *
* @see from
*/
fun to(
to: SceneKey,
+ key: TransitionKey? = null,
builder: TransitionBuilder.() -> Unit = {},
): TransitionSpec
@@ -55,7 +59,8 @@
* the destination scene via the [to] argument.
*
* When looking up which transition should be used when animating from scene A to scene B, we
- * pick the single transition matching one of these predicates (in order of importance):
+ * pick the single transition with the given [key] and matching one of these predicates (in
+ * order of importance):
* 1. from == A && to == B
* 2. to == A && from == B, which is then treated in reverse.
* 3. (from == A && to == null) || (from == null && to == B)
@@ -64,6 +69,7 @@
fun from(
from: SceneKey,
to: SceneKey? = null,
+ key: TransitionKey? = null,
builder: TransitionBuilder.() -> Unit = {},
): TransitionSpec
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index df186a1..9b16d46 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -49,21 +49,27 @@
val transitionSpecs = mutableListOf<TransitionSpecImpl>()
- override fun to(to: SceneKey, builder: TransitionBuilder.() -> Unit): TransitionSpec {
- return transition(from = null, to = to, builder)
+ override fun to(
+ to: SceneKey,
+ key: TransitionKey?,
+ builder: TransitionBuilder.() -> Unit
+ ): TransitionSpec {
+ return transition(from = null, to = to, key = key, builder)
}
override fun from(
from: SceneKey,
to: SceneKey?,
+ key: TransitionKey?,
builder: TransitionBuilder.() -> Unit
): TransitionSpec {
- return transition(from = from, to = to, builder)
+ return transition(from = from, to = to, key = key, builder)
}
private fun transition(
from: SceneKey?,
to: SceneKey?,
+ key: TransitionKey?,
builder: TransitionBuilder.() -> Unit,
): TransitionSpec {
fun transformationSpec(): TransformationSpecImpl {
@@ -75,7 +81,7 @@
)
}
- val spec = TransitionSpecImpl(from, to, ::transformationSpec)
+ val spec = TransitionSpecImpl(key, from, to, ::transformationSpec)
transitionSpecs.add(spec)
return spec
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
index e8cc0ec..2dc94a4 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
@@ -38,6 +38,8 @@
import com.android.compose.test.runMonotonicClockTest
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
import org.junit.Test
import org.junit.runner.RunWith
@@ -47,7 +49,7 @@
@RunWith(AndroidJUnit4::class)
class SceneGestureHandlerTest {
private class TestGestureScope(
- val coroutineScope: MonotonicClockTestScope,
+ private val testScope: MonotonicClockTestScope,
) {
private val layoutState =
MutableSceneTransitionLayoutStateImpl(SceneA, EmptyTestTransitions)
@@ -92,17 +94,16 @@
swipeSourceDetector = DefaultEdgeDetector,
transitionInterceptionThreshold = transitionInterceptionThreshold,
builder = scenesBuilder,
- coroutineScope = coroutineScope,
+ coroutineScope = testScope,
)
.apply { setScenesTargetSizeForTest(LAYOUT_SIZE) }
val sceneGestureHandler = layoutImpl.gestureHandler(Orientation.Vertical)
val horizontalSceneGestureHandler = layoutImpl.gestureHandler(Orientation.Horizontal)
- val draggable = sceneGestureHandler.draggable
fun nestedScrollConnection(nestedScrollBehavior: NestedScrollBehavior) =
SceneNestedScrollHandler(
- layoutImpl,
+ layoutImpl = layoutImpl,
orientation = sceneGestureHandler.orientation,
topOrLeftBehavior = nestedScrollBehavior,
bottomOrRightBehavior = nestedScrollBehavior,
@@ -117,8 +118,12 @@
fun up(fractionOfScreen: Float) =
if (fractionOfScreen < 0f) error("use down()") else -down(fractionOfScreen)
- // Offset y: 10% of the screen
- val offsetY10 = Offset(x = 0f, y = down(0.1f))
+ fun downOffset(fractionOfScreen: Float) =
+ if (fractionOfScreen < 0f) {
+ error("upOffset() is required, not implemented yet")
+ } else {
+ Offset(x = 0f, y = down(fractionOfScreen))
+ }
val transitionState: TransitionState
get() = layoutState.transitionState
@@ -127,15 +132,15 @@
get() = (transitionState as Transition).progress
fun advanceUntilIdle() {
- coroutineScope.testScheduler.advanceUntilIdle()
+ testScope.testScheduler.advanceUntilIdle()
}
fun runCurrent() {
- coroutineScope.testScheduler.runCurrent()
+ testScope.testScheduler.runCurrent()
}
fun assertIdle(currentScene: SceneKey) {
- assertWithMessage("transitionState must be Idle").that(transitionState is Idle).isTrue()
+ assertThat(transitionState).isInstanceOf(Idle::class.java)
assertWithMessage("currentScene does not match")
.that(transitionState.currentScene)
.isEqualTo(currentScene)
@@ -148,11 +153,8 @@
progress: Float? = null,
isUserInputOngoing: Boolean? = null
) {
- val transition = transitionState
- assertWithMessage("transitionState must be Transition")
- .that(transition is Transition)
- .isTrue()
- transition as Transition
+ assertThat(transitionState).isInstanceOf(Transition::class.java)
+ val transition = transitionState as Transition
if (currentScene != null)
assertWithMessage("currentScene does not match")
@@ -180,47 +182,115 @@
.that(transition.isUserInputOngoing)
.isEqualTo(isUserInputOngoing)
}
+
+ fun onDragStarted(
+ startedPosition: Offset = Offset.Zero,
+ overSlop: Float,
+ pointersDown: Int = 1
+ ) {
+ // overSlop should be 0f only if the drag gesture starts with startDragImmediately
+ if (overSlop == 0f) error("Consider using onDragStartedImmediately()")
+ onDragStarted(sceneGestureHandler.draggable, startedPosition, overSlop, pointersDown)
+ }
+
+ fun onDragStartedImmediately(startedPosition: Offset = Offset.Zero, pointersDown: Int = 1) {
+ onDragStarted(
+ sceneGestureHandler.draggable,
+ startedPosition,
+ overSlop = 0f,
+ pointersDown
+ )
+ }
+
+ fun onDragStarted(
+ draggableHandler: DraggableHandler,
+ startedPosition: Offset = Offset.Zero,
+ overSlop: Float = 0f,
+ pointersDown: Int = 1
+ ) {
+ draggableHandler.onDragStarted(
+ startedPosition = startedPosition,
+ overSlop = overSlop,
+ pointersDown = pointersDown,
+ )
+
+ // MultiPointerDraggable will always call onDelta with the initial overSlop right after
+ onDelta(pixels = overSlop)
+ }
+
+ fun onDelta(pixels: Float) {
+ sceneGestureHandler.draggable.onDelta(pixels = pixels)
+ }
+
+ fun onDragStopped(velocity: Float) {
+ sceneGestureHandler.draggable.onDragStopped(velocity = velocity)
+ runCurrent()
+ }
+
+ fun NestedScrollConnection.scroll(
+ available: Offset,
+ consumedByScroll: Offset = Offset.Zero,
+ ) {
+ val consumedByPreScroll =
+ onPreScroll(
+ available = available,
+ source = NestedScrollSource.Drag,
+ )
+ val consumed = consumedByPreScroll + consumedByScroll
+
+ onPostScroll(
+ consumed = consumed,
+ available = available - consumed,
+ source = NestedScrollSource.Drag
+ )
+ }
+
+ fun NestedScrollConnection.preFling(
+ available: Velocity,
+ coroutineScope: CoroutineScope = testScope,
+ ) {
+ // onPreFling is a suspend function that returns the consumed velocity once it finishes
+ // consuming it. In the current scenario, it returns after completing the animation.
+ // To return immediately, we can initiate a job that allows us to check the status
+ // before the animation starts.
+ coroutineScope.launch { onPreFling(available = available) }
+ runCurrent()
+ }
}
private fun runGestureTest(block: suspend TestGestureScope.() -> Unit) {
- runMonotonicClockTest { TestGestureScope(coroutineScope = this).block() }
- }
+ runMonotonicClockTest {
+ val testGestureScope = TestGestureScope(testScope = this)
- private fun DraggableHandler.onDragStarted(
- overSlop: Float = 0f,
- startedPosition: Offset = Offset.Zero,
- ) {
- onDragStarted(startedPosition, overSlop)
- // MultiPointerDraggable will always call onDelta with the initial overSlop right after
- onDelta(overSlop)
+ // run the test
+ testGestureScope.block()
+ }
}
@Test fun testPreconditions() = runGestureTest { assertIdle(currentScene = SceneA) }
@Test
fun onDragStarted_shouldStartATransition() = runGestureTest {
- draggable.onDragStarted(overSlop = down(0.1f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
}
@Test
fun afterSceneTransitionIsStarted_interceptDragEvents() = runGestureTest {
- draggable.onDragStarted(overSlop = down(0.1f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
assertThat(progress).isEqualTo(0.1f)
- draggable.onDelta(pixels = down(0.1f))
+ onDelta(pixels = down(fractionOfScreen = 0.1f))
assertThat(progress).isEqualTo(0.2f)
}
@Test
fun onDragStoppedAfterDrag_velocityLowerThanThreshold_remainSameScene() = runGestureTest {
- draggable.onDragStarted(overSlop = down(0.1f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
- draggable.onDragStopped(
- velocity = velocityThreshold - 0.01f,
- )
+ onDragStopped(velocity = velocityThreshold - 0.01f)
assertTransition(currentScene = SceneA)
// wait for the stop animation
@@ -230,11 +300,10 @@
@Test
fun onDragStoppedAfterDrag_velocityAtLeastThreshold_goToNextScene() = runGestureTest {
- draggable.onDragStarted(overSlop = down(0.1f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
- draggable.onDragStopped(velocity = velocityThreshold)
-
+ onDragStopped(velocity = velocityThreshold)
assertTransition(currentScene = SceneC)
// wait for the stop animation
@@ -244,10 +313,10 @@
@Test
fun onDragStoppedAfterStarted_returnToIdle() = runGestureTest {
- draggable.onDragStarted(overSlop = down(0.1f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
- draggable.onDragStopped(velocity = 0f)
+ onDragStopped(velocity = 0f)
advanceUntilIdle()
assertIdle(currentScene = SceneA)
}
@@ -255,7 +324,7 @@
@Test
fun onDragReversedDirection_changeToScene() = runGestureTest {
// Drag A -> B with progress 0.6
- draggable.onDragStarted(overSlop = -60f)
+ onDragStarted(overSlop = -60f)
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
@@ -264,7 +333,7 @@
)
// Reverse direction such that A -> C now with 0.4
- draggable.onDelta(100f)
+ onDelta(pixels = 100f)
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
@@ -273,7 +342,7 @@
)
// After the drag stopped scene C should be committed
- draggable.onDragStopped(velocity = velocityThreshold)
+ onDragStopped(velocity = velocityThreshold)
assertTransition(currentScene = SceneC, fromScene = SceneA, toScene = SceneC)
// wait for the stop animation
@@ -283,9 +352,12 @@
@Test
fun onDragStartedWithoutActionsInBothDirections_stayIdle() = runGestureTest {
- horizontalSceneGestureHandler.draggable.onDragStarted(up(0.3f))
+ val horizontalDraggableHandler = horizontalSceneGestureHandler.draggable
+
+ onDragStarted(horizontalDraggableHandler, overSlop = up(fractionOfScreen = 0.3f))
assertIdle(currentScene = SceneA)
- horizontalSceneGestureHandler.draggable.onDragStarted(down(0.3f))
+
+ onDragStarted(horizontalDraggableHandler, overSlop = down(fractionOfScreen = 0.3f))
assertIdle(currentScene = SceneA)
}
@@ -294,7 +366,7 @@
navigateToSceneC()
// We are on SceneC which has no action in Down direction
- draggable.onDragStarted(10f)
+ onDragStarted(overSlop = 10f)
assertTransition(
currentScene = SceneC,
fromScene = SceneC,
@@ -303,7 +375,7 @@
)
// Reverse drag direction, it will consume the previous drag
- draggable.onDelta(-10f)
+ onDelta(pixels = -10f)
assertTransition(
currentScene = SceneC,
fromScene = SceneC,
@@ -312,7 +384,7 @@
)
// Continue reverse drag direction, it should record progress to Scene B
- draggable.onDelta(-10f)
+ onDelta(pixels = -10f)
assertTransition(
currentScene = SceneC,
fromScene = SceneC,
@@ -326,7 +398,10 @@
navigateToSceneC()
// Start dragging from the bottom
- draggable.onDragStarted(up(0.1f), Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE))
+ onDragStarted(
+ startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE),
+ overSlop = up(fractionOfScreen = 0.1f)
+ )
assertTransition(
currentScene = SceneC,
fromScene = SceneC,
@@ -337,14 +412,14 @@
@Test
fun onDragToExactlyZero_toSceneIsSet() = runGestureTest {
- draggable.onDragStarted(down(0.3f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.3f))
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
toScene = SceneC,
progress = 0.3f
)
- draggable.onDelta(up(0.3f))
+ onDelta(pixels = up(fractionOfScreen = 0.3f))
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
@@ -355,8 +430,8 @@
private fun TestGestureScope.navigateToSceneC() {
assertIdle(currentScene = SceneA)
- draggable.onDragStarted(down(1f))
- draggable.onDragStopped(0f)
+ onDragStarted(overSlop = down(fractionOfScreen = 1f))
+ onDragStopped(velocity = 0f)
advanceUntilIdle()
assertIdle(currentScene = SceneC)
}
@@ -364,7 +439,7 @@
@Test
fun onAccelaratedScroll_scrollToThirdScene() = runGestureTest {
// Drag A -> B with progress 0.2
- draggable.onDragStarted(overSlop = up(0.2f))
+ onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
@@ -373,13 +448,13 @@
)
// Start animation A -> B with progress 0.2 -> 1.0
- draggable.onDragStopped(velocity = -velocityThreshold)
+ onDragStopped(velocity = -velocityThreshold)
assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
// While at A -> B do a 100% screen drag (progress 1.2). This should go past B and change
// the transition to B -> C with progress 0.2
- draggable.onDragStarted()
- draggable.onDelta(up(1f))
+ onDragStartedImmediately()
+ onDelta(pixels = up(fractionOfScreen = 1f))
assertTransition(
currentScene = SceneB,
fromScene = SceneB,
@@ -388,7 +463,7 @@
)
// After the drag stopped scene C should be committed
- draggable.onDragStopped(velocity = -velocityThreshold)
+ onDragStopped(velocity = -velocityThreshold)
assertTransition(currentScene = SceneC, fromScene = SceneB, toScene = SceneC)
// wait for the stop animation
@@ -398,9 +473,9 @@
@Test
fun onAccelaratedScrollBothTargetsBecomeNull_settlesToIdle() = runGestureTest {
- draggable.onDragStarted(overSlop = up(0.2f))
- draggable.onDelta(up(0.2f))
- draggable.onDragStopped(velocity = -velocityThreshold)
+ onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
+ onDelta(pixels = up(fractionOfScreen = 0.2f))
+ onDragStopped(velocity = -velocityThreshold)
assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
mutableUserActionsA.remove(Swipe.Up)
@@ -409,83 +484,79 @@
mutableUserActionsB.remove(Swipe.Down)
// start accelaratedScroll and scroll over to B -> null
- draggable.onDragStarted()
- draggable.onDelta(up(0.5f))
- draggable.onDelta(up(0.5f))
+ onDragStartedImmediately()
+ onDelta(pixels = up(fractionOfScreen = 0.5f))
+ onDelta(pixels = up(fractionOfScreen = 0.5f))
// here onDragStopped is already triggered, but subsequent onDelta/onDragStopped calls may
// still be called. Make sure that they don't crash or change the scene
- draggable.onDelta(up(0.5f))
- draggable.onDragStopped(0f)
+ onDelta(pixels = up(fractionOfScreen = 0.5f))
+ onDragStopped(velocity = 0f)
advanceUntilIdle()
assertIdle(SceneB)
// These events can still come in after the animation has settled
- draggable.onDelta(up(0.5f))
- draggable.onDragStopped(0f)
+ onDelta(pixels = up(fractionOfScreen = 0.5f))
+ onDragStopped(velocity = 0f)
assertIdle(SceneB)
}
@Test
fun onDragTargetsChanged_targetStaysTheSame() = runGestureTest {
- draggable.onDragStarted(up(0.1f))
+ onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
mutableUserActionsA[Swipe.Up] = SceneC
- draggable.onDelta(up(0.1f))
+ onDelta(pixels = up(fractionOfScreen = 0.1f))
// target stays B even though UserActions changed
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.2f)
- draggable.onDragStopped(down(0.1f))
+ onDragStopped(velocity = down(fractionOfScreen = 0.1f))
advanceUntilIdle()
// now target changed to C for new drag
- draggable.onDragStarted(up(0.1f))
+ onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.1f)
}
@Test
fun onDragTargetsChanged_targetsChangeWhenStartingNewDrag() = runGestureTest {
- draggable.onDragStarted(up(0.1f))
+ onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
mutableUserActionsA[Swipe.Up] = SceneC
- draggable.onDelta(up(0.1f))
- draggable.onDragStopped(down(0.1f))
+ onDelta(pixels = up(fractionOfScreen = 0.1f))
+ onDragStopped(velocity = down(fractionOfScreen = 0.1f))
// now target changed to C for new drag that started before previous drag settled to Idle
- draggable.onDragStarted(overSlop = 0f)
- draggable.onDelta(up(0.1f))
+ onDragStartedImmediately()
+ onDelta(pixels = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.3f)
}
@Test
fun startGestureDuringAnimatingOffset_shouldImmediatelyStopTheAnimation() = runGestureTest {
- draggable.onDragStarted(overSlop = down(0.1f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
- draggable.onDragStopped(
- velocity = velocityThreshold,
- )
+ onDragStopped(velocity = velocityThreshold)
- // The stop animation is not started yet
- assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isFalse()
-
- runCurrent()
-
- assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isTrue()
- assertThat(sceneGestureHandler.isDrivingTransition).isTrue()
assertTransition(currentScene = SceneC)
+ assertThat(sceneGestureHandler.isDrivingTransition).isTrue()
+ assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isTrue()
// Start a new gesture while the offset is animating
- draggable.onDragStarted()
+ onDragStartedImmediately()
assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isFalse()
}
@Test
fun onInitialPreScroll_EdgeWithOverscroll_doNotChangeState() = runGestureTest {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
- nestedScroll.onPreScroll(available = offsetY10, source = NestedScrollSource.Drag)
+ nestedScroll.onPreScroll(
+ available = downOffset(fractionOfScreen = 0.1f),
+ source = NestedScrollSource.Drag
+ )
assertIdle(currentScene = SceneA)
}
@@ -509,40 +580,29 @@
val consumed =
nestedScroll.onPostScroll(
consumed = Offset.Zero,
- available = offsetY10,
+ available = downOffset(fractionOfScreen = 0.1f),
source = NestedScrollSource.Drag
)
assertTransition(currentScene = SceneA)
assertThat(progress).isEqualTo(0.1f)
- assertThat(consumed).isEqualTo(offsetY10)
- }
-
- private fun NestedScrollConnection.scroll(
- available: Offset,
- consumedByScroll: Offset = Offset.Zero,
- ) {
- val consumedByPreScroll =
- onPreScroll(available = available, source = NestedScrollSource.Drag)
- val consumed = consumedByPreScroll + consumedByScroll
- onPostScroll(
- consumed = consumed,
- available = available - consumed,
- source = NestedScrollSource.Drag
- )
+ assertThat(consumed).isEqualTo(downOffset(fractionOfScreen = 0.1f))
}
@Test
fun afterSceneTransitionIsStarted_interceptPreScrollEvents() = runGestureTest {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
- nestedScroll.scroll(available = offsetY10)
+ nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
assertThat(progress).isEqualTo(0.1f)
// start intercept preScroll
val consumed =
- nestedScroll.onPreScroll(available = offsetY10, source = NestedScrollSource.Drag)
+ nestedScroll.onPreScroll(
+ available = downOffset(fractionOfScreen = 0.1f),
+ source = NestedScrollSource.Drag
+ )
assertThat(progress).isEqualTo(0.2f)
// do nothing on postScroll
@@ -553,12 +613,12 @@
)
assertThat(progress).isEqualTo(0.2f)
- nestedScroll.scroll(available = offsetY10)
+ nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
assertThat(progress).isEqualTo(0.3f)
assertTransition(currentScene = SceneA)
}
- private suspend fun TestGestureScope.preScrollAfterSceneTransition(
+ private fun TestGestureScope.preScrollAfterSceneTransition(
firstScroll: Float,
secondScroll: Float
) {
@@ -567,10 +627,13 @@
nestedScroll.scroll(available = Offset(0f, firstScroll))
// stop scene transition (start the "stop animation")
- nestedScroll.onPreFling(available = Velocity.Zero)
+ nestedScroll.preFling(available = Velocity.Zero)
// a pre scroll event, that could be intercepted by SceneGestureHandler
- nestedScroll.onPreScroll(Offset(0f, secondScroll), NestedScrollSource.Drag)
+ nestedScroll.onPreScroll(
+ available = Offset(0f, secondScroll),
+ source = NestedScrollSource.Drag
+ )
}
@Test
@@ -610,16 +673,17 @@
preScrollAfterSceneTransition(firstScroll = firstScroll, secondScroll = secondScroll)
+ advanceUntilIdle()
assertIdle(SceneB)
}
@Test
fun onPreFling_velocityLowerThanThreshold_remainSameScene() = runGestureTest {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
- nestedScroll.scroll(available = offsetY10)
+ nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
- nestedScroll.onPreFling(available = Velocity.Zero)
+ nestedScroll.preFling(available = Velocity.Zero)
assertTransition(currentScene = SceneA)
// wait for the stop animation
@@ -627,15 +691,15 @@
assertIdle(currentScene = SceneA)
}
- private suspend fun TestGestureScope.flingAfterScroll(
+ private fun TestGestureScope.flingAfterScroll(
use: NestedScrollBehavior,
idleAfterScroll: Boolean,
) {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = use)
- nestedScroll.scroll(available = offsetY10)
+ nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
if (idleAfterScroll) assertIdle(SceneA) else assertTransition(SceneA)
- nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold))
+ nestedScroll.preFling(available = Velocity(0f, velocityThreshold))
}
@Test
@@ -679,19 +743,22 @@
}
/** we started the scroll in the scene, then fling with the velocityThreshold */
- private suspend fun TestGestureScope.flingAfterScrollStartedInScene(
+ private fun TestGestureScope.flingAfterScrollStartedInScene(
use: NestedScrollBehavior,
idleAfterScroll: Boolean,
) {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = use)
// scroll consumed in child
- nestedScroll.scroll(available = offsetY10, consumedByScroll = offsetY10)
+ nestedScroll.scroll(
+ available = downOffset(fractionOfScreen = 0.1f),
+ consumedByScroll = downOffset(fractionOfScreen = 0.1f)
+ )
// scroll offsetY10 is all available for parents
- nestedScroll.scroll(available = offsetY10)
+ nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
if (idleAfterScroll) assertIdle(SceneA) else assertTransition(SceneA)
- nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold))
+ nestedScroll.preFling(available = Velocity(0f, velocityThreshold))
}
@Test
@@ -732,20 +799,20 @@
@Test
fun beforeDraggableStart_drag_shouldBeIgnored() = runGestureTest {
- draggable.onDelta(down(0.1f))
+ onDelta(pixels = down(fractionOfScreen = 0.1f))
assertIdle(currentScene = SceneA)
}
@Test
fun beforeDraggableStart_stop_shouldBeIgnored() = runGestureTest {
- draggable.onDragStopped(velocityThreshold)
+ onDragStopped(velocity = velocityThreshold)
assertIdle(currentScene = SceneA)
}
@Test
fun beforeNestedScrollStart_stop_shouldBeIgnored() = runGestureTest {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
- nestedScroll.onPreFling(Velocity(0f, velocityThreshold))
+ nestedScroll.preFling(available = Velocity(0f, velocityThreshold))
assertIdle(currentScene = SceneA)
}
@@ -753,8 +820,10 @@
fun startNestedScrollWhileDragging() = runGestureTest {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways)
+ val offsetY10 = downOffset(fractionOfScreen = 0.1f)
+
// Start a drag and then stop it, given that
- draggable.onDragStarted(overSlop = up(0.1f))
+ onDragStarted(overSlop = up(0.1f))
assertTransition(currentScene = SceneA)
assertThat(progress).isEqualTo(0.1f)
@@ -764,7 +833,7 @@
assertThat(progress).isEqualTo(0.2f)
// this should be ignored, we are scrolling now!
- draggable.onDragStopped(-velocityThreshold)
+ onDragStopped(-velocityThreshold)
assertTransition(currentScene = SceneA)
nestedScroll.scroll(available = -offsetY10)
@@ -773,7 +842,7 @@
nestedScroll.scroll(available = -offsetY10)
assertThat(progress).isEqualTo(0.4f)
- nestedScroll.onPreFling(available = Velocity(0f, -velocityThreshold))
+ nestedScroll.preFling(available = Velocity(0f, -velocityThreshold))
assertTransition(currentScene = SceneB)
// wait for the stop animation
@@ -788,7 +857,7 @@
// Swipe up from the middle to transition to scene B.
val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
- draggable.onDragStarted(startedPosition = middle, overSlop = up(0.1f))
+ onDragStarted(startedPosition = middle, overSlop = up(0.1f))
assertTransition(
currentScene = SceneC,
fromScene = SceneC,
@@ -802,7 +871,7 @@
// should intercept it. Because it is intercepted, the overSlop passed to onDragStarted()
// should be 0f.
assertThat(sceneGestureHandler.shouldImmediatelyIntercept(middle)).isTrue()
- draggable.onDragStarted(startedPosition = middle, overSlop = 0f)
+ onDragStartedImmediately(startedPosition = middle)
// We should have intercepted the transition, so the transition should be the same object.
assertTransition(currentScene = SceneC, fromScene = SceneC, toScene = SceneB)
@@ -813,7 +882,7 @@
// instead animate from C to A.
val bottom = Offset(SCREEN_SIZE / 2, SCREEN_SIZE)
assertThat(sceneGestureHandler.shouldImmediatelyIntercept(bottom)).isFalse()
- draggable.onDragStarted(startedPosition = bottom, overSlop = up(0.1f))
+ onDragStarted(startedPosition = bottom, overSlop = up(0.1f))
assertTransition(
currentScene = SceneC,
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index 9a25d43..c61917d 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -45,7 +45,10 @@
@Test
fun isTransitioningTo_transition() {
val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
- state.startTransition(transition(from = TestScenes.SceneA, to = TestScenes.SceneB))
+ state.startTransition(
+ transition(from = TestScenes.SceneA, to = TestScenes.SceneB),
+ transitionKey = null
+ )
assertThat(state.isTransitioning()).isTrue()
assertThat(state.isTransitioning(from = TestScenes.SceneA)).isTrue()
@@ -116,4 +119,43 @@
testScheduler.advanceUntilIdle()
assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneB))
}
+
+ @Test
+ fun setTargetScene_withTransitionKey() = runMonotonicClockTest {
+ val transitionkey = TransitionKey(debugName = "foo")
+ val state =
+ MutableSceneTransitionLayoutState(
+ TestScenes.SceneA,
+ transitions =
+ transitions {
+ from(TestScenes.SceneA, to = TestScenes.SceneB) { fade(TestElements.Foo) }
+ from(TestScenes.SceneA, to = TestScenes.SceneB, key = transitionkey) {
+ fade(TestElements.Foo)
+ fade(TestElements.Bar)
+ }
+ },
+ )
+ as MutableSceneTransitionLayoutStateImpl
+
+ // Default transition from A to B.
+ assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNotNull()
+ assertThat(state.transformationSpec.transformations).hasSize(1)
+
+ // Go back to A.
+ state.setTargetScene(TestScenes.SceneA, coroutineScope = this)
+ testScheduler.advanceUntilIdle()
+ assertThat(state.currentTransition).isNull()
+ assertThat(state.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+
+ // Specific transition from A to B.
+ assertThat(
+ state.setTargetScene(
+ TestScenes.SceneB,
+ coroutineScope = this,
+ transitionKey = transitionkey,
+ )
+ )
+ .isNotNull()
+ assertThat(state.transformationSpec.transformations).hasSize(2)
+ }
}
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 af1a5b8..543ed04 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
@@ -384,7 +384,13 @@
scene(
TestScenes.SceneA,
userActions =
- mapOf(Swipe.Down to TestScenes.SceneB withDistance verticalSwipeDistance),
+ mapOf(
+ Swipe.Down to
+ UserActionResult(
+ toScene = TestScenes.SceneB,
+ distance = verticalSwipeDistance,
+ )
+ ),
) {
Spacer(Modifier.fillMaxSize())
}
@@ -492,4 +498,54 @@
}
assertThat(layoutState.currentTransition).isNotNull()
}
+
+ @Test
+ fun transitionKey() {
+ val transitionkey = TransitionKey(debugName = "foo")
+ val state =
+ MutableSceneTransitionLayoutState(
+ TestScenes.SceneA,
+ transitions {
+ from(TestScenes.SceneA, to = TestScenes.SceneB) { fade(TestElements.Foo) }
+ from(TestScenes.SceneA, to = TestScenes.SceneB, key = transitionkey) {
+ fade(TestElements.Foo)
+ fade(TestElements.Bar)
+ }
+ }
+ )
+ as MutableSceneTransitionLayoutStateImpl
+
+ var touchSlop = 0f
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ SceneTransitionLayout(state, Modifier.size(LayoutWidth, LayoutHeight)) {
+ scene(
+ TestScenes.SceneA,
+ userActions =
+ mapOf(
+ Swipe.Down to TestScenes.SceneB,
+ Swipe.Up to
+ UserActionResult(TestScenes.SceneB, transitionKey = transitionkey)
+ )
+ ) {
+ Box(Modifier.fillMaxSize())
+ }
+ scene(TestScenes.SceneB) { Box(Modifier.fillMaxSize()) }
+ }
+ }
+
+ // Swipe down for the default transition from A to B.
+ rule.onRoot().performTouchInput {
+ down(middle)
+ moveBy(Offset(0f, touchSlop), delayMillis = 1_000)
+ }
+
+ assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB)).isTrue()
+ assertThat(state.transformationSpec.transformations).hasSize(1)
+
+ // Move the pointer up to swipe to scene B using the new transition.
+ rule.onRoot().performTouchInput { moveBy(Offset(0f, -1.dp.toPx()), delayMillis = 1_000) }
+ assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB)).isTrue()
+ assertThat(state.transformationSpec.transformations).hasSize(2)
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
index 140baca..1beafcc 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
@@ -177,7 +177,7 @@
// to B we defined.
val transformations =
transitions
- .transitionSpec(from = TestScenes.SceneB, to = TestScenes.SceneA)
+ .transitionSpec(from = TestScenes.SceneB, to = TestScenes.SceneA, key = null)
.transformationSpec()
.transformations
@@ -207,7 +207,7 @@
// A => B does not have a custom spec.
assertThat(
transitions
- .transitionSpec(from = TestScenes.SceneA, to = TestScenes.SceneB)
+ .transitionSpec(from = TestScenes.SceneA, to = TestScenes.SceneB, key = null)
.transformationSpec()
.swipeSpec
)
@@ -216,7 +216,7 @@
// A => C has a custom swipe spec.
assertThat(
transitions
- .transitionSpec(from = TestScenes.SceneA, to = TestScenes.SceneC)
+ .transitionSpec(from = TestScenes.SceneA, to = TestScenes.SceneC, key = null)
.transformationSpec()
.swipeSpec
)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
index fbcd5b2..0245cf2 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
@@ -3,7 +3,9 @@
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.TestMonotonicFrameClock
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.withContext
@@ -12,16 +14,38 @@
* function.
*
* The [TestCoroutineScheduler] is passed to provide the functionality to wait for idle.
+ *
+ * Note: Please refer to the documentation for [runTest], as this feature utilizes it. This will
+ * provide a comprehensive understanding of all its behaviors.
*/
-@OptIn(ExperimentalTestApi::class)
+@OptIn(ExperimentalTestApi::class, ExperimentalCoroutinesApi::class)
fun runMonotonicClockTest(block: suspend MonotonicClockTestScope.() -> Unit) = runTest {
- // We need a CoroutineScope (like a TestScope) to create a TestMonotonicFrameClock.
- withContext(TestMonotonicFrameClock(this)) {
- MonotonicClockTestScope(coroutineScope = this, testScheduler = testScheduler).block()
+ val testScope: TestScope = this
+
+ withContext(TestMonotonicFrameClock(coroutineScope = testScope)) {
+ val testScopeWithMonotonicFrameClock: CoroutineScope = this
+
+ val scope =
+ MonotonicClockTestScope(
+ testScope = testScopeWithMonotonicFrameClock,
+ testScheduler = testScope.testScheduler,
+ backgroundScope = backgroundScope,
+ )
+
+ // Run the test
+ scope.block()
}
}
+/**
+ * A coroutine scope that for launching test coroutines for Compose.
+ *
+ * @param testScheduler The delay-skipping scheduler used by the test dispatchers running the code
+ * in this scope (see [TestScope.testScheduler]).
+ * @param backgroundScope A scope for background work (see [TestScope.backgroundScope]).
+ */
class MonotonicClockTestScope(
- coroutineScope: CoroutineScope,
- val testScheduler: TestCoroutineScheduler
-) : CoroutineScope by coroutineScope
+ testScope: CoroutineScope,
+ val testScheduler: TestCoroutineScheduler,
+ val backgroundScope: CoroutineScope,
+) : CoroutineScope by testScope
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
index 6d6d575..2d71a6e 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
@@ -89,7 +89,7 @@
SceneTransitionLayout(
currentScene,
onChangeScene,
- transitions { from(fromScene, to = toScene, transition) },
+ transitions { from(fromScene, to = toScene, builder = transition) },
layoutModifier,
) {
scene(fromScene, content = fromSceneContent)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
index 4b21105..e39d7ed 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
@@ -20,8 +20,10 @@
import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
@@ -32,6 +34,12 @@
private val backgroundDispatcher: CoroutineDispatcher,
private val secureSettingsRepository: SecureSettingsRepository,
) {
+ val isNotificationHistoryEnabled: Flow<Boolean> =
+ secureSettingsRepository
+ .intSetting(name = Settings.Secure.NOTIFICATION_HISTORY_ENABLED)
+ .map { it == 1 }
+ .distinctUntilChanged()
+
/** The current state of the notification setting. */
val isShowNotificationsOnLockScreenEnabled: StateFlow<Boolean> =
secureSettingsRepository
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt
index 9ec6ec8..04e8090 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt
@@ -23,6 +23,8 @@
class NotificationSettingsInteractor(
private val repository: NotificationSettingsRepository,
) {
+ val isNotificationHistoryEnabled = repository.isNotificationHistoryEnabled
+
/** Should notifications be visible on the lockscreen? */
val isShowNotificationsOnLockScreenEnabled: StateFlow<Boolean> =
repository.isShowNotificationsOnLockScreenEnabled
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/SysuiTestCaseSelfTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/SysuiTestCaseSelfTest.kt
index be6bb9c..107293e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/SysuiTestCaseSelfTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/SysuiTestCaseSelfTest.kt
@@ -24,6 +24,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class SysuiTestCaseSelfTest : SysuiTestCase() {
private val contextBeforeSetup = context
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 9287edf..c2f0c6f 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
@@ -31,6 +31,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class AccessibilityQsShortcutsRepositoryImplTest : SysuiTestCase() {
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
index c86c747..fa47a02 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
@@ -37,6 +37,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class ColorCorrectionRepositoryImplTest : SysuiTestCase() {
private val testUser1 = UserHandle.of(1)!!
@@ -58,15 +59,21 @@
}
@Test
+ fun isEnabled_settingNotInitialized_returnsFalseByDefault() =
+ scope.runTest {
+ val actualValue by collectLastValue(underTest.isEnabled(testUser1))
+
+ runCurrent()
+
+ Truth.assertThat(actualValue).isFalse()
+ }
+
+ @Test
fun isEnabled_initiallyGetsSettingsValue() =
scope.runTest {
val actualValue by collectLastValue(underTest.isEnabled(testUser1))
- settings.putIntForUser(
- SETTING_NAME,
- ENABLED,
- testUser1.identifier
- )
+ settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
runCurrent()
Truth.assertThat(actualValue).isTrue()
@@ -77,25 +84,13 @@
scope.runTest {
val flowValues: List<Boolean> by collectValues(underTest.isEnabled(testUser1))
- settings.putIntForUser(
- SETTING_NAME,
- DISABLED,
- testUser1.identifier
- )
+ settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
runCurrent()
- settings.putIntForUser(
- SETTING_NAME,
- ENABLED,
- testUser1.identifier
- )
+ settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
runCurrent()
- settings.putIntForUser(
- SETTING_NAME,
- DISABLED,
- testUser1.identifier
- )
+ settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
runCurrent()
Truth.assertThat(flowValues.size).isEqualTo(3)
@@ -108,26 +103,14 @@
val lastValueUser1 by collectLastValue(underTest.isEnabled(testUser1))
val lastValueUser2 by collectLastValue(underTest.isEnabled(testUser2))
- settings.putIntForUser(
- SETTING_NAME,
- DISABLED,
- testUser1.identifier
- )
- settings.putIntForUser(
- SETTING_NAME,
- DISABLED,
- testUser2.identifier
- )
+ settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
+ settings.putIntForUser(SETTING_NAME, DISABLED, testUser2.identifier)
runCurrent()
Truth.assertThat(lastValueUser1).isFalse()
Truth.assertThat(lastValueUser2).isFalse()
- settings.putIntForUser(
- SETTING_NAME,
- ENABLED,
- testUser1.identifier
- )
+ settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
runCurrent()
Truth.assertThat(lastValueUser1).isTrue()
@@ -141,11 +124,7 @@
runCurrent()
Truth.assertThat(success).isTrue()
- val actualValue =
- settings.getIntForUser(
- SETTING_NAME,
- testUser1.identifier
- )
+ val actualValue = settings.getIntForUser(SETTING_NAME, testUser1.identifier)
Truth.assertThat(actualValue).isEqualTo(ENABLED)
}
@@ -156,11 +135,7 @@
runCurrent()
Truth.assertThat(success).isTrue()
- val actualValue =
- settings.getIntForUser(
- SETTING_NAME,
- testUser1.identifier
- )
+ val actualValue = settings.getIntForUser(SETTING_NAME, testUser1.identifier)
Truth.assertThat(actualValue).isEqualTo(DISABLED)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
index 4853529..3d8159e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
@@ -24,6 +24,7 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -37,6 +38,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class ColorInversionRepositoryImplTest : SysuiTestCase() {
private val testUser1 = UserHandle.of(1)!!
@@ -58,6 +60,16 @@
}
@Test
+ fun isEnabled_settingNotInitialized_returnsFalseByDefault() =
+ scope.runTest {
+ val actualValue by collectLastValue(underTest.isEnabled(testUser1))
+
+ runCurrent()
+
+ Truth.assertThat(actualValue).isFalse()
+ }
+
+ @Test
fun isEnabled_initiallyGetsSettingsValue() =
scope.runTest {
val actualValue by collectLastValue(underTest.isEnabled(testUser1))
@@ -71,8 +83,7 @@
@Test
fun isEnabled_settingUpdated_valueUpdated() =
scope.runTest {
- val flowValues: List<Boolean> by
- collectValues(underTest.isEnabled(testUser1))
+ val flowValues: List<Boolean> by collectValues(underTest.isEnabled(testUser1))
settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt
index ce22e28..ed3b4c0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt
@@ -31,6 +31,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class UserA11yQsShortcutsRepositoryTest : SysuiTestCase() {
private val secureSettings = FakeSettings()
private val testDispatcher = StandardTestDispatcher()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt
index 39f0d57..0596205 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt
@@ -34,6 +34,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class FaceHelpMessageDeferralTest : SysuiTestCase() {
val threshold = .75f
@Mock lateinit var logger: BiometricMessageDeferralLogger
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt
index a67b093..d317aeb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt
@@ -37,6 +37,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class EmergencyServicesRepositoryImplTest : SysuiTestCase() {
private val kosmos = testKosmos()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
index 4aea4f3..db83b3b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
@@ -29,6 +29,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class PrimaryBouncerCallbackInteractorTest : SysuiTestCase() {
private val mPrimaryBouncerCallbackInteractor = PrimaryBouncerCallbackInteractor()
@Mock
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index c193d14..fbb5415 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.bouncer.ui.viewmodel
+import android.content.pm.UserInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -26,18 +27,27 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.inputmethod.data.model.InputMethodModel
+import com.android.systemui.inputmethod.data.repository.fakeInputMethodRepository
+import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.testKosmos
+import com.android.systemui.user.data.model.SelectedUserModel
+import com.android.systemui.user.data.model.SelectionStatus
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
import com.google.common.truth.Truth.assertThat
+import java.util.UUID
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -51,19 +61,22 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val authenticationInteractor = kosmos.authenticationInteractor
+ private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
+ private val selectedUserInteractor by lazy { kosmos.selectedUserInteractor }
+ private val inputMethodInteractor by lazy { kosmos.inputMethodInteractor }
private val bouncerViewModel by lazy { kosmos.bouncerViewModel }
private val isInputEnabled = MutableStateFlow(true)
- private val underTest by lazy {
+ private val underTest =
PasswordBouncerViewModel(
viewModelScope = testScope.backgroundScope,
+ isInputEnabled = isInputEnabled.asStateFlow(),
interactor = bouncerInteractor,
- isInputEnabled.asStateFlow(),
+ inputMethodInteractor = inputMethodInteractor,
+ selectedUserInteractor = selectedUserInteractor,
)
- }
@Before
fun setUp() {
@@ -270,6 +283,52 @@
assertThat(isTextFieldFocusRequested).isTrue()
}
+ @Test
+ fun isImeSwitcherButtonVisible() =
+ testScope.runTest {
+ val selectedUserId by collectLastValue(selectedUserInteractor.selectedUser)
+ selectUser(USER_INFOS.first())
+
+ enableInputMethodsForUser(checkNotNull(selectedUserId))
+
+ // Assert initial value, before the UI subscribes.
+ assertThat(underTest.isImeSwitcherButtonVisible.value).isFalse()
+
+ // Subscription starts; verify a fresh value is fetched.
+ val isImeSwitcherButtonVisible by collectLastValue(underTest.isImeSwitcherButtonVisible)
+ assertThat(isImeSwitcherButtonVisible).isTrue()
+
+ // Change the user, verify a fresh value is fetched.
+ selectUser(USER_INFOS.last())
+
+ assertThat(
+ inputMethodInteractor.hasMultipleEnabledImesOrSubtypes(
+ checkNotNull(selectedUserId)
+ )
+ )
+ .isFalse()
+ assertThat(isImeSwitcherButtonVisible).isFalse()
+
+ // Enable IMEs and add another subscriber; verify a fresh value is fetched.
+ enableInputMethodsForUser(checkNotNull(selectedUserId))
+ val collector2 by collectLastValue(underTest.isImeSwitcherButtonVisible)
+ assertThat(collector2).isTrue()
+ }
+
+ @Test
+ fun onImeSwitcherButtonClicked() =
+ testScope.runTest {
+ val displayId = 7
+ assertThat(kosmos.fakeInputMethodRepository.inputMethodPickerShownDisplayId)
+ .isNotEqualTo(displayId)
+
+ underTest.onImeSwitcherButtonClicked(displayId)
+ runCurrent()
+
+ assertThat(kosmos.fakeInputMethodRepository.inputMethodPickerShownDisplayId)
+ .isEqualTo(displayId)
+ }
+
private fun TestScope.switchToScene(toScene: SceneKey) {
val currentScene by collectLastValue(sceneInteractor.desiredScene)
val bouncerShown = currentScene?.key != SceneKey.Bouncer && toScene == SceneKey.Bouncer
@@ -310,8 +369,45 @@
runCurrent()
}
+ private fun TestScope.selectUser(userInfo: UserInfo) {
+ kosmos.fakeUserRepository.selectedUser.value =
+ SelectedUserModel(
+ userInfo = userInfo,
+ selectionStatus = SelectionStatus.SELECTION_COMPLETE
+ )
+ advanceTimeBy(PasswordBouncerViewModel.DELAY_TO_FETCH_IMES)
+ }
+
+ private suspend fun enableInputMethodsForUser(userId: Int) {
+ kosmos.fakeInputMethodRepository.setEnabledInputMethods(
+ userId,
+ createInputMethodWithSubtypes(auxiliarySubtypes = 0, nonAuxiliarySubtypes = 0),
+ createInputMethodWithSubtypes(auxiliarySubtypes = 0, nonAuxiliarySubtypes = 1),
+ )
+ assertThat(inputMethodInteractor.hasMultipleEnabledImesOrSubtypes(userId)).isTrue()
+ }
+
+ private fun createInputMethodWithSubtypes(
+ auxiliarySubtypes: Int,
+ nonAuxiliarySubtypes: Int,
+ ): InputMethodModel {
+ return InputMethodModel(
+ imeId = UUID.randomUUID().toString(),
+ subtypes =
+ List(auxiliarySubtypes + nonAuxiliarySubtypes) {
+ InputMethodModel.Subtype(subtypeId = it, isAuxiliary = it < auxiliarySubtypes)
+ }
+ )
+ }
+
companion object {
private const val ENTER_YOUR_PASSWORD = "Enter your password"
private const val WRONG_PASSWORD = "Wrong password"
+
+ private val USER_INFOS =
+ listOf(
+ UserInfo(100, "First user", 0),
+ UserInfo(101, "Second user", 0),
+ )
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt
index 55016bb..25a287c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt
@@ -24,6 +24,7 @@
*/
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class PinInputViewModelTest : SysuiTestCase() {
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
index 92b75cb..1642e52 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
@@ -20,6 +20,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.media.controls.models.player.MediaData
import com.android.systemui.media.controls.pipeline.MediaDataManager
import com.android.systemui.util.mockito.KotlinArgumentCaptor
@@ -40,30 +41,36 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class CommunalMediaRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var mediaDataManager: MediaDataManager
@Mock private lateinit var mediaData: MediaData
+ @Mock private lateinit var tableLogBuffer: TableLogBuffer
+
+ private lateinit var underTest: CommunalMediaRepositoryImpl
private val mediaDataListenerCaptor: KotlinArgumentCaptor<MediaDataManager.Listener> by lazy {
KotlinArgumentCaptor(MediaDataManager.Listener::class.java)
}
- private lateinit var mediaRepository: CommunalMediaRepository
-
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+
+ underTest =
+ CommunalMediaRepositoryImpl(
+ mediaDataManager,
+ tableLogBuffer,
+ )
}
@Test
fun hasAnyMediaOrRecommendation_defaultsToFalse() =
testScope.runTest {
- mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
-
- val mediaModel = collectLastValue(mediaRepository.mediaModel)
+ val mediaModel = collectLastValue(underTest.mediaModel)
runCurrent()
assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
}
@@ -71,13 +78,11 @@
@Test
fun mediaModel_updatesWhenMediaDataLoaded() =
testScope.runTest {
- mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
-
// Listener is added
verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
// Initial value is false.
- val mediaModel = collectLastValue(mediaRepository.mediaModel)
+ val mediaModel = collectLastValue(underTest.mediaModel)
runCurrent()
assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
@@ -95,8 +100,6 @@
@Test
fun mediaModel_updatesWhenMediaDataRemoved() =
testScope.runTest {
- mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
-
// Listener is added
verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
@@ -106,7 +109,7 @@
runCurrent()
// Media active now returns true.
- val mediaModel = collectLastValue(mediaRepository.mediaModel)
+ val mediaModel = collectLastValue(underTest.mediaModel)
assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isTrue()
// Change to media unavailable and notify the listener.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
index 820bfbf..6bff0dc7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
@@ -25,6 +25,8 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.settings.UserFileManager
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
@@ -36,10 +38,15 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class CommunalPrefsRepositoryImplTest : SysuiTestCase() {
+ @Mock private lateinit var tableLogBuffer: TableLogBuffer
+
private lateinit var underTest: CommunalPrefsRepositoryImpl
private val kosmos = testKosmos()
@@ -50,6 +57,8 @@
@Before
fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
userRepository = kosmos.fakeUserRepository
userRepository.setUserInfos(USER_INFOS)
@@ -66,6 +75,8 @@
kosmos.testDispatcher,
userRepository,
userFileManager,
+ logcatLogBuffer("CommunalPrefsRepositoryImplTest"),
+ tableLogBuffer,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
index c4a8582..2911a50 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
@@ -26,6 +26,7 @@
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.settings.FakeSettings
@@ -34,11 +35,15 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mock
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class CommunalTutorialRepositoryImplTest : SysuiTestCase() {
+ @Mock private lateinit var tableLogBuffer: TableLogBuffer
+
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
@@ -63,6 +68,7 @@
userRepository,
secureSettings,
logcatLogBuffer("CommunalTutorialRepositoryImplTest"),
+ tableLogBuffer,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index c979ca6..475179d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -198,13 +198,22 @@
}
@Test
- fun deleteWidget_removeWidgetId_andDeleteFromDb() =
+ fun deleteWidgetFromDb() =
testScope.runTest {
val id = 1
- underTest.deleteWidget(id)
+ underTest.deleteWidgetFromDb(id)
runCurrent()
verify(communalWidgetDao).deleteWidgetById(id)
+ }
+
+ @Test
+ fun deleteWidgetFromHost() =
+ testScope.runTest {
+ val id = 1
+ underTest.deleteWidgetFromHost(id)
+ runCurrent()
+
verify(appWidgetHost).deleteAppWidgetId(id)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index ee01bf9..c5485c5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -642,6 +642,53 @@
}
@Test
+ fun isCommunalVisible() =
+ testScope.runTest {
+ val transitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(
+ ObservableCommunalTransitionState.Idle(CommunalSceneKey.Blank)
+ )
+ communalRepository.setTransitionState(transitionState)
+
+ // isCommunalVisible is false when not on communal.
+ val isCommunalVisible by collectLastValue(underTest.isCommunalVisible)
+ assertThat(isCommunalVisible).isEqualTo(false)
+
+ // Start transition to communal.
+ transitionState.value =
+ ObservableCommunalTransitionState.Transition(
+ fromScene = CommunalSceneKey.Blank,
+ toScene = CommunalSceneKey.Communal,
+ progress = flowOf(0f),
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+
+ // isCommunalVisible is true once transition starts.
+ assertThat(isCommunalVisible).isEqualTo(true)
+
+ // Finish transition to communal
+ transitionState.value =
+ ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+
+ // isCommunalVisible is true since we're on communal.
+ assertThat(isCommunalVisible).isEqualTo(true)
+
+ // Start transition away from communal.
+ transitionState.value =
+ ObservableCommunalTransitionState.Transition(
+ fromScene = CommunalSceneKey.Communal,
+ toScene = CommunalSceneKey.Blank,
+ progress = flowOf(1.0f),
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+
+ // isCommunalVisible is still true as the false as soon as transition away runs.
+ assertThat(isCommunalVisible).isEqualTo(true)
+ }
+
+ @Test
fun testShowWidgetEditorStartsActivity() =
testScope.runTest {
underTest.showWidgetEditor()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
index 3aa99c4..89a4c04 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
@@ -23,16 +23,27 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.communal.widgets.CommunalAppWidgetHostView
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWithLooper(setAsMainLooper = true)
@RunWith(AndroidJUnit4::class)
class CommunalAppWidgetHostTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private lateinit var testableLooper: TestableLooper
private lateinit var underTest: CommunalAppWidgetHost
@@ -43,9 +54,11 @@
underTest =
CommunalAppWidgetHost(
context = context,
+ backgroundScope = kosmos.applicationCoroutineScope,
hostId = 116,
interactionHandler = mock(),
- looper = testableLooper.looper
+ looper = testableLooper.looper,
+ logBuffer = logcatLogBuffer("CommunalAppWidgetHostTest"),
)
}
@@ -64,4 +77,23 @@
assertThat(view).isNotNull()
assertThat(view.appWidgetId).isEqualTo(appWidgetId)
}
+
+ @Test
+ fun appWidgetIdToRemove_emit() =
+ testScope.runTest {
+ val appWidgetIdToRemove by collectLastValue(underTest.appWidgetIdToRemove)
+
+ // Nothing should be emitted yet
+ assertThat(appWidgetIdToRemove).isNull()
+
+ underTest.onAppWidgetRemoved(appWidgetId = 1)
+ runCurrent()
+
+ assertThat(appWidgetIdToRemove).isEqualTo(1)
+
+ underTest.onAppWidgetRemoved(appWidgetId = 2)
+ runCurrent()
+
+ assertThat(appWidgetIdToRemove).isEqualTo(2)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 273d1cd..cf727cf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -36,6 +36,7 @@
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
@@ -82,6 +83,7 @@
kosmos.communalInteractor,
mediaHost,
uiEventLogger,
+ logcatLogBuffer("CommunalEditModeViewModelTest"),
)
}
@@ -142,6 +144,49 @@
}
@Test
+ fun deleteWidget() =
+ testScope.runTest {
+ tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+
+ // Widgets available.
+ val widgets =
+ listOf(
+ CommunalWidgetContentModel(
+ appWidgetId = 0,
+ priority = 30,
+ providerInfo = mock(),
+ ),
+ CommunalWidgetContentModel(
+ appWidgetId = 1,
+ priority = 20,
+ providerInfo = mock(),
+ ),
+ )
+ widgetRepository.setCommunalWidgets(widgets)
+
+ val communalContent by collectLastValue(underTest.communalContent)
+
+ // Widgets and CTA tile are shown.
+ assertThat(communalContent?.size).isEqualTo(3)
+ assertThat(communalContent?.get(0))
+ .isInstanceOf(CommunalContentModel.Widget::class.java)
+ assertThat(communalContent?.get(1))
+ .isInstanceOf(CommunalContentModel.Widget::class.java)
+ assertThat(communalContent?.get(2))
+ .isInstanceOf(CommunalContentModel.CtaTileInEditMode::class.java)
+
+ underTest.onDeleteWidget(widgets.get(0).appWidgetId)
+
+ // Only one widget and CTA tile remain.
+ assertThat(communalContent?.size).isEqualTo(2)
+ val item = communalContent?.get(0)
+ val appWidgetId = if (item is CommunalContentModel.Widget) item.appWidgetId else null
+ assertThat(appWidgetId).isEqualTo(widgets.get(1).appWidgetId)
+ assertThat(communalContent?.get(1))
+ .isInstanceOf(CommunalContentModel.CtaTileInEditMode::class.java)
+ }
+
+ @Test
fun reorderWidget_uiEventLogging_start() {
underTest.onReorderWidgetStart()
verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_START)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 0723e83..73d3091 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -40,6 +40,7 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.ui.MediaHierarchyManager
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
@@ -99,6 +100,7 @@
kosmos.communalInteractor,
kosmos.communalTutorialInteractor,
mediaHost,
+ logcatLogBuffer("CommunalViewModelTest"),
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
index a3654b6..032d76f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
@@ -21,14 +21,21 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.kosmos.applicationCoroutineScope
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.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -47,6 +54,8 @@
@Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
+ private lateinit var appWidgetIdToRemove: MutableSharedFlow<Int>
+
private lateinit var underTest: CommunalAppWidgetHostStartable
@Before
@@ -54,6 +63,9 @@
MockitoAnnotations.initMocks(this)
kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO))
+ appWidgetIdToRemove = MutableSharedFlow()
+ whenever(appWidgetHost.appWidgetIdToRemove).thenReturn(appWidgetIdToRemove)
+
underTest =
CommunalAppWidgetHostStartable(
appWidgetHost,
@@ -120,6 +132,38 @@
}
}
+ @Test
+ fun removeAppWidgetReportedByHost() =
+ with(kosmos) {
+ testScope.runTest {
+ // Set up communal widgets
+ val widget1 =
+ mock<CommunalWidgetContentModel> { whenever(this.appWidgetId).thenReturn(1) }
+ val widget2 =
+ mock<CommunalWidgetContentModel> { whenever(this.appWidgetId).thenReturn(2) }
+ val widget3 =
+ mock<CommunalWidgetContentModel> { whenever(this.appWidgetId).thenReturn(3) }
+ fakeCommunalWidgetRepository.setCommunalWidgets(listOf(widget1, widget2, widget3))
+
+ underTest.start()
+
+ // Assert communal widgets has 3
+ val communalWidgets by
+ collectLastValue(fakeCommunalWidgetRepository.communalWidgets)
+ assertThat(communalWidgets).containsExactly(widget1, widget2, widget3)
+
+ // Report app widget 1 to remove and assert widget removed
+ appWidgetIdToRemove.emit(1)
+ runCurrent()
+ assertThat(communalWidgets).containsExactly(widget2, widget3)
+
+ // Report app widget 3 to remove and assert widget removed
+ appWidgetIdToRemove.emit(3)
+ runCurrent()
+ assertThat(communalWidgets).containsExactly(widget2)
+ }
+ }
+
private suspend fun setCommunalAvailable(available: Boolean) =
with(kosmos) {
fakeKeyguardRepository.setIsEncryptedOrLockdown(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
index b54c5bd..9536084 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
@@ -32,6 +32,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class DeviceEntryRepositoryTest : SysuiTestCase() {
@Mock private lateinit var lockPatternUtils: LockPatternUtils
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
index 32943a1..51db451 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
@@ -33,6 +33,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class DeviceUnlockedInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt
index 2c6c793..d9dcfdc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt
@@ -31,6 +31,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class DreamOverlayCallbackControllerTest : SysuiTestCase() {
@Mock private lateinit var callback: DreamOverlayCallbackController.Callback
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java
index d379dc6..5ae8595 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java
@@ -39,6 +39,7 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
+@android.platform.test.annotations.EnabledOnRavenwood
public class DreamOverlayNotificationCountProviderTest extends SysuiTestCase {
@Mock
NotificationListener mNotificationListener;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index 8bf878c..b46f2aa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -50,6 +50,7 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
+@android.platform.test.annotations.EnabledOnRavenwood
public class DreamOverlayStateControllerTest extends SysuiTestCase {
@Mock
DreamOverlayStateController.Callback mCallback;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java
index 7ff345c..ad353ce 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java
@@ -37,6 +37,7 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
+@android.platform.test.annotations.EnabledOnRavenwood
public class DreamOverlayStatusBarItemsProviderTest extends SysuiTestCase {
@Mock
DreamOverlayStatusBarItemsProvider.Callback mCallback;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
index e0c6ab2..cb5702ad 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
@@ -42,6 +42,7 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
+@android.platform.test.annotations.EnabledOnRavenwood
public class AssistantAttentionConditionTest extends SysuiTestCase {
@Mock
Condition.Callback mCallback;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
index 480754c..96d3c93 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
@@ -45,6 +45,7 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
+@android.platform.test.annotations.EnabledOnRavenwood
public class DreamConditionTest extends SysuiTestCase {
@Mock
Condition.Callback mCallback;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
index 74c1970..2a9bc4a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
@@ -28,7 +28,6 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.shared.system.InputChannelCompat;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import org.junit.Before;
@@ -47,8 +46,6 @@
@Mock
CentralSurfaces mCentralSurfaces;
@Mock
- NotificationShadeWindowController mNotificationShadeWindowController;
- @Mock
DreamTouchHandler.TouchSession mTouchSession;
CommunalTouchHandler mTouchHandler;
@@ -59,17 +56,10 @@
MockitoAnnotations.initMocks(this);
mTouchHandler = new CommunalTouchHandler(
Optional.of(mCentralSurfaces),
- mNotificationShadeWindowController,
INITIATION_WIDTH);
}
@Test
- public void testSessionStartForcesShadeOpen() {
- mTouchHandler.onSessionStart(mTouchSession);
- verify(mNotificationShadeWindowController).setForcePluginOpen(true, mTouchHandler);
- }
-
- @Test
public void testEventPropagation() {
final MotionEvent motionEvent = Mockito.mock(MotionEvent.class);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java
index 017fdbe..97052a8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java
@@ -39,6 +39,7 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
+@android.platform.test.annotations.EnabledOnRavenwood
public class BouncerlessScrimControllerTest extends SysuiTestCase {
@Mock
BouncerlessScrimController.Callback mCallback;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java
index 4ee4a60..ebbcf98 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java
@@ -39,6 +39,7 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
+@android.platform.test.annotations.EnabledOnRavenwood
public class ScrimManagerTest extends SysuiTestCase {
@Mock
ScrimController mBouncerlessScrimController;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/fold/ui/helper/FoldPostureTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/fold/ui/helper/FoldPostureTest.kt
index 61b2057..db52a45 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/fold/ui/helper/FoldPostureTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/fold/ui/helper/FoldPostureTest.kt
@@ -28,6 +28,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class FoldPostureTest : SysuiTestCase() {
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.kt
new file mode 100644
index 0000000..857cdce
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.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.inputmethod.data.repository
+
+import android.os.UserHandle
+import android.view.inputmethod.InputMethodInfo
+import android.view.inputmethod.InputMethodManager
+import android.view.inputmethod.InputMethodSubtype
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.count
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class InputMethodRepositoryTest : SysuiTestCase() {
+
+ @Mock private lateinit var inputMethodManager: InputMethodManager
+
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
+
+ private lateinit var underTest: InputMethodRepository
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(inputMethodManager.getEnabledInputMethodSubtypeList(eq(null), anyBoolean()))
+ .thenReturn(listOf())
+
+ underTest =
+ InputMethodRepositoryImpl(
+ backgroundDispatcher = kosmos.testDispatcher,
+ inputMethodManager = inputMethodManager,
+ )
+ }
+
+ @Test
+ fun enabledInputMethods_noImes_emptyFlow() =
+ testScope.runTest {
+ whenever(inputMethodManager.getEnabledInputMethodListAsUser(eq(USER_HANDLE)))
+ .thenReturn(listOf())
+ whenever(inputMethodManager.getEnabledInputMethodSubtypeList(any(), anyBoolean()))
+ .thenReturn(listOf())
+
+ assertThat(underTest.enabledInputMethods(USER_ID, fetchSubtypes = true).count())
+ .isEqualTo(0)
+ }
+
+ @Test
+ fun selectedInputMethodSubtypes_returnsSubtypeList() =
+ testScope.runTest {
+ val subtypeId = 123
+ val isAuxiliary = true
+ whenever(inputMethodManager.getEnabledInputMethodListAsUser(eq(USER_HANDLE)))
+ .thenReturn(listOf(mock<InputMethodInfo>()))
+ whenever(inputMethodManager.getEnabledInputMethodSubtypeList(any(), anyBoolean()))
+ .thenReturn(listOf())
+ whenever(inputMethodManager.getEnabledInputMethodSubtypeList(eq(null), anyBoolean()))
+ .thenReturn(
+ listOf(
+ InputMethodSubtype.InputMethodSubtypeBuilder()
+ .setSubtypeId(subtypeId)
+ .setIsAuxiliary(isAuxiliary)
+ .build()
+ )
+ )
+
+ val result = underTest.selectedInputMethodSubtypes()
+ assertThat(result).hasSize(1)
+ assertThat(result.first().subtypeId).isEqualTo(subtypeId)
+ assertThat(result.first().isAuxiliary).isEqualTo(isAuxiliary)
+ }
+
+ @Test
+ fun showImePicker_forwardsDisplayId() =
+ testScope.runTest {
+ val displayId = 7
+
+ underTest.showInputMethodPicker(displayId, /* showAuxiliarySubtypes = */ true)
+
+ verify(inputMethodManager)
+ .showInputMethodPickerFromSystem(
+ /* showAuxiliarySubtypes = */ eq(true),
+ /* displayId = */ eq(displayId)
+ )
+ }
+
+ companion object {
+ private const val USER_ID = 100
+ private val USER_HANDLE = UserHandle.of(USER_ID)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt
new file mode 100644
index 0000000..d23ff2a
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt
@@ -0,0 +1,157 @@
+/*
+ * 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.inputmethod.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.inputmethod.data.model.InputMethodModel
+import com.android.systemui.inputmethod.data.repository.fakeInputMethodRepository
+import com.android.systemui.inputmethod.data.repository.inputMethodRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.google.common.truth.Truth.assertThat
+import java.util.UUID
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class InputMethodInteractorTest : SysuiTestCase() {
+
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
+ private val fakeInputMethodRepository = kosmos.fakeInputMethodRepository
+
+ private val underTest = InputMethodInteractor(repository = kosmos.inputMethodRepository)
+
+ @Test
+ fun hasMultipleEnabledImesOrSubtypes_noImes_returnsFalse() =
+ testScope.runTest {
+ fakeInputMethodRepository.setEnabledInputMethods(USER_ID)
+
+ assertThat(underTest.hasMultipleEnabledImesOrSubtypes(USER_ID)).isFalse()
+ }
+
+ @Test
+ fun hasMultipleEnabledImesOrSubtypes_noMatches_returnsFalse() =
+ testScope.runTest {
+ fakeInputMethodRepository.setEnabledInputMethods(
+ USER_ID,
+ createInputMethodWithSubtypes(auxiliarySubtypes = 1, nonAuxiliarySubtypes = 0),
+ createInputMethodWithSubtypes(auxiliarySubtypes = 3, nonAuxiliarySubtypes = 0),
+ )
+
+ assertThat(underTest.hasMultipleEnabledImesOrSubtypes(USER_ID)).isFalse()
+ }
+
+ @Test
+ fun hasMultipleEnabledImesOrSubtypes_oneMatch_returnsFalse() =
+ testScope.runTest {
+ fakeInputMethodRepository.setEnabledInputMethods(
+ USER_ID,
+ createInputMethodWithSubtypes(auxiliarySubtypes = 0, nonAuxiliarySubtypes = 0),
+ )
+
+ assertThat(underTest.hasMultipleEnabledImesOrSubtypes(USER_ID)).isFalse()
+ }
+
+ @Test
+ fun hasMultipleEnabledImesOrSubtypes_twoMatches_returnsTrue() =
+ testScope.runTest {
+ fakeInputMethodRepository.setEnabledInputMethods(
+ USER_ID,
+ createInputMethodWithSubtypes(auxiliarySubtypes = 0, nonAuxiliarySubtypes = 1),
+ createInputMethodWithSubtypes(auxiliarySubtypes = 0, nonAuxiliarySubtypes = 0),
+ )
+
+ assertThat(underTest.hasMultipleEnabledImesOrSubtypes(USER_ID)).isTrue()
+ }
+
+ @Test
+ fun hasMultipleEnabledImesOrSubtypes_oneWithNonAux_returnsFalse() =
+ testScope.runTest {
+ fakeInputMethodRepository.setEnabledInputMethods(
+ USER_ID,
+ createInputMethodWithSubtypes(auxiliarySubtypes = 0, nonAuxiliarySubtypes = 2),
+ )
+
+ assertThat(underTest.hasMultipleEnabledImesOrSubtypes(USER_ID)).isFalse()
+ }
+
+ @Test
+ fun hasMultipleEnabledImesOrSubtypes_twoWithAux_returnsFalse() =
+ testScope.runTest {
+ fakeInputMethodRepository.setEnabledInputMethods(
+ USER_ID,
+ createInputMethodWithSubtypes(auxiliarySubtypes = 3, nonAuxiliarySubtypes = 0),
+ createInputMethodWithSubtypes(auxiliarySubtypes = 5, nonAuxiliarySubtypes = 0),
+ )
+
+ assertThat(underTest.hasMultipleEnabledImesOrSubtypes(USER_ID)).isFalse()
+ }
+
+ @Test
+ fun hasMultipleEnabledImesOrSubtypes_selectedHasOneSubtype_returnsFalse() =
+ testScope.runTest {
+ fakeInputMethodRepository.selectedInputMethodSubtypes =
+ listOf(InputMethodModel.Subtype(1, isAuxiliary = false))
+
+ assertThat(underTest.hasMultipleEnabledImesOrSubtypes(USER_ID)).isFalse()
+ }
+
+ @Test
+ fun hasMultipleEnabledImesOrSubtypes_selectedHasTwoSubtypes_returnsTrue() =
+ testScope.runTest {
+ fakeInputMethodRepository.selectedInputMethodSubtypes =
+ listOf(
+ InputMethodModel.Subtype(subtypeId = 1, isAuxiliary = false),
+ InputMethodModel.Subtype(subtypeId = 2, isAuxiliary = false),
+ )
+
+ assertThat(underTest.hasMultipleEnabledImesOrSubtypes(USER_ID)).isTrue()
+ }
+
+ @Test
+ fun showImePicker_shownOnCorrectId() =
+ testScope.runTest {
+ val displayId = 7
+
+ underTest.showInputMethodPicker(displayId, showAuxiliarySubtypes = false)
+
+ assertThat(fakeInputMethodRepository.inputMethodPickerShownDisplayId)
+ .isEqualTo(displayId)
+ }
+
+ private fun createInputMethodWithSubtypes(
+ auxiliarySubtypes: Int,
+ nonAuxiliarySubtypes: Int,
+ ): InputMethodModel {
+ return InputMethodModel(
+ imeId = UUID.randomUUID().toString(),
+ subtypes =
+ List(auxiliarySubtypes + nonAuxiliarySubtypes) {
+ InputMethodModel.Subtype(subtypeId = it, isAuxiliary = it < auxiliarySubtypes)
+ }
+ )
+ }
+
+ companion object {
+ private const val USER_ID = 100
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
index e20d3af..ee3e241 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
@@ -43,6 +43,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class CameraQuickAffordanceConfigTest : SysuiTestCase() {
@Mock private lateinit var cameraGestureHelper: CameraGestureHelper
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
index 4ae144c..77e0f4e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
@@ -42,6 +42,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class FlashlightQuickAffordanceConfigTest : LeakCheckedTest() {
@Mock private lateinit var context: Context
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
index 521dea3..ca64cec 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -40,6 +40,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class QrCodeScannerKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
@Mock private lateinit var controller: QRCodeScannerController
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
index 6b7d263..558e7e6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
@@ -50,6 +50,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() {
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var authController: AuthController
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt
index ae6c5b7..a0b8542 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt
@@ -40,6 +40,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class DevicePostureRepositoryTest : SysuiTestCase() {
private lateinit var underTest: DevicePostureRepository
private lateinit var testScope: TestScope
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index c01c79d..128b465 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -63,6 +63,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class KeyguardRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var statusBarStateController: StatusBarStateController
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
index ee47c58f4..5f0f24d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
@@ -46,6 +46,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class TrustRepositoryTest : SysuiTestCase() {
@Mock private lateinit var trustManager: TrustManager
@Captor private lateinit var listener: ArgumentCaptor<TrustManager.TrustListener>
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 6828041..9368097 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -45,6 +45,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
@kotlinx.coroutines.ExperimentalCoroutinesApi
+@android.platform.test.annotations.EnabledOnRavenwood
class KeyguardTransitionInteractorTest : SysuiTestCase() {
val kosmos = testKosmos()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManagerTest.kt
index e850456..4695ea4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManagerTest.kt
@@ -13,6 +13,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class KeyguardRemotePreviewManagerTest : SysuiTestCase() {
private val testDispatcher = StandardTestDispatcher()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
index d4dd2ac..0b80ff8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
@@ -36,6 +36,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class LockscreenContentViewModelTest : SysuiTestCase() {
private val kosmos: Kosmos = testKosmos()
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
new file mode 100644
index 0000000..b7b3fdb
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.qs.tiles.impl.fontscaling.domain
+
+import android.graphics.drawable.TestStubDrawable
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel
+import com.android.systemui.qs.tiles.impl.fontscaling.qsFontScalingTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FontScalingTileMapperTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val fontScalingTileConfig = kosmos.qsFontScalingTileConfig
+
+ private val mapper by lazy {
+ FontScalingTileMapper(
+ context.orCreateTestableResources
+ .apply { addOverride(R.drawable.ic_qs_font_scaling, TestStubDrawable()) }
+ .resources,
+ context.theme
+ )
+ }
+
+ @Test
+ fun activeStateMatchesEnabledModel() {
+ val inputModel = FontScalingTileModel
+
+ val outputState = mapper.map(fontScalingTileConfig, inputModel)
+
+ val expectedState = createFontScalingTileState()
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ private fun createFontScalingTileState(): QSTileState =
+ QSTileState(
+ {
+ Icon.Loaded(
+ context.getDrawable(
+ R.drawable.ic_qs_font_scaling,
+ )!!,
+ null
+ )
+ },
+ context.getString(R.string.quick_settings_font_scaling_label),
+ QSTileState.ActivationState.ACTIVE,
+ null,
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+ context.getString(R.string.quick_settings_font_scaling_label),
+ null,
+ QSTileState.SideViewIcon.Chevron,
+ QSTileState.EnabledState.ENABLED,
+ Switch::class.qualifiedName
+ )
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractorTest.kt
new file mode 100644
index 0000000..39bc8a6
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractorTest.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.qs.tiles.impl.fontscaling.domain.interactor
+
+import android.os.UserHandle
+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.coroutines.collectValues
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FontScalingTileDataInteractorTest : SysuiTestCase() {
+ private val underTest: FontScalingTileDataInteractor = FontScalingTileDataInteractor()
+ private val testUser = UserHandle.of(1)
+
+ @Test
+ fun collectsExactlyOneValue() = runTest {
+ val flowValues by
+ collectValues(underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)))
+
+ runCurrent()
+
+ Truth.assertThat(flowValues.size).isEqualTo(1)
+ }
+
+ @Test
+ fun lastValueIsNotEmpty() = runTest {
+ val flowValue by
+ collectLastValue(underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)))
+
+ runCurrent()
+
+ Truth.assertThat(flowValue).isNotNull()
+ }
+
+ @Test
+ fun isAvailable() = runTest {
+ val availability by collectLastValue(underTest.availability(testUser))
+
+ Truth.assertThat(availability).isTrue()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingUserActionInteractorTest.kt
new file mode 100644
index 0000000..2384915
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingUserActionInteractorTest.kt
@@ -0,0 +1,142 @@
+/*
+ * 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.qs.tiles.impl.fontscaling.domain.interactor
+
+import android.provider.Settings
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.fontscaling.FontScalingDialogDelegate
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.intentInputs
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel
+import com.android.systemui.statusbar.phone.FakeKeyguardStateController
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FontScalingUserActionInteractorTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val qsTileIntentUserActionHandler = FakeQSTileIntentUserInputHandler()
+ private val keyguardStateController = FakeKeyguardStateController()
+
+ private lateinit var underTest: FontScalingTileUserActionInteractor
+
+ @Mock private lateinit var fontScalingDialogDelegate: FontScalingDialogDelegate
+ @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+ @Mock private lateinit var dialog: SystemUIDialog
+ @Mock private lateinit var activityStarter: ActivityStarter
+
+ @Captor private lateinit var argumentCaptor: ArgumentCaptor<Runnable>
+
+ @Before
+ fun setup() {
+ activityStarter = mock<ActivityStarter>()
+ dialogLaunchAnimator = mock<DialogLaunchAnimator>()
+ dialog = mock<SystemUIDialog>()
+ fontScalingDialogDelegate =
+ mock<FontScalingDialogDelegate> { whenever(createDialog()).thenReturn(dialog) }
+ argumentCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+
+ underTest =
+ FontScalingTileUserActionInteractor(
+ kosmos.testScope.coroutineContext,
+ qsTileIntentUserActionHandler,
+ { fontScalingDialogDelegate },
+ keyguardStateController,
+ dialogLaunchAnimator,
+ activityStarter
+ )
+ }
+
+ @Test
+ fun clickTile_screenUnlocked_showDialogAnimationFromView() =
+ kosmos.testScope.runTest {
+ keyguardStateController.isShowing = false
+ val testView = View(context)
+
+ underTest.handleInput(click(FontScalingTileModel, view = testView))
+
+ verify(activityStarter)
+ .executeRunnableDismissingKeyguard(
+ argumentCaptor.capture(),
+ eq(null),
+ eq(true),
+ eq(true),
+ eq(false)
+ )
+ argumentCaptor.value.run()
+ verify(dialogLaunchAnimator).showFromView(any(), eq(testView), nullable(), anyBoolean())
+ }
+
+ @Test
+ fun clickTile_onLockScreen_neverShowDialogAnimationFromView_butShowsDialog() =
+ kosmos.testScope.runTest {
+ keyguardStateController.isShowing = true
+ val testView = View(context)
+
+ underTest.handleInput(click(FontScalingTileModel, view = testView))
+
+ verify(activityStarter)
+ .executeRunnableDismissingKeyguard(
+ argumentCaptor.capture(),
+ eq(null),
+ eq(true),
+ eq(true),
+ eq(false)
+ )
+ argumentCaptor.value.run()
+ verify(dialogLaunchAnimator, never())
+ .showFromView(any(), eq(testView), nullable(), anyBoolean())
+ verify(dialog).show()
+ }
+
+ @Test
+ fun handleLongClick() =
+ kosmos.testScope.runTest {
+ underTest.handleInput(QSTileInputTestKtx.longClick(FontScalingTileModel))
+
+ Truth.assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1)
+ val intentInput = qsTileIntentUserActionHandler.intentInputs.last()
+ val actualIntentAction = intentInput.intent.action
+ val expectedIntentAction = Settings.ACTION_TEXT_READING_SETTINGS
+ Truth.assertThat(actualIntentAction).isEqualTo(expectedIntentAction)
+ }
+}
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 504ded3..d6d2509 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -76,6 +76,8 @@
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
+import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
@@ -262,6 +264,7 @@
simBouncerInteractor = dagger.Lazy { kosmos.simBouncerInteractor },
authenticationInteractor = dagger.Lazy { kosmos.authenticationInteractor },
windowController = mock(),
+ deviceProvisioningInteractor = kosmos.deviceProvisioningInteractor,
)
startable.start()
@@ -518,6 +521,17 @@
assertCurrentScene(SceneKey.Lockscreen)
}
+ @Test
+ fun factoryResetProtectionActive_isNotVisible() =
+ testScope.runTest {
+ val isVisible by collectLastValue(sceneContainerViewModel.isVisible)
+ assertThat(isVisible).isTrue()
+
+ kosmos.fakeDeviceProvisioningRepository.setFactoryResetProtectionActive(isActive = true)
+
+ assertThat(isVisible).isFalse()
+ }
+
/**
* Asserts that the current scene in the view-model matches what's expected.
*
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
index b267720..2ad872c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
@@ -40,6 +40,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class SceneContainerRepositoryTest : SysuiTestCase() {
private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt
index 7ae501d..13b5b54 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt
@@ -30,6 +30,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class WindowRootViewVisibilityRepositoryTest : SysuiTestCase() {
private val iStatusBarService = mock<IStatusBarService>()
private val executor = FakeExecutor(FakeSystemClock())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 16cb623..1abbc92 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -49,6 +49,7 @@
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
@@ -112,6 +113,7 @@
simBouncerInteractor = dagger.Lazy { kosmos.simBouncerInteractor },
authenticationInteractor = dagger.Lazy { authenticationInteractor },
windowController = windowController,
+ deviceProvisioningInteractor = kosmos.deviceProvisioningInteractor,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt
index cb83e7c..bbbee90 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt
@@ -30,6 +30,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class BcSmartspaceConfigProviderTest : SysuiTestCase() {
@Mock private lateinit var featureFlags: FeatureFlags
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt
index 0b5aea7..089eb43e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt
@@ -37,6 +37,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
+@android.platform.test.annotations.EnabledOnRavenwood
class LockscreenPreconditionTest : SysuiTestCase() {
@Mock
private lateinit var deviceProvisionedController: DeviceProvisionedController
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenTargetFilterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenTargetFilterTest.kt
index bf33010..6616786 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenTargetFilterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenTargetFilterTest.kt
@@ -52,6 +52,7 @@
@SmallTest
@TestableLooper.RunWithLooper
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class LockscreenTargetFilterTest : SysuiTestCase() {
@Mock private lateinit var secureSettings: SecureSettings
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt
index 8a0400d..f7a8858 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt
@@ -38,6 +38,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class RemoteInputRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var remoteInputManager: NotificationRemoteInputManager
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractorTest.kt
index 12469dd..60da53c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractorTest.kt
@@ -34,6 +34,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class RemoteInputInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
index 6a2e317..4d7d5d3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
@@ -87,21 +87,21 @@
}
@Test
- fun updateShadeExpansion() =
+ fun shadeExpansion_goneToShade() =
testScope.runTest {
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(scene = SceneKey.Gone)
+ )
+ sceneInteractor.setTransitionState(transitionState)
val expandFraction by collectLastValue(appearanceViewModel.expandFraction)
assertThat(expandFraction).isEqualTo(0f)
- val transitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Idle(scene = SceneKey.Lockscreen)
- )
- sceneInteractor.setTransitionState(transitionState)
sceneInteractor.changeScene(SceneModel(SceneKey.Shade), "reason")
val transitionProgress = MutableStateFlow(0f)
transitionState.value =
ObservableTransitionState.Transition(
- fromScene = SceneKey.Lockscreen,
+ fromScene = SceneKey.Gone,
toScene = SceneKey.Shade,
progress = transitionProgress,
isInitiatedByUserInput = false,
@@ -118,4 +118,49 @@
sceneInteractor.onSceneChanged(SceneModel(SceneKey.Shade), "reason")
assertThat(expandFraction).isWithin(0.01f).of(1f)
}
+
+ @Test
+ fun shadeExpansion_idleOnLockscreen() =
+ testScope.runTest {
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(scene = SceneKey.Lockscreen)
+ )
+ sceneInteractor.setTransitionState(transitionState)
+ val expandFraction by collectLastValue(appearanceViewModel.expandFraction)
+ assertThat(expandFraction).isEqualTo(1f)
+ }
+
+ @Test
+ fun shadeExpansion_shadeToQs() =
+ testScope.runTest {
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(scene = SceneKey.Shade)
+ )
+ sceneInteractor.setTransitionState(transitionState)
+ val expandFraction by collectLastValue(appearanceViewModel.expandFraction)
+ assertThat(expandFraction).isEqualTo(1f)
+
+ sceneInteractor.changeScene(SceneModel(SceneKey.QuickSettings), "reason")
+ val transitionProgress = MutableStateFlow(0f)
+ transitionState.value =
+ ObservableTransitionState.Transition(
+ fromScene = SceneKey.Shade,
+ toScene = SceneKey.QuickSettings,
+ progress = transitionProgress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ val steps = 10
+ repeat(steps) { repetition ->
+ val progress = (1f / steps) * (repetition + 1)
+ transitionProgress.value = progress
+ runCurrent()
+ assertThat(expandFraction).isEqualTo(1f)
+ }
+
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.QuickSettings), "reason")
+ assertThat(expandFraction).isEqualTo(1f)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
index c7411cd..ffe6e6d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
@@ -30,6 +30,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class NotificationStackAppearanceInteractorTest : SysuiTestCase() {
private val kosmos = Kosmos()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java
index c669c6f..1f6ba29 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java
@@ -33,6 +33,10 @@
mOccluded = occluded;
}
+ public void setShowing(boolean isShowing) {
+ mShowing = isShowing;
+ }
+
@Override
public boolean isShowing() {
return mShowing;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt
index ce00250..18825a2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt
@@ -28,6 +28,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class DisabledWifiRepositoryTest : SysuiTestCase() {
private lateinit var underTest: DisabledWifiRepository
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
index 7fbbfc7..84c728c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
@@ -43,6 +43,7 @@
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class WifiInteractorImplTest : SysuiTestCase() {
private lateinit var underTest: WifiInteractor
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/data/repository/UserSetupRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/data/repository/UserSetupRepositoryTest.kt
index ebc81be..4c8bbe0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/data/repository/UserSetupRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/data/repository/UserSetupRepositoryTest.kt
@@ -45,6 +45,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class UserSetupRepositoryTest : SysuiTestCase() {
private val kosmos = testKosmos()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/UserSetupInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/UserSetupInteractorTest.kt
index 26c0f80..7ec0a61 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/UserSetupInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/UserSetupInteractorTest.kt
@@ -30,6 +30,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class UserSetupInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt
index b4a0a37..96d1c0d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt
@@ -34,6 +34,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class BooleanFlowOperatorsTest : SysuiTestCase() {
val kosmos = testKosmos()
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index 5d85fba..c9e2989 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -264,15 +264,18 @@
}
}
- @ProvidesInterface(version = BooleanState.VERSION)
- public static class BooleanState extends State {
+ /**
+ * Distinguished from [BooleanState] for use-case purposes such as allowing null secondary label
+ */
+ @ProvidesInterface(version = AdapterState.VERSION)
+ class AdapterState extends State {
public static final int VERSION = 1;
public boolean value;
public boolean forceExpandIcon;
@Override
public boolean copyTo(State other) {
- final BooleanState o = (BooleanState) other;
+ final AdapterState o = (AdapterState) other;
final boolean changed = super.copyTo(other)
|| o.value != value
|| o.forceExpandIcon != forceExpandIcon;
@@ -291,6 +294,18 @@
@Override
public State copy() {
+ AdapterState state = new AdapterState();
+ copyTo(state);
+ return state;
+ }
+ }
+
+ @ProvidesInterface(version = BooleanState.VERSION)
+ class BooleanState extends AdapterState {
+ public static final int VERSION = 1;
+
+ @Override
+ public State copy() {
BooleanState state = new BooleanState();
copyTo(state);
return state;
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index fa89fcd..33bdca3 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -903,6 +903,10 @@
Keep it the same as in Launcher-->
<dimen name="communal_enforced_rounded_corner_max_radius">16dp</dimen>
+ <!-- Width and height used to filter widgets displayed in the communal widget picker -->
+ <dimen name="communal_widget_picker_desired_width">464dp</dimen>
+ <dimen name="communal_widget_picker_desired_height">307dp</dimen>
+
<!-- The width/height of the unlock icon view on keyguard. -->
<dimen name="keyguard_lock_height">42dp</dimen>
<dimen name="keyguard_lock_padding">20dp</dimen>
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 326c7ef..4e04af6 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -53,6 +53,7 @@
"SystemUIPluginLib",
"SystemUIUnfoldLib",
"SystemUISharedLib-Keyguard",
+ "WindowManager-Shell-shared",
"tracinglib",
"androidx.dynamicanimation_dynamicanimation",
"androidx.concurrent_concurrent-futures",
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
index 88b9c02..a78080f 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
@@ -38,7 +38,7 @@
import android.window.IRemoteTransitionFinishedCallback;
import android.window.TransitionInfo;
-import com.android.wm.shell.util.CounterRotator;
+import com.android.wm.shell.shared.CounterRotator;
public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner.Stub {
private static final String TAG = "RemoteAnimRunnerCompat";
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index d4ac195..e4d9243 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -22,7 +22,7 @@
import android.window.TransitionInfo;
import android.window.TransitionInfo.Change;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
import java.util.ArrayList;
import java.util.function.Predicate;
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 82410fd..91e6b62 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -39,7 +39,7 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.DisplaySpecific
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags.REGION_SAMPLING
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -47,7 +47,6 @@
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.log.core.Logger
-import com.android.systemui.log.core.LogLevel.DEBUG
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockFaceController
import com.android.systemui.plugins.clocks.ClockMessageBuffers
@@ -92,7 +91,7 @@
@Main private val mainExecutor: DelayableExecutor,
@Background private val bgExecutor: Executor,
private val clockBuffers: ClockMessageBuffers,
- private val featureFlags: FeatureFlags,
+ private val featureFlags: FeatureFlagsClassic,
private val zenModeController: ZenModeController,
) {
var loggers = listOf(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index ad30317..28013c6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -196,6 +196,9 @@
mSmallClockFrame = findViewById(R.id.lockscreen_clock_view);
mLargeClockFrame = findViewById(R.id.lockscreen_clock_view_large);
mStatusArea = findViewById(R.id.keyguard_status_area);
+ } else {
+ removeView(findViewById(R.id.lockscreen_clock_view));
+ removeView(findViewById(R.id.lockscreen_clock_view_large));
}
onConfigChanged();
}
@@ -263,6 +266,9 @@
}
void updateClockTargetRegions() {
+ if (migrateClocksToBlueprint()) {
+ return;
+ }
if (mClock != null) {
if (mSmallClockFrame.isLaidOut()) {
Rect targetRegion = getSmallClockRegion(mSmallClockFrame);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index de7c12d..2db3795 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -246,8 +246,11 @@
protected void onInit() {
mKeyguardSliceViewController.init();
- mSmallClockFrame = mView.findViewById(R.id.lockscreen_clock_view);
- mLargeClockFrame = mView.findViewById(R.id.lockscreen_clock_view_large);
+ if (!migrateClocksToBlueprint()) {
+ mSmallClockFrame = mView.findViewById(R.id.lockscreen_clock_view);
+ mLargeClockFrame = mView.findViewById(R.id.lockscreen_clock_view_large);
+ }
+
if (!mOnlyClock) {
mDumpManager.unregisterDumpable(getClass().getSimpleName()); // unregister previous
@@ -526,13 +529,15 @@
*/
void updatePosition(int x, float scale, AnimationProperties props, boolean animate) {
x = getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? -x : x;
+ if (!migrateClocksToBlueprint()) {
+ PropertyAnimator.setProperty(mSmallClockFrame, AnimatableProperty.TRANSLATION_X,
+ x, props, animate);
+ PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_X,
+ scale, props, animate);
+ PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_Y,
+ scale, props, animate);
- PropertyAnimator.setProperty(mSmallClockFrame, AnimatableProperty.TRANSLATION_X,
- x, props, animate);
- PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_X,
- scale, props, animate);
- PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_Y,
- scale, props, animate);
+ }
if (mStatusArea != null) {
PropertyAnimator.setProperty(mStatusArea, KeyguardStatusAreaView.TRANSLATE_X_AOD,
@@ -550,6 +555,10 @@
return 0;
}
+ if (migrateClocksToBlueprint()) {
+ return 0;
+ }
+
if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
// This gets the expected clock bottom if mLargeClockFrame had a top margin, but it's
// top margin only contributed to height and didn't move the top of the view (as this
@@ -581,6 +590,9 @@
}
boolean isClockTopAligned() {
+ if (migrateClocksToBlueprint()) {
+ return mKeyguardClockInteractor.getClockSize().getValue() == LARGE;
+ }
return mLargeClockFrame.getVisibility() != View.VISIBLE;
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt
index c7e5b64..c23a051 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt
@@ -61,7 +61,8 @@
.observerFlow(userHandle.identifier, SETTING_NAME)
.onStart { emit(Unit) }
.map {
- secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED
+ secureSettings.getIntForUser(SETTING_NAME, DISABLED, userHandle.identifier) ==
+ ENABLED
}
.distinctUntilChanged()
.flowOn(bgCoroutineContext)
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt
index 419eada..8525797 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt
@@ -61,7 +61,8 @@
.observerFlow(userHandle.identifier, SETTING_NAME)
.onStart { emit(Unit) }
.map {
- secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED
+ secureSettings.getIntForUser(SETTING_NAME, DISABLED, userHandle.identifier) ==
+ ENABLED
}
.distinctUntilChanged()
.flowOn(bgCoroutineContext)
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
index 135ab35..4047623 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
@@ -31,6 +31,10 @@
import com.android.systemui.qs.tiles.impl.colorcorrection.domain.interactor.ColorCorrectionTileDataInteractor
import com.android.systemui.qs.tiles.impl.colorcorrection.domain.interactor.ColorCorrectionUserActionInteractor
import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.FontScalingTileMapper
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor.FontScalingTileDataInteractor
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor.FontScalingTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel
import com.android.systemui.qs.tiles.impl.inversion.domain.ColorInversionTileMapper
import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionTileDataInteractor
import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionUserActionInteractor
@@ -93,6 +97,7 @@
companion object {
const val COLOR_CORRECTION_TILE_SPEC = "color_correction"
const val COLOR_INVERSION_TILE_SPEC = "inversion"
+ const val FONT_SCALING_TILE_SPEC = "font_scaling"
@Provides
@IntoMap
@@ -155,5 +160,36 @@
stateInteractor,
mapper,
)
+
+ @Provides
+ @IntoMap
+ @StringKey(FONT_SCALING_TILE_SPEC)
+ fun provideFontScalingTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+ QSTileConfig(
+ tileSpec = TileSpec.create(FONT_SCALING_TILE_SPEC),
+ uiConfig =
+ QSTileUIConfig.Resource(
+ iconRes = R.drawable.ic_qs_font_scaling,
+ labelRes = R.string.quick_settings_font_scaling_label,
+ ),
+ instanceId = uiEventLogger.getNewInstanceId(),
+ )
+
+ /** Inject FontScaling Tile into tileViewModelMap in QSModule */
+ @Provides
+ @IntoMap
+ @StringKey(FONT_SCALING_TILE_SPEC)
+ fun provideFontScalingTileViewModel(
+ factory: QSTileViewModelFactory.Static<FontScalingTileModel>,
+ mapper: FontScalingTileMapper,
+ stateInteractor: FontScalingTileDataInteractor,
+ userActionInteractor: FontScalingTileUserActionInteractor
+ ): QSTileViewModel =
+ factory.create(
+ TileSpec.create(FONT_SCALING_TILE_SPEC),
+ userActionInteractor,
+ stateInteractor,
+ mapper,
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index 49f34f1..454ed27 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -39,7 +39,7 @@
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.user.data.repository.UserRepository
-import com.android.systemui.util.kotlin.pairwise
+import com.android.systemui.util.kotlin.onSubscriberAdded
import com.android.systemui.util.time.SystemClock
import dagger.Binds
import dagger.Module
@@ -54,7 +54,6 @@
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
@@ -355,10 +354,7 @@
userRepository.selectedUserInfo.map { it.id }.distinctUntilChanged(),
// Emits a value only when the number of downstream subscribers of this flow
// increases.
- flow.subscriptionCount.pairwise(initialValue = 0).filter { (previous, current)
- ->
- current > previous
- },
+ flow.onSubscriberAdded(),
) { selectedUserId, _ ->
selectedUserId
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 4176083..4ea5f4c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -198,11 +198,13 @@
UdfpsTouchOverlayBinder.bind(
view = this,
viewModel = deviceEntryUdfpsTouchOverlayViewModel.get(),
+ udfpsOverlayInteractor = udfpsOverlayInteractor,
)
else ->
UdfpsTouchOverlayBinder.bind(
view = this,
viewModel = defaultUdfpsTouchOverlayViewModel.get(),
+ udfpsOverlayInteractor = udfpsOverlayInteractor,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt
index 70be0f2..a742c0e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt
@@ -19,22 +19,15 @@
import android.hardware.biometrics.AuthenticateOptions
import android.hardware.biometrics.IBiometricContextListener
import android.util.Log
-import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-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.keyguard.WakefulnessLifecycle
+import com.android.systemui.display.data.repository.DeviceStateRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED
-import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
-import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN
-import com.android.systemui.unfold.updates.FoldStateProvider
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
-import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.catch
@@ -80,15 +73,11 @@
@Inject
constructor(
@Application private val applicationScope: CoroutineScope,
- private val foldProvider: FoldStateProvider,
+ deviceStateRepository: DeviceStateRepository,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
udfpsOverlayInteractor: UdfpsOverlayInteractor,
) : LogContextInteractor {
- init {
- applicationScope.launch { foldProvider.start() }
- }
-
override val displayState =
keyguardTransitionInteractor.startedKeyguardTransitionStep.map {
when (it.to) {
@@ -123,32 +112,21 @@
.distinctUntilChanged()
override val foldState: Flow<Int> =
- conflatedCallbackFlow {
- val callback =
- object : FoldStateProvider.FoldUpdatesListener {
- override fun onHingeAngleUpdate(angle: Float) {}
-
- override fun onFoldUpdate(@FoldStateProvider.FoldUpdate update: Int) {
- val loggedState =
- when (update) {
- FOLD_UPDATE_FINISH_HALF_OPEN ->
- IBiometricContextListener.FoldState.HALF_OPENED
- FOLD_UPDATE_FINISH_FULL_OPEN ->
- IBiometricContextListener.FoldState.FULLY_OPENED
- FOLD_UPDATE_FINISH_CLOSED ->
- IBiometricContextListener.FoldState.FULLY_CLOSED
- else -> null
- }
- if (loggedState != null) {
- trySendWithFailureLogging(loggedState, TAG)
- }
- }
- }
-
- foldProvider.addCallback(callback)
- trySendWithFailureLogging(IBiometricContextListener.FoldState.UNKNOWN, TAG)
- awaitClose { foldProvider.removeCallback(callback) }
+ deviceStateRepository.state
+ .map {
+ when (it) {
+ DeviceStateRepository.DeviceState.UNFOLDED,
+ DeviceStateRepository.DeviceState.REAR_DISPLAY,
+ DeviceStateRepository.DeviceState.CONCURRENT_DISPLAY ->
+ IBiometricContextListener.FoldState.FULLY_OPENED
+ DeviceStateRepository.DeviceState.FOLDED ->
+ IBiometricContextListener.FoldState.FULLY_CLOSED
+ DeviceStateRepository.DeviceState.HALF_FOLDED ->
+ IBiometricContextListener.FoldState.HALF_OPENED
+ else -> IBiometricContextListener.FoldState.UNKNOWN
+ }
}
+ .distinctUntilChanged()
.shareIn(applicationScope, started = SharingStarted.Eagerly, replay = 1)
override fun addBiometricContextListener(listener: IBiometricContextListener): Job {
@@ -178,6 +156,3 @@
private const val TAG = "ContextRepositoryImpl"
}
}
-
-private val WakefulnessLifecycle.isAwake: Boolean
- get() = wakefulness == WakefulnessLifecycle.WAKEFULNESS_AWAKE
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt
index bb6a68b..2e29c3b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt
@@ -16,9 +16,11 @@
package com.android.systemui.biometrics.ui.binder
+import android.util.Log
import androidx.core.view.isInvisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.biometrics.ui.view.UdfpsTouchOverlay
import com.android.systemui.biometrics.ui.viewmodel.UdfpsTouchOverlayViewModel
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
@@ -31,19 +33,26 @@
/**
* Updates visibility for the UdfpsTouchOverlay which controls whether the view will receive
- * touches or not.
+ * touches or not. For some devices, this is instead handled by UdfpsOverlayInteractor, so this
+ * viewBinder will send the information to the interactor.
*/
@JvmStatic
fun bind(
view: UdfpsTouchOverlay,
viewModel: UdfpsTouchOverlayViewModel,
+ udfpsOverlayInteractor: UdfpsOverlayInteractor,
) {
if (DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()) return
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch {
viewModel.shouldHandleTouches.collect { shouldHandleTouches ->
+ Log.d(
+ "UdfpsTouchOverlayBinder",
+ "[$view]: update shouldHandleTouches=$shouldHandleTouches"
+ )
view.isInvisible = !shouldHandleTouches
+ udfpsOverlayInteractor.setHandleTouches(shouldHandleTouches)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt
index 7265c0c..d849b3a 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt
@@ -21,8 +21,6 @@
import com.android.systemui.flags.Flags
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
/** Provides access to bouncer-related application state. */
@SysUISingleton
@@ -31,15 +29,10 @@
constructor(
private val flags: FeatureFlagsClassic,
) {
- private val _message = MutableStateFlow<String?>(null)
/** The user-facing message to show in the bouncer. */
- val message: StateFlow<String?> = _message.asStateFlow()
+ val message = MutableStateFlow<String?>(null)
/** Whether the user switcher should be displayed within the bouncer UI on large screens. */
val isUserSwitcherVisible: Boolean
get() = flags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)
-
- fun setMessage(message: String?) {
- _message.value = message
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index c8ce245..d8be1af 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -120,7 +120,7 @@
}
fun setMessage(message: String?) {
- repository.setMessage(message)
+ repository.message.value = message
}
/**
@@ -129,13 +129,13 @@
*/
fun resetMessage() {
applicationScope.launch {
- repository.setMessage(promptMessage(authenticationInteractor.getAuthenticationMethod()))
+ setMessage(promptMessage(authenticationInteractor.getAuthenticationMethod()))
}
}
/** Removes the user-facing message. */
fun clearMessage() {
- repository.setMessage(null)
+ setMessage(null)
}
/**
@@ -196,7 +196,7 @@
* message without having the attempt trigger lockout.
*/
private suspend fun showWrongInputMessage() {
- repository.setMessage(wrongInputMessage(authenticationInteractor.getAuthenticationMethod()))
+ setMessage(wrongInputMessage(authenticationInteractor.getAuthenticationMethod()))
}
/** Notifies that the input method editor (software keyboard) has been hidden by the user. */
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index 4d686a1..4466cbb 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -34,7 +34,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.inputmethod.domain.interactor.InputMethodInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.user.ui.viewmodel.UserActionViewModel
import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
import com.android.systemui.user.ui.viewmodel.UserViewModel
@@ -66,8 +68,10 @@
@Application private val applicationScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
private val bouncerInteractor: BouncerInteractor,
+ private val inputMethodInteractor: InputMethodInteractor,
private val simBouncerInteractor: SimBouncerInteractor,
private val authenticationInteractor: AuthenticationInteractor,
+ private val selectedUserInteractor: SelectedUserInteractor,
flags: SceneContainerFlags,
selectedUser: Flow<UserViewModel>,
users: Flow<List<UserViewModel>>,
@@ -346,8 +350,10 @@
is AuthenticationMethodModel.Password ->
PasswordBouncerViewModel(
viewModelScope = newViewModelScope,
- interactor = bouncerInteractor,
isInputEnabled = isInputEnabled,
+ interactor = bouncerInteractor,
+ inputMethodInteractor = inputMethodInteractor,
+ selectedUserInteractor = selectedUserInteractor,
)
is AuthenticationMethodModel.Pattern ->
PatternBouncerViewModel(
@@ -467,11 +473,13 @@
@Application applicationScope: CoroutineScope,
@Main mainDispatcher: CoroutineDispatcher,
bouncerInteractor: BouncerInteractor,
+ imeInteractor: InputMethodInteractor,
simBouncerInteractor: SimBouncerInteractor,
+ actionButtonInteractor: BouncerActionButtonInteractor,
authenticationInteractor: AuthenticationInteractor,
+ selectedUserInteractor: SelectedUserInteractor,
flags: SceneContainerFlags,
userSwitcherViewModel: UserSwitcherViewModel,
- actionButtonInteractor: BouncerActionButtonInteractor,
clock: SystemClock,
devicePolicyManager: DevicePolicyManager,
): BouncerViewModel {
@@ -480,8 +488,10 @@
applicationScope = applicationScope,
mainDispatcher = mainDispatcher,
bouncerInteractor = bouncerInteractor,
+ inputMethodInteractor = imeInteractor,
simBouncerInteractor = simBouncerInteractor,
authenticationInteractor = authenticationInteractor,
+ selectedUserInteractor = selectedUserInteractor,
flags = flags,
selectedUser = userSwitcherViewModel.selectedUser,
users = userSwitcherViewModel.users,
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index 5c9c997..1c8b84d 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -16,23 +16,32 @@
package com.android.systemui.bouncer.ui.viewmodel
+import androidx.annotation.VisibleForTesting
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.inputmethod.domain.interactor.InputMethodInteractor
import com.android.systemui.res.R
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.kotlin.onSubscriberAdded
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
/** Holds UI state and handles user input for the password bouncer UI. */
class PasswordBouncerViewModel(
viewModelScope: CoroutineScope,
- interactor: BouncerInteractor,
isInputEnabled: StateFlow<Boolean>,
+ interactor: BouncerInteractor,
+ private val inputMethodInteractor: InputMethodInteractor,
+ private val selectedUserInteractor: SelectedUserInteractor,
) :
AuthMethodBouncerViewModel(
viewModelScope = viewModelScope,
@@ -49,6 +58,9 @@
override val lockoutMessageId = R.string.kg_too_many_failed_password_attempts_dialog_message
+ /** Informs the UI whether the input method switcher button should be visible. */
+ val isImeSwitcherButtonVisible: StateFlow<Boolean> = imeSwitcherRefreshingFlow()
+
/** Whether the text field element currently has focus. */
private val isTextFieldFocused = MutableStateFlow(false)
@@ -87,6 +99,13 @@
_password.value = newPassword
}
+ /** Notifies that the user clicked the button to change the input method. */
+ fun onImeSwitcherButtonClicked(displayId: Int) {
+ viewModelScope.launch {
+ inputMethodInteractor.showInputMethodPicker(displayId, showAuxiliarySubtypes = false)
+ }
+ }
+
/** Notifies that the user has pressed the key for attempting to authenticate the password. */
fun onAuthenticateKeyPressed() {
if (_password.value.isNotEmpty()) {
@@ -103,4 +122,35 @@
fun onTextFieldFocusChanged(isFocused: Boolean) {
isTextFieldFocused.value = isFocused
}
+
+ /**
+ * Whether the input method switcher button should be displayed in the password bouncer UI. The
+ * value may be stale at the moment of subscription to this flow, but it is guaranteed to be
+ * shortly updated with a fresh value.
+ *
+ * Note: Each added subscription triggers an IPC call in the background, so this should only be
+ * subscribed to by the UI once in its lifecycle (i.e. when the bouncer is shown).
+ */
+ private fun imeSwitcherRefreshingFlow(): StateFlow<Boolean> {
+ val isImeSwitcherButtonVisible = MutableStateFlow(value = false)
+ viewModelScope.launch {
+ // Re-fetch the currently-enabled IMEs whenever the selected user changes, and whenever
+ // the UI subscribes to the `isImeSwitcherButtonVisible` flow.
+ combine(
+ // InputMethodManagerService sometimes takes some time to update its internal
+ // state when the selected user changes. As a workaround, delay fetching the IME
+ // info.
+ selectedUserInteractor.selectedUser.onEach { delay(DELAY_TO_FETCH_IMES) },
+ isImeSwitcherButtonVisible.onSubscriberAdded()
+ ) { selectedUserId, _ ->
+ inputMethodInteractor.hasMultipleEnabledImesOrSubtypes(selectedUserId)
+ }
+ .collect { isImeSwitcherButtonVisible.value = it }
+ }
+ return isImeSwitcherButtonVisible.asStateFlow()
+ }
+
+ companion object {
+ @VisibleForTesting val DELAY_TO_FETCH_IMES = 300.milliseconds
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
index 779446d..3b727d2 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
@@ -97,7 +97,7 @@
fun getWidgets(): Flow<Map<CommunalItemRank, CommunalWidgetItem>>
@Query("SELECT * FROM communal_widget_table WHERE widget_id = :id")
- fun getWidgetByIdNow(id: Int): CommunalWidgetItem
+ fun getWidgetByIdNow(id: Int): CommunalWidgetItem?
@Delete fun deleteWidgets(vararg widgets: CommunalWidgetItem)
@@ -120,7 +120,9 @@
fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {
widgetIdToPriorityMap.forEach { (id, priority) ->
val widget = getWidgetByIdNow(id)
- updateItemRank(widget.itemId, priority)
+ if (widget != null) {
+ updateItemRank(widget.itemId, priority)
+ }
}
}
@@ -134,9 +136,13 @@
}
@Transaction
- fun deleteWidgetById(widgetId: Int) {
- val widget = getWidgetByIdNow(widgetId)
+ fun deleteWidgetById(widgetId: Int): Boolean {
+ val widget =
+ getWidgetByIdNow(widgetId) ?: // no entry to delete from db
+ return false
+
deleteItemRankById(widget.itemId)
deleteWidgets(widget)
+ return true
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
index cf2e33c..c46f0d1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
@@ -16,15 +16,34 @@
package com.android.systemui.communal.data.model
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
+
/** Data model of media on the communal hub. */
data class CommunalMediaModel(
val hasAnyMediaOrRecommendation: Boolean,
val createdTimestampMillis: Long = 0L,
-) {
+) : Diffable<CommunalMediaModel> {
companion object {
val INACTIVE =
CommunalMediaModel(
hasAnyMediaOrRecommendation = false,
)
}
+
+ override fun logDiffs(prevVal: CommunalMediaModel, row: TableRowLogger) {
+ if (hasAnyMediaOrRecommendation != prevVal.hasAnyMediaOrRecommendation) {
+ row.logChange(
+ columnName = "isMediaActive",
+ value = hasAnyMediaOrRecommendation,
+ )
+ }
+
+ if (createdTimestampMillis != prevVal.createdTimestampMillis) {
+ row.logChange(
+ columnName = "mediaCreationTimestamp",
+ value = createdTimestampMillis.toString(),
+ )
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
index e8a561b..2b66491 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
@@ -18,6 +18,9 @@
import com.android.systemui.communal.data.model.CommunalMediaModel
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.dagger.CommunalTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.media.controls.models.player.MediaData
import com.android.systemui.media.controls.pipeline.MediaDataManager
import javax.inject.Inject
@@ -34,6 +37,7 @@
@Inject
constructor(
private val mediaDataManager: MediaDataManager,
+ @CommunalTableLog tableLogBuffer: TableLogBuffer,
) : CommunalMediaRepository {
private val mediaDataListener =
@@ -61,7 +65,12 @@
private val _mediaModel: MutableStateFlow<CommunalMediaModel> =
MutableStateFlow(CommunalMediaModel.INACTIVE)
- override val mediaModel: Flow<CommunalMediaModel> = _mediaModel
+ override val mediaModel: Flow<CommunalMediaModel> =
+ _mediaModel.logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnPrefix = "",
+ initialValue = CommunalMediaModel.INACTIVE,
+ )
private fun updateMediaModel(data: MediaData? = null) {
if (mediaDataManager.hasAnyMediaOrRecommendation()) {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
index 4b77774..0e9b32f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
@@ -21,6 +21,12 @@
import android.content.pm.UserInfo
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.log.dagger.CommunalTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.settings.UserFileManager
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
@@ -59,11 +65,21 @@
@Background private val bgDispatcher: CoroutineDispatcher,
private val userRepository: UserRepository,
private val userFileManager: UserFileManager,
+ @CommunalLog logBuffer: LogBuffer,
+ @CommunalTableLog tableLogBuffer: TableLogBuffer,
) : CommunalPrefsRepository {
+ private val logger = Logger(logBuffer, "CommunalPrefsRepositoryImpl")
+
override val isCtaDismissed: Flow<Boolean> =
userRepository.selectedUserInfo
.flatMapLatest(::observeCtaDismissState)
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnPrefix = "",
+ columnName = "isCtaDismissed",
+ initialValue = false,
+ )
.stateIn(
scope = backgroundScope,
started = SharingStarted.WhileSubscribed(),
@@ -76,6 +92,8 @@
.edit()
.putBoolean(CTA_DISMISSED_STATE, true)
.apply()
+
+ logger.i("Dismissed CTA tile")
}
private fun observeCtaDismissState(user: UserInfo): Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
index 046aaaa..4c06e97 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
@@ -25,6 +25,9 @@
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.log.dagger.CommunalTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
@@ -66,6 +69,7 @@
private val userRepository: UserRepository,
private val secureSettings: SecureSettings,
@CommunalLog logBuffer: LogBuffer,
+ @CommunalTableLog tableLogBuffer: TableLogBuffer,
) : CommunalTutorialRepository {
companion object {
@@ -94,6 +98,12 @@
settingsState
.map { it.hubModeTutorialState }
.filterNotNull()
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnPrefix = "",
+ columnName = "tutorialSettingState",
+ initialValue = HUB_MODE_TUTORIAL_NOT_STARTED,
+ )
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index f36547b..2ac9d05 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -54,8 +54,11 @@
configurator: WidgetConfigurator? = null
) {}
- /** Delete a widget by id from app widget service and the database. */
- fun deleteWidget(widgetId: Int) {}
+ /** Delete a widget by id from the database. */
+ fun deleteWidgetFromDb(widgetId: Int) {}
+
+ /** Delete a widget by id from app widget host. */
+ fun deleteWidgetFromHost(widgetId: Int) {}
/**
* Update the order of widgets in the database.
@@ -143,9 +146,18 @@
}
}
- override fun deleteWidget(widgetId: Int) {
+ override fun deleteWidgetFromDb(widgetId: Int) {
bgScope.launch {
- communalWidgetDao.deleteWidgetById(widgetId)
+ if (communalWidgetDao.deleteWidgetById(widgetId)) {
+ logger.i("Deleted widget with id $widgetId from DB .")
+ } else {
+ logger.w("Widget with id $widgetId cannot be deleted from DB.")
+ }
+ }
+ }
+
+ override fun deleteWidgetFromHost(widgetId: Int) {
+ bgScope.launch {
appWidgetHost.deleteAppWidgetId(widgetId)
logger.i("Deleted widget with id $widgetId.")
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
index ab0a2d0d..d7f163b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
@@ -26,6 +26,7 @@
import com.android.systemui.communal.widgets.WidgetInteractionHandler
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.dagger.CommunalLog
@@ -35,6 +36,7 @@
import dagger.Provides
import java.util.Optional
import javax.inject.Named
+import kotlinx.coroutines.CoroutineScope
@Module
interface CommunalWidgetRepositoryModule {
@@ -52,10 +54,19 @@
@Provides
fun provideCommunalAppWidgetHost(
@Application context: Context,
+ @Background backgroundScope: CoroutineScope,
interactionHandler: WidgetInteractionHandler,
@Main looper: Looper,
+ @CommunalLog logBuffer: LogBuffer,
): CommunalAppWidgetHost {
- return CommunalAppWidgetHost(context, APP_WIDGET_HOST_ID, interactionHandler, looper)
+ return CommunalAppWidgetHost(
+ context,
+ backgroundScope,
+ APP_WIDGET_HOST_ID,
+ interactionHandler,
+ looper,
+ logBuffer,
+ )
}
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 80fee64..75a27a2 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -33,16 +33,25 @@
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.log.dagger.CommunalTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.smartspace.data.repository.SmartspaceRepository
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.BooleanFlowOperators.and
import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import com.android.systemui.util.kotlin.BooleanFlowOperators.or
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
@@ -50,6 +59,8 @@
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.shareIn
/** Encapsulates business-logic related to communal mode. */
@OptIn(ExperimentalCoroutinesApi::class)
@@ -57,6 +68,7 @@
class CommunalInteractor
@Inject
constructor(
+ @Application applicationScope: CoroutineScope,
private val communalRepository: CommunalRepository,
private val widgetRepository: CommunalWidgetRepository,
private val communalPrefsRepository: CommunalPrefsRepository,
@@ -65,8 +77,12 @@
userRepository: UserRepository,
keyguardInteractor: KeyguardInteractor,
private val appWidgetHost: CommunalAppWidgetHost,
- private val editWidgetsActivityStarter: EditWidgetsActivityStarter
+ private val editWidgetsActivityStarter: EditWidgetsActivityStarter,
+ @CommunalLog logBuffer: LogBuffer,
+ @CommunalTableLog tableLogBuffer: TableLogBuffer,
) {
+ private val logger = Logger(logBuffer, "CommunalInteractor")
+
private val _editModeOpen = MutableStateFlow(false)
/** Whether edit mode is currently open. */
@@ -85,6 +101,22 @@
or(keyguardInteractor.isKeyguardVisible, keyguardInteractor.isDreaming)
)
.distinctUntilChanged()
+ .onEach { available ->
+ logger.i({ "Communal is ${if (bool1) "" else "un"}available" }) {
+ bool1 = available
+ }
+ }
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnPrefix = "",
+ columnName = "isCommunalAvailable",
+ initialValue = false,
+ )
+ .shareIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ replay = 1,
+ )
/**
* Target scene as requested by the underlying [SceneTransitionLayout] or through
@@ -129,11 +161,36 @@
.distinctUntilChanged()
/**
- * Flow that emits a boolean if the communal UI is showing, ie. the [desiredScene] is the
- * [CommunalSceneKey.Communal].
+ * Flow that emits a boolean if the communal UI is the target scene, ie. the [desiredScene] is
+ * the [CommunalSceneKey.Communal].
+ *
+ * This will be true as soon as the desired scene is set programmatically or at whatever point
+ * during a fling that SceneTransitionLayout determines that the end state will be the communal
+ * scene. The value also does not change while flinging away until the target scene is no longer
+ * communal.
+ *
+ * If you need a flow that is only true when communal is fully showing and not in transition,
+ * use [isIdleOnCommunal].
*/
+ // TODO(b/323215860): rename to something more appropriate after cleaning up usages
val isCommunalShowing: Flow<Boolean> =
- communalRepository.desiredScene.map { it == CommunalSceneKey.Communal }
+ communalRepository.desiredScene
+ .map { it == CommunalSceneKey.Communal }
+ .distinctUntilChanged()
+ .onEach { showing ->
+ logger.i({ "Communal is ${if (bool1) "showing" else "gone"}" }) { bool1 = showing }
+ }
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnPrefix = "",
+ columnName = "isCommunalShowing",
+ initialValue = false,
+ )
+ .shareIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ replay = 1,
+ )
/**
* Flow that emits a boolean if the communal UI is fully visible and not in transition.
@@ -146,6 +203,16 @@
it is ObservableCommunalTransitionState.Idle && it.scene == CommunalSceneKey.Communal
}
+ /**
+ * Flow that emits a boolean if any portion of the communal UI is visible at all.
+ *
+ * This flow will be true during any transition and when idle on the communal scene.
+ */
+ val isCommunalVisible: Flow<Boolean> =
+ communalRepository.transitionState.map {
+ !(it is ObservableCommunalTransitionState.Idle && it.scene == CommunalSceneKey.Blank)
+ }
+
/** Callback received whenever the [SceneTransitionLayout] finishes a scene transition. */
fun onSceneChanged(newScene: CommunalSceneKey) {
communalRepository.setDesiredScene(newScene)
@@ -170,9 +237,15 @@
configurator: WidgetConfigurator?,
) = widgetRepository.addWidget(componentName, priority, configurator)
- /** Delete a widget by id. */
- fun deleteWidget(id: Int) = widgetRepository.deleteWidget(id)
+ /**
+ * Delete a widget by id from the database. [CommunalAppWidgetHostStartable] invokes this
+ * function to manage the deletion from the database for uninstalled or user-deleted widgets,
+ * following the removal of a widget from the host.
+ */
+ fun deleteWidgetFromDb(id: Int) = widgetRepository.deleteWidgetFromDb(id)
+ /** Delete a widget by id from AppWidgetHost. Called when user deletes a widget from the hub */
+ fun deleteWidgetFromHost(id: Int) = widgetRepository.deleteWidgetFromHost(id)
/**
* Reorder the widgets.
*
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
index 309c84e..1404ee2 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
@@ -22,6 +22,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.log.dagger.CommunalTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -49,6 +52,7 @@
keyguardInteractor: KeyguardInteractor,
private val communalRepository: CommunalRepository,
communalInteractor: CommunalInteractor,
+ @CommunalTableLog tableLogBuffer: TableLogBuffer,
) {
/** An observable for whether the tutorial is available. */
val isTutorialAvailable: StateFlow<Boolean> =
@@ -61,6 +65,12 @@
isKeyguardVisible &&
tutorialSettingState != Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
}
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnPrefix = "",
+ columnName = "isTutorialAvailable",
+ initialValue = false,
+ )
.stateIn(
scope = scope,
started = SharingStarted.WhileSubscribed(),
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index ebcfb8b..69d5581 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -21,6 +21,9 @@
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.media.dagger.MediaModule
import javax.inject.Inject
@@ -29,6 +32,7 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
/** The view model for communal hub in edit mode. */
@SysUISingleton
@@ -38,21 +42,27 @@
private val communalInteractor: CommunalInteractor,
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
private val uiEventLogger: UiEventLogger,
+ @CommunalLog logBuffer: LogBuffer,
) : BaseCommunalViewModel(communalInteractor, mediaHost) {
+
+ private val logger = Logger(logBuffer, "CommunalEditModeViewModel")
+
override val isEditMode = true
// Only widgets are editable. The CTA tile comes last in the list and remains visible.
override val communalContent: Flow<List<CommunalContentModel>> =
- communalInteractor.widgetContent.map { widgets ->
- widgets + listOf(CommunalContentModel.CtaTileInEditMode())
- }
+ communalInteractor.widgetContent
+ .map { widgets -> widgets + listOf(CommunalContentModel.CtaTileInEditMode()) }
+ .onEach { models ->
+ logger.d({ "Content updated: $str1" }) { str1 = models.joinToString { it.key } }
+ }
private val _reorderingWidgets = MutableStateFlow(false)
override val reorderingWidgets: StateFlow<Boolean>
get() = _reorderingWidgets
- override fun onDeleteWidget(id: Int) = communalInteractor.deleteWidget(id)
+ override fun onDeleteWidget(id: Int) = communalInteractor.deleteWidgetFromHost(id)
override fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) =
communalInteractor.updateWidgetOrder(widgetIdToPriorityMap)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index d7a3705..0c12841 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -21,6 +21,9 @@
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
import com.android.systemui.media.controls.ui.MediaHierarchyManager
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.media.controls.ui.MediaHostState
@@ -37,6 +40,7 @@
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
/** The default view model used for showing the communal hub. */
@@ -48,22 +52,30 @@
private val communalInteractor: CommunalInteractor,
tutorialInteractor: CommunalTutorialInteractor,
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
+ @CommunalLog logBuffer: LogBuffer,
) : BaseCommunalViewModel(communalInteractor, mediaHost) {
+
+ private val logger = Logger(logBuffer, "CommunalViewModel")
+
@OptIn(ExperimentalCoroutinesApi::class)
override val communalContent: Flow<List<CommunalContentModel>> =
- tutorialInteractor.isTutorialAvailable.flatMapLatest { isTutorialMode ->
- if (isTutorialMode) {
- return@flatMapLatest flowOf(communalInteractor.tutorialContent)
+ tutorialInteractor.isTutorialAvailable
+ .flatMapLatest { isTutorialMode ->
+ if (isTutorialMode) {
+ return@flatMapLatest flowOf(communalInteractor.tutorialContent)
+ }
+ combine(
+ communalInteractor.ongoingContent,
+ communalInteractor.widgetContent,
+ communalInteractor.ctaTileContent,
+ ) { ongoing, widgets, ctaTile,
+ ->
+ ongoing + widgets + ctaTile
+ }
}
- combine(
- communalInteractor.ongoingContent,
- communalInteractor.widgetContent,
- communalInteractor.ctaTileContent,
- ) { ongoing, widgets, ctaTile,
- ->
- ongoing + widgets + ctaTile
+ .onEach { models ->
+ logger.d({ "Content updated: $str1" }) { str1 = models.joinToString { it.key } }
}
- }
private val _isPopupOnDismissCtaShowing: MutableStateFlow<Boolean> = MutableStateFlow(false)
override val isPopupOnDismissCtaShowing: Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
index 61db026..5f1d89e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
@@ -22,14 +22,31 @@
import android.content.Context
import android.os.Looper
import android.widget.RemoteViews
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.launch
/** Communal app widget host that creates a [CommunalAppWidgetHostView]. */
class CommunalAppWidgetHost(
context: Context,
+ private val backgroundScope: CoroutineScope,
hostId: Int,
interactionHandler: RemoteViews.InteractionHandler,
- looper: Looper
+ looper: Looper,
+ logBuffer: LogBuffer,
) : AppWidgetHost(context, hostId, interactionHandler, looper) {
+
+ private val logger = Logger(logBuffer, TAG)
+
+ private val _appWidgetIdToRemove = MutableSharedFlow<Int>()
+
+ /** App widget ids that have been removed and no longer available. */
+ val appWidgetIdToRemove: SharedFlow<Int> = _appWidgetIdToRemove.asSharedFlow()
+
override fun onCreateView(
context: Context,
appWidgetId: Int,
@@ -52,4 +69,15 @@
// `createView`, but we are sure that the hostView is `CommunalAppWidgetHostView`
return createView(context, appWidgetId, appWidget) as CommunalAppWidgetHostView
}
+
+ override fun onAppWidgetRemoved(appWidgetId: Int) {
+ backgroundScope.launch {
+ logger.i({ "App widget removed from system: $int1" }) { int1 = appWidgetId }
+ _appWidgetIdToRemove.emit(appWidgetId)
+ }
+ }
+
+ companion object {
+ private const val TAG = "CommunalAppWidgetHost"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
index 586df32..6fd0fbe 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
@@ -40,6 +40,7 @@
@Background private val bgScope: CoroutineScope,
@Main private val uiDispatcher: CoroutineDispatcher
) : CoreStartable {
+
override fun start() {
or(communalInteractor.isCommunalAvailable, communalInteractor.editModeOpen)
// Only trigger updates on state changes, ignoring the initial false value.
@@ -47,6 +48,10 @@
.filter { (previous, new) -> previous != new }
.onEach { (_, shouldListen) -> updateAppWidgetHostActive(shouldListen) }
.launchIn(bgScope)
+
+ appWidgetHost.appWidgetIdToRemove
+ .onEach { appWidgetId -> communalInteractor.deleteWidgetFromDb(appWidgetId) }
+ .launchIn(bgScope)
}
private suspend fun updateAppWidgetHostActive(active: Boolean) =
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 54c709d..92e8153 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -32,6 +32,10 @@
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.compose.ComposeFacade.setCommunalEditWidgetActivityContent
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.res.R
import javax.inject.Inject
/** An Activity for editing the widgets that appear in hub mode. */
@@ -41,16 +45,21 @@
private val communalViewModel: CommunalEditModeViewModel,
private var windowManagerService: IWindowManager? = null,
private val uiEventLogger: UiEventLogger,
- private val widgetConfiguratorFactory: WidgetConfigurationController.Factory
+ private val widgetConfiguratorFactory: WidgetConfigurationController.Factory,
+ @CommunalLog logBuffer: LogBuffer,
) : ComponentActivity() {
companion object {
private const val EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag"
- private const val EXTRA_FILTER_STRATEGY = "filter_strategy"
- private const val FILTER_STRATEGY_GLANCEABLE_HUB = 1
+ private const val EXTRA_DESIRED_WIDGET_WIDTH = "desired_widget_width"
+
+ private const val EXTRA_DESIRED_WIDGET_HEIGHT = "desired_widget_height"
+
private const val TAG = "EditWidgetsActivity"
const val EXTRA_PRESELECTED_KEY = "preselected_key"
}
+ private val logger = Logger(logBuffer, "EditWidgetsActivity")
+
private val widgetConfigurator by lazy { widgetConfiguratorFactory.create(this) }
private val addWidgetActivityLauncher: ActivityResultLauncher<Intent> =
@@ -111,11 +120,19 @@
?.let { packageName ->
try {
addWidgetActivityLauncher.launch(
- Intent(Intent.ACTION_PICK).also {
- it.setPackage(packageName)
- it.putExtra(
- EXTRA_FILTER_STRATEGY,
- FILTER_STRATEGY_GLANCEABLE_HUB
+ Intent(Intent.ACTION_PICK).apply {
+ setPackage(packageName)
+ putExtra(
+ EXTRA_DESIRED_WIDGET_WIDTH,
+ resources.getDimensionPixelSize(
+ R.dimen.communal_widget_picker_desired_width
+ )
+ )
+ putExtra(
+ EXTRA_DESIRED_WIDGET_HEIGHT,
+ resources.getDimensionPixelSize(
+ R.dimen.communal_widget_picker_desired_height
+ )
)
}
)
@@ -146,11 +163,15 @@
override fun onStart() {
super.onStart()
+
+ logger.i("Starting the communal widget editor activity")
uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_EDIT_MODE_SHOWN)
}
override fun onStop() {
super.onStop()
+
+ logger.i("Stopping the communal widget editor activity")
uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_EDIT_MODE_GONE)
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index e9d1e94..dd186d6 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -42,7 +42,7 @@
import com.android.systemui.rotationlock.RotationLockModule;
import com.android.systemui.scene.SceneContainerFrameworkModule;
import com.android.systemui.screenshot.ReferenceScreenshotModule;
-import com.android.systemui.settings.dagger.MultiUserUtilsModule;
+import com.android.systemui.settings.MultiUserUtilsModule;
import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
import com.android.systemui.shade.ShadeModule;
import com.android.systemui.statusbar.CommandQueue;
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 5ee2045..a3d6ad4 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -47,7 +47,7 @@
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
import com.android.systemui.media.taptotransfer.sender.MediaTttSenderCoordinator
import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherCoreStartable
-import com.android.systemui.settings.dagger.MultiUserUtilsModule
+import com.android.systemui.settings.MultiUserUtilsModule
import com.android.systemui.shortcut.ShortcutKeyDispatcher
import com.android.systemui.statusbar.ImmersiveModeConfirmation
import com.android.systemui.statusbar.gesture.GesturePointerEventListener
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 2587e2d..efcbd47 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -57,6 +57,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.FlagDependenciesModule;
import com.android.systemui.flags.FlagsModule;
+import com.android.systemui.inputmethod.InputMethodModule;
import com.android.systemui.keyboard.KeyboardModule;
import com.android.systemui.keyevent.data.repository.KeyEventRepositoryModule;
import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule;
@@ -193,6 +194,7 @@
FlagsModule.class,
FlagDependenciesModule.class,
FooterActionsModule.class,
+ InputMethodModule.class,
KeyEventRepositoryModule.class,
KeyboardModule.class,
KeyguardBlueprintModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
index c9b56a2..05279fc 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
@@ -23,7 +23,6 @@
import android.view.GestureDetector;
import android.view.MotionEvent;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import java.util.Optional;
@@ -34,17 +33,14 @@
/** {@link DreamTouchHandler} responsible for handling touches to open communal hub. **/
public class CommunalTouchHandler implements DreamTouchHandler {
private final int mInitiationWidth;
- private final NotificationShadeWindowController mNotificationShadeWindowController;
private final Optional<CentralSurfaces> mCentralSurfaces;
@Inject
public CommunalTouchHandler(
Optional<CentralSurfaces> centralSurfaces,
- NotificationShadeWindowController notificationShadeWindowController,
@Named(COMMUNAL_GESTURE_INITIATION_WIDTH) int initiationWidth) {
mInitiationWidth = initiationWidth;
mCentralSurfaces = centralSurfaces;
- mNotificationShadeWindowController = notificationShadeWindowController;
}
@Override
@@ -60,9 +56,8 @@
}
private void handleSessionStart(CentralSurfaces surfaces, TouchSession session) {
- // Force the notification shade window open (otherwise the hub won't show while swiping).
- mNotificationShadeWindowController.setForcePluginOpen(true, this);
-
+ // Notification shade window has its own logic to be visible if the hub is open, no need to
+ // do anything here other than send touch events over.
session.registerInputListener(ev -> {
surfaces.handleDreamTouch((MotionEvent) ev);
if (ev != null && ((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 1540423..df0566e 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -29,6 +29,8 @@
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor
+import com.android.systemui.statusbar.notification.shared.NotificationAvalancheSuppression
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import javax.inject.Inject
@@ -45,6 +47,7 @@
// Internal notification frontend dependencies
NotificationsLiveDataStoreRefactor.token dependsOn NotificationIconContainerRefactor.token
FooterViewRefactor.token dependsOn NotificationIconContainerRefactor.token
+ NotificationAvalancheSuppression.token dependsOn VisualInterruptionRefactor.token
// Internal keyguard dependencies
KeyguardShadeMigrationNssl.token dependsOn keyguardBottomAreaRefactor
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index c69c9ef..6eff792 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -585,10 +585,6 @@
@JvmField
val SHARE_WIFI_QS_BUTTON = releasedFlag("share_wifi_qs_button")
- // TODO(b/287205379): Tracking bug
- @JvmField
- val QS_CONTAINER_GRAPH_OPTIMIZER = releasedFlag( "qs_container_graph_optimizer")
-
/** Enable showing a dialog when clicking on Quick Settings bluetooth tile. */
@JvmField
val BLUETOOTH_QS_TILE_DIALOG = releasedFlag("bluetooth_qs_tile_dialog")
diff --git a/packages/SystemUI/src/com/android/systemui/inputmethod/InputMethodModule.kt b/packages/SystemUI/src/com/android/systemui/inputmethod/InputMethodModule.kt
new file mode 100644
index 0000000..bac48f1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputmethod/InputMethodModule.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.inputmethod
+
+import com.android.systemui.inputmethod.data.repository.InputMethodRepositoryModule
+import dagger.Module
+
+/** Module for providing objects exposed by the input method package. */
+@Module(
+ includes =
+ [
+ InputMethodRepositoryModule::class,
+ ],
+)
+object InputMethodModule
diff --git a/packages/SystemUI/src/com/android/systemui/inputmethod/data/model/InputMethodModel.kt b/packages/SystemUI/src/com/android/systemui/inputmethod/data/model/InputMethodModel.kt
new file mode 100644
index 0000000..bdc18b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputmethod/data/model/InputMethodModel.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.inputmethod.data.model
+
+/**
+ * Models an input method editor (IME).
+ *
+ * @see android.view.inputmethod.InputMethodInfo
+ */
+data class InputMethodModel(
+ /** A unique ID for this input method. */
+ val imeId: String,
+ /** The subtypes of this IME (may be empty). */
+ val subtypes: List<Subtype>,
+) {
+ /**
+ * A Subtype can describe locale (e.g. en_US, fr_FR...) and mode (e.g. voice, keyboard), and is
+ * used for IME switch and settings.
+ *
+ * @see android.view.inputmethod.InputMethodSubtype
+ */
+ data class Subtype(
+ /** A unique ID for this IME subtype. */
+ val subtypeId: Int,
+ /**
+ * Whether this subtype is auxiliary. An auxiliary subtype will not be shown in the list of
+ * enabled IMEs for choosing the current IME in Settings, but it will be shown in the list
+ * of IMEs in the IME switcher to allow the user to tentatively switch to this subtype while
+ * an IME is shown.
+ *
+ * The intent of this flag is to allow for IMEs that are invoked in a one-shot way as
+ * auxiliary input mode, and return to the previous IME once it is finished (e.g. voice
+ * input).
+ */
+ val isAuxiliary: Boolean,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/inputmethod/data/repository/InputMethodRepository.kt b/packages/SystemUI/src/com/android/systemui/inputmethod/data/repository/InputMethodRepository.kt
new file mode 100644
index 0000000..5f316c4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputmethod/data/repository/InputMethodRepository.kt
@@ -0,0 +1,139 @@
+/*
+ * 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.inputmethod.data.repository
+
+import android.annotation.SuppressLint
+import android.os.UserHandle
+import android.view.inputmethod.InputMethodInfo
+import android.view.inputmethod.InputMethodManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.inputmethod.data.model.InputMethodModel
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.asFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.withContext
+
+/** Provides access to input-method related application state in the bouncer. */
+interface InputMethodRepository {
+ /**
+ * Creates and returns a new `Flow` of installed input methods that are enabled for the
+ * specified user.
+ *
+ * @param fetchSubtypes Whether to fetch the IME Subtypes as well (requires an additional IPC
+ * call for each IME, avoid if not needed).
+ * @see InputMethodManager.getEnabledInputMethodListAsUser
+ */
+ suspend fun enabledInputMethods(userId: Int, fetchSubtypes: Boolean): Flow<InputMethodModel>
+
+ /** Returns enabled subtypes for the currently selected input method. */
+ suspend fun selectedInputMethodSubtypes(): List<InputMethodModel.Subtype>
+
+ /**
+ * Shows the system's input method picker dialog.
+ *
+ * @param displayId The display ID on which to show the dialog.
+ * @param showAuxiliarySubtypes Whether to show auxiliary input method subtypes in the list of
+ * enabled IMEs.
+ * @see InputMethodManager.showInputMethodPickerFromSystem
+ */
+ suspend fun showInputMethodPicker(displayId: Int, showAuxiliarySubtypes: Boolean)
+}
+
+@SysUISingleton
+class InputMethodRepositoryImpl
+@Inject
+constructor(
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val inputMethodManager: InputMethodManager,
+) : InputMethodRepository {
+
+ override suspend fun enabledInputMethods(
+ userId: Int,
+ fetchSubtypes: Boolean
+ ): Flow<InputMethodModel> {
+ return withContext(backgroundDispatcher) {
+ inputMethodManager.getEnabledInputMethodListAsUser(UserHandle.of(userId))
+ }
+ .asFlow()
+ .map { inputMethodInfo ->
+ InputMethodModel(
+ imeId = inputMethodInfo.id,
+ subtypes =
+ if (fetchSubtypes) {
+ enabledInputMethodSubtypes(
+ inputMethodInfo,
+ allowsImplicitlyEnabledSubtypes = true
+ )
+ } else {
+ listOf()
+ }
+ )
+ }
+ }
+
+ override suspend fun selectedInputMethodSubtypes(): List<InputMethodModel.Subtype> {
+ return enabledInputMethodSubtypes(
+ inputMethodInfo = null, // Fetch subtypes for the currently-selected IME.
+ allowsImplicitlyEnabledSubtypes = false
+ )
+ }
+
+ @SuppressLint("MissingPermission")
+ override suspend fun showInputMethodPicker(displayId: Int, showAuxiliarySubtypes: Boolean) {
+ withContext(backgroundDispatcher) {
+ inputMethodManager.showInputMethodPickerFromSystem(showAuxiliarySubtypes, displayId)
+ }
+ }
+
+ /**
+ * Returns a list of enabled input method subtypes for the specified input method info.
+ *
+ * @param inputMethodInfo The [InputMethodInfo] whose subtypes list will be returned. If `null`,
+ * returns enabled subtypes for the currently selected [InputMethodInfo].
+ * @param allowsImplicitlyEnabledSubtypes Whether to allow to return the implicitly enabled
+ * subtypes. If an input method info doesn't have enabled subtypes, the framework will
+ * implicitly enable subtypes according to the current system language.
+ * @see InputMethodManager.getEnabledInputMethodSubtypeList
+ */
+ private suspend fun enabledInputMethodSubtypes(
+ inputMethodInfo: InputMethodInfo?,
+ allowsImplicitlyEnabledSubtypes: Boolean
+ ): List<InputMethodModel.Subtype> {
+ return withContext(backgroundDispatcher) {
+ inputMethodManager.getEnabledInputMethodSubtypeList(
+ inputMethodInfo,
+ allowsImplicitlyEnabledSubtypes
+ )
+ }
+ .map {
+ InputMethodModel.Subtype(
+ subtypeId = it.subtypeId,
+ isAuxiliary = it.isAuxiliary,
+ )
+ }
+ }
+}
+
+@Module
+interface InputMethodRepositoryModule {
+ @Binds fun repository(impl: InputMethodRepositoryImpl): InputMethodRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractor.kt b/packages/SystemUI/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractor.kt
new file mode 100644
index 0000000..c54aa7f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractor.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.inputmethod.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.inputmethod.data.repository.InputMethodRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.count
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.take
+
+/** Hosts application business logic related to input methods (e.g. software keyboard). */
+@SysUISingleton
+class InputMethodInteractor
+@Inject
+constructor(
+ private val repository: InputMethodRepository,
+) {
+ /**
+ * Returns whether there are multiple enabled input methods to choose from for password input.
+ *
+ * Method adapted from `com.android.inputmethod.latin.Utils`.
+ */
+ suspend fun hasMultipleEnabledImesOrSubtypes(userId: Int): Boolean {
+ // Count IMEs that either have no subtypes, or have at least one non-auxiliary subtype.
+ val matchingInputMethods =
+ repository
+ .enabledInputMethods(userId, fetchSubtypes = true)
+ .filter { ime -> ime.subtypes.isEmpty() || ime.subtypes.any { !it.isAuxiliary } }
+ .take(2) // Short-circuit if we find at least 2 matching IMEs.
+
+ return matchingInputMethods.count() > 1 || repository.selectedInputMethodSubtypes().size > 1
+ }
+
+ /** Shows the system's input method picker dialog. */
+ suspend fun showInputMethodPicker(displayId: Int, showAuxiliarySubtypes: Boolean) {
+ repository.showInputMethodPicker(displayId, showAuxiliarySubtypes)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt
index 26e83a3..f16a3bd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt
@@ -17,7 +17,7 @@
package com.android.systemui.keyboard
-import com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag
+import android.hardware.input.InputSettings
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
@@ -40,7 +40,7 @@
if (featureFlags.isEnabled(Flags.KEYBOARD_BACKLIGHT_INDICATOR)) {
keyboardBacklightDialogCoordinator.get().startListening()
}
- if (keyboardA11yStickyKeysFlag()) {
+ if (InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
stickyKeysIndicatorCoordinator.get().startListening()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt
index b68551b..c3a618d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt
@@ -58,7 +58,6 @@
dialog = null
} else if (dialog == null) {
dialog = ComposeFacade.createStickyKeysDialog(dialogFactory, viewModel).apply {
- setCanceledOnTouchOutside(false)
window?.setAttributes()
show()
}
@@ -70,6 +69,7 @@
private fun Window.setAttributes() {
setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
+ addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
setGravity(Gravity.TOP or Gravity.END)
attributes = WindowManager.LayoutParams().apply {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index f10b87e..4cabd70 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -17,7 +17,6 @@
package com.android.systemui.keyguard;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
@@ -61,7 +60,6 @@
import android.view.RemoteAnimationDefinition;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
-import android.view.WindowManager;
import android.view.WindowManagerPolicyConstants;
import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
@@ -85,10 +83,10 @@
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.power.shared.model.ScreenPowerState;
import com.android.systemui.settings.DisplayTracker;
+import com.android.wm.shell.shared.CounterRotator;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.transition.ShellTransitions;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.CounterRotator;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.Map;
@@ -108,20 +106,7 @@
private final ScreenOnCoordinator mScreenOnCoordinator;
private final ShellTransitions mShellTransitions;
private final DisplayTracker mDisplayTracker;
- private PowerInteractor mPowerInteractor;
-
- private static int newModeToLegacyMode(int newMode) {
- switch (newMode) {
- case WindowManager.TRANSIT_OPEN:
- case WindowManager.TRANSIT_TO_FRONT:
- return MODE_OPENING;
- case WindowManager.TRANSIT_CLOSE:
- case WindowManager.TRANSIT_TO_BACK:
- return MODE_CLOSING;
- default:
- return 2; // MODE_CHANGING
- }
- }
+ private final PowerInteractor mPowerInteractor;
private static RemoteAnimationTarget[] wrap(TransitionInfo info, boolean wallpapers,
SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap,
@@ -253,8 +238,7 @@
public void mergeAnimation(IBinder candidateTransition, TransitionInfo candidateInfo,
SurfaceControl.Transaction candidateT, IBinder currentTransition,
- IRemoteTransitionFinishedCallback candidateFinishCallback)
- throws RemoteException {
+ IRemoteTransitionFinishedCallback candidateFinishCallback) {
if ((candidateInfo.getFlags() & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0) {
keyguardViewMediator.setPendingLock(true);
keyguardViewMediator.cancelKeyguardExitAnimation();
@@ -265,13 +249,13 @@
runner.onAnimationCancelled();
finish(currentTransition);
} catch (RemoteException e) {
- // nothing, we'll just let it finish on its own I guess.
+ // Ignore.
}
}
@Override
- public void onTransitionConsumed(IBinder transition, boolean aborted)
- throws RemoteException {
+ public void onTransitionConsumed(IBinder transition, boolean aborted) {
+ // No-op.
}
private static void initAlphaForAnimationTargets(@NonNull SurfaceControl.Transaction t,
@@ -283,7 +267,7 @@
}
private void finish(IBinder transition) throws RemoteException {
- IRemoteTransitionFinishedCallback finishCallback = null;
+ final IRemoteTransitionFinishedCallback finishCallback;
SurfaceControl.Transaction finishTransaction = null;
synchronized (mLeashMap) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index afef875..7f43fac 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -41,6 +41,7 @@
import com.android.systemui.keyguard.ui.view.layout.KeyguardBlueprintCommandListener
import com.android.systemui.keyguard.ui.viewmodel.AodAlphaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
@@ -86,6 +87,7 @@
private val vibratorHelper: VibratorHelper,
private val falsingManager: FalsingManager,
private val aodAlphaViewModel: AodAlphaViewModel,
+ private val keyguardClockViewModel: KeyguardClockViewModel,
) : CoreStartable {
private var rootViewHandle: DisposableHandle? = null
@@ -113,7 +115,11 @@
initializeViews()
if (!SceneContainerFlag.isEnabled) {
- KeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel)
+ KeyguardBlueprintViewBinder.bind(
+ keyguardRootView,
+ keyguardBlueprintViewModel,
+ keyguardClockViewModel
+ )
}
keyguardBlueprintCommandListener.start()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 794befa..f085e88 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -45,12 +45,12 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
import android.app.AlarmManager;
import android.app.BroadcastOptions;
import android.app.IActivityTaskManager;
import android.app.PendingIntent;
import android.app.StatusBarManager;
-import android.app.WallpaperManager;
import android.app.WindowConfiguration;
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
@@ -174,6 +174,8 @@
import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
import com.android.wm.shell.keyguard.KeyguardTransitions;
+import dagger.Lazy;
+
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -183,7 +185,6 @@
import java.util.concurrent.Executor;
import java.util.function.Consumer;
-import dagger.Lazy;
import kotlinx.coroutines.CoroutineDispatcher;
/**
@@ -326,7 +327,6 @@
private AlarmManager mAlarmManager;
private AudioManager mAudioManager;
private StatusBarManager mStatusBarManager;
- private WallpaperManager mWallpaperManager;
private final IStatusBarService mStatusBarService;
private final IBinder mStatusBarDisableToken = new Binder();
private final UserTracker mUserTracker;
@@ -356,13 +356,13 @@
private final SecureSettings mSecureSettings;
private final SystemSettings mSystemSettings;
private final SystemClock mSystemClock;
- private SystemPropertiesHelper mSystemPropertiesHelper;
+ private final SystemPropertiesHelper mSystemPropertiesHelper;
/**
* Used to keep the device awake while to ensure the keyguard finishes opening before
* we sleep.
*/
- private PowerManager.WakeLock mShowKeyguardWakeLock;
+ private final PowerManager.WakeLock mShowKeyguardWakeLock;
private final Lazy<KeyguardViewController> mKeyguardViewControllerLazy;
@@ -405,13 +405,13 @@
private boolean mWakeAndUnlocking = false;
/**
- * Helps remember whether the screen has turned on since the last time
- * it turned off due to timeout. see {@link #onScreenTurnedOff(int)}
+ * Helps remember whether the screen has turned on since the last time it turned off due to
+ * timeout. See {@link #onScreenTurnedOff}
*/
private int mDelayedShowingSequence;
/**
- * Similar to {@link #mDelayedProfileShowingSequence}, but it is for profile case.
+ * Similar to {@link #mDelayedShowingSequence}, but it is for profile case.
*/
private int mDelayedProfileShowingSequence;
@@ -439,7 +439,7 @@
private boolean mGoingToSleep;
// last known state of the cellular connection
- private String mPhoneState = TelephonyManager.EXTRA_STATE_IDLE;
+ private final String mPhoneState = TelephonyManager.EXTRA_STATE_IDLE;
/**
* Whether a hide is pending and we are just waiting for #startKeyguardExitAnimation to be
@@ -1088,8 +1088,7 @@
public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
- if (!handleOnAnimationStart(
- transit, apps, wallpapers, nonApps, finishedCallback)) {
+ if (!handleOnAnimationStart(apps, finishedCallback)) {
// Usually we rely on animation completion to synchronize occluded status,
// but there was no animation to play, so just update it now.
setOccluded(true /* isOccluded */, false /* animate */);
@@ -1097,9 +1096,8 @@
}
}
- private boolean handleOnAnimationStart(int transit, RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
- IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
+ private boolean handleOnAnimationStart(RemoteAnimationTarget[] apps,
+ IRemoteAnimationFinishedCallback finishedCallback) {
if (apps == null || apps.length == 0 || apps[0] == null) {
Log.d(TAG, "No apps provided to the OccludeByDream runner; "
+ "skipping occluding animation.");
@@ -1107,8 +1105,8 @@
}
final RemoteAnimationTarget primary = apps[0];
- final boolean isDream = (apps[0].taskInfo != null
- && apps[0].taskInfo.topActivityType
+ final boolean isDream = (primary.taskInfo != null
+ && primary.taskInfo.topActivityType
== WindowConfiguration.ACTIVITY_TYPE_DREAM);
if (!isDream) {
Log.w(TAG, "The occluding app isn't Dream; "
@@ -1322,9 +1320,9 @@
}
private DeviceConfigProxy mDeviceConfig;
- private DozeParameters mDozeParameters;
- private SelectedUserInteractor mSelectedUserInteractor;
- private KeyguardInteractor mKeyguardInteractor;
+ private final DozeParameters mDozeParameters;
+ private final SelectedUserInteractor mSelectedUserInteractor;
+ private final KeyguardInteractor mKeyguardInteractor;
@VisibleForTesting
protected FoldGracePeriodProvider mFoldGracePeriodProvider =
new FoldGracePeriodProvider();
@@ -1346,14 +1344,12 @@
private final Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
private final InteractionJankMonitor mInteractionJankMonitor;
private boolean mWallpaperSupportsAmbientMode;
- private ScreenOnCoordinator mScreenOnCoordinator;
private final KeyguardTransitions mKeyguardTransitions;
- private Lazy<ActivityLaunchAnimator> mActivityLaunchAnimator;
- private Lazy<ScrimController> mScrimControllerLazy;
- private IActivityTaskManager mActivityTaskManagerService;
+ private final Lazy<ActivityLaunchAnimator> mActivityLaunchAnimator;
+ private final Lazy<ScrimController> mScrimControllerLazy;
+ private final IActivityTaskManager mActivityTaskManagerService;
- private FeatureFlags mFeatureFlags;
private final UiEventLogger mUiEventLogger;
private final SessionTracker mSessionTracker;
private final CoroutineDispatcher mMainDispatcher;
@@ -1361,7 +1357,7 @@
mDreamingToLockscreenTransitionViewModel;
private RemoteAnimationTarget mRemoteAnimationTarget;
- private Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
+ private final Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
/**
* Injected constructor. See {@link KeyguardModule}.
@@ -1433,7 +1429,6 @@
mShadeController = shadeControllerLazy;
dumpManager.registerDumpable(this);
mDeviceConfig = deviceConfig;
- mScreenOnCoordinator = screenOnCoordinator;
mKeyguardTransitions = keyguardTransitions;
mNotificationShadeWindowControllerLazy = notificationShadeWindowControllerLazy;
mShowHomeOverLockscreen = mDeviceConfig.getBoolean(
@@ -1445,9 +1440,8 @@
mHandler::post,
mOnPropertiesChangedListener);
mInGestureNavigationMode =
- QuickStepContract.isGesturalMode(navigationModeController.addListener(mode -> {
- mInGestureNavigationMode = QuickStepContract.isGesturalMode(mode);
- }));
+ QuickStepContract.isGesturalMode(navigationModeController.addListener(mode ->
+ mInGestureNavigationMode = QuickStepContract.isGesturalMode(mode)));
mDozeParameters = dozeParameters;
mSelectedUserInteractor = selectedUserInteractor;
mKeyguardInteractor = keyguardInteractor;
@@ -1474,7 +1468,6 @@
mDreamOpenAnimationDuration = (int) DREAMING_ANIMATION_DURATION_MS;
- mFeatureFlags = featureFlags;
mUiEventLogger = uiEventLogger;
mSessionTracker = sessionTracker;
@@ -1578,14 +1571,6 @@
this::setWallpaperSupportsAmbientMode);
}
- // TODO(b/273443374) remove, temporary util to get a feature flag
- private WallpaperManager getWallpaperManager() {
- if (mWallpaperManager == null) {
- mWallpaperManager = mContext.getSystemService(WallpaperManager.class);
- }
- return mWallpaperManager;
- }
-
@Override
public void start() {
synchronized (this) {
@@ -1611,11 +1596,11 @@
ViewRootImpl viewRootImpl = mKeyguardViewControllerLazy.get().getViewRootImpl();
if (viewRootImpl != null) {
- collectFlow(viewRootImpl.getView(),
- mDreamingToLockscreenTransitionViewModel.get().getDreamOverlayAlpha(),
+ DreamingToLockscreenTransitionViewModel viewModel =
+ mDreamingToLockscreenTransitionViewModel.get();
+ collectFlow(viewRootImpl.getView(), viewModel.getDreamOverlayAlpha(),
getRemoteSurfaceAlphaApplier(), mMainDispatcher);
- collectFlow(viewRootImpl.getView(),
- mDreamingToLockscreenTransitionViewModel.get().getTransitionEnded(),
+ collectFlow(viewRootImpl.getView(), viewModel.getTransitionEnded(),
getFinishedCallbackConsumer(), mMainDispatcher);
}
}
@@ -2304,6 +2289,7 @@
showKeyguard(options);
}
+ @SuppressLint("MissingPermission")
private void lockProfile(int userId) {
mTrustManager.setDeviceLockedForUser(userId, true);
}
@@ -2497,13 +2483,12 @@
};
/**
- * This handler will be associated with the policy thread, which will also
- * be the UI thread of the keyguard. Since the apis of the policy, and therefore
- * this class, can be called by other threads, any action that directly
- * interacts with the keyguard ui should be posted to this handler, rather
- * than called directly.
+ * This handler will be associated with the policy thread, which will also be the UI thread of
+ * the keyguard. Since the apis of the policy, and therefore this class, can be called by other
+ * threads, any action that directly interacts with the keyguard ui should be posted to this
+ * handler, rather than called directly.
*/
- private Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) {
+ private final Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) {
@Override
public void handleMessage(Message msg) {
String message = "";
@@ -2766,7 +2751,7 @@
try {
mActivityTaskManagerService.setLockScreenShown(showing, aodShowing);
- } catch (RemoteException e) {
+ } catch (RemoteException ignored) {
}
});
}
@@ -2790,9 +2775,8 @@
if (!mSystemReady) {
if (DEBUG) Log.d(TAG, "ignoring handleShow because system is not ready.");
return;
- } else {
- if (DEBUG) Log.d(TAG, "handleShow");
}
+ if (DEBUG) Log.d(TAG, "handleShow");
mKeyguardExitAnimationRunner = null;
mWakeAndUnlocking = false;
@@ -2851,6 +2835,7 @@
}
private final Runnable mKeyguardGoingAwayRunnable = new Runnable() {
+ @SuppressLint("MissingPermission")
@Override
public void run() {
Trace.beginSection("KeyguardViewMediator.mKeyGuardGoingAwayRunnable");
@@ -2925,24 +2910,13 @@
return;
}
- final String reasonDescription;
-
- switch(reason) {
- case WakeAndUnlockUpdateReason.FULFILL:
- reasonDescription = "fulfilling existing request";
- break;
- case WakeAndUnlockUpdateReason.HIDE:
- reasonDescription = "hiding keyguard";
- break;
- case WakeAndUnlockUpdateReason.SHOW:
- reasonDescription = "showing keyguard";
- break;
- case WakeAndUnlockUpdateReason.WAKE_AND_UNLOCK:
- reasonDescription = "waking to unlock";
- break;
- default:
- throw new IllegalStateException("Unexpected value: " + reason);
- }
+ final String reasonDescription = switch (reason) {
+ case WakeAndUnlockUpdateReason.FULFILL -> "fulfilling existing request";
+ case WakeAndUnlockUpdateReason.HIDE -> "hiding keyguard";
+ case WakeAndUnlockUpdateReason.SHOW -> "showing keyguard";
+ case WakeAndUnlockUpdateReason.WAKE_AND_UNLOCK -> "waking to unlock";
+ default -> throw new IllegalStateException("Unexpected value: " + reason);
+ };
final boolean unsetUnfulfilled = !updatedValue
&& reason != WakeAndUnlockUpdateReason.FULFILL;
@@ -3057,7 +3031,7 @@
IRemoteAnimationFinishedCallback callback =
new IRemoteAnimationFinishedCallback() {
@Override
- public void onAnimationFinished() throws RemoteException {
+ public void onAnimationFinished() {
if (!KeyguardWmStateRefactor.isEnabled()) {
try {
finishedCallback.onAnimationFinished();
@@ -3542,11 +3516,6 @@
/**
* Registers the CentralSurfaces to which the Keyguard View is mounted.
*
- * @param centralSurfaces
- * @param panelView
- * @param biometricUnlockController
- * @param notificationContainer
- * @param bypassController
* @return the View Controller for the Keyguard View this class is mediating.
*/
public KeyguardViewController registerCentralSurfaces(CentralSurfaces centralSurfaces,
@@ -3773,9 +3742,7 @@
}
});
updateInputRestrictedLocked();
- mUiBgExecutor.execute(() -> {
- mTrustManager.reportKeyguardShowingChanged();
- });
+ mUiBgExecutor.execute(mTrustManager::reportKeyguardShowingChanged);
}
private void notifyTrustedChangedLocked(boolean trusted) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index e16f8dc..70da3e7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -90,10 +90,12 @@
import java.util.concurrent.Executor;
import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
/**
* Dagger Module providing keyguard.
*/
+@ExperimentalCoroutinesApi
@Module(subcomponents = {
KeyguardQsUserSwitchComponent.class,
KeyguardStatusBarViewComponent.class,
@@ -115,7 +117,7 @@
*/
@Provides
@SysUISingleton
- public static KeyguardViewMediator newKeyguardViewMediator(
+ static KeyguardViewMediator newKeyguardViewMediator(
Context context,
UiEventLogger uiEventLogger,
SessionTracker sessionTracker,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
index 48b6634..9381830 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
@@ -23,6 +23,8 @@
import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint.Companion.DEFAULT
import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransitionType
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransitionType.DefaultTransition
import java.io.PrintWriter
import java.util.TreeMap
import javax.inject.Inject
@@ -54,6 +56,8 @@
TreeMap<String, KeyguardBlueprint>().apply { putAll(blueprints.associateBy { it.id }) }
val blueprint: MutableStateFlow<KeyguardBlueprint> = MutableStateFlow(blueprintIdMap[DEFAULT]!!)
val refreshBluePrint: MutableSharedFlow<Unit> = MutableSharedFlow(extraBufferCapacity = 1)
+ val refreshBlueprintTransition: MutableSharedFlow<IntraBlueprintTransitionType> =
+ MutableSharedFlow(extraBufferCapacity = 1)
val configurationChange: Flow<Unit> = configurationRepository.onAnyConfigurationChange
/**
@@ -103,7 +107,12 @@
/** Re-emits the last emitted blueprint value if possible. */
fun refreshBlueprint() {
+ refreshBlueprintWithTransition(DefaultTransition)
+ }
+
+ fun refreshBlueprintWithTransition(type: IntraBlueprintTransitionType = DefaultTransition) {
refreshBluePrint.tryEmit(Unit)
+ refreshBlueprintTransition.tryEmit(type)
}
/** Prints all available blueprints to the PrintWriter. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 3965648..deb70b7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -405,7 +405,9 @@
interpolator = Interpolators.LINEAR
duration =
when (toState) {
- KeyguardState.DREAMING -> TO_DREAMING_DURATION
+ // Adds 100ms to the overall delay to workaround legacy setOccluded calls
+ // being delayed in KeyguardViewMediator
+ KeyguardState.DREAMING -> TO_DREAMING_DURATION + 100.milliseconds
KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION
KeyguardState.AOD -> TO_AOD_DURATION
KeyguardState.DOZING -> TO_DOZING_DURATION
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
index ba44e68..566e006 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
@@ -24,12 +24,13 @@
import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransitionType
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransitionType.DefaultTransition
import com.android.systemui.statusbar.policy.SplitShadeStateController
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
@@ -48,13 +49,15 @@
*
* This flow can also emit the same blueprint value if refreshBlueprint is emitted.
*/
- val blueprint: Flow<KeyguardBlueprint> =
- merge(
- keyguardBlueprintRepository.blueprint,
- keyguardBlueprintRepository.refreshBluePrint.map {
- keyguardBlueprintRepository.blueprint.value
- }
- )
+ val blueprint: Flow<KeyguardBlueprint> = keyguardBlueprintRepository.blueprint
+
+ val blueprintWithTransition =
+ combine(
+ keyguardBlueprintRepository.refreshBluePrint,
+ keyguardBlueprintRepository.refreshBlueprintTransition
+ ) { _, source ->
+ source
+ }
init {
applicationScope.launch {
@@ -107,6 +110,10 @@
keyguardBlueprintRepository.refreshBlueprint()
}
+ fun refreshBlueprintWithTransition(type: IntraBlueprintTransitionType = DefaultTransition) {
+ keyguardBlueprintRepository.refreshBlueprintWithTransition(type)
+ }
+
fun getCurrentBlueprint(): KeyguardBlueprint {
return keyguardBlueprintRepository.blueprint.value
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/Edge.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/Edge.kt
new file mode 100644
index 0000000..a0f9be6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/Edge.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+/** FROM -> TO keyguard transition. null values are allowed to signify FROM -> *, or * -> TO */
+data class Edge(
+ val from: KeyguardState?,
+ val to: KeyguardState?,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
index 4abda74..00b7989 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -22,6 +22,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
@@ -174,11 +175,6 @@
}
}
- data class Edge(
- val from: KeyguardState?,
- val to: KeyguardState?,
- )
-
data class StateToValue(
val transitionState: TransitionState,
val value: Float?,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
index 8d6493f..404046b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
@@ -26,7 +26,10 @@
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.BaseBlueprintTransition
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransitionType
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import kotlinx.coroutines.launch
@@ -34,7 +37,11 @@
companion object {
private const val TAG = "KeyguardBlueprintViewBinder"
- fun bind(constraintLayout: ConstraintLayout, viewModel: KeyguardBlueprintViewModel) {
+ fun bind(
+ constraintLayout: ConstraintLayout,
+ viewModel: KeyguardBlueprintViewModel,
+ clockViewModel: KeyguardClockViewModel
+ ) {
constraintLayout.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch {
@@ -42,6 +49,7 @@
val prevBluePrint = viewModel.currentBluePrint
Trace.beginSection("KeyguardBlueprint#applyBlueprint")
Log.d(TAG, "applying blueprint: $blueprint")
+ TransitionManager.endTransitions(constraintLayout)
val cs =
ConstraintSet().apply {
@@ -54,11 +62,28 @@
}
// Apply transition.
- if (!keyguardBottomAreaRefactor() && prevBluePrint != null &&
- prevBluePrint != blueprint) {
+ if (
+ !keyguardBottomAreaRefactor() &&
+ prevBluePrint != null &&
+ prevBluePrint != blueprint
+ ) {
TransitionManager.beginDelayedTransition(
constraintLayout,
- BaseBlueprintTransition()
+ BaseBlueprintTransition(clockViewModel)
+ .addTransition(
+ IntraBlueprintTransition(
+ IntraBlueprintTransitionType.NoTransition,
+ clockViewModel
+ )
+ )
+ )
+ } else {
+ TransitionManager.beginDelayedTransition(
+ constraintLayout,
+ IntraBlueprintTransition(
+ IntraBlueprintTransitionType.NoTransition,
+ clockViewModel
+ )
)
}
@@ -71,6 +96,25 @@
Trace.endSection()
}
}
+
+ launch {
+ viewModel.blueprintWithTransition.collect { source ->
+ TransitionManager.endTransitions(constraintLayout)
+
+ val cs =
+ ConstraintSet().apply {
+ clone(constraintLayout)
+ viewModel.currentBluePrint?.applyConstraints(this)
+ }
+
+ TransitionManager.beginDelayedTransition(
+ constraintLayout,
+ IntraBlueprintTransition(source, clockViewModel)
+ )
+ cs.applyTo(constraintLayout)
+ Trace.endSection()
+ }
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
index 3c3ebdf..f0e89f9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
@@ -16,34 +16,30 @@
package com.android.systemui.keyguard.ui.binder
-import android.animation.Animator
-import android.animation.ValueAnimator
-import android.transition.Transition
import android.transition.TransitionManager
import android.transition.TransitionSet
-import android.transition.TransitionValues
-import android.view.ViewGroup
+import android.util.Log
+import android.view.View.INVISIBLE
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.helper.widget.Layer
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
-import com.android.app.animation.Interpolators
import com.android.keyguard.KeyguardClockSwitch.LARGE
import com.android.keyguard.KeyguardClockSwitch.SMALL
import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransitionType
import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.res.R
+import com.android.systemui.shared.clocks.DEFAULT_CLOCK_ID
import kotlinx.coroutines.launch
-private const val KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION_MS = 1000L
-
object KeyguardClockViewBinder {
@JvmStatic
fun bind(
@@ -67,32 +63,56 @@
viewModel.clock = currentClock
addClockViews(currentClock, keyguardRootView)
updateBurnInLayer(keyguardRootView, viewModel)
- blueprintInteractor.refreshBlueprint()
+ applyConstraints(clockSection, keyguardRootView, true)
}
}
launch {
if (!migrateClocksToBlueprint()) return@launch
viewModel.clockSize.collect {
updateBurnInLayer(keyguardRootView, viewModel)
- blueprintInteractor.refreshBlueprint()
+ blueprintInteractor.refreshBlueprintWithTransition(
+ IntraBlueprintTransitionType.ClockSize
+ )
}
}
launch {
if (!migrateClocksToBlueprint()) return@launch
- viewModel.clockShouldBeCentered.collect {
+ viewModel.clockShouldBeCentered.collect { clockShouldBeCentered ->
+ Log.d(
+ "ClockViewBinder",
+ "Sherry clockShouldBeCentered $clockShouldBeCentered"
+ )
viewModel.clock?.let {
- if (it.largeClock.config.hasCustomPositionUpdatedAnimation) {
- playClockCenteringAnimation(clockSection, keyguardRootView, it)
+ // Weather clock also has hasCustomPositionUpdatedAnimation as true
+ // TODO(b/323020908): remove ID check
+ if (
+ it.largeClock.config.hasCustomPositionUpdatedAnimation &&
+ it.config.id == DEFAULT_CLOCK_ID
+ ) {
+ blueprintInteractor.refreshBlueprintWithTransition(
+ IntraBlueprintTransitionType.DefaultClockStepping
+ )
} else {
- blueprintInteractor.refreshBlueprint()
+ blueprintInteractor.refreshBlueprintWithTransition(
+ IntraBlueprintTransitionType.DefaultTransition
+ )
}
}
}
}
launch {
if (!migrateClocksToBlueprint()) return@launch
- viewModel.isAodIconsVisible.collect {
- applyConstraints(clockSection, keyguardRootView, true)
+ viewModel.isAodIconsVisible.collect { isAodIconsVisible ->
+ viewModel.clock?.let {
+ // Weather clock also has hasCustomPositionUpdatedAnimation as true
+ if (
+ viewModel.useLargeClock && it.config.id == "DIGITAL_CLOCK_WEATHER"
+ ) {
+ blueprintInteractor.refreshBlueprintWithTransition(
+ IntraBlueprintTransitionType.DefaultTransition
+ )
+ }
+ }
}
}
}
@@ -155,7 +175,9 @@
}
// small clock should either be a single view or container with id
// `lockscreen_clock_view`
- clock.smallClock.layout.views.forEach { rootView.addView(it) }
+ clock.smallClock.layout.views.forEach {
+ rootView.addView(it).apply { it.visibility = INVISIBLE }
+ }
clock.largeClock.layout.views.forEach { rootView.addView(it) }
}
}
@@ -173,74 +195,4 @@
}
constraintSet.applyTo(rootView)
}
-
- private fun playClockCenteringAnimation(
- clockSection: ClockSection,
- keyguardRootView: ConstraintLayout,
- clock: ClockController,
- ) {
- // Find the clock, so we can exclude it from this transition.
- val clockView = clock.largeClock.view
- val set = TransitionSet()
- val adapter = SplitShadeTransitionAdapter(clock)
- adapter.setInterpolator(Interpolators.LINEAR)
- adapter.setDuration(KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION_MS)
- adapter.addTarget(clockView)
- set.addTransition(adapter)
- applyConstraints(clockSection, keyguardRootView, true, set)
- }
-
- internal class SplitShadeTransitionAdapter
- @VisibleForTesting
- constructor(private val clock: ClockController) : Transition() {
- private fun captureValues(transitionValues: TransitionValues) {
- transitionValues.values[PROP_BOUNDS_LEFT] = transitionValues.view.left
- val locationInWindowTmp = IntArray(2)
- transitionValues.view.getLocationInWindow(locationInWindowTmp)
- transitionValues.values[PROP_X_IN_WINDOW] = locationInWindowTmp[0]
- }
-
- override fun captureEndValues(transitionValues: TransitionValues) {
- captureValues(transitionValues)
- }
-
- override fun captureStartValues(transitionValues: TransitionValues) {
- captureValues(transitionValues)
- }
-
- override fun createAnimator(
- sceneRoot: ViewGroup,
- startValues: TransitionValues?,
- endValues: TransitionValues?
- ): Animator? {
- if (startValues == null || endValues == null) {
- return null
- }
- val anim = ValueAnimator.ofFloat(0f, 1f)
- val fromLeft = startValues.values[PROP_BOUNDS_LEFT] as Int
- val fromWindowX = startValues.values[PROP_X_IN_WINDOW] as Int
- val toWindowX = endValues.values[PROP_X_IN_WINDOW] as Int
- // Using windowX, to determine direction, instead of left, as in RTL the difference of
- // toLeft - fromLeft is always positive, even when moving left.
- val direction = if (toWindowX - fromWindowX > 0) 1 else -1
- anim.addUpdateListener { animation: ValueAnimator ->
- clock.largeClock.animations.onPositionUpdated(
- fromLeft,
- direction,
- animation.animatedFraction
- )
- }
- return anim
- }
-
- override fun getTransitionProperties(): Array<String> {
- return TRANSITION_PROPERTIES
- }
-
- companion object {
- private const val PROP_BOUNDS_LEFT = "splitShadeTransitionAdapter:boundsLeft"
- private const val PROP_X_IN_WINDOW = "splitShadeTransitionAdapter:xInWindow"
- private val TRANSITION_PROPERTIES = arrayOf(PROP_BOUNDS_LEFT, PROP_X_IN_WINDOW)
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 48092c6..789d30f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -191,6 +191,7 @@
.collect { y ->
childViews[burnInLayerId]?.translationY = y
childViews[largeClockId]?.translationY = y
+ childViews[aodNotificationIconContainerId]?.translationY = y
}
}
@@ -200,6 +201,7 @@
.collect { x ->
childViews[burnInLayerId]?.translationX = x
childViews[largeClockId]?.translationX = x
+ childViews[aodNotificationIconContainerId]?.translationX = x
}
}
@@ -219,6 +221,10 @@
// transition with other parts in burnInLayer
childViews[burnInLayerId]?.scaleX = scaleViewModel.scale
childViews[burnInLayerId]?.scaleY = scaleViewModel.scale
+ childViews[aodNotificationIconContainerId]?.scaleX =
+ scaleViewModel.scale
+ childViews[aodNotificationIconContainerId]?.scaleY =
+ scaleViewModel.scale
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
index 10392e3..08a2b9c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
@@ -49,14 +49,15 @@
clockViewModel,
smartspaceViewModel
)
- blueprintInteractor.refreshBlueprint()
+ blueprintInteractor.refreshBlueprintWithTransition()
}
}
launch {
+ if (!migrateClocksToBlueprint()) return@launch
smartspaceViewModel.bcSmartspaceVisibility.collect {
updateBCSmartspaceInBurnInLayer(keyguardRootView, clockViewModel)
- blueprintInteractor.refreshBlueprint()
+ blueprintInteractor.refreshBlueprintWithTransition()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.kt
index fd530b7..9c9df80 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.kt
@@ -19,30 +19,32 @@
import android.animation.Animator
import android.animation.ObjectAnimator
import android.transition.ChangeBounds
+import android.transition.Transition
import android.transition.TransitionSet
import android.transition.TransitionValues
import android.transition.Visibility
import android.view.View
import android.view.ViewGroup
import androidx.constraintlayout.helper.widget.Layer
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
-import com.android.systemui.res.R
-class BaseBlueprintTransition : TransitionSet() {
+class BaseBlueprintTransition(val clockViewModel: KeyguardClockViewModel) : TransitionSet() {
init {
ordering = ORDERING_SEQUENTIAL
addTransition(AlphaOutVisibility())
.addTransition(ChangeBounds())
.addTransition(AlphaInVisibility())
excludeTarget(Layer::class.java, /* exclude= */ true)
- excludeClockAndSmartspaceViews()
+ excludeClockAndSmartspaceViews(this)
}
- private fun excludeClockAndSmartspaceViews() {
- excludeTarget(R.id.lockscreen_clock_view, true)
- excludeTarget(R.id.lockscreen_clock_view_large, true)
- excludeTarget(SmartspaceView::class.java, true)
- // TODO(b/319468190): need to exclude views from large weather clock
+ private fun excludeClockAndSmartspaceViews(transition: Transition) {
+ transition.excludeTarget(SmartspaceView::class.java, true)
+ clockViewModel.clock?.let { clock ->
+ clock.largeClock.layout.views.forEach { view -> transition.excludeTarget(view, true) }
+ clock.smallClock.layout.views.forEach { view -> transition.excludeTarget(view, true) }
+ }
}
class AlphaOutVisibility : Visibility() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt
new file mode 100644
index 0000000..524aa1a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.blueprints.transitions
+
+import android.transition.TransitionSet
+import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition
+import com.android.systemui.keyguard.ui.view.layout.sections.transitions.DefaultClockSteppingTransition
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+
+enum class IntraBlueprintTransitionType {
+ ClockSize,
+ ClockCenter,
+ DefaultClockStepping,
+ DefaultTransition,
+ AodNotifIconsTransition,
+ // When transition between blueprint, we don't need any duration or interpolator but we need
+ // all elements go to correct state
+ NoTransition,
+}
+
+class IntraBlueprintTransition(
+ type: IntraBlueprintTransitionType,
+ clockViewModel: KeyguardClockViewModel
+) : TransitionSet() {
+ init {
+ ordering = ORDERING_TOGETHER
+ if (type == IntraBlueprintTransitionType.DefaultClockStepping)
+ addTransition(clockViewModel.clock?.let { DefaultClockSteppingTransition(it) })
+ addTransition(ClockSizeTransition(type, clockViewModel))
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
index 3d36eb0..9a1fcc1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
@@ -41,11 +41,13 @@
return
}
- val nic = constraintLayout.requireViewById<View>(R.id.aod_notification_icon_container)
+ // The burn-in layer requires at least 1 view at all times
+ val emptyView = View(context, null).apply { id = View.generateViewId() }
+ constraintLayout.addView(emptyView)
burnInLayer =
AodBurnInLayer(context).apply {
id = R.id.burn_in_layer
- addView(nic)
+ addView(emptyView)
if (!migrateClocksToBlueprint()) {
val statusView =
constraintLayout.requireViewById<View>(R.id.keyguard_status_view)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index fe4f07d..b1178f5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -166,10 +166,7 @@
context.resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset)
largeClockTopMargin += getDimen(DATE_WEATHER_VIEW_HEIGHT)
largeClockTopMargin += getDimen(ENHANCED_SMARTSPACE_HEIGHT)
- if (!keyguardClockViewModel.useLargeClock) {
- largeClockTopMargin -=
- context.resources.getDimensionPixelSize(customizationR.dimen.small_clock_height)
- }
+
connect(R.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin)
constrainHeight(R.id.lockscreen_clock_view_large, WRAP_CONTENT)
constrainWidth(R.id.lockscreen_clock_view_large, WRAP_CONTENT)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
new file mode 100644
index 0000000..99565b1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.sections.transitions
+
+import android.animation.Animator
+import android.animation.ObjectAnimator
+import android.transition.ChangeBounds
+import android.transition.TransitionSet
+import android.transition.TransitionValues
+import android.transition.Visibility
+import android.view.View
+import android.view.ViewGroup
+import com.android.app.animation.Interpolators
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransitionType
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import com.android.systemui.res.R
+import com.android.systemui.shared.R as sharedR
+
+const val CLOCK_OUT_MILLIS = 133L
+const val CLOCK_IN_MILLIS = 167L
+val CLOCK_IN_INTERPOLATOR = Interpolators.LINEAR_OUT_SLOW_IN
+const val CLOCK_IN_START_DELAY_MILLIS = 133L
+val CLOCK_OUT_INTERPOLATOR = Interpolators.LINEAR
+
+class ClockSizeTransition(
+ val type: IntraBlueprintTransitionType,
+ clockViewModel: KeyguardClockViewModel
+) : TransitionSet() {
+ init {
+ ordering = ORDERING_TOGETHER
+ addTransition(ClockOutTransition(clockViewModel, type))
+ addTransition(ClockInTransition(clockViewModel, type))
+ addTransition(SmartspaceChangeBounds(clockViewModel, type))
+ addTransition(ClockInChangeBounds(clockViewModel, type))
+ addTransition(ClockOutChangeBounds(clockViewModel, type))
+ }
+
+ class ClockInTransition(viewModel: KeyguardClockViewModel, type: IntraBlueprintTransitionType) :
+ Visibility() {
+ init {
+ mode = MODE_IN
+ if (type != IntraBlueprintTransitionType.NoTransition) {
+ duration = CLOCK_IN_MILLIS
+ startDelay = CLOCK_IN_START_DELAY_MILLIS
+ interpolator = Interpolators.LINEAR_OUT_SLOW_IN
+ } else {
+ duration = 0
+ startDelay = 0
+ }
+
+ addTarget(sharedR.id.bc_smartspace_view)
+ addTarget(sharedR.id.date_smartspace_view)
+ addTarget(sharedR.id.weather_smartspace_view)
+ if (viewModel.useLargeClock) {
+ viewModel.clock?.let { it.largeClock.layout.views.forEach { addTarget(it) } }
+ } else {
+ addTarget(R.id.lockscreen_clock_view)
+ }
+ }
+
+ override fun onAppear(
+ sceneRoot: ViewGroup?,
+ view: View,
+ startValues: TransitionValues?,
+ endValues: TransitionValues?
+ ): Animator {
+ return ObjectAnimator.ofFloat(view, "alpha", 1f).also {
+ it.duration = duration
+ it.startDelay = startDelay
+ it.interpolator = interpolator
+ it.addUpdateListener { view.alpha = it.animatedValue as Float }
+ it.start()
+ }
+ }
+ }
+
+ class ClockOutTransition(
+ viewModel: KeyguardClockViewModel,
+ type: IntraBlueprintTransitionType
+ ) : Visibility() {
+ init {
+ mode = MODE_OUT
+ if (type != IntraBlueprintTransitionType.NoTransition) {
+ duration = CLOCK_OUT_MILLIS
+ interpolator = CLOCK_OUT_INTERPOLATOR
+ } else {
+ duration = 0
+ }
+
+ addTarget(sharedR.id.bc_smartspace_view)
+ addTarget(sharedR.id.date_smartspace_view)
+ addTarget(sharedR.id.weather_smartspace_view)
+ if (viewModel.useLargeClock) {
+ addTarget(R.id.lockscreen_clock_view)
+ } else {
+ viewModel.clock?.let { it.largeClock.layout.views.forEach { addTarget(it) } }
+ }
+ }
+
+ override fun onDisappear(
+ sceneRoot: ViewGroup?,
+ view: View,
+ startValues: TransitionValues?,
+ endValues: TransitionValues?
+ ): Animator {
+ return ObjectAnimator.ofFloat(view, "alpha", 0f).also {
+ it.duration = duration
+ it.interpolator = interpolator
+ it.addUpdateListener { view.alpha = it.animatedValue as Float }
+ it.start()
+ }
+ }
+ }
+
+ class ClockInChangeBounds(
+ viewModel: KeyguardClockViewModel,
+ type: IntraBlueprintTransitionType
+ ) : ChangeBounds() {
+ init {
+ if (type != IntraBlueprintTransitionType.NoTransition) {
+ duration = CLOCK_IN_MILLIS
+ startDelay = CLOCK_IN_START_DELAY_MILLIS
+ interpolator = CLOCK_IN_INTERPOLATOR
+ } else {
+ duration = 0
+ startDelay = 0
+ }
+
+ if (viewModel.useLargeClock) {
+ viewModel.clock?.let { it.largeClock.layout.views.forEach { addTarget(it) } }
+ } else {
+ addTarget(R.id.lockscreen_clock_view)
+ }
+ }
+ }
+
+ class ClockOutChangeBounds(
+ viewModel: KeyguardClockViewModel,
+ type: IntraBlueprintTransitionType
+ ) : ChangeBounds() {
+ init {
+ if (type != IntraBlueprintTransitionType.NoTransition) {
+ duration = CLOCK_OUT_MILLIS
+ interpolator = CLOCK_OUT_INTERPOLATOR
+ } else {
+ duration = 0
+ }
+ if (viewModel.useLargeClock) {
+ addTarget(R.id.lockscreen_clock_view)
+ } else {
+ viewModel.clock?.let { it.largeClock.layout.views.forEach { addTarget(it) } }
+ }
+ }
+ }
+
+ class SmartspaceChangeBounds(
+ viewModel: KeyguardClockViewModel,
+ val type: IntraBlueprintTransitionType = IntraBlueprintTransitionType.DefaultTransition
+ ) : ChangeBounds() {
+ init {
+ if (type != IntraBlueprintTransitionType.NoTransition) {
+ duration =
+ if (viewModel.useLargeClock) {
+ STATUS_AREA_MOVE_UP_MILLIS
+ } else {
+ STATUS_AREA_MOVE_DOWN_MILLIS
+ }
+ interpolator = Interpolators.EMPHASIZED
+ } else {
+ duration = 0
+ }
+ addTarget(sharedR.id.date_smartspace_view)
+ addTarget(sharedR.id.weather_smartspace_view)
+ addTarget(sharedR.id.bc_smartspace_view)
+ }
+
+ companion object {
+ const val STATUS_AREA_MOVE_UP_MILLIS = 967L
+ const val STATUS_AREA_MOVE_DOWN_MILLIS = 467L
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/DefaultClockSteppingTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/DefaultClockSteppingTransition.kt
new file mode 100644
index 0000000..c35dad7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/DefaultClockSteppingTransition.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.keyguard.ui.view.layout.sections.transitions
+
+import android.animation.Animator
+import android.animation.ValueAnimator
+import android.transition.Transition
+import android.transition.TransitionValues
+import android.view.ViewGroup
+import com.android.app.animation.Interpolators
+import com.android.systemui.plugins.clocks.ClockController
+
+class DefaultClockSteppingTransition(private val clock: ClockController) : Transition() {
+ init {
+ interpolator = Interpolators.LINEAR
+ duration = KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION_MS
+ addTarget(clock.largeClock.view)
+ }
+ private fun captureValues(transitionValues: TransitionValues) {
+ transitionValues.values[PROP_BOUNDS_LEFT] = transitionValues.view.left
+ val locationInWindowTmp = IntArray(2)
+ transitionValues.view.getLocationInWindow(locationInWindowTmp)
+ transitionValues.values[PROP_X_IN_WINDOW] = locationInWindowTmp[0]
+ }
+
+ override fun captureEndValues(transitionValues: TransitionValues) {
+ captureValues(transitionValues)
+ }
+
+ override fun captureStartValues(transitionValues: TransitionValues) {
+ captureValues(transitionValues)
+ }
+
+ override fun createAnimator(
+ sceneRoot: ViewGroup,
+ startValues: TransitionValues?,
+ endValues: TransitionValues?
+ ): Animator? {
+ if (startValues == null || endValues == null) {
+ return null
+ }
+ val anim = ValueAnimator.ofFloat(0f, 1f)
+ val fromLeft = startValues.values[PROP_BOUNDS_LEFT] as Int
+ val fromWindowX = startValues.values[PROP_X_IN_WINDOW] as Int
+ val toWindowX = endValues.values[PROP_X_IN_WINDOW] as Int
+ // Using windowX, to determine direction, instead of left, as in RTL the difference of
+ // toLeft - fromLeft is always positive, even when moving left.
+ val direction = if (toWindowX - fromWindowX > 0) 1 else -1
+ anim.addUpdateListener { animation: ValueAnimator ->
+ clock.largeClock.animations.onPositionUpdated(
+ fromLeft,
+ direction,
+ animation.animatedFraction
+ )
+ }
+ return anim
+ }
+
+ override fun getTransitionProperties(): Array<String> {
+ return TRANSITION_PROPERTIES
+ }
+
+ companion object {
+ private const val PROP_BOUNDS_LEFT = "splitShadeTransitionAdapter:boundsLeft"
+ private const val PROP_X_IN_WINDOW = "splitShadeTransitionAdapter:xInWindow"
+ private val TRANSITION_PROPERTIES = arrayOf(PROP_BOUNDS_LEFT, PROP_X_IN_WINDOW)
+ private const val KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION_MS = 1000L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
index 310ec95..ad6a36c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
@@ -27,6 +27,7 @@
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
import com.android.systemui.res.R
import javax.inject.Inject
+import kotlin.math.roundToInt
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -74,7 +75,18 @@
isTransitionToAod && isUdfps
}
.distinctUntilChanged()
- private val padding: Flow<Int> = udfpsOverlayInteractor.iconPadding
+
+ private val padding: Flow<Int> =
+ deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { udfpsSupported ->
+ if (udfpsSupported) {
+ udfpsOverlayInteractor.iconPadding
+ } else {
+ configurationInteractor.scaleForResolution.map { scale ->
+ (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
+ .roundToInt()
+ }
+ }
+ }
val viewModel: Flow<ForegroundIconViewModel> =
combine(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
index e2bfc36..d22856b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
@@ -26,4 +26,5 @@
constructor(keyguardBlueprintInteractor: KeyguardBlueprintInteractor) {
var currentBluePrint: KeyguardBlueprint? = null
val blueprint = keyguardBlueprintInteractor.blueprint
+ val blueprintWithTransition = keyguardBlueprintInteractor.blueprintWithTransition
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTableLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTableLog.kt
new file mode 100644
index 0000000..fe7dc4b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTableLog.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.dagger
+
+import javax.inject.Qualifier
+
+/** A [TableLogBuffer] for communal-related logging. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class CommunalTableLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 3e00940..ac579d6 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -579,6 +579,16 @@
return factory.create("CommunalLog", 250);
}
+ /**
+ * Provides a {@link TableLogBuffer} for communal-related logs.
+ */
+ @Provides
+ @SysUISingleton
+ @CommunalTableLog
+ public static TableLogBuffer provideCommunalTableLogBuffer(TableLogBufferFactory factory) {
+ return factory.create("CommunalTableLog", 250);
+ }
+
/** Provides a {@link LogBuffer} for display metrics related logs. */
@Provides
@SysUISingleton
@@ -618,4 +628,13 @@
public static LogBuffer providePackageChangeRepoLogBuffer(LogBufferFactory factory) {
return factory.create("PackageChangeRepo", 50);
}
+
+ /** Provides a {@link LogBuffer} for NavBarButtonClicks. */
+ @Provides
+ @SysUISingleton
+ @NavBarButtonClickLog
+ public static LogBuffer provideNavBarButtonClickLogBuffer(LogBufferFactory factory) {
+ return factory.create("NavBarButtonClick", 50);
+ }
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NavBarButtonClickLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NavBarButtonClickLog.java
new file mode 100644
index 0000000..939dab2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NavBarButtonClickLog.java
@@ -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.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/** A {@link LogBuffer} for {@link com.android.systemui.navigationbar.NavigationBar}. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface NavBarButtonClickLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index 523414c..c33ab12 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -586,7 +586,7 @@
coroutineScope.launch {
communalInteractor.isCommunalShowing.collect { value ->
isCommunalShowing = value
- updateDesiredLocation(forceNoAnimation = true)
+ updateDesiredLocation()
}
}
}
@@ -1149,13 +1149,17 @@
when {
mediaFlags.isSceneContainerEnabled() -> desiredLocation
dreamOverlayActive && dreamMediaComplicationActive -> LOCATION_DREAM_OVERLAY
+
+ // UMO should show in communal unless the shade is expanding or visible.
+ isCommunalShowing && qsExpansion == 0.0f -> LOCATION_COMMUNAL_HUB
(qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS
qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
onLockscreen && isSplitShadeExpanding() -> LOCATION_QS
onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS
- // TODO(b/311234666): revisit logic once interactions between the hub and
- // shade/keyguard state are finalized
- isCommunalShowing && communalInteractor.isCommunalEnabled -> LOCATION_COMMUNAL_HUB
+
+ // Communal does not have its own StatusBarState so it should always have higher
+ // priority for the UMO over the lockscreen.
+ isCommunalShowing -> LOCATION_COMMUNAL_HUB
onLockscreen && allowMediaPlayerOnLockScreen -> LOCATION_LOCKSCREEN
else -> LOCATION_QQS
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarButtonClickLogger.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarButtonClickLogger.kt
new file mode 100644
index 0000000..408acf3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarButtonClickLogger.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.navigationbar
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.NavBarButtonClickLog
+import javax.inject.Inject
+
+class NavBarButtonClickLogger
+@Inject
+constructor(@NavBarButtonClickLog private val buffer: LogBuffer) {
+ fun logHomeButtonClick() {
+ buffer.log(TAG, LogLevel.DEBUG, {}, { "Home Button Triggered" })
+ }
+
+ fun logBackButtonClick() {
+ buffer.log(TAG, LogLevel.DEBUG, {}, { "Back Button Triggered" })
+ }
+
+ fun logRecentsButtonClick() {
+ buffer.log(TAG, LogLevel.DEBUG, {}, { "Recents Button Triggered" })
+ }
+
+ fun logImeSwitcherClick() {
+ buffer.log(TAG, LogLevel.DEBUG, {}, { "Ime Switcher Triggered" })
+ }
+
+ fun logAccessibilityButtonClick() {
+ buffer.log(TAG, LogLevel.DEBUG, {}, { "Accessibility Button Triggered" })
+ }
+}
+
+private const val TAG = "NavBarButtonClick"
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 068e5fd..95b75ac 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -84,11 +84,7 @@
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.Transaction;
import android.view.View;
-import android.view.ViewRootImpl;
-import android.view.ViewRootImpl.SurfaceChangedCallback;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.InternalInsetsInfo;
import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
@@ -285,6 +281,7 @@
private boolean mImeVisible;
private final Rect mSamplingBounds = new Rect();
private final Binder mInsetsSourceOwner = new Binder();
+ private final NavBarButtonClickLogger mNavBarButtonClickLogger;
/**
* When quickswitching between apps of different orientations, we draw a secondary home handle
@@ -559,7 +556,8 @@
UserContextProvider userContextProvider,
WakefulnessLifecycle wakefulnessLifecycle,
TaskStackChangeListeners taskStackChangeListeners,
- DisplayTracker displayTracker) {
+ DisplayTracker displayTracker,
+ NavBarButtonClickLogger navBarButtonClickLogger) {
super(navigationBarView);
mFrame = navigationBarFrame;
mContext = context;
@@ -601,6 +599,7 @@
mTaskStackChangeListeners = taskStackChangeListeners;
mDisplayTracker = displayTracker;
mEdgeBackGestureHandler = navBarHelper.getEdgeBackGestureHandler();
+ mNavBarButtonClickLogger = navBarButtonClickLogger;
mNavColorSampleMargin = getResources()
.getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin);
@@ -1276,6 +1275,10 @@
ButtonDispatcher homeButton = mView.getHomeButton();
homeButton.setOnTouchListener(this::onHomeTouch);
+ homeButton.setNavBarButtonClickLogger(mNavBarButtonClickLogger);
+
+ ButtonDispatcher backButton = mView.getBackButton();
+ backButton.setNavBarButtonClickLogger(mNavBarButtonClickLogger);
reconfigureHomeLongClick();
@@ -1388,6 +1391,8 @@
}
private void onRecentsClick(View v) {
+ mNavBarButtonClickLogger.logRecentsButtonClick();
+
if (LatencyTracker.isEnabled(mContext)) {
LatencyTracker.getInstance(mContext).onActionStart(
LatencyTracker.ACTION_TOGGLE_RECENTS);
@@ -1397,6 +1402,7 @@
}
private void onImeSwitcherClick(View v) {
+ mNavBarButtonClickLogger.logImeSwitcherClick();
mInputMethodManager.showInputMethodPickerFromSystem(
true /* showAuxiliarySubtypes */, mDisplayId);
mUiEventLogger.log(KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_TAP);
@@ -1486,6 +1492,7 @@
}
private void onAccessibilityClick(View v) {
+ mNavBarButtonClickLogger.logAccessibilityButtonClick();
final Display display = v.getDisplay();
mAccessibilityManager.notifyAccessibilityButtonClicked(
display != null ? display.getDisplayId() : mDisplayTracker.getDefaultDisplayId());
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
index 5739abc..fc37b9f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
@@ -23,6 +23,9 @@
import android.animation.ValueAnimator;
import android.view.View;
import android.view.View.AccessibilityDelegate;
+import android.view.ViewGroup;
+
+import com.android.systemui.navigationbar.NavBarButtonClickLogger;
import java.util.ArrayList;
@@ -52,6 +55,7 @@
private boolean mVertical;
private ValueAnimator mFadeAnimator;
private AccessibilityDelegate mAccessibilityDelegate;
+ private NavBarButtonClickLogger mNavBarButtonClickLogger;
private final ValueAnimator.AnimatorUpdateListener mAlphaListener = animation ->
setAlpha(
@@ -341,4 +345,36 @@
*/
public void onDestroy() {
}
+
+ /**
+ * Sets the NavBarButtonClickLogger for all the KeyButtonViews respectively.
+ */
+ public void setNavBarButtonClickLogger(NavBarButtonClickLogger navBarButtonClickLogger) {
+ if (navBarButtonClickLogger != null) {
+ mNavBarButtonClickLogger = navBarButtonClickLogger;
+ final int size = mViews.size();
+ for (int i = 0; i < size; i++) {
+ final View v = mViews.get(i);
+ setNavBarButtonClickLoggerForViewChildren(v);
+ }
+ }
+ }
+
+ /**
+ * Recursively explores view hierarchy until the children of provided view are of type
+ * KeyButtonView, so the NavBarButtonClickLogger can be set on them.
+ */
+ private void setNavBarButtonClickLoggerForViewChildren(View v) {
+ if (v instanceof KeyButtonView) {
+ ((KeyButtonView) v).setNavBarButtonClickLogger(mNavBarButtonClickLogger);
+ return;
+ }
+
+ if (v instanceof ViewGroup viewGroup) {
+ final int childrenCount = viewGroup.getChildCount();
+ for (int i = 0; i < childrenCount; i++) {
+ setNavBarButtonClickLoggerForViewChildren(viewGroup.getChildAt(i));
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
index df6843d..dbe87ea 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
@@ -59,6 +59,7 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.navigationbar.NavBarButtonClickLogger;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.res.R;
import com.android.systemui.shared.navigationbar.KeyButtonRipple;
@@ -86,6 +87,7 @@
private final Paint mOvalBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
private float mDarkIntensity;
private boolean mHasOvalBg = false;
+ private NavBarButtonClickLogger mNavBarButtonClickLogger;
@VisibleForTesting
public enum NavBarButtonEvent implements UiEventLogger.UiEventEnum {
@@ -197,6 +199,10 @@
mOnClickListener = onClickListener;
}
+ public void setNavBarButtonClickLogger(NavBarButtonClickLogger navBarButtonClickLogger) {
+ mNavBarButtonClickLogger = navBarButtonClickLogger;
+ }
+
public void loadAsync(Icon icon) {
new AsyncTask<Icon, Void, Drawable>() {
@Override
@@ -389,11 +395,19 @@
uiEvent = longPressSet
? NavBarButtonEvent.NAVBAR_BACK_BUTTON_LONGPRESS
: NavBarButtonEvent.NAVBAR_BACK_BUTTON_TAP;
+
+ if (mNavBarButtonClickLogger != null) {
+ mNavBarButtonClickLogger.logBackButtonClick();
+ }
break;
case KeyEvent.KEYCODE_HOME:
uiEvent = longPressSet
? NavBarButtonEvent.NAVBAR_HOME_BUTTON_LONGPRESS
: NavBarButtonEvent.NAVBAR_HOME_BUTTON_TAP;
+
+ if (mNavBarButtonClickLogger != null) {
+ mNavBarButtonClickLogger.logHomeButtonClick();
+ }
break;
case KeyEvent.KEYCODE_APP_SWITCH:
uiEvent = longPressSet
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index ac0bd29..1c37510f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -77,6 +77,7 @@
private Consumer<Boolean> mMediaVisibilityChangedListener;
@Orientation
private int mLastOrientation;
+ private int mLastScreenLayout;
private String mCachedSpecs = "";
@Nullable
private QSTileRevealController mQsTileRevealController;
@@ -93,15 +94,19 @@
public void onConfigurationChange(Configuration newConfig) {
final boolean previousSplitShadeState = mShouldUseSplitNotificationShade;
final int previousOrientation = mLastOrientation;
+ final int previousScreenLayout = mLastScreenLayout;
mShouldUseSplitNotificationShade = mSplitShadeStateController
.shouldUseSplitNotificationShade(getResources());
mLastOrientation = newConfig.orientation;
+ mLastScreenLayout = newConfig.screenLayout;
mQSLogger.logOnConfigurationChanged(
/* oldOrientation= */ previousOrientation,
/* newOrientation= */ mLastOrientation,
/* oldShouldUseSplitShade= */ previousSplitShadeState,
/* newShouldUseSplitShade= */ mShouldUseSplitNotificationShade,
+ /* oldScreenLayout= */ previousScreenLayout,
+ /* newScreenLayout= */ mLastScreenLayout,
/* containerName= */ mView.getDumpableTag());
switchTileLayoutIfNeeded();
@@ -198,6 +203,7 @@
mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener);
setTiles();
mLastOrientation = getResources().getConfiguration().orientation;
+ mLastScreenLayout = getResources().getConfiguration().screenLayout;
mQSLogger.logOnViewAttached(mLastOrientation, mView.getDumpableTag());
switchTileLayout(true);
@@ -443,11 +449,13 @@
}
boolean shouldUseHorizontalLayout() {
- if (mShouldUseSplitNotificationShade) {
+ if (mShouldUseSplitNotificationShade) {
return false;
}
return mUsingMediaPlayer && mMediaHost.getVisible()
- && mLastOrientation == Configuration.ORIENTATION_LANDSCAPE;
+ && mLastOrientation == Configuration.ORIENTATION_LANDSCAPE
+ && (mLastScreenLayout & Configuration.SCREENLAYOUT_LONG_MASK)
+ == Configuration.SCREENLAYOUT_LONG_YES;
}
private void logTiles() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
index 38e7972..b515ce0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
@@ -19,6 +19,8 @@
import android.content.res.Configuration.ORIENTATION_LANDSCAPE
import android.content.res.Configuration.ORIENTATION_PORTRAIT
import android.content.res.Configuration.Orientation
+import android.content.res.Configuration.SCREENLAYOUT_LONG_NO
+import android.content.res.Configuration.SCREENLAYOUT_LONG_YES
import android.service.quicksettings.Tile
import android.view.View
import com.android.systemui.log.ConstantStringsLogger
@@ -266,8 +268,10 @@
fun logOnConfigurationChanged(
@Orientation oldOrientation: Int,
@Orientation newOrientation: Int,
- newShouldUseSplitShade: Boolean,
oldShouldUseSplitShade: Boolean,
+ newShouldUseSplitShade: Boolean,
+ oldScreenLayout: Int,
+ newScreenLayout: Int,
containerName: String
) {
configChangedBuffer.log(
@@ -277,6 +281,8 @@
str1 = containerName
int1 = oldOrientation
int2 = newOrientation
+ long1 = oldScreenLayout.toLong()
+ long2 = newScreenLayout.toLong()
bool1 = oldShouldUseSplitShade
bool2 = newShouldUseSplitShade
},
@@ -284,6 +290,8 @@
"config change: " +
"$str1 orientation=${toOrientationString(int2)} " +
"(was ${toOrientationString(int1)}), " +
+ "screen layout=${toScreenLayoutString(long1.toInt())} " +
+ "(was ${toScreenLayoutString(long2.toInt())}), " +
"splitShade=$bool2 (was $bool1)"
}
)
@@ -370,3 +378,11 @@
else -> "undefined"
}
}
+
+private inline fun toScreenLayoutString(screenLayout: Int): String {
+ return when (screenLayout) {
+ SCREENLAYOUT_LONG_YES -> "long"
+ SCREENLAYOUT_LONG_NO -> "notlong"
+ else -> "undefined"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index c5eeb2f..9fefcbd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -52,7 +52,7 @@
import com.android.systemui.animation.LaunchableViewDelegate
import com.android.systemui.plugins.qs.QSIconView
import com.android.systemui.plugins.qs.QSTile
-import com.android.systemui.plugins.qs.QSTile.BooleanState
+import com.android.systemui.plugins.qs.QSTile.AdapterState
import com.android.systemui.plugins.qs.QSTileView
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH
@@ -506,13 +506,12 @@
state.expandedAccessibilityClassName
}
- if (state is BooleanState) {
+ if (state is AdapterState) {
val newState = state.value
if (tileState != newState) {
tileState = newState
}
}
- //
// Labels
if (!Objects.equals(label.text, state.label)) {
@@ -625,7 +624,7 @@
customDrawableView.setImageDrawable(state.sideViewCustomDrawable)
customDrawableView.visibility = VISIBLE
chevronView.visibility = GONE
- } else if (state !is BooleanState || state.forceExpandIcon) {
+ } else if (state !is AdapterState || state.forceExpandIcon) {
customDrawableView.setImageDrawable(null)
customDrawableView.visibility = GONE
chevronView.visibility = VISIBLE
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
new file mode 100644
index 0000000..26069c7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.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.qs.tiles.impl.fontscaling.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.fontscaling.domain.model.FontScalingTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [FontScalingTileModel] to [QSTileState]. */
+class FontScalingTileMapper
+@Inject
+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) {
+ val icon =
+ Icon.Loaded(
+ resources.getDrawable(
+ R.drawable.ic_qs_font_scaling,
+ theme,
+ ),
+ contentDescription = null
+ )
+ this.icon = { icon }
+ contentDescription = label
+ activationState = QSTileState.ActivationState.ACTIVE
+ sideViewIcon = QSTileState.SideViewIcon.Chevron
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractor.kt
new file mode 100644
index 0000000..745e6a3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractor.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.qs.tiles.impl.fontscaling.domain.interactor
+
+import android.os.UserHandle
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+/** Provides [FontScalingTileModel]. */
+class FontScalingTileDataInteractor @Inject constructor() :
+ QSTileDataInteractor<FontScalingTileModel> {
+ override fun tileData(
+ user: UserHandle,
+ triggers: Flow<DataUpdateTrigger>
+ ): Flow<FontScalingTileModel> = flowOf(FontScalingTileModel)
+
+ override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileUserActionInteractor.kt
new file mode 100644
index 0000000..b6f4afb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileUserActionInteractor.kt
@@ -0,0 +1,96 @@
+/*
+ * 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.qs.tiles.impl.fontscaling.domain.interactor
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.accessibility.fontscaling.FontScalingDialogDelegate
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import javax.inject.Inject
+import javax.inject.Provider
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.withContext
+
+/** Handles font scaling tile clicks. */
+class FontScalingTileUserActionInteractor
+@Inject
+constructor(
+ @Main private val coroutineContext: CoroutineContext,
+ private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+ private val fontScalingDialogDelegateProvider: Provider<FontScalingDialogDelegate>,
+ private val keyguardStateController: KeyguardStateController,
+ private val dialogLaunchAnimator: DialogLaunchAnimator,
+ private val activityStarter: ActivityStarter,
+) : QSTileUserActionInteractor<FontScalingTileModel> {
+
+ override suspend fun handleInput(input: QSTileInput<FontScalingTileModel>): Unit =
+ with(input) {
+ when (action) {
+ is QSTileUserAction.Click -> {
+ // We animate from the touched view only if we are not on the keyguard
+ val animateFromView: Boolean =
+ action.view != null && !keyguardStateController.isShowing
+ val runnable = Runnable {
+ val dialog: SystemUIDialog =
+ fontScalingDialogDelegateProvider.get().createDialog()
+ if (animateFromView) {
+ dialogLaunchAnimator.showFromView(
+ dialog,
+ action.view!!,
+ DialogCuj(
+ InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG
+ )
+ )
+ } else {
+ dialog.show()
+ }
+ }
+
+ withContext(coroutineContext) {
+ activityStarter.executeRunnableDismissingKeyguard(
+ runnable,
+ /* cancelAction= */ null,
+ /* dismissShade= */ true,
+ /* afterKeyguardGone= */ true,
+ /* deferred= */ false
+ )
+ }
+ }
+ is QSTileUserAction.LongClick -> {
+ qsTileIntentUserActionHandler.handle(
+ action.view,
+ Intent(Settings.ACTION_TEXT_READING_SETTINGS)
+ )
+ }
+ }
+ }
+ companion object {
+ private const val INTERACTION_JANK_TAG = "font_scaling"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/model/FontScalingTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/model/FontScalingTileModel.kt
new file mode 100644
index 0000000..76042df
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/model/FontScalingTileModel.kt
@@ -0,0 +1,20 @@
+/*
+ * 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.qs.tiles.impl.fontscaling.domain.model
+
+/** FontScaling tile model. No data needed as the tile just opens a dialog. */
+data object FontScalingTileModel
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 4780a2e..5a389f3 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
@@ -227,7 +227,7 @@
): QSTile.State =
// we have to use QSTile.BooleanState to support different side icons
// which are bound to instanceof QSTile.BooleanState in QSTileView.
- QSTile.BooleanState().apply {
+ QSTile.AdapterState().apply {
spec = config.tileSpec.spec
label = viewModelState.label
// This value is synthetic and doesn't have any meaning. It's only needed to satisfy
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 246ccb1..37abc40 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
@@ -42,6 +42,7 @@
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
+import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
import com.android.systemui.util.asIndenting
import com.android.systemui.util.printSection
import com.android.systemui.util.println
@@ -55,6 +56,7 @@
import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch
@@ -81,6 +83,7 @@
private val simBouncerInteractor: Lazy<SimBouncerInteractor>,
private val authenticationInteractor: Lazy<AuthenticationInteractor>,
private val windowController: NotificationShadeWindowController,
+ private val deviceProvisioningInteractor: DeviceProvisioningInteractor,
) : CoreStartable {
override fun start() {
@@ -112,26 +115,33 @@
private fun hydrateVisibility() {
applicationScope.launch {
// TODO(b/296114544): Combine with some global hun state to make it visible!
- sceneInteractor.transitionState
- .mapNotNull { state ->
- when (state) {
- is ObservableTransitionState.Idle -> {
- if (state.scene != SceneKey.Gone) {
- true to "scene is not Gone"
- } else {
- false to "scene is Gone"
+ deviceProvisioningInteractor.isFactoryResetProtectionActive
+ .flatMapLatest { isFrpActive ->
+ if (isFrpActive) {
+ flowOf(false to "Factory Reset Protection is active")
+ } else {
+ sceneInteractor.transitionState
+ .mapNotNull { state ->
+ when (state) {
+ is ObservableTransitionState.Idle -> {
+ if (state.scene != SceneKey.Gone) {
+ true to "scene is not Gone"
+ } else {
+ false to "scene is Gone"
+ }
+ }
+ is ObservableTransitionState.Transition -> {
+ if (state.fromScene == SceneKey.Gone) {
+ true to "scene transitioning away from Gone"
+ } else {
+ null
+ }
+ }
+ }
}
- }
- is ObservableTransitionState.Transition -> {
- if (state.fromScene == SceneKey.Gone) {
- true to "scene transitioning away from Gone"
- } else {
- null
- }
- }
+ .distinctUntilChanged()
}
}
- .distinctUntilChanged()
.collect { (isVisible, loggingReason) ->
sceneInteractor.setVisible(isVisible, loggingReason)
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 7aa0dad..b5a1313 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -25,7 +25,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
-import android.graphics.Bitmap;
import android.graphics.drawable.Icon;
import android.media.MediaRecorder;
import android.net.Uri;
@@ -379,10 +378,9 @@
.addExtras(extras);
// Add thumbnail if available
- Bitmap thumbnailBitmap = recording.getThumbnail();
- if (thumbnailBitmap != null) {
+ if (recording.getThumbnail() != null) {
Notification.BigPictureStyle pictureStyle = new Notification.BigPictureStyle()
- .bigPicture(thumbnailBitmap)
+ .bigPicture(recording.getThumbnail())
.showBigPictureWhenCollapsed(true);
builder.setStyle(pictureStyle);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
index a170d0da..5469a4e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
@@ -23,10 +23,12 @@
import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.MIC_AND_INTERNAL;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.graphics.Bitmap;
+import android.graphics.drawable.Icon;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.MediaCodec;
@@ -51,6 +53,7 @@
import android.view.Surface;
import android.view.WindowManager;
+import com.android.internal.R;
import com.android.systemui.mediaprojection.MediaProjectionCaptureTarget;
import java.io.Closeable;
@@ -361,14 +364,27 @@
Files.copy(mTempVideoFile.toPath(), os);
os.close();
if (mTempAudioFile != null) mTempAudioFile.delete();
- DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
- Size size = new Size(metrics.widthPixels, metrics.heightPixels);
- SavedRecording recording = new SavedRecording(itemUri, mTempVideoFile, size);
+ SavedRecording recording = new SavedRecording(
+ itemUri, mTempVideoFile, getRequiredThumbnailSize());
mTempVideoFile.delete();
return recording;
}
/**
+ * Returns the required {@code Size} of the thumbnail.
+ */
+ private Size getRequiredThumbnailSize() {
+ boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
+ int thumbnailIconHeight = mContext.getResources().getDimensionPixelSize(isLowRam
+ ? R.dimen.notification_big_picture_max_height_low_ram
+ : R.dimen.notification_big_picture_max_height);
+ int thumbnailIconWidth = mContext.getResources().getDimensionPixelSize(isLowRam
+ ? R.dimen.notification_big_picture_max_width_low_ram
+ : R.dimen.notification_big_picture_max_width);
+ return new Size(thumbnailIconWidth, thumbnailIconHeight);
+ }
+
+ /**
* Release the resources without saving the data
*/
protected void release() {
@@ -386,13 +402,14 @@
public class SavedRecording {
private Uri mUri;
- private Bitmap mThumbnailBitmap;
+ private Icon mThumbnailIcon;
protected SavedRecording(Uri uri, File file, Size thumbnailSize) {
mUri = uri;
try {
- mThumbnailBitmap = ThumbnailUtils.createVideoThumbnail(
+ Bitmap thumbnailBitmap = ThumbnailUtils.createVideoThumbnail(
file, thumbnailSize, null);
+ mThumbnailIcon = Icon.createWithBitmap(thumbnailBitmap);
} catch (IOException e) {
Log.e(TAG, "Error creating thumbnail", e);
}
@@ -402,8 +419,8 @@
return mUri;
}
- public @Nullable Bitmap getThumbnail() {
- return mThumbnailBitmap;
+ public @Nullable Icon getThumbnail() {
+ return mThumbnailIcon;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java b/packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java
similarity index 85%
rename from packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java
rename to packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java
index a0dd924..fd807db 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.settings.dagger;
+package com.android.systemui.settings;
import android.app.ActivityManager;
import android.app.IActivityManager;
@@ -29,14 +29,6 @@
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlagsClassic;
-import com.android.systemui.settings.DisplayTracker;
-import com.android.systemui.settings.DisplayTrackerImpl;
-import com.android.systemui.settings.UserContentResolverProvider;
-import com.android.systemui.settings.UserContextProvider;
-import com.android.systemui.settings.UserFileManager;
-import com.android.systemui.settings.UserFileManagerImpl;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.settings.UserTrackerImpl;
import dagger.Binds;
import dagger.Module;
diff --git a/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt
new file mode 100644
index 0000000..76d1d3d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.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/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 0053474..a01ac70 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -331,8 +331,8 @@
);
collectFlow(
mWindowRootView,
- mCommunalInteractor.get().isCommunalShowing(),
- this::onCommunalShowingChanged
+ mCommunalInteractor.get().isCommunalVisible(),
+ this::onCommunalVisibleChanged
);
}
@@ -475,6 +475,9 @@
}
visible = true;
mLogger.d("Visibility forced to be true");
+ } else if (state.communalVisible) {
+ visible = true;
+ mLogger.d("Visibility forced to be true by communal");
}
if (mWindowRootView != null) {
if (visible) {
@@ -510,15 +513,15 @@
}
private void applyUserActivityTimeout(NotificationShadeWindowState state) {
- final Boolean communalShowing = state.isCommunalShowingAndNotOccluded();
+ final Boolean communalVisible = state.isCommunalVisibleAndNotOccluded();
final Boolean keyguardShowing = state.isKeyguardShowingAndNotOccluded();
long timeout = -1;
- if ((communalShowing || keyguardShowing)
+ if ((communalVisible || keyguardShowing)
&& state.statusBarState == StatusBarState.KEYGUARD
&& !state.qsExpanded) {
if (state.bouncerShowing) {
timeout = KeyguardViewMediator.AWAKE_INTERVAL_BOUNCER_MS;
- } else if (communalShowing) {
+ } else if (communalVisible) {
timeout = CommunalInteractor.AWAKE_INTERVAL_MS;
} else if (keyguardShowing) {
timeout = mLockScreenDisplayTimeout;
@@ -624,7 +627,7 @@
state.dozing,
state.scrimsVisibility,
state.backgroundBlurRadius,
- state.communalShowing
+ state.communalVisible
);
}
@@ -749,8 +752,8 @@
}
@VisibleForTesting
- void onCommunalShowingChanged(Boolean showing) {
- mCurrentState.communalShowing = showing;
+ void onCommunalVisibleChanged(Boolean visible) {
+ mCurrentState.communalVisible = visible;
apply(mCurrentState);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
index f9c9d83..e0a98b3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
@@ -58,15 +58,15 @@
@JvmField var dreaming: Boolean = false,
@JvmField var scrimsVisibility: Int = 0,
@JvmField var backgroundBlurRadius: Int = 0,
- @JvmField var communalShowing: Boolean = false,
+ @JvmField var communalVisible: Boolean = false,
) {
fun isKeyguardShowingAndNotOccluded(): Boolean {
return keyguardShowing && !keyguardOccluded
}
- fun isCommunalShowingAndNotOccluded(): Boolean {
- return communalShowing && !keyguardOccluded
+ fun isCommunalVisibleAndNotOccluded(): Boolean {
+ return communalVisible && !keyguardOccluded
}
/** List of [String] to be used as a [Row] with [DumpsysTableLogger]. */
@@ -99,7 +99,7 @@
dozing.toString(),
scrimsVisibility.toString(),
backgroundBlurRadius.toString(),
- communalShowing.toString(),
+ communalVisible.toString(),
)
}
@@ -140,7 +140,7 @@
dozing: Boolean,
scrimsVisibility: Int,
backgroundBlurRadius: Int,
- communalShowing: Boolean,
+ communalVisible: Boolean,
) {
buffer.advance().apply {
this.keyguardShowing = keyguardShowing
@@ -172,7 +172,7 @@
this.dozing = dozing
this.scrimsVisibility = scrimsVisibility
this.backgroundBlurRadius = backgroundBlurRadius
- this.communalShowing = communalShowing
+ this.communalVisible = communalVisible
}
}
@@ -218,7 +218,7 @@
"dozing",
"scrimsVisibility",
"backgroundBlurRadius",
- "communalShowing"
+ "communalVisible"
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 19a5840..ef820f3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -18,6 +18,7 @@
import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
import static com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON;
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import android.app.StatusBarManager;
@@ -477,8 +478,9 @@
if (KeyguardShadeMigrationNssl.isEnabled()) {
// When on lockscreen, if the touch originates at the top of the screen
// go directly to QS and not the shade
- if (mQuickSettingsController.shouldQuickSettingsIntercept(
- ev.getX(), ev.getY(), 0)) {
+ if (mStatusBarStateController.getState() == KEYGUARD
+ && mQuickSettingsController.shouldQuickSettingsIntercept(
+ ev.getX(), ev.getY(), 0)) {
mShadeLogger.d("NSWVC: QS intercepted");
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index 84cad1d..c0afa32 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -30,8 +30,6 @@
import com.android.systemui.Flags.centralizedStatusBarDimensRefactor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.fragments.FragmentService
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -59,18 +57,17 @@
@SysUISingleton
class NotificationsQSContainerController @Inject constructor(
- view: NotificationsQuickSettingsContainer,
- private val navigationModeController: NavigationModeController,
- private val overviewProxyService: OverviewProxyService,
- private val shadeHeaderController: ShadeHeaderController,
- private val shadeInteractor: ShadeInteractor,
- private val fragmentService: FragmentService,
- @Main private val delayableExecutor: DelayableExecutor,
- private val featureFlags: FeatureFlags,
- private val
- notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
- private val splitShadeStateController: SplitShadeStateController,
- private val largeScreenHeaderHelperLazy: Lazy<LargeScreenHeaderHelper>,
+ view: NotificationsQuickSettingsContainer,
+ private val navigationModeController: NavigationModeController,
+ private val overviewProxyService: OverviewProxyService,
+ private val shadeHeaderController: ShadeHeaderController,
+ private val shadeInteractor: ShadeInteractor,
+ private val fragmentService: FragmentService,
+ @Main private val delayableExecutor: DelayableExecutor,
+ private val
+ notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
+ private val splitShadeStateController: SplitShadeStateController,
+ private val largeScreenHeaderHelperLazy: Lazy<LargeScreenHeaderHelper>,
) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController {
private var splitShadeEnabled = false
@@ -133,9 +130,6 @@
isGestureNavigation = QuickStepContract.isGesturalMode(currentMode)
mView.setStackScroller(notificationStackScrollLayoutController.getView())
- if (featureFlags.isEnabled(Flags.QS_CONTAINER_GRAPH_OPTIMIZER)){
- mView.enableGraphOptimization()
- }
}
public override fun onViewAttached() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
index de3d16a..25e558e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
@@ -32,10 +32,10 @@
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
-import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
-import com.android.systemui.res.R;
import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.AboveShelfObserver;
import java.util.ArrayList;
@@ -73,6 +73,7 @@
public NotificationsQuickSettingsContainer(Context context, AttributeSet attrs) {
super(context, attrs);
+ setOptimizationLevel(getOptimizationLevel() | OPTIMIZATION_GRAPH);
}
@Override
@@ -180,10 +181,6 @@
super.dispatchDraw(canvas);
}
- void enableGraphOptimization() {
- setOptimizationLevel(getOptimizationLevel() | OPTIMIZATION_GRAPH);
- }
-
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return TouchLogger.logDispatchTouch("NotificationsQuickSettingsContainer", ev,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
index d3459b1..e40bcd5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
@@ -46,7 +46,7 @@
/**
* Adds a listener that will be notified when the panel expansion fraction has changed and
- * returns the current state in a ShadeExpansionChangeEvent for legacy purposes (b/23035507).
+ * returns the current state in a ShadeExpansionChangeEvent for legacy purposes (b/281038056).
*
* @see #addExpansionListener
*/
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
index a71cf95..e619806 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
@@ -25,9 +25,10 @@
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository
import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
+import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
+import com.android.systemui.util.kotlin.combine
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@@ -44,7 +45,7 @@
@Inject
constructor(
@Application val scope: CoroutineScope,
- deviceProvisioningRepository: DeviceProvisioningRepository,
+ deviceProvisioningInteractor: DeviceProvisioningInteractor,
disableFlagsRepository: DisableFlagsRepository,
dozeParams: DozeParameters,
keyguardRepository: KeyguardRepository,
@@ -56,7 +57,7 @@
) : ShadeInteractor, BaseShadeInteractor by baseShadeInteractor {
override val isShadeEnabled: StateFlow<Boolean> =
combine(
- deviceProvisioningRepository.isFactoryResetProtectionActive,
+ deviceProvisioningInteractor.isFactoryResetProtectionActive,
disableFlagsRepository.disableFlags,
) { isFrpActive, isDisabledByFlags ->
isDisabledByFlags.isShadeEnabled() && !isFrpActive
@@ -83,7 +84,7 @@
powerInteractor.isAsleep,
keyguardTransitionInteractor.isInTransitionToStateWhere { it == KeyguardState.AOD },
keyguardRepository.dozeTransitionModel.map { it.to == DozeStateModel.DOZE_PULSING },
- deviceProvisioningRepository.isFactoryResetProtectionActive,
+ deviceProvisioningInteractor.isFactoryResetProtectionActive,
) { isAsleep, goingToSleep, isPulsing, isFrpActive ->
when {
// Touches are disabled when Factory Reset Protection is active
@@ -103,7 +104,7 @@
isShadeEnabled,
keyguardRepository.isDozing,
userSetupRepository.isUserSetUp,
- deviceProvisioningRepository.isDeviceProvisioned,
+ deviceProvisioningInteractor.isDeviceProvisioned,
) { disableFlags, isShadeEnabled, isDozing, isUserSetup, isDeviceProvisioned ->
isDeviceProvisioned &&
// Disallow QS during setup if it's a simple user switcher. (The user intends to
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index ada7d3e..ffb11dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -56,6 +56,7 @@
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
+import android.view.accessibility.Flags;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
@@ -305,6 +306,13 @@
default void setTopAppHidesStatusBar(boolean topAppHidesStatusBar) { }
default void addQsTile(ComponentName tile) { }
+
+ /**
+ * Add a tile to the Quick Settings Panel
+ * @param tile the ComponentName of the {@link android.service.quicksettings.TileService}
+ * @param end if true, the tile will be added at the end. If false, at the beginning.
+ */
+ default void addQsTileToFrontOrEnd(ComponentName tile, boolean end) { }
default void remQsTile(ComponentName tile) { }
default void setQsTiles(String[] tiles) {}
@@ -904,8 +912,29 @@
@Override
public void addQsTile(ComponentName tile) {
- synchronized (mLock) {
- mHandler.obtainMessage(MSG_ADD_QS_TILE, tile).sendToTarget();
+ if (Flags.a11yQsShortcut()) {
+ addQsTileToFrontOrEnd(tile, false);
+ } else {
+ synchronized (mLock) {
+ mHandler.obtainMessage(MSG_ADD_QS_TILE, tile).sendToTarget();
+ }
+ }
+ }
+
+ /**
+ * Add a tile to the Quick Settings Panel
+ * @param tile the ComponentName of the {@link android.service.quicksettings.TileService}
+ * @param end if true, the tile will be added at the end. If false, at the beginning.
+ */
+ @Override
+ public void addQsTileToFrontOrEnd(ComponentName tile, boolean end) {
+ if (Flags.a11yQsShortcut()) {
+ synchronized (mLock) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = tile;
+ args.arg2 = end;
+ mHandler.obtainMessage(MSG_ADD_QS_TILE, args).sendToTarget();
+ }
}
}
@@ -1546,11 +1575,21 @@
mCallbacks.get(i).showPictureInPictureMenu();
}
break;
- case MSG_ADD_QS_TILE:
- for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).addQsTile((ComponentName) msg.obj);
+ case MSG_ADD_QS_TILE: {
+ if (Flags.a11yQsShortcut()) {
+ SomeArgs someArgs = (SomeArgs) msg.obj;
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).addQsTileToFrontOrEnd(
+ (ComponentName) someArgs.arg1, (boolean) someArgs.arg2);
+ }
+ someArgs.recycle();
+ } else {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).addQsTile((ComponentName) msg.obj);
+ }
}
break;
+ }
case MSG_REMOVE_QS_TILE:
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).remQsTile((ComponentName) msg.obj);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 32cd56c..b64e0b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -56,7 +56,6 @@
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl;
import com.android.systemui.statusbar.phone.StatusBarIconList;
-import com.android.systemui.statusbar.phone.StatusBarNotificationPresenterModule;
import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -73,7 +72,7 @@
* their own version of CentralSurfaces can include just dependencies, without injecting
* CentralSurfaces itself.
*/
-@Module(includes = {StatusBarNotificationPresenterModule.class})
+@Module
public interface CentralSurfacesDependenciesModule {
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesModule.java
index 99d4b2e..27536bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesModule.java
@@ -18,12 +18,14 @@
import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
import com.android.systemui.statusbar.notification.row.NotificationRowModule;
+import com.android.systemui.statusbar.phone.StatusBarNotificationPresenterModule;
import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
import dagger.Module;
-/** */
-@Module(includes = {StatusBarPhoneModule.class, CentralSurfacesDependenciesModule.class,
+/** */
+@Module(includes = {CentralSurfacesDependenciesModule.class,
+ StatusBarNotificationPresenterModule.class, StatusBarPhoneModule.class,
NotificationsModule.class, NotificationRowModule.class})
public interface CentralSurfacesModule {
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
index ae4ba27..29627e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
@@ -18,7 +18,7 @@
import android.os.UserHandle
import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.Flags.screenshareNotificationHiding
+import com.android.server.notification.Flags.screenshareNotificationHiding
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index cd816ae..954e805 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.notification.collection.inflation;
-import static com.android.systemui.Flags.screenshareNotificationHiding;
+import static com.android.server.notification.Flags.screenshareNotificationHiding;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 6bba72b..92b0c04 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -51,6 +51,7 @@
import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.data.NotificationDataLayerModule;
+import com.android.systemui.statusbar.notification.domain.NotificationDomainLayerModule;
import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor;
import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModelModule;
import com.android.systemui.statusbar.notification.icon.ConversationIconManager;
@@ -78,14 +79,14 @@
import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
import com.android.systemui.statusbar.policy.HeadsUpManager;
-import javax.inject.Provider;
-
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.ClassKey;
import dagger.multibindings.IntoMap;
+import javax.inject.Provider;
+
/**
* Dagger Module for classes found within the com.android.systemui.statusbar.notification package.
*/
@@ -94,6 +95,7 @@
FooterViewModelModule.class,
KeyguardNotificationVisibilityProviderModule.class,
NotificationDataLayerModule.class,
+ NotificationDomainLayerModule.class,
NotifPipelineChoreographerModule.class,
NotificationSectionHeadersModule.class,
ActivatableNotificationViewModelModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
index 2cac000..b187cf1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
@@ -17,4 +17,10 @@
import dagger.Module
-@Module(includes = []) interface NotificationDataLayerModule
+@Module(
+ includes =
+ [
+ NotificationSettingsRepositoryModule::class,
+ ]
+)
+interface NotificationDataLayerModule
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
new file mode 100644
index 0000000..a7970c7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.statusbar.notification.data
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.SecureSettingsRepositoryModule
+import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
+import dagger.Module
+import dagger.Provides
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+
+@Module(includes = [SecureSettingsRepositoryModule::class])
+object NotificationSettingsRepositoryModule {
+ @Provides
+ @SysUISingleton
+ fun provideNotificationSettingsRepository(
+ @Background backgroundScope: CoroutineScope,
+ @Background backgroundDispatcher: CoroutineDispatcher,
+ secureSettingsRepository: SecureSettingsRepository,
+ ): NotificationSettingsRepository =
+ NotificationSettingsRepository(
+ backgroundScope,
+ backgroundDispatcher,
+ secureSettingsRepository
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/NotificationDomainLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/NotificationDomainLayerModule.kt
new file mode 100644
index 0000000..5c49b28
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/NotificationDomainLayerModule.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.statusbar.notification.domain
+
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationSettingsInteractorModule
+import dagger.Module
+
+@Module(includes = [NotificationSettingsInteractorModule::class])
+object NotificationDomainLayerModule
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationSettingsInteractorModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationSettingsInteractorModule.kt
new file mode 100644
index 0000000..0a9e12a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationSettingsInteractorModule.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.statusbar.notification.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
+import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
+import dagger.Module
+import dagger.Provides
+
+@Module
+object NotificationSettingsInteractorModule {
+ @Provides
+ @SysUISingleton
+ fun provideNotificationSettingsInteractor(repository: NotificationSettingsRepository) =
+ NotificationSettingsInteractor(repository)
+}
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 3616fd6d..16f18a3 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
@@ -54,7 +54,7 @@
private static final String TAG = "FooterView";
private FooterViewButton mClearAllButton;
- private FooterViewButton mManageButton;
+ private FooterViewButton mManageOrHistoryButton;
private boolean mShowHistory;
// String cache, for performance reasons.
// Reading them from a Resources object can be quite slow sometimes.
@@ -68,6 +68,8 @@
private @StringRes int mClearAllButtonTextId;
private @StringRes int mClearAllButtonDescriptionId;
+ private @StringRes int mManageOrHistoryButtonTextId;
+ private @StringRes int mManageOrHistoryButtonDescriptionId;
private @StringRes int mMessageStringId;
private @DrawableRes int mMessageIconId;
@@ -155,6 +157,43 @@
mClearAllButton.setContentDescription(getContext().getString(mClearAllButtonDescriptionId));
}
+ /** Set the text label for the "Manage"/"History" button. */
+ public void setManageOrHistoryButtonText(@StringRes int textId) {
+ if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
+ if (mManageOrHistoryButtonTextId == textId) {
+ return; // nothing changed
+ }
+ mManageOrHistoryButtonTextId = textId;
+ updateManageOrHistoryButtonText();
+ }
+
+ private void updateManageOrHistoryButtonText() {
+ if (mManageOrHistoryButtonTextId == 0) {
+ return; // not initialized yet
+ }
+ mManageOrHistoryButton.setText(getContext().getString(mManageOrHistoryButtonTextId));
+ }
+
+ /** Set the accessibility content description for the "Clear all" button. */
+ public void setManageOrHistoryButtonDescription(@StringRes int contentDescriptionId) {
+ if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
+ return;
+ }
+ if (mManageOrHistoryButtonDescriptionId == contentDescriptionId) {
+ return; // nothing changed
+ }
+ mManageOrHistoryButtonDescriptionId = contentDescriptionId;
+ updateManageOrHistoryButtonDescription();
+ }
+
+ private void updateManageOrHistoryButtonDescription() {
+ if (mManageOrHistoryButtonDescriptionId == 0) {
+ return; // not initialized yet
+ }
+ mManageOrHistoryButton.setContentDescription(
+ getContext().getString(mManageOrHistoryButtonDescriptionId));
+ }
+
/** Set the string for a message to be shown instead of the buttons. */
public void setMessageString(@StringRes int messageId) {
if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
@@ -173,7 +212,6 @@
mSeenNotifsFooterTextView.setText(messageString);
}
-
/** Set the icon to be shown before the message (see {@link #setMessageString(int)}). */
public void setMessageIcon(@DrawableRes int iconId) {
if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
@@ -203,9 +241,11 @@
protected void onFinishInflate() {
super.onFinishInflate();
mClearAllButton = (FooterViewButton) findSecondaryView();
- mManageButton = findViewById(R.id.manage_text);
+ mManageOrHistoryButton = findViewById(R.id.manage_text);
mSeenNotifsFooterTextView = findViewById(R.id.unlock_prompt_footer);
- updateResources();
+ if (!FooterViewRefactor.isEnabled()) {
+ updateResources();
+ }
updateContent();
updateColors();
}
@@ -213,11 +253,11 @@
/** Show a message instead of the footer buttons. */
public void setFooterLabelVisible(boolean isVisible) {
if (isVisible) {
- mManageButton.setVisibility(View.GONE);
+ mManageOrHistoryButton.setVisibility(View.GONE);
mClearAllButton.setVisibility(View.GONE);
mSeenNotifsFooterTextView.setVisibility(View.VISIBLE);
} else {
- mManageButton.setVisibility(View.VISIBLE);
+ mManageOrHistoryButton.setVisibility(View.VISIBLE);
mClearAllButton.setVisibility(View.VISIBLE);
mSeenNotifsFooterTextView.setVisibility(View.GONE);
}
@@ -225,7 +265,7 @@
/** Set onClickListener for the manage/history button. */
public void setManageButtonClickListener(OnClickListener listener) {
- mManageButton.setOnClickListener(listener);
+ mManageOrHistoryButton.setOnClickListener(listener);
}
/** Set onClickListener for the clear all (end) button. */
@@ -252,6 +292,7 @@
/** Show "History" instead of "Manage" on the start button. */
public void showHistory(boolean showHistory) {
+ FooterViewRefactor.assertInLegacyMode();
if (mShowHistory == showHistory) {
return;
}
@@ -260,17 +301,13 @@
}
private void updateContent() {
- if (mShowHistory) {
- mManageButton.setText(mManageNotificationHistoryText);
- mManageButton.setContentDescription(mManageNotificationHistoryText);
- } else {
- mManageButton.setText(mManageNotificationText);
- mManageButton.setContentDescription(mManageNotificationText);
- }
if (FooterViewRefactor.isEnabled()) {
updateClearAllButtonText();
updateClearAllButtonDescription();
+ updateManageOrHistoryButtonText();
+ updateManageOrHistoryButtonDescription();
+
updateMessageString();
updateMessageIcon();
} else {
@@ -285,6 +322,14 @@
// `updateResources`, which will eventually be removed. There are, however, still
// situations in which we want to update the views even if the resource IDs didn't
// change, such as configuration changes.
+ if (mShowHistory) {
+ mManageOrHistoryButton.setText(mManageNotificationHistoryText);
+ mManageOrHistoryButton.setContentDescription(mManageNotificationHistoryText);
+ } else {
+ mManageOrHistoryButton.setText(mManageNotificationText);
+ mManageOrHistoryButton.setContentDescription(mManageNotificationText);
+ }
+
mClearAllButton.setText(R.string.clear_all_notifications_text);
mClearAllButton.setContentDescription(
mContext.getString(R.string.accessibility_clear_all));
@@ -297,6 +342,7 @@
/** Whether the start button shows "History" (true) or "Manage" (false). */
public boolean isHistoryShown() {
+ FooterViewRefactor.assertInLegacyMode();
return mShowHistory;
}
@@ -304,7 +350,9 @@
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
updateColors();
- updateResources();
+ if (!FooterViewRefactor.isEnabled()) {
+ updateResources();
+ }
updateContent();
}
@@ -328,23 +376,22 @@
}
mClearAllButton.setBackground(clearAllBg);
mClearAllButton.setTextColor(onSurface);
- mManageButton.setBackground(manageBg);
- mManageButton.setTextColor(onSurface);
+ mManageOrHistoryButton.setBackground(manageBg);
+ mManageOrHistoryButton.setTextColor(onSurface);
mSeenNotifsFooterTextView.setTextColor(onSurface);
mSeenNotifsFooterTextView.setCompoundDrawableTintList(ColorStateList.valueOf(onSurface));
}
private void updateResources() {
+ FooterViewRefactor.assertInLegacyMode();
mManageNotificationText = getContext().getString(R.string.manage_notifications_text);
mManageNotificationHistoryText = getContext()
.getString(R.string.manage_notifications_history_text);
- if (!FooterViewRefactor.isEnabled()) {
- int unlockIconSize = getResources()
- .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size);
- mSeenNotifsFilteredText = getContext().getString(R.string.unlock_to_see_notif_text);
- mSeenNotifsFilteredIcon = getContext().getDrawable(R.drawable.ic_friction_lock_closed);
- mSeenNotifsFilteredIcon.setBounds(0, 0, unlockIconSize, unlockIconSize);
- }
+ int unlockIconSize = getResources()
+ .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size);
+ mSeenNotifsFilteredText = getContext().getString(R.string.unlock_to_see_notif_text);
+ mSeenNotifsFilteredIcon = getContext().getDrawable(R.drawable.ic_friction_lock_closed);
+ mSeenNotifsFilteredIcon.setBounds(0, 0, unlockIconSize, unlockIconSize);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
index e0eee96..9fb453a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
@@ -25,49 +25,136 @@
import com.android.systemui.util.ui.stopAnimating
import com.android.systemui.util.ui.value
import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
/** Binds a [FooterView] to its [view model][FooterViewModel]. */
object FooterViewBinder {
- fun bind(
+ fun bindWhileAttached(
footer: FooterView,
viewModel: FooterViewModel,
clearAllNotifications: View.OnClickListener,
+ launchNotificationSettings: View.OnClickListener,
+ launchNotificationHistory: View.OnClickListener,
): DisposableHandle {
+ return footer.repeatWhenAttached {
+ lifecycleScope.launch {
+ bind(
+ footer,
+ viewModel,
+ clearAllNotifications,
+ launchNotificationSettings,
+ launchNotificationHistory
+ )
+ }
+ }
+ }
+
+ suspend fun bind(
+ footer: FooterView,
+ viewModel: FooterViewModel,
+ clearAllNotifications: View.OnClickListener,
+ launchNotificationSettings: View.OnClickListener,
+ launchNotificationHistory: View.OnClickListener
+ ) = coroutineScope {
+ launch {
+ bindClearAllButton(
+ footer,
+ viewModel,
+ clearAllNotifications,
+ )
+ }
+ launch {
+ bindManageOrHistoryButton(
+ footer,
+ viewModel,
+ launchNotificationSettings,
+ launchNotificationHistory
+ )
+ }
+ launch { bindMessage(footer, viewModel) }
+ }
+
+ private suspend fun bindClearAllButton(
+ footer: FooterView,
+ viewModel: FooterViewModel,
+ clearAllNotifications: View.OnClickListener,
+ ) = coroutineScope {
+ footer.setClearAllButtonClickListener(clearAllNotifications)
+
+ launch {
+ viewModel.clearAllButton.labelId.collect { textId ->
+ footer.setClearAllButtonText(textId)
+ }
+ }
+
+ launch {
+ viewModel.clearAllButton.accessibilityDescriptionId.collect { textId ->
+ footer.setClearAllButtonDescription(textId)
+ }
+ }
+
+ launch {
+ viewModel.clearAllButton.isVisible.collect { isVisible ->
+ if (isVisible.isAnimating) {
+ footer.setClearAllButtonVisible(
+ isVisible.value,
+ /* animate = */ true,
+ ) { _ ->
+ isVisible.stopAnimating()
+ }
+ } else {
+ footer.setClearAllButtonVisible(
+ isVisible.value,
+ /* animate = */ false,
+ )
+ }
+ }
+ }
+ }
+
+ private suspend fun bindManageOrHistoryButton(
+ footer: FooterView,
+ viewModel: FooterViewModel,
+ launchNotificationSettings: View.OnClickListener,
+ launchNotificationHistory: View.OnClickListener,
+ ) = coroutineScope {
+ launch {
+ viewModel.manageButtonShouldLaunchHistory.collect { shouldLaunchHistory ->
+ if (shouldLaunchHistory) {
+ footer.setManageButtonClickListener(launchNotificationHistory)
+ } else {
+ footer.setManageButtonClickListener(launchNotificationSettings)
+ }
+ }
+ }
+
+ launch {
+ viewModel.manageOrHistoryButton.labelId.collect { textId ->
+ footer.setManageOrHistoryButtonText(textId)
+ }
+ }
+
+ launch {
+ viewModel.clearAllButton.accessibilityDescriptionId.collect { textId ->
+ footer.setManageOrHistoryButtonDescription(textId)
+ }
+ }
+
+ // NOTE: The manage/history button is always visible as long as the footer is visible, no
+ // need to update the visibility here.
+ }
+
+ private suspend fun bindMessage(
+ footer: FooterView,
+ viewModel: FooterViewModel,
+ ) = coroutineScope {
// Bind the resource IDs
footer.setMessageString(viewModel.message.messageId)
footer.setMessageIcon(viewModel.message.iconId)
- footer.setClearAllButtonText(viewModel.clearAllButton.labelId)
- footer.setClearAllButtonDescription(viewModel.clearAllButton.accessibilityDescriptionId)
- // Bind the click listeners
- footer.setClearAllButtonClickListener(clearAllNotifications)
-
- // Listen for visibility changes when the view is attached.
- return footer.repeatWhenAttached {
- lifecycleScope.launch {
- viewModel.clearAllButton.isVisible.collect { isVisible ->
- if (isVisible.isAnimating) {
- footer.setClearAllButtonVisible(
- isVisible.value,
- /* animate = */ true,
- ) { _ ->
- isVisible.stopAnimating()
- }
- } else {
- footer.setClearAllButtonVisible(
- isVisible.value,
- /* animate = */ false,
- )
- }
- }
- }
-
- lifecycleScope.launch {
- viewModel.message.isVisible.collect { visible ->
- footer.setFooterLabelVisible(visible)
- }
- }
+ launch {
+ viewModel.message.isVisible.collect { visible -> footer.setFooterLabelVisible(visible) }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt
index 244555a..691dc42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt
@@ -21,7 +21,7 @@
import kotlinx.coroutines.flow.Flow
data class FooterButtonViewModel(
- @StringRes val labelId: Int,
- @StringRes val accessibilityDescriptionId: Int,
+ @StringRes val labelId: Flow<Int>,
+ @StringRes val accessibilityDescriptionId: Flow<Int>,
val isVisible: Flow<AnimatedValue<Boolean>>,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
index e6b0abc..5111c11 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
@@ -19,30 +19,36 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.ui.AnimatableEvent
+import com.android.systemui.util.ui.AnimatedValue
import com.android.systemui.util.ui.toAnimatedValueFlow
import dagger.Module
import dagger.Provides
import java.util.Optional
import javax.inject.Provider
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
/** ViewModel for [FooterView]. */
class FooterViewModel(
activeNotificationsInteractor: ActiveNotificationsInteractor,
+ notificationSettingsInteractor: NotificationSettingsInteractor,
seenNotificationsInteractor: SeenNotificationsInteractor,
shadeInteractor: ShadeInteractor,
) {
val clearAllButton: FooterButtonViewModel =
FooterButtonViewModel(
- labelId = R.string.clear_all_notifications_text,
- accessibilityDescriptionId = R.string.accessibility_clear_all,
+ labelId = flowOf(R.string.clear_all_notifications_text),
+ accessibilityDescriptionId = flowOf(R.string.accessibility_clear_all),
isVisible =
activeNotificationsInteractor.hasClearableNotifications
.sample(
@@ -59,6 +65,22 @@
.toAnimatedValueFlow(),
)
+ val manageButtonShouldLaunchHistory =
+ notificationSettingsInteractor.isNotificationHistoryEnabled
+
+ private val manageOrHistoryButtonText: Flow<Int> =
+ manageButtonShouldLaunchHistory.map { shouldLaunchHistory ->
+ if (shouldLaunchHistory) R.string.manage_notifications_history_text
+ else R.string.manage_notifications_text
+ }
+
+ val manageOrHistoryButton: FooterButtonViewModel =
+ FooterButtonViewModel(
+ labelId = manageOrHistoryButtonText,
+ accessibilityDescriptionId = manageOrHistoryButtonText,
+ isVisible = flowOf(AnimatedValue.NotAnimating(true)),
+ )
+
val message: FooterMessageViewModel =
FooterMessageViewModel(
messageId = R.string.unlock_to_see_notif_text,
@@ -73,6 +95,7 @@
@SysUISingleton
fun provideOptional(
activeNotificationsInteractor: Provider<ActiveNotificationsInteractor>,
+ notificationSettingsInteractor: Provider<NotificationSettingsInteractor>,
seenNotificationsInteractor: Provider<SeenNotificationsInteractor>,
shadeInteractor: Provider<ShadeInteractor>,
): Optional<FooterViewModel> {
@@ -80,6 +103,7 @@
Optional.of(
FooterViewModel(
activeNotificationsInteractor.get(),
+ notificationSettingsInteractor.get(),
seenNotificationsInteractor.get(),
shadeInteractor.get()
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index dd04531..5e0110b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -617,7 +617,11 @@
private final ScrollAdapter mScrollAdapter = new ScrollAdapter() {
@Override
public boolean isScrolledToTop() {
- return mOwnScrollY == 0;
+ if (SceneContainerFlag.isEnabled()) {
+ return mController.isPlaceholderScrolledToTop();
+ } else {
+ return mOwnScrollY == 0;
+ }
}
@Override
@@ -1442,7 +1446,14 @@
fraction = BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(fraction);
}
final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
- mAmbientState.setStackY(stackY);
+ // TODO(b/322228881): Clean up scene container vs legacy behavior in NSSL
+ if (SceneContainerFlag.isEnabled()) {
+ // stackY should be driven by scene container, not NSSL
+ mAmbientState.setStackY(mTopPadding);
+ } else {
+ mAmbientState.setStackY(stackY);
+ }
+
if (mOnStackYChanged != null) {
mOnStackYChanged.accept(listenerNeedsAnimation);
}
@@ -4687,6 +4698,7 @@
* this will return false.
**/
public boolean isHistoryShown() {
+ FooterViewRefactor.assertInLegacyMode();
return mFooterView != null && mFooterView.isHistoryShown();
}
@@ -4699,10 +4711,10 @@
}
mFooterView = footerView;
addView(mFooterView, index);
- if (mManageButtonClickListener != null) {
- mFooterView.setManageButtonClickListener(mManageButtonClickListener);
- }
if (!FooterViewRefactor.isEnabled()) {
+ if (mManageButtonClickListener != null) {
+ mFooterView.setManageButtonClickListener(mManageButtonClickListener);
+ }
mFooterView.setClearAllButtonClickListener(v -> {
if (mFooterClearAllListener != null) {
mFooterClearAllListener.onClearAll();
@@ -4783,8 +4795,8 @@
}
boolean animate = mIsExpanded && mAnimationsEnabled;
mFooterView.setVisible(visible, animate);
- mFooterView.showHistory(showHistory);
if (!FooterViewRefactor.isEnabled()) {
+ mFooterView.showHistory(showHistory);
mFooterView.setClearAllButtonVisible(showDismissView, animate);
mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications);
}
@@ -5479,6 +5491,7 @@
* Register a {@link View.OnClickListener} to be invoked when the Manage button is clicked.
*/
public void setManageButtonClickListener(@Nullable OnClickListener listener) {
+ FooterViewRefactor.assertInLegacyMode();
mManageButtonClickListener = listener;
if (mFooterView != null) {
mFooterView.setManageButtonClickListener(mManageButtonClickListener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 49fde39..eaff8a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -22,7 +22,7 @@
import static com.android.app.animation.Interpolators.STANDARD;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
-import static com.android.systemui.Flags.screenshareNotificationHiding;
+import static com.android.server.notification.Flags.screenshareNotificationHiding;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnEmptySpaceClickListener;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnOverscrollTopChangedListener;
@@ -845,11 +845,13 @@
mView.setKeyguardBypassEnabled(mKeyguardBypassController.getBypassEnabled());
mKeyguardBypassController
.registerOnBypassStateChangedListener(mView::setKeyguardBypassEnabled);
- mView.setManageButtonClickListener(v -> {
- if (mNotificationActivityStarter != null) {
- mNotificationActivityStarter.startHistoryIntent(v, mView.isHistoryShown());
- }
- });
+ if (!FooterViewRefactor.isEnabled()) {
+ mView.setManageButtonClickListener(v -> {
+ if (mNotificationActivityStarter != null) {
+ mNotificationActivityStarter.startHistoryIntent(v, mView.isHistoryShown());
+ }
+ });
+ }
mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
mHeadsUpManager.setAnimationStateHandler(mView::setHeadsUpGoingAwayAnimationsAllowed);
@@ -1144,6 +1146,14 @@
return mStackAppearanceInteractor.getStackBounds().getValue().getTop();
}
+ /**
+ * Returns whether the notification stack is scrolled to the top; i.e., it cannot be scrolled
+ * down any further.
+ */
+ public boolean isPlaceholderScrolledToTop() {
+ return mStackAppearanceInteractor.getScrolledToTop().getValue();
+ }
+
/** Set the intrinsic height of the stack content without additional padding. */
public void setIntrinsicContentHeight(float intrinsicContentHeight) {
mStackAppearanceInteractor.setIntrinsicContentHeight(intrinsicContentHeight);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
index aac3c28..0197264 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
@@ -44,4 +44,10 @@
* screen.
*/
val contentTop = MutableStateFlow(0f)
+
+ /**
+ * Whether the notification stack is scrolled to the top; i.e., it cannot be scrolled down any
+ * further.
+ */
+ val scrolledToTop = MutableStateFlow(true)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
index 1dfde09..8307397 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
@@ -42,10 +42,16 @@
* notifications, this can exceed the space available on screen to show notifications, at which
* point the notification stack should become scrollable.
*/
- val intrinsicContentHeight = repository.intrinsicContentHeight.asStateFlow()
+ val intrinsicContentHeight: StateFlow<Float> = repository.intrinsicContentHeight.asStateFlow()
/** The y-coordinate in px of top of the contents of the notification stack. */
- val contentTop = repository.contentTop.asStateFlow()
+ val contentTop: StateFlow<Float> = repository.contentTop.asStateFlow()
+
+ /**
+ * Whether the notification stack is scrolled to the top; i.e., it cannot be scrolled down any
+ * further.
+ */
+ val scrolledToTop: StateFlow<Boolean> = repository.scrolledToTop.asStateFlow()
/** Sets the position of the notification stack in the current scene. */
fun setStackBounds(bounds: NotificationContainerBounds) {
@@ -62,4 +68,9 @@
fun setContentTop(startY: Float) {
repository.contentTop.value = startY
}
+
+ /** Sets whether the notification stack is scrolled to the top. */
+ fun setScrolledToTop(scrolledToTop: Boolean) {
+ repository.scrolledToTop.value = scrolledToTop
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index 44a7e7e..4d65b9d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -29,6 +29,7 @@
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.notification.NotificationActivityStarter
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder
@@ -45,6 +46,7 @@
import com.android.systemui.util.kotlin.getOrNull
import java.util.Optional
import javax.inject.Inject
+import javax.inject.Provider
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
@@ -58,9 +60,11 @@
private val configuration: ConfigurationState,
private val falsingManager: FalsingManager,
private val iconAreaController: NotificationIconAreaController,
+ private val loggerOptional: Optional<NotificationStatsLogger>,
private val metricsLogger: MetricsLogger,
private val nicBinder: NotificationIconContainerShelfViewBinder,
- private val loggerOptional: Optional<NotificationStatsLogger>,
+ // Using a provider to avoid a circular dependency.
+ private val notificationActivityStarter: Provider<NotificationActivityStarter>,
private val viewModel: NotificationListViewModel,
) {
@@ -115,7 +119,7 @@
) { footerView: FooterView ->
traceSection("bind FooterView") {
val disposableHandle =
- FooterViewBinder.bind(
+ FooterViewBinder.bindWhileAttached(
footerView,
footerViewModel,
clearAllNotifications = {
@@ -124,6 +128,16 @@
)
parentView.clearAllNotifications()
},
+ launchNotificationSettings = { view ->
+ notificationActivityStarter
+ .get()
+ .startHistoryIntent(view, /* showHistory = */ false)
+ },
+ launchNotificationHistory = { view ->
+ notificationActivityStarter
+ .get()
+ .startHistoryIntent(view, /* showHistory = */ true)
+ },
)
parentView.setFooterView(footerView)
return@reinflateAndBindLatest disposableHandle
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
index ed15f55..6c2cbbe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
@@ -25,7 +25,6 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel
-import kotlin.math.pow
import kotlin.math.roundToInt
import kotlinx.coroutines.launch
@@ -65,7 +64,9 @@
viewModel.expandFraction.collect { expandFraction ->
ambientState.expansionFraction = expandFraction
controller.expandedHeight = expandFraction * controller.view.height
- controller.setMaxAlphaForExpansion(expandFraction.pow(0.75f))
+ controller.setMaxAlphaForExpansion(
+ ((expandFraction - 0.5f) / 0.5f).coerceAtLeast(0f)
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
index 74db583..56ff7f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
@@ -19,11 +19,15 @@
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
/** ViewModel which represents the state of the NSSL/Controller in the world of flexiglass */
@SysUISingleton
@@ -32,9 +36,40 @@
constructor(
stackAppearanceInteractor: NotificationStackAppearanceInteractor,
shadeInteractor: ShadeInteractor,
+ sceneInteractor: SceneInteractor,
) {
- /** The expansion fraction from the top of the notification shade. */
- val expandFraction: Flow<Float> = shadeInteractor.shadeExpansion
+ /**
+ * The expansion fraction of the notification stack. It should go from 0 to 1 when transitioning
+ * from Gone to Shade scenes, and remain at 1 when in Lockscreen or Shade scenes and while
+ * transitioning from Shade to QuickSettings scenes.
+ */
+ val expandFraction: Flow<Float> =
+ combine(
+ shadeInteractor.shadeExpansion,
+ sceneInteractor.transitionState,
+ ) { shadeExpansion, transitionState ->
+ when (transitionState) {
+ is ObservableTransitionState.Idle -> {
+ if (transitionState.scene == SceneKey.Lockscreen) {
+ 1f
+ } else {
+ shadeExpansion
+ }
+ }
+ is ObservableTransitionState.Transition -> {
+ if (
+ (transitionState.fromScene == SceneKey.Shade &&
+ transitionState.toScene == SceneKey.QuickSettings) ||
+ (transitionState.fromScene == SceneKey.QuickSettings &&
+ transitionState.toScene == SceneKey.Shade)
+ ) {
+ 1f
+ } else {
+ shadeExpansion
+ }
+ }
+ }
+ }
/** The bounds of the notification stack in the current scene. */
val stackBounds: Flow<NotificationContainerBounds> = stackAppearanceInteractor.stackBounds
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 385f061..a436f17 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -87,4 +87,9 @@
fun onContentTopChanged(padding: Float) {
interactor.setContentTop(padding)
}
+
+ /** Sets whether the notification stack is scrolled to the top. */
+ fun setScrolledToTop(scrolledToTop: Boolean) {
+ interactor.setScrolledToTop(scrolledToTop)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 4617ce4..3915c376 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -24,13 +24,24 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel
@@ -51,6 +62,7 @@
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
@@ -69,43 +81,52 @@
communalInteractor: CommunalInteractor,
private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
lockscreenToOccludedTransitionViewModel: LockscreenToOccludedTransitionViewModel,
+ dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
+ lockscreenToDreamingTransitionViewModel: LockscreenToDreamingTransitionViewModel,
glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel,
lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel,
private val aodBurnInViewModel: AodBurnInViewModel,
) {
- private val statesForConstrainedNotifications =
- setOf(
- KeyguardState.AOD,
- KeyguardState.LOCKSCREEN,
- KeyguardState.DOZING,
- KeyguardState.ALTERNATE_BOUNCER,
- KeyguardState.PRIMARY_BOUNCER
+ private val statesForConstrainedNotifications: Set<KeyguardState> =
+ setOf(AOD, LOCKSCREEN, DOZING, ALTERNATE_BOUNCER, PRIMARY_BOUNCER)
+
+ private val edgeToAlphaViewModel =
+ mapOf<Edge?, Flow<Float>>(
+ Edge(from = LOCKSCREEN, to = DREAMING) to
+ lockscreenToDreamingTransitionViewModel.lockscreenAlpha,
+ Edge(from = DREAMING, to = LOCKSCREEN) to
+ dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
+ Edge(from = LOCKSCREEN, to = OCCLUDED) to
+ lockscreenToOccludedTransitionViewModel.lockscreenAlpha,
+ Edge(from = OCCLUDED, to = LOCKSCREEN) to
+ occludedToLockscreenTransitionViewModel.lockscreenAlpha,
)
- private val lockscreenToOccludedRunning =
- keyguardTransitionInteractor
- .transition(KeyguardState.LOCKSCREEN, KeyguardState.OCCLUDED)
- .map { it.transitionState == STARTED || it.transitionState == RUNNING }
+ private val lockscreenTransitionInProgress: Flow<Edge?> =
+ keyguardTransitionInteractor.transitions
+ .map { step ->
+ if (
+ (step.transitionState == STARTED || step.transitionState == RUNNING) &&
+ (step.from == LOCKSCREEN || step.to == LOCKSCREEN)
+ ) {
+ Edge(step.from, step.to)
+ } else {
+ null
+ }
+ }
.distinctUntilChanged()
- .onStart { emit(false) }
-
- private val occludedToLockscreenRunning =
- keyguardTransitionInteractor
- .transition(KeyguardState.OCCLUDED, KeyguardState.LOCKSCREEN)
- .map { it.transitionState == STARTED || it.transitionState == RUNNING }
- .distinctUntilChanged()
- .onStart { emit(false) }
+ .onStart { emit(null) }
private val lockscreenToGlanceableHubRunning =
keyguardTransitionInteractor
- .transition(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB)
+ .transition(LOCKSCREEN, GLANCEABLE_HUB)
.map { it.transitionState == STARTED || it.transitionState == RUNNING }
.distinctUntilChanged()
.onStart { emit(false) }
private val glanceableHubToLockscreenRunning =
keyguardTransitionInteractor
- .transition(KeyguardState.GLANCEABLE_HUB, KeyguardState.LOCKSCREEN)
+ .transition(GLANCEABLE_HUB, LOCKSCREEN)
.map { it.transitionState == STARTED || it.transitionState == RUNNING }
.distinctUntilChanged()
.onStart { emit(false) }
@@ -141,7 +162,7 @@
statesForConstrainedNotifications.contains(it)
},
keyguardTransitionInteractor
- .transitionValue(KeyguardState.LOCKSCREEN)
+ .transitionValue(LOCKSCREEN)
.onStart { emit(0f) }
.map { it > 0 }
) { constrainedNotificationState, transitioningToOrFromLockscreen ->
@@ -242,38 +263,46 @@
initialValue = NotificationContainerBounds(),
)
+ /** As QS is expanding, fade out notifications unless in splitshade */
+ private val alphaForQsExpansion: Flow<Float> =
+ interactor.configurationBasedDimensions.flatMapLatest {
+ if (it.useSplitShade) {
+ flowOf(1f)
+ } else {
+ shadeInteractor.qsExpansion.map { 1f - it }
+ }
+ }
+
val expansionAlpha: Flow<Float> =
// Due to issues with the legacy shade, some shade expansion events are sent incorrectly,
- // such as when the shade resets. This can happen while the LOCKSCREEN<->OCCLUDED transition
+ // such as when the shade resets. This can happen while the transition to/from LOCKSCREEN
// is running. Therefore use a series of flatmaps to prevent unwanted interruptions while
// those transitions are in progress. Without this, the alpha value will produce a visible
// flicker.
- lockscreenToOccludedRunning.flatMapLatest { isLockscreenToOccludedRunning ->
- if (isLockscreenToOccludedRunning) {
- lockscreenToOccludedTransitionViewModel.lockscreenAlpha
- } else {
- occludedToLockscreenRunning.flatMapLatest { isOccludedToLockscreenRunning ->
- if (isOccludedToLockscreenRunning) {
- occludedToLockscreenTransitionViewModel.lockscreenAlpha.onStart { emit(0f) }
- } else {
+ lockscreenTransitionInProgress
+ .flatMapLatest { edge ->
+ edgeToAlphaViewModel.getOrElse(
+ edge,
+ {
isOnLockscreenWithoutShade.flatMapLatest { isOnLockscreenWithoutShade ->
combineTransform(
keyguardInteractor.keyguardAlpha,
shadeCollpaseFadeIn,
- ) { alpha, shadeCollpaseFadeIn ->
+ alphaForQsExpansion,
+ ) { alpha, shadeCollpaseFadeIn, alphaForQsExpansion ->
if (isOnLockscreenWithoutShade) {
if (!shadeCollpaseFadeIn) {
emit(alpha)
}
} else {
- emit(1f)
+ emit(alphaForQsExpansion)
}
}
}
}
- }
+ )
}
- }
+ .distinctUntilChanged()
/**
* Returns a flow of the expected alpha while running a LOCKSCREEN<->GLANCEABLE_HUB transition
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 60a4606..39ca7b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -179,6 +179,11 @@
}
@Override
+ public void addQsTileToFrontOrEnd(ComponentName tile, boolean end) {
+ mQSHost.addTile(tile, end);
+ }
+
+ @Override
public void remQsTile(ComponentName tile) {
mQSHost.removeTileByUser(tile);
}
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 0051161..1c33d3f 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
@@ -18,6 +18,7 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
import com.android.systemui.statusbar.pipeline.satellite.domain.interactor.DeviceBasedSatelliteInteractor
import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel
import javax.inject.Inject
@@ -40,6 +41,7 @@
constructor(
interactor: DeviceBasedSatelliteInteractor,
@Application scope: CoroutineScope,
+ airplaneModeRepository: AirplaneModeRepository,
) {
private val shouldShowIcon: StateFlow<Boolean> =
interactor.areAllConnectionsOutOfService
@@ -47,7 +49,11 @@
if (!allOos) {
flowOf(false)
} else {
- interactor.isSatelliteAllowed
+ combine(interactor.isSatelliteAllowed, airplaneModeRepository.isAirplaneMode) {
+ isSatelliteAllowed,
+ isAirplaneMode ->
+ isSatelliteAllowed && !isAirplaneMode
+ }
}
}
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
index 3c4ca44..11e374f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
@@ -16,12 +16,13 @@
package com.android.systemui.statusbar.policy;
-import static com.android.systemui.Flags.screenshareNotificationHiding;
+import static com.android.server.notification.Flags.screenshareNotificationHiding;
import android.media.projection.MediaProjectionInfo;
import android.media.projection.MediaProjectionManager;
import android.os.Handler;
import android.os.Trace;
+import android.service.notification.StatusBarNotification;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.SysUISingleton;
@@ -46,8 +47,9 @@
public void onStart(MediaProjectionInfo info) {
Trace.beginSection(
"SNPC.onProjectionStart");
- mProjection = info;
- mListeners.forEach(Runnable::run);
+ // Only enable sensitive content protection if sharing full screen
+ // Launch cookie only set (non-null) if sharing single app/task
+ updateProjectionState((info.getLaunchCookie() == null) ? info : null);
Trace.endSection();
}
@@ -55,10 +57,22 @@
public void onStop(MediaProjectionInfo info) {
Trace.beginSection(
"SNPC.onProjectionStop");
- mProjection = null;
- mListeners.forEach(Runnable::run);
+ updateProjectionState(null);
Trace.endSection();
}
+
+ private void updateProjectionState(MediaProjectionInfo info) {
+ // capture previous state
+ boolean wasSensitive = isSensitiveStateActive();
+
+ // update internal state
+ mProjection = info;
+
+ // if either previous or new state is sensitive, notify listeners.
+ if (wasSensitive || isSensitiveStateActive()) {
+ mListeners.forEach(Runnable::run);
+ }
+ }
};
@Inject
@@ -86,7 +100,6 @@
public boolean isSensitiveStateActive() {
// TODO(b/316955558): Add disabled by developer option
// TODO(b/316955306): Add feature exemption for sysui and bug handlers
- // TODO(b/316955346): Add feature exemption for single app screen sharing
return mProjection != null;
}
@@ -96,9 +109,18 @@
return false;
}
+ MediaProjectionInfo projection = mProjection;
+ if (projection == null) {
+ return false;
+ }
+
// Exempt foreground service notifications from protection in effort to keep screen share
// stop actions easily accessible
- // TODO(b/316955208): Exempt FGS notifications only for app that started projection
- return !entry.getSbn().getNotification().isFgsOrUij();
+ StatusBarNotification sbn = entry.getSbn();
+ if (sbn.getNotification().isFgsOrUij()) {
+ return !sbn.getPackageName().equals(projection.getPackageName());
+ }
+
+ return true;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractor.kt
new file mode 100644
index 0000000..32cf86d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractor.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.statusbar.policy.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Encapsulates device-provisioning related business logic. */
+@SysUISingleton
+class DeviceProvisioningInteractor
+@Inject
+constructor(
+ repository: DeviceProvisioningRepository,
+) {
+ /**
+ * Whether this device has been provisioned.
+ *
+ * @see android.provider.Settings.Global.DEVICE_PROVISIONED
+ */
+ val isDeviceProvisioned: Flow<Boolean> = repository.isDeviceProvisioned
+
+ /** Whether Factory Reset Protection (FRP) is currently active, locking the device. */
+ val isFactoryResetProtectionActive: Flow<Boolean> = repository.isFactoryResetProtectionActive
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index cf76c0d..37be1c6 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.user.data.repository
+import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.UserInfo
import android.os.UserHandle
@@ -209,18 +210,15 @@
override val selectedUserInfo: Flow<UserInfo> = selectedUser.map { it.userInfo }
+ @SuppressLint("MissingPermission")
override fun refreshUsers() {
applicationScope.launch {
- val result = withContext(backgroundDispatcher) { manager.aliveUsers }
-
- if (result != null) {
- _userInfos.value =
- result
- // Users should be sorted by ascending creation time.
- .sortedBy { it.creationTime }
- // The guest user is always last, regardless of creation time.
- .sortedBy { it.isGuest }
- }
+ _userInfos.value =
+ withContext(backgroundDispatcher) { manager.aliveUsers }
+ // Users should be sorted by ascending creation time.
+ .sortedBy { it.creationTime }
+ // The guest user is always last, regardless of creation time.
+ .sortedBy { it.isGuest }
if (mainUserId == UserHandle.USER_NULL) {
val mainUser = withContext(backgroundDispatcher) { manager.mainUser }
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
index 8fe57e11..d47413f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
@@ -20,21 +20,19 @@
import com.android.systemui.util.time.SystemClockImpl
import java.util.concurrent.atomic.AtomicReference
import kotlin.math.max
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
/**
@@ -106,6 +104,14 @@
/** Holds a [newValue] emitted from a [Flow], along with the [previousValue] emitted value. */
data class WithPrev<out S, out T : S>(val previousValue: S, val newValue: T)
+/** Emits a [Unit] only when the number of downstream subscribers of this flow increases. */
+fun <T> MutableSharedFlow<T>.onSubscriberAdded(): Flow<Unit> {
+ return subscriptionCount
+ .pairwise(initialValue = 0)
+ .filter { (previous, current) -> current > previous }
+ .map {}
+}
+
/**
* Returns a new [Flow] that combines the [Set] changes between each emission from [this] using
* [transform].
@@ -183,34 +189,6 @@
/**
* Returns a flow that mirrors the original flow, but delays values following emitted values for the
- * given [periodMs]. If the original flow emits more than one value during this period, only the
- * latest value is emitted.
- *
- * Example:
- * ```kotlin
- * flow {
- * emit(1) // t=0ms
- * delay(90)
- * emit(2) // t=90ms
- * delay(90)
- * emit(3) // t=180ms
- * delay(1010)
- * emit(4) // t=1190ms
- * delay(1010)
- * emit(5) // t=2200ms
- * }.throttle(1000)
- * ```
- *
- * produces the following emissions at the following times
- *
- * ```text
- * 1 (t=0ms), 3 (t=1000ms), 4 (t=2000ms), 5 (t=3000ms)
- * ```
- */
-fun <T> Flow<T>.throttle(periodMs: Long): Flow<T> = this.throttle(periodMs, SystemClockImpl())
-
-/**
- * Returns a flow that mirrors the original flow, but delays values following emitted values for the
* given [periodMs] as reported by the given [clock]. If the original flow emits more than one value
* during this period, only The latest value is emitted.
*
@@ -235,70 +213,37 @@
* 1 (t=0ms), 3 (t=1000ms), 4 (t=2000ms), 5 (t=3000ms)
* ```
*/
-fun <T> Flow<T>.throttle(periodMs: Long, clock: SystemClock): Flow<T> = channelFlow {
- coroutineScope {
- var previousEmitTimeMs = 0L
- var delayJob: Job? = null
- var sendJob: Job? = null
- val outerScope = this
+fun <T> Flow<T>.throttle(periodMs: Long, clock: SystemClock = SystemClockImpl()): Flow<T> =
+ channelFlow {
+ coroutineScope {
+ var previousEmitTimeMs = 0L
+ var delayJob: Job? = null
+ var sendJob: Job? = null
+ val outerScope = this
- collect {
- delayJob?.cancel()
- sendJob?.join()
- val currentTimeMs = clock.elapsedRealtime()
- val timeSinceLastEmit = currentTimeMs - previousEmitTimeMs
- val timeUntilNextEmit = max(0L, periodMs - timeSinceLastEmit)
- if (timeUntilNextEmit > 0L) {
- // We create delayJob to allow cancellation during the delay period
- delayJob = launch {
- delay(timeUntilNextEmit)
- sendJob =
- outerScope.launch(start = CoroutineStart.UNDISPATCHED) {
- send(it)
- previousEmitTimeMs = clock.elapsedRealtime()
- }
+ collect {
+ delayJob?.cancel()
+ sendJob?.join()
+ val currentTimeMs = clock.elapsedRealtime()
+ val timeSinceLastEmit = currentTimeMs - previousEmitTimeMs
+ val timeUntilNextEmit = max(0L, periodMs - timeSinceLastEmit)
+ if (timeUntilNextEmit > 0L) {
+ // We create delayJob to allow cancellation during the delay period
+ delayJob = launch {
+ delay(timeUntilNextEmit)
+ sendJob =
+ outerScope.launch(start = CoroutineStart.UNDISPATCHED) {
+ send(it)
+ previousEmitTimeMs = clock.elapsedRealtime()
+ }
+ }
+ } else {
+ send(it)
+ previousEmitTimeMs = currentTimeMs
}
- } else {
- send(it)
- previousEmitTimeMs = currentTimeMs
}
}
}
-}
-
-/**
- * Returns a [StateFlow] launched in the surrounding [CoroutineScope]. This [StateFlow] gets its
- * value by invoking [getValue] whenever an event is emitted from [changedSignals]. It will also
- * immediately invoke [getValue] to establish its initial value.
- */
-inline fun <T> CoroutineScope.stateFlow(
- changedSignals: Flow<*>,
- crossinline getValue: () -> T,
-): StateFlow<T> =
- changedSignals.map { getValue() }.stateIn(this, SharingStarted.Eagerly, getValue())
-
-inline fun <T1, T2, T3, T4, T5, T6, R> combine(
- flow: Flow<T1>,
- flow2: Flow<T2>,
- flow3: Flow<T3>,
- flow4: Flow<T4>,
- flow5: Flow<T5>,
- flow6: Flow<T6>,
- crossinline transform: suspend (T1, T2, T3, T4, T5, T6) -> R
-): Flow<R> {
- return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6) { args: Array<*>
- ->
- @Suppress("UNCHECKED_CAST")
- transform(
- args[0] as T1,
- args[1] as T2,
- args[2] as T3,
- args[3] as T4,
- args[4] as T5,
- args[5] as T6
- )
- }
-}
inline fun <T1, T2, T3, T4, T5, T6, T7, R> combine(
flow: Flow<T1>,
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
index a13d85b..cabe831 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
@@ -25,9 +25,13 @@
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.newFixedThreadPoolContext
import kotlinx.coroutines.plus
+private const val LIMIT_BACKGROUND_DISPATCHER_THREADS = true
+
/** Providers for various SystemIU specific coroutines-related constructs. */
@Module
class SysUICoroutinesModule {
@@ -40,14 +44,13 @@
): CoroutineScope = applicationScope.plus(coroutineContext)
/**
- * Provide a [CoroutineDispatcher] backed by a thread pool containing at most X threads, where X
- * is the number of CPU cores available.
+ * Default Coroutine dispatcher for background operations.
*
- * Because there are multiple threads at play, there is no serialization order guarantee. You
- * should use a [kotlinx.coroutines.channels.Channel] for serialization if necessary.
- *
- * @see Dispatchers.Default
+ * Note that this is explicitly limiting the number of threads. In the past, we used
+ * [Dispatchers.IO]. This caused >40 threads to be spawned, and a lot of thread list lock
+ * contention between then, eventually causing jank.
*/
+ @OptIn(DelicateCoroutinesApi::class)
@Provides
@SysUISingleton
@Background
@@ -55,12 +58,29 @@
"Use @Background CoroutineContext instead",
ReplaceWith("bgCoroutineContext()", "kotlin.coroutines.CoroutineContext")
)
- fun bgDispatcher(): CoroutineDispatcher = Dispatchers.IO
+ fun bgDispatcher(): CoroutineDispatcher {
+ return if (LIMIT_BACKGROUND_DISPATCHER_THREADS) {
+ // Why a new ThreadPool instead of just using Dispatchers.IO with
+ // CoroutineDispatcher.limitedParallelism? Because, if we were to use Dispatchers.IO, we
+ // would share those threads with other dependencies using Dispatchers.IO.
+ // Using a dedicated thread pool we have guarantees only SystemUI is able to schedule
+ // code on those.
+ newFixedThreadPoolContext(
+ nThreads = Runtime.getRuntime().availableProcessors(),
+ name = "SystemUIBg"
+ )
+ } else {
+ Dispatchers.IO
+ }
+ }
@Provides
@Background
@SysUISingleton
- fun bgCoroutineContext(@Tracing tracingCoroutineContext: CoroutineContext): CoroutineContext {
- return Dispatchers.IO + tracingCoroutineContext
+ fun bgCoroutineContext(
+ @Tracing tracingCoroutineContext: CoroutineContext,
+ @Background bgCoroutineDispatcher: CoroutineDispatcher,
+ ): CoroutineContext {
+ return bgCoroutineDispatcher + tracingCoroutineContext
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
index 8d5e55a..ff1daea 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.volume.dagger
+import android.content.Context
import android.media.AudioManager
import com.android.settingslib.volume.data.repository.AudioRepository
import com.android.settingslib.volume.data.repository.AudioRepositoryImpl
@@ -35,10 +36,12 @@
@Provides
fun provideAudioRepository(
+ @Application context: Context,
audioManager: AudioManager,
@Background coroutineContext: CoroutineContext,
@Application coroutineScope: CoroutineScope,
- ): AudioRepository = AudioRepositoryImpl(audioManager, coroutineContext, coroutineScope)
+ ): AudioRepository =
+ AudioRepositoryImpl(context, audioManager, coroutineContext, coroutineScope)
@Provides
fun provideAudioModeInteractor(repository: AudioRepository): AudioModeInteractor =
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 1e801ae..e832506 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -347,10 +347,10 @@
desktopMode.addVisibleTasksListener(
new DesktopModeTaskRepository.VisibleTasksListener() {
@Override
- public void onVisibilityChanged(int displayId, boolean hasFreeformTasks) {
+ public void onTasksVisibilityChanged(int displayId, int visibleTasksCount) {
if (displayId == Display.DEFAULT_DISPLAY) {
mSysUiState.setFlag(SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE,
- hasFreeformTasks)
+ visibleTasksCount > 0)
.commitUpdate(mDisplayTracker.getDefaultDisplayId());
}
// TODO(b/278084491): update sysui state for changes on other displays
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index 8299acb..375ebe8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -51,6 +51,7 @@
import android.view.accessibility.IRemoteMagnificationAnimationCallback;
import android.view.animation.AccelerateInterpolator;
+import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
@@ -79,6 +80,7 @@
@LargeTest
@RunWith(AndroidTestingRunner.class)
+@FlakyTest(bugId = 308501761)
public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
@Rule
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
index b1e471a..fc34255 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
@@ -1,3 +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.
+ */
+
package com.android.systemui.biometrics.domain.interactor
import android.hardware.biometrics.AuthenticateOptions
@@ -5,24 +21,19 @@
import android.hardware.biometrics.IBiometricContextListener.FoldState
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.AuthController
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.display.data.repository.DeviceStateRepository
+import com.android.systemui.display.data.repository.fakeDeviceStateRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED
-import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
-import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN
-import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING
-import com.android.systemui.unfold.updates.FOLD_UPDATE_START_OPENING
-import com.android.systemui.unfold.updates.FoldStateProvider
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
-import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -30,8 +41,6 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-import org.mockito.Mock
-import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
@OptIn(ExperimentalCoroutinesApi::class)
@@ -41,31 +50,20 @@
@JvmField @Rule var mockitoRule = MockitoJUnit.rule()
- private val testScope = TestScope()
-
- @Mock private lateinit var foldProvider: FoldStateProvider
- @Mock private lateinit var authController: AuthController
- @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
-
- private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
- private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val deviceStateRepository = kosmos.fakeDeviceStateRepository
+ private val udfpsOverlayInteractor = kosmos.udfpsOverlayInteractor
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
private lateinit var interactor: LogContextInteractorImpl
@Before
fun setup() {
- keyguardTransitionRepository = FakeKeyguardTransitionRepository()
- udfpsOverlayInteractor =
- UdfpsOverlayInteractor(
- context,
- authController,
- selectedUserInteractor,
- testScope.backgroundScope,
- )
interactor =
LogContextInteractorImpl(
testScope.backgroundScope,
- foldProvider,
+ deviceStateRepository,
KeyguardTransitionInteractorFactory.create(
repository = keyguardTransitionRepository,
scope = testScope.backgroundScope,
@@ -189,33 +187,31 @@
@Test
fun foldStateChanges() =
testScope.runTest {
- val foldState = collectLastValue(interactor.foldState)
- runCurrent()
- val listener = foldProvider.captureListener()
+ val foldState by collectLastValue(interactor.foldState)
- listener.onFoldUpdate(FOLD_UPDATE_START_OPENING)
- assertThat(foldState()).isEqualTo(FoldState.UNKNOWN)
+ deviceStateRepository.emit(DeviceStateRepository.DeviceState.HALF_FOLDED)
+ assertThat(foldState).isEqualTo(FoldState.HALF_OPENED)
- listener.onFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN)
- assertThat(foldState()).isEqualTo(FoldState.HALF_OPENED)
+ deviceStateRepository.emit(DeviceStateRepository.DeviceState.CONCURRENT_DISPLAY)
+ assertThat(foldState).isEqualTo(FoldState.FULLY_OPENED)
- listener.onFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN)
- assertThat(foldState()).isEqualTo(FoldState.FULLY_OPENED)
+ deviceStateRepository.emit(DeviceStateRepository.DeviceState.UNFOLDED)
+ assertThat(foldState).isEqualTo(FoldState.FULLY_OPENED)
- listener.onFoldUpdate(FOLD_UPDATE_START_CLOSING)
- assertThat(foldState()).isEqualTo(FoldState.FULLY_OPENED)
+ deviceStateRepository.emit(DeviceStateRepository.DeviceState.FOLDED)
+ assertThat(foldState).isEqualTo(FoldState.FULLY_CLOSED)
- listener.onFoldUpdate(FOLD_UPDATE_FINISH_CLOSED)
- assertThat(foldState()).isEqualTo(FoldState.FULLY_CLOSED)
+ deviceStateRepository.emit(DeviceStateRepository.DeviceState.REAR_DISPLAY)
+ assertThat(foldState).isEqualTo(FoldState.FULLY_OPENED)
+
+ deviceStateRepository.emit(DeviceStateRepository.DeviceState.UNKNOWN)
+ assertThat(foldState).isEqualTo(FoldState.UNKNOWN)
}
@Test
fun contextSubscriberChanges() =
testScope.runTest {
- runCurrent()
- val foldListener = foldProvider.captureListener()
- foldListener.onFoldUpdate(FOLD_UPDATE_START_CLOSING)
- foldListener.onFoldUpdate(FOLD_UPDATE_FINISH_CLOSED)
+ deviceStateRepository.emit(DeviceStateRepository.DeviceState.FOLDED)
keyguardTransitionRepository.startTransitionTo(KeyguardState.AOD)
var folded: Int? = null
@@ -243,8 +239,7 @@
assertThat(displayState).isEqualTo(AuthenticateOptions.DISPLAY_STATE_AOD)
assertThat(ignoreTouches).isFalse()
- foldListener.onFoldUpdate(FOLD_UPDATE_START_OPENING)
- foldListener.onFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN)
+ deviceStateRepository.emit(DeviceStateRepository.DeviceState.HALF_FOLDED)
keyguardTransitionRepository.startTransitionTo(KeyguardState.LOCKSCREEN)
runCurrent()
@@ -259,7 +254,7 @@
job.cancel()
// stale updates should be ignored
- foldListener.onFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN)
+ deviceStateRepository.emit(DeviceStateRepository.DeviceState.UNFOLDED)
keyguardTransitionRepository.startTransitionTo(KeyguardState.AOD)
runCurrent()
@@ -270,8 +265,3 @@
private suspend fun FakeKeyguardTransitionRepository.startTransitionTo(newState: KeyguardState) =
sendTransitionStep(TransitionStep(to = newState, transitionState = TransitionState.STARTED))
-
-private fun FoldStateProvider.captureListener() =
- withArgCaptor<FoldStateProvider.FoldUpdatesListener> {
- verify(this@captureListener).addCallback(capture())
- }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
index 9c5cd71..20dd913 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
@@ -77,6 +77,18 @@
}
@Test
+ fun deleteWidget_notInDb_returnsFalse() =
+ testScope.runTest {
+ val (widgetId, provider, priority) = widgetInfo1
+ communalWidgetDao.addWidget(
+ widgetId = widgetId,
+ provider = provider,
+ priority = priority,
+ )
+ assertThat(communalWidgetDao.deleteWidgetById(widgetId = 123)).isFalse()
+ }
+
+ @Test
fun addWidget_emitsActiveWidgetsInDb(): Unit =
testScope.runTest {
val widgetsToAdd = listOf(widgetInfo1, widgetInfo2)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
index 9fe40d7..8b16da2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
@@ -23,6 +23,7 @@
import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository
import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransitionType
import com.android.systemui.statusbar.policy.SplitShadeStateController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
@@ -46,6 +47,10 @@
private lateinit var underTest: KeyguardBlueprintInteractor
private lateinit var testScope: TestScope
+ val refreshBluePrint: MutableSharedFlow<Unit> = MutableSharedFlow(extraBufferCapacity = 1)
+ val refreshBlueprintTransition: MutableSharedFlow<IntraBlueprintTransitionType> =
+ MutableSharedFlow(extraBufferCapacity = 1)
+
@Mock private lateinit var splitShadeStateController: SplitShadeStateController
@Mock private lateinit var keyguardBlueprintRepository: KeyguardBlueprintRepository
@@ -54,6 +59,9 @@
MockitoAnnotations.initMocks(this)
testScope = TestScope(StandardTestDispatcher())
whenever(keyguardBlueprintRepository.configurationChange).thenReturn(configurationFlow)
+ whenever(keyguardBlueprintRepository.refreshBluePrint).thenReturn(refreshBluePrint)
+ whenever(keyguardBlueprintRepository.refreshBlueprintTransition)
+ .thenReturn(refreshBlueprintTransition)
underTest =
KeyguardBlueprintInteractor(
@@ -105,4 +113,11 @@
underTest.transitionToBlueprint("abc")
verify(keyguardBlueprintRepository).applyBlueprint("abc")
}
+
+ @Test
+ fun testRefreshBlueprintWithTransition() {
+ underTest.refreshBlueprintWithTransition(IntraBlueprintTransitionType.DefaultTransition)
+ verify(keyguardBlueprintRepository)
+ .refreshBlueprintWithTransition(IntraBlueprintTransitionType.DefaultTransition)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
index acb6ff0..2da74b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
@@ -165,7 +165,7 @@
val cs = ConstraintSet()
underTest.applyDefaultConstraints(cs)
- val expectedLargeClockTopMargin = LARGE_CLOCK_TOP - CLOCK_FADE_TRANSLATION_Y
+ val expectedLargeClockTopMargin = LARGE_CLOCK_TOP
assetLargeClockTop(cs, expectedLargeClockTopMargin)
val expectedSmallClockTopMargin = SMALL_CLOCK_TOP_SPLIT_SHADE
@@ -178,7 +178,7 @@
setSplitShade(false)
val cs = ConstraintSet()
underTest.applyDefaultConstraints(cs)
- val expectedLargeClockTopMargin = LARGE_CLOCK_TOP - CLOCK_FADE_TRANSLATION_Y
+ val expectedLargeClockTopMargin = LARGE_CLOCK_TOP
assetLargeClockTop(cs, expectedLargeClockTopMargin)
val expectedSmallClockTopMargin = SMALL_CLOCK_TOP_NON_SPLIT_SHADE
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
index 437a35f..e3c4c28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
@@ -475,10 +475,7 @@
// Then the device name is the PhoneMediaDevice string
val data = captureDeviceData(KEY)
- assertThat(data.name)
- .isEqualTo(
- context.getString(com.android.settingslib.R.string.media_transfer_this_device_name)
- )
+ assertThat(data.name).isEqualTo(PhoneMediaDevice.getMediaTransferThisDeviceName(context))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
index ba7927d..edba902 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
@@ -30,6 +30,7 @@
import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
import com.android.systemui.dreams.DreamOverlayStateController
import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.pipeline.MediaDataManager
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.dream.MediaDreamComplication
@@ -52,8 +53,6 @@
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertNotNull
@@ -106,8 +105,7 @@
private lateinit var dreamOverlayCallback:
ArgumentCaptor<(DreamOverlayStateController.Callback)>
@JvmField @Rule val mockito = MockitoJUnit.rule()
- private val testDispatcher = StandardTestDispatcher()
- private val testScope = TestScope(testDispatcher)
+ private val testScope = kosmos.testScope
private lateinit var mediaHierarchyManager: MediaHierarchyManager
private lateinit var isQsBypassingShade: MutableStateFlow<Boolean>
private lateinit var mediaFrame: ViewGroup
@@ -532,6 +530,58 @@
}
@Test
+ fun testCommunalLocation_showsOverLockscreen() =
+ testScope.runTest {
+ // Device is on lock screen.
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+
+ // UMO goes to communal even over the lock screen.
+ communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
+ runCurrent()
+ verify(mediaCarouselController)
+ .onDesiredLocationChanged(
+ eq(MediaHierarchyManager.LOCATION_COMMUNAL_HUB),
+ nullable(),
+ eq(false),
+ anyLong(),
+ anyLong()
+ )
+ }
+
+ @Test
+ fun testCommunalLocation_showsUntilQsExpands() =
+ testScope.runTest {
+ // Device is on lock screen.
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+
+ communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
+ runCurrent()
+ verify(mediaCarouselController)
+ .onDesiredLocationChanged(
+ eq(MediaHierarchyManager.LOCATION_COMMUNAL_HUB),
+ nullable(),
+ eq(false),
+ anyLong(),
+ anyLong()
+ )
+ clearInvocations(mediaCarouselController)
+
+ // Start opening the shade.
+ mediaHierarchyManager.qsExpansion = 0.1f
+ runCurrent()
+
+ // UMO goes to the shade instead.
+ verify(mediaCarouselController)
+ .onDesiredLocationChanged(
+ eq(MediaHierarchyManager.LOCATION_QS),
+ any(MediaHostState::class.java),
+ eq(false),
+ anyLong(),
+ anyLong()
+ )
+ }
+
+ @Test
fun testQsExpandedChanged_noQqsMedia() {
// When we are looking at QQS with active media
whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index db5bd9b..0d1e874 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -182,6 +182,8 @@
@Mock
private UiEventLogger mUiEventLogger;
@Mock
+ private NavBarButtonClickLogger mNavBarButtonClickLogger;
+ @Mock
private ViewTreeObserver mViewTreeObserver;
NavBarHelper mNavBarHelper;
@Mock
@@ -596,7 +598,8 @@
mUserContextProvider,
mWakefulnessLifecycle,
mTaskStackChangeListeners,
- new FakeDisplayTracker(mContext)));
+ new FakeDisplayTracker(mContext),
+ mNavBarButtonClickLogger));
}
private void processAllMessages() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index a92111e..da8d29c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -246,8 +246,9 @@
@Test
- public void testShouldUzeHorizontalLayout_falseForSplitShade() {
+ public void testShouldUseHorizontalLayout_falseForSplitShade() {
mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ mConfiguration.screenLayout = Configuration.SCREENLAYOUT_LONG_YES;
when(mMediaHost.getVisible()).thenReturn(true);
when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(false);
@@ -270,12 +271,13 @@
}
@Test
- public void testChangeConfiguration_shouldUseHorizontalLayout() {
+ public void testChangeConfiguration_shouldUseHorizontalLayoutInLandscape_true() {
when(mMediaHost.getVisible()).thenReturn(true);
mController.setUsingHorizontalLayoutChangeListener(mHorizontalLayoutListener);
- // When device is rotated to landscape
+ // When device is rotated to landscape and is long
mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ mConfiguration.screenLayout = Configuration.SCREENLAYOUT_LONG_YES;
mController.mOnConfigurationChangedListener.onConfigurationChange(mConfiguration);
// Then the layout changes
@@ -292,6 +294,29 @@
}
@Test
+ public void testChangeConfiguration_shouldUseHorizontalLayoutInLongDevices_true() {
+ when(mMediaHost.getVisible()).thenReturn(true);
+ mController.setUsingHorizontalLayoutChangeListener(mHorizontalLayoutListener);
+
+ // When device is rotated to landscape and is long
+ mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ mConfiguration.screenLayout = Configuration.SCREENLAYOUT_LONG_YES;
+ mController.mOnConfigurationChangedListener.onConfigurationChange(mConfiguration);
+
+ // Then the layout changes
+ assertThat(mController.shouldUseHorizontalLayout()).isTrue();
+ verify(mHorizontalLayoutListener).run();
+
+ // When device changes to not-long
+ mConfiguration.screenLayout = Configuration.SCREENLAYOUT_LONG_NO;
+ mController.mOnConfigurationChangedListener.onConfigurationChange(mConfiguration);
+
+ // Then the layout changes back
+ assertThat(mController.shouldUseHorizontalLayout()).isFalse();
+ verify(mHorizontalLayoutListener, times(2)).run();
+ }
+
+ @Test
public void testRefreshAllTilesDoesntRefreshListeningTiles() {
when(mQSHost.getTiles()).thenReturn(List.of(mQSTile, mOtherTile));
mController.setTiles();
@@ -310,6 +335,7 @@
when(mMediaHost.getVisible()).thenReturn(true);
when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(false);
mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ mConfiguration.screenLayout = Configuration.SCREENLAYOUT_LONG_YES;
mController.setUsingHorizontalLayoutChangeListener(mHorizontalLayoutListener);
mController.mOnConfigurationChangedListener.onConfigurationChange(mConfiguration);
assertThat(mController.shouldUseHorizontalLayout()).isTrue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index a6e240b..0831971 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -44,6 +44,7 @@
import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.BeforeClass
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
@@ -51,6 +52,7 @@
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+@Ignore("b/323053208")
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index b3e386e..cc27cbd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -189,7 +189,6 @@
import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
-import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
@@ -411,7 +410,7 @@
mShadeInteractor = new ShadeInteractorImpl(
mTestScope.getBackgroundScope(),
- new FakeDeviceProvisioningRepository(),
+ mKosmos.getDeviceProvisioningInteractor(),
new FakeDisableFlagsRepository(),
mDozeParameters,
mFakeKeyguardRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 461db8e..7f4508a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -99,7 +99,6 @@
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
-import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
@@ -260,7 +259,7 @@
mShadeInteractor = new ShadeInteractorImpl(
mTestScope.getBackgroundScope(),
- new FakeDeviceProvisioningRepository(),
+ mKosmos.getDeviceProvisioningInteractor(),
new FakeDisableFlagsRepository(),
mock(DozeParameters.class),
keyguardRepository,
@@ -452,11 +451,11 @@
}
@Test
- public void setCommunalShowing_userTimeout() {
+ public void setCommunalVisible_userTimeout() {
setKeyguardShowing();
clearInvocations(mWindowManager);
- mNotificationShadeWindowController.onCommunalShowingChanged(true);
+ mNotificationShadeWindowController.onCommunalVisibleChanged(true);
verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
assertThat(mLayoutParameters.getValue().userActivityTimeout)
.isEqualTo(CommunalInteractor.AWAKE_INTERVAL_MS);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
index 697b05a..c226121 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
@@ -28,8 +28,6 @@
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.fragments.FragmentHostManager
import com.android.systemui.fragments.FragmentService
import com.android.systemui.navigationbar.NavigationModeController
@@ -94,7 +92,6 @@
lateinit var underTest: NotificationsQSContainerController
- private lateinit var featureFlags: FakeFeatureFlags
private lateinit var navigationModeCallback: ModeChangedListener
private lateinit var taskbarVisibilityCallback: OverviewProxyListener
private lateinit var windowInsetsCallback: Consumer<WindowInsets>
@@ -106,7 +103,6 @@
MockitoAnnotations.initMocks(this)
fakeSystemClock = FakeSystemClock()
delayableExecutor = FakeExecutor(fakeSystemClock)
- featureFlags = FakeFeatureFlags().apply { set(Flags.QS_CONTAINER_GRAPH_OPTIMIZER, false) }
mContext.ensureTestableResources()
whenever(view.context).thenReturn(mContext)
whenever(view.resources).thenReturn(mContext.resources)
@@ -123,7 +119,6 @@
shadeInteractor,
fragmentService,
delayableExecutor,
- featureFlags,
notificationStackScrollLayoutController,
ResourcesSplitShadeStateController(),
largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }
@@ -536,7 +531,6 @@
shadeInteractor,
fragmentService,
delayableExecutor,
- featureFlags,
notificationStackScrollLayoutController,
ResourcesSplitShadeStateController(),
largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
index e66251a..c326350 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
@@ -28,8 +28,6 @@
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.fragments.FragmentHostManager
import com.android.systemui.fragments.FragmentService
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
@@ -91,7 +89,6 @@
lateinit var underTest: NotificationsQSContainerController
- private lateinit var featureFlags: FakeFeatureFlags
private lateinit var navigationModeCallback: ModeChangedListener
private lateinit var taskbarVisibilityCallback: OverviewProxyListener
private lateinit var windowInsetsCallback: Consumer<WindowInsets>
@@ -104,7 +101,6 @@
fakeSystemClock = FakeSystemClock()
delayableExecutor = FakeExecutor(fakeSystemClock)
mSetFlagsRule.enableFlags(KeyguardShadeMigrationNssl.FLAG_NAME)
- featureFlags = FakeFeatureFlags().apply { set(Flags.QS_CONTAINER_GRAPH_OPTIMIZER, true) }
mContext.ensureTestableResources()
whenever(view.context).thenReturn(mContext)
whenever(view.resources).thenReturn(mContext.resources)
@@ -122,7 +118,6 @@
shadeInteractor,
fragmentService,
delayableExecutor,
- featureFlags,
notificationStackScrollLayoutController,
ResourcesSplitShadeStateController(),
largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }
@@ -513,7 +508,6 @@
shadeInteractor,
fragmentService,
delayableExecutor,
- featureFlags,
notificationStackScrollLayoutController,
ResourcesSplitShadeStateController(),
largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index 3e0a647..7bd9d92 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -103,7 +103,6 @@
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
-import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
@@ -205,9 +204,7 @@
mStatusBarStateController = mKosmos.getStatusBarStateController();
mInteractionJankMonitor = mKosmos.getInteractionJankMonitor();
- FakeDeviceProvisioningRepository deviceProvisioningRepository =
- new FakeDeviceProvisioningRepository();
- deviceProvisioningRepository.setDeviceProvisioned(true);
+ mKosmos.getFakeDeviceProvisioningRepository().setDeviceProvisioned(true);
FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic();
FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
@@ -294,7 +291,7 @@
mShadeInteractor = new ShadeInteractorImpl(
mTestScope.getBackgroundScope(),
- deviceProvisioningRepository,
+ mKosmos.getDeviceProvisioningInteractor(),
mDisableFlagsRepository,
mDozeParameters,
mKeyguardRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
index 50349be..0dd988d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
@@ -82,4 +82,22 @@
underTest.setShowNotificationsOnLockscreenEnabled(false)
assertThat(showNotifs).isEqualTo(false)
}
+
+ @Test
+ fun testGetIsNotificationHistoryEnabled() =
+ testScope.runTest {
+ val historyEnabled by collectLastValue(underTest.isNotificationHistoryEnabled)
+
+ secureSettingsRepository.setInt(
+ name = Settings.Secure.NOTIFICATION_HISTORY_ENABLED,
+ value = 1,
+ )
+ assertThat(historyEnabled).isEqualTo(true)
+
+ secureSettingsRepository.setInt(
+ name = Settings.Secure.NOTIFICATION_HISTORY_ENABLED,
+ value = 0,
+ )
+ assertThat(historyEnabled).isEqualTo(false)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
index 190ee81..460892a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
@@ -49,7 +49,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
import org.junit.Before;
import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 260bef8..5da3a56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -25,6 +25,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
import android.content.ComponentName;
import android.graphics.Rect;
@@ -32,11 +33,14 @@
import android.hardware.biometrics.PromptInfo;
import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.os.Bundle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.view.KeyEvent;
import android.view.WindowInsets;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
+import android.view.accessibility.Flags;
import androidx.test.filters.SmallTest;
@@ -365,14 +369,50 @@
}
@Test
- public void testAddQsTile() {
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void addQsTile_withA11yQsShortcutFlagOff() {
ComponentName c = new ComponentName("testpkg", "testcls");
+
mCommandQueue.addQsTile(c);
waitForIdleSync();
+
verify(mCallbacks).addQsTile(eq(c));
}
@Test
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void addQsTileToFrontOrEnd_withA11yQsShortcutFlagOff_doNothing() {
+ ComponentName c = new ComponentName("testpkg", "testcls");
+
+ mCommandQueue.addQsTileToFrontOrEnd(c, true);
+ waitForIdleSync();
+
+ verifyZeroInteractions(mCallbacks);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void addQsTile_withA11yQsShortcutFlagOn() {
+ ComponentName c = new ComponentName("testpkg", "testcls");
+
+ mCommandQueue.addQsTile(c);
+ waitForIdleSync();
+
+ verify(mCallbacks).addQsTileToFrontOrEnd(eq(c), eq(false));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void addQsTileAtTheEnd_withA11yQsShortcutFlagOn() {
+ ComponentName c = new ComponentName("testpkg", "testcls");
+
+ mCommandQueue.addQsTileToFrontOrEnd(c, true);
+ waitForIdleSync();
+
+ verify(mCallbacks).addQsTileToFrontOrEnd(eq(c), eq(true));
+ }
+
+ @Test
public void testRemoveQsTile() {
ComponentName c = new ComponentName("testpkg", "testcls");
mCommandQueue.remQsTile(c);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index 8cb064d..5450537 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -56,8 +56,8 @@
import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
-import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
+import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import kotlinx.coroutines.flow.emptyFlow
@@ -192,7 +192,7 @@
shadeInteractor =
ShadeInteractorImpl(
testScope.backgroundScope,
- FakeDeviceProvisioningRepository(),
+ kosmos.deviceProvisioningInteractor,
FakeDisableFlagsRepository(),
mock(),
keyguardRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
index 350ed2d..7d99d05 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
@@ -21,7 +21,7 @@
import android.service.notification.StatusBarNotification
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING
+import com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationLockscreenUserManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
index 57dac3a..cac4a8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
@@ -17,10 +17,13 @@
package com.android.systemui.statusbar.notification.footer.ui.view;
import static com.android.systemui.log.LogAssertKt.assertLogsWtf;
+
import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
+
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
@@ -95,6 +98,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void setHistoryShown() {
mView.showHistory(true);
assertTrue(mView.isHistoryShown());
@@ -103,6 +107,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void setHistoryNotShown() {
mView.showHistory(false);
assertFalse(mView.isHistoryShown());
@@ -128,6 +133,62 @@
@Test
@EnableFlags(FooterViewRefactor.FLAG_NAME)
+ public void testSetManageOrHistoryButtonText_resourceOnlyFetchedOnce() {
+ int resId = R.string.manage_notifications_history_text;
+ mView.setManageOrHistoryButtonText(resId);
+ verify(mSpyContext).getString(eq(resId));
+
+ clearInvocations(mSpyContext);
+
+ assertThat(((TextView) mView.findViewById(R.id.manage_text))
+ .getText().toString()).contains("History");
+
+ // Set it a few more times, it shouldn't lead to the resource being fetched again
+ mView.setManageOrHistoryButtonText(resId);
+ mView.setManageOrHistoryButtonText(resId);
+
+ verify(mSpyContext, never()).getString(anyInt());
+ }
+
+ @Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
+ public void testSetManageOrHistoryButtonText_expectsFlagEnabled() {
+ clearInvocations(mSpyContext);
+ int resId = R.string.manage_notifications_history_text;
+ assertLogsWtf(() -> mView.setManageOrHistoryButtonText(resId));
+ verify(mSpyContext, never()).getString(anyInt());
+ }
+
+ @Test
+ @EnableFlags(FooterViewRefactor.FLAG_NAME)
+ public void testSetManageOrHistoryButtonDescription_resourceOnlyFetchedOnce() {
+ int resId = R.string.manage_notifications_history_text;
+ mView.setManageOrHistoryButtonDescription(resId);
+ verify(mSpyContext).getString(eq(resId));
+
+ clearInvocations(mSpyContext);
+
+ assertThat(((TextView) mView.findViewById(R.id.manage_text))
+ .getContentDescription().toString()).contains("History");
+
+ // Set it a few more times, it shouldn't lead to the resource being fetched again
+ mView.setManageOrHistoryButtonDescription(resId);
+ mView.setManageOrHistoryButtonDescription(resId);
+
+ verify(mSpyContext, never()).getString(anyInt());
+ }
+
+ @Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
+ public void testSetManageOrHistoryButtonDescription_expectsFlagEnabled() {
+ clearInvocations(mSpyContext);
+ int resId = R.string.accessibility_clear_all;
+ assertLogsWtf(() -> mView.setManageOrHistoryButtonDescription(resId));
+ verify(mSpyContext, never()).getString(anyInt());
+ }
+
+ @Test
+ @EnableFlags(FooterViewRefactor.FLAG_NAME)
public void testSetClearAllButtonText_resourceOnlyFetchedOnce() {
int resId = R.string.clear_all_notifications_text;
mView.setClearAllButtonText(resId);
@@ -150,7 +211,7 @@
public void testSetClearAllButtonText_expectsFlagEnabled() {
clearInvocations(mSpyContext);
int resId = R.string.clear_all_notifications_text;
- assertLogsWtf(()-> mView.setClearAllButtonText(resId));
+ assertLogsWtf(() -> mView.setClearAllButtonText(resId));
verify(mSpyContext, never()).getString(anyInt());
}
@@ -178,7 +239,7 @@
public void testSetClearAllButtonDescription_expectsFlagEnabled() {
clearInvocations(mSpyContext);
int resId = R.string.accessibility_clear_all;
- assertLogsWtf(()-> mView.setClearAllButtonDescription(resId));
+ assertLogsWtf(() -> mView.setClearAllButtonDescription(resId));
verify(mSpyContext, never()).getString(anyInt());
}
@@ -206,7 +267,7 @@
public void testSetMessageString_expectsFlagEnabled() {
clearInvocations(mSpyContext);
int resId = R.string.unlock_to_see_notif_text;
- assertLogsWtf(()-> mView.setMessageString(resId));
+ assertLogsWtf(() -> mView.setMessageString(resId));
verify(mSpyContext, never()).getString(anyInt());
}
@@ -231,7 +292,7 @@
public void testSetMessageIcon_expectsFlagEnabled() {
clearInvocations(mSpyContext);
int resId = R.drawable.ic_friction_lock_closed;
- assertLogsWtf(()-> mView.setMessageIcon(resId));
+ assertLogsWtf(() -> mView.setMessageIcon(resId));
verify(mSpyContext, never()).getDrawable(anyInt());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
index 8ab13f5..620d972 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
@@ -14,109 +14,61 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.statusbar.notification.footer.ui.viewmodel
import android.platform.test.annotations.EnableFlags
+import android.provider.Settings
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.collectLastValue
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.powerRepository
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.power.shared.model.WakefulnessState
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.res.R
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shared.settings.data.repository.fakeSecureSettingsRepository
import com.android.systemui.statusbar.notification.collection.render.NotifStats
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule
-import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.user.domain.interactor.HeadlessSystemUserModeModule
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.testKosmos
import com.android.systemui.util.ui.isAnimating
import com.android.systemui.util.ui.value
import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
-import java.util.Optional
-import org.junit.Before
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@SmallTest
@EnableFlags(FooterViewRefactor.FLAG_NAME)
class FooterViewModelTest : SysuiTestCase() {
- private lateinit var footerViewModel: FooterViewModel
-
- @SysUISingleton
- @Component(
- modules =
- [
- SysUITestModule::class,
- ActivatableNotificationViewModelModule::class,
- FooterViewModelModule::class,
- HeadlessSystemUserModeModule::class,
- ]
- )
- interface TestComponent : SysUITestComponent<Optional<FooterViewModel>> {
- val activeNotificationListRepository: ActiveNotificationListRepository
- val configurationRepository: FakeConfigurationRepository
- val keyguardRepository: FakeKeyguardRepository
- val keyguardTransitionRepository: FakeKeyguardTransitionRepository
- val shadeRepository: FakeShadeRepository
- val powerRepository: FakePowerRepository
-
- @Component.Factory
- interface Factory {
- fun create(
- @BindsInstance test: SysuiTestCase,
- featureFlags: FakeFeatureFlagsClassicModule,
- mocks: TestMocksModule,
- ): TestComponent
+ private val kosmos =
+ testKosmos().apply {
+ fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
}
- }
+ private val testScope = kosmos.testScope
+ private val activeNotificationListRepository = kosmos.activeNotificationListRepository
+ private val fakeKeyguardRepository = kosmos.fakeKeyguardRepository
+ private val shadeRepository = kosmos.shadeRepository
+ private val powerRepository = kosmos.powerRepository
+ private val fakeSecureSettingsRepository = kosmos.fakeSecureSettingsRepository
- private val dozeParameters: DozeParameters = mock()
-
- private val testComponent: TestComponent =
- DaggerFooterViewModelTest_TestComponent.factory()
- .create(
- test = this,
- featureFlags =
- FakeFeatureFlagsClassicModule {
- set(com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER, true)
- },
- mocks =
- TestMocksModule(
- dozeParameters = dozeParameters,
- )
- )
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- // The underTest in the component is Optional, because that matches the provider we
- // currently have for the footer view model.
- footerViewModel = testComponent.underTest.get()
- }
+ val underTest = kosmos.footerViewModel
@Test
fun testMessageVisible_whenFilteredNotifications() =
- testComponent.runTest {
- val visible by collectLastValue(footerViewModel.message.isVisible)
+ testScope.runTest {
+ val visible by collectLastValue(underTest.message.isVisible)
activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true
@@ -125,8 +77,8 @@
@Test
fun testMessageVisible_whenNoFilteredNotifications() =
- testComponent.runTest {
- val visible by collectLastValue(footerViewModel.message.isVisible)
+ testScope.runTest {
+ val visible by collectLastValue(underTest.message.isVisible)
activeNotificationListRepository.hasFilteredOutSeenNotifications.value = false
@@ -135,8 +87,8 @@
@Test
fun testClearAllButtonVisible_whenHasClearableNotifs() =
- testComponent.runTest {
- val visible by collectLastValue(footerViewModel.clearAllButton.isVisible)
+ testScope.runTest {
+ val visible by collectLastValue(underTest.clearAllButton.isVisible)
activeNotificationListRepository.notifStats.value =
NotifStats(
@@ -153,8 +105,8 @@
@Test
fun testClearAllButtonVisible_whenHasNoClearableNotifs() =
- testComponent.runTest {
- val visible by collectLastValue(footerViewModel.clearAllButton.isVisible)
+ testScope.runTest {
+ val visible by collectLastValue(underTest.clearAllButton.isVisible)
activeNotificationListRepository.notifStats.value =
NotifStats(
@@ -171,12 +123,12 @@
@Test
fun testClearAllButtonAnimating_whenShadeExpandedAndTouchable() =
- testComponent.runTest {
- val visible by collectLastValue(footerViewModel.clearAllButton.isVisible)
+ testScope.runTest {
+ val visible by collectLastValue(underTest.clearAllButton.isVisible)
runCurrent()
// WHEN shade is expanded
- keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
shadeRepository.setLegacyShadeExpansion(1f)
// AND QS not expanded
shadeRepository.setQsExpansion(0f)
@@ -205,12 +157,12 @@
@Test
fun testClearAllButtonAnimating_whenShadeNotExpanded() =
- testComponent.runTest {
- val visible by collectLastValue(footerViewModel.clearAllButton.isVisible)
+ testScope.runTest {
+ val visible by collectLastValue(underTest.clearAllButton.isVisible)
runCurrent()
// WHEN shade is collapsed
- keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
shadeRepository.setLegacyShadeExpansion(0f)
// AND QS not expanded
shadeRepository.setQsExpansion(0f)
@@ -236,4 +188,30 @@
// THEN button visibility should not animate
assertThat(visible?.isAnimating).isFalse()
}
+
+ @Test
+ fun testManageButton_whenHistoryDisabled() =
+ testScope.runTest {
+ val buttonLabel by collectLastValue(underTest.manageOrHistoryButton.labelId)
+ runCurrent()
+
+ // WHEN notification history is disabled
+ fakeSecureSettingsRepository.setInt(Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0)
+
+ // THEN label is "Manage"
+ assertThat(buttonLabel).isEqualTo(R.string.manage_notifications_text)
+ }
+
+ @Test
+ fun testHistoryButton_whenHistoryEnabled() =
+ testScope.runTest {
+ val buttonLabel by collectLastValue(underTest.manageOrHistoryButton.labelId)
+ runCurrent()
+
+ // WHEN notification history is disabled
+ fakeSecureSettingsRepository.setInt(Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 1)
+
+ // THEN label is "History"
+ assertThat(buttonLabel).isEqualTo(R.string.manage_notifications_history_text)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index dbe63f2..7589a49 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.notification.stack;
-import static com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING;
+import static com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING;
import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index 4188c5d..88e4f5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -23,36 +23,27 @@
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.collectLastValue
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.statusbar.notification.dagger.NotificationStatsLoggerModule
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
-import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModelModule
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule
-import com.android.systemui.statusbar.policy.FakeConfigurationController
-import com.android.systemui.statusbar.policy.data.repository.FakeZenModeRepository
-import com.android.systemui.unfold.UnfoldTransitionModule
-import com.android.systemui.user.domain.interactor.HeadlessSystemUserModeModule
+import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
+import com.android.systemui.statusbar.policy.fakeConfigurationController
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -62,46 +53,18 @@
@RunWith(AndroidJUnit4::class)
@EnableFlags(FooterViewRefactor.FLAG_NAME)
class NotificationListViewModelTest : SysuiTestCase() {
-
- @SysUISingleton
- @Component(
- modules =
- [
- SysUITestModule::class,
- ActivatableNotificationViewModelModule::class,
- FooterViewModelModule::class,
- HeadlessSystemUserModeModule::class,
- UnfoldTransitionModule.Bindings::class,
- NotificationStatsLoggerModule::class,
- ]
- )
- interface TestComponent : SysUITestComponent<NotificationListViewModel> {
- val activeNotificationListRepository: ActiveNotificationListRepository
- val keyguardTransitionRepository: FakeKeyguardTransitionRepository
- val shadeRepository: FakeShadeRepository
- val zenModeRepository: FakeZenModeRepository
- val configurationController: FakeConfigurationController
-
- @Component.Factory
- interface Factory {
- fun create(
- @BindsInstance test: SysuiTestCase,
- featureFlags: FakeFeatureFlagsClassicModule,
- mocks: TestMocksModule,
- ): TestComponent
+ private val kosmos =
+ testKosmos().apply {
+ fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
}
- }
+ private val testScope = kosmos.testScope
+ private val activeNotificationListRepository = kosmos.activeNotificationListRepository
+ private val fakeKeyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val fakeShadeRepository = kosmos.fakeShadeRepository
+ private val zenModeRepository = kosmos.zenModeRepository
+ private val fakeConfigurationController = kosmos.fakeConfigurationController
- private val testComponent: TestComponent =
- DaggerNotificationListViewModelTest_TestComponent.factory()
- .create(
- test = this,
- featureFlags =
- FakeFeatureFlagsClassicModule {
- set(com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER, true)
- },
- mocks = TestMocksModule()
- )
+ val underTest = kosmos.notificationListViewModel
@Before
fun setUp() {
@@ -110,11 +73,11 @@
@Test
fun testIsImportantForAccessibility_falseWhenNoNotifs() =
- testComponent.runTest {
+ testScope.runTest {
val important by collectLastValue(underTest.isImportantForAccessibility)
// WHEN on lockscreen
- keyguardTransitionRepository.sendTransitionSteps(
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.GONE,
to = KeyguardState.LOCKSCREEN,
testScope,
@@ -129,11 +92,11 @@
@Test
fun testIsImportantForAccessibility_trueWhenNotifs() =
- testComponent.runTest {
+ testScope.runTest {
val important by collectLastValue(underTest.isImportantForAccessibility)
// WHEN on lockscreen
- keyguardTransitionRepository.sendTransitionSteps(
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.GONE,
to = KeyguardState.LOCKSCREEN,
testScope,
@@ -148,11 +111,11 @@
@Test
fun testIsImportantForAccessibility_trueWhenNotKeyguard() =
- testComponent.runTest {
+ testScope.runTest {
val important by collectLastValue(underTest.isImportantForAccessibility)
// WHEN not on lockscreen
- keyguardTransitionRepository.sendTransitionSteps(
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.GONE,
testScope,
@@ -167,7 +130,7 @@
@Test
fun testShouldShowEmptyShadeView_trueWhenNoNotifs() =
- testComponent.runTest {
+ testScope.runTest {
val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
// WHEN has no notifs
@@ -180,7 +143,7 @@
@Test
fun testShouldShowEmptyShadeView_falseWhenNotifs() =
- testComponent.runTest {
+ testScope.runTest {
val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
// WHEN has notifs
@@ -193,13 +156,13 @@
@Test
fun testShouldShowEmptyShadeView_falseWhenQsExpandedDefault() =
- testComponent.runTest {
+ testScope.runTest {
val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
// WHEN has no notifs
activeNotificationListRepository.setActiveNotifs(count = 0)
// AND quick settings are expanded
- shadeRepository.legacyQsFullscreen.value = true
+ fakeShadeRepository.legacyQsFullscreen.value = true
runCurrent()
// THEN should not show
@@ -208,16 +171,16 @@
@Test
fun testShouldShowEmptyShadeView_trueWhenQsExpandedInSplitShade() =
- testComponent.runTest {
+ testScope.runTest {
val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
// WHEN has no notifs
activeNotificationListRepository.setActiveNotifs(count = 0)
// AND quick settings are expanded
- shadeRepository.setQsExpansion(1f)
+ fakeShadeRepository.setQsExpansion(1f)
// AND split shade is enabled
overrideResource(R.bool.config_use_split_notification_shade, true)
- configurationController.notifyConfigurationChanged()
+ fakeConfigurationController.notifyConfigurationChanged()
runCurrent()
// THEN should show
@@ -226,13 +189,13 @@
@Test
fun testShouldShowEmptyShadeView_falseWhenTransitioningToAOD() =
- testComponent.runTest {
+ testScope.runTest {
val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
// WHEN has no notifs
activeNotificationListRepository.setActiveNotifs(count = 0)
// AND transitioning to AOD
- keyguardTransitionRepository.sendTransitionStep(
+ fakeKeyguardTransitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.STARTED,
from = KeyguardState.LOCKSCREEN,
@@ -248,13 +211,13 @@
@Test
fun testShouldShowEmptyShadeView_falseWhenBouncerShowing() =
- testComponent.runTest {
+ testScope.runTest {
val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
// WHEN has no notifs
activeNotificationListRepository.setActiveNotifs(count = 0)
// AND is on bouncer
- keyguardTransitionRepository.sendTransitionSteps(
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.PRIMARY_BOUNCER,
testScope,
@@ -267,7 +230,7 @@
@Test
fun testAreNotificationsHiddenInShade_true() =
- testComponent.runTest {
+ testScope.runTest {
val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
zenModeRepository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST)
@@ -279,7 +242,7 @@
@Test
fun testAreNotificationsHiddenInShade_false() =
- testComponent.runTest {
+ testScope.runTest {
val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
zenModeRepository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST)
@@ -291,7 +254,7 @@
@Test
fun testHasFilteredOutSeenNotifications_true() =
- testComponent.runTest {
+ testScope.runTest {
val hasFilteredNotifs by collectLastValue(underTest.hasFilteredOutSeenNotifications)
activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true
@@ -302,7 +265,7 @@
@Test
fun testHasFilteredOutSeenNotifications_false() =
- testComponent.runTest {
+ testScope.runTest {
val hasFilteredNotifs by collectLastValue(underTest.hasFilteredOutSeenNotifications)
activeNotificationListRepository.hasFilteredOutSeenNotifications.value = false
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index 912c27d..ea4ae17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -26,6 +26,7 @@
import android.app.ActivityManager;
import android.app.StatusBarManager;
+import android.content.ComponentName;
import android.os.PowerManager;
import android.os.UserHandle;
import android.os.Vibrator;
@@ -190,4 +191,31 @@
HapticFeedbackConstants.GESTURE_START
);
}
+
+ @Test
+ public void addQsTile_delegateCallToQsHost() {
+ ComponentName c = new ComponentName("testpkg", "testcls");
+
+ mSbcqCallbacks.addQsTile(c);
+
+ verify(mQSHost).addTile(c);
+ }
+
+ @Test
+ public void addQsTileToFrontOrEnd_toTheEnd_delegateCallToQsHost() {
+ ComponentName c = new ComponentName("testpkg", "testcls");
+
+ mSbcqCallbacks.addQsTileToFrontOrEnd(c, true);
+
+ verify(mQSHost).addTile(c, true);
+ }
+
+ @Test
+ public void addQsTileToFrontOrEnd_toTheFront_delegateCallToQsHost() {
+ ComponentName c = new ComponentName("testpkg", "testcls");
+
+ mSbcqCallbacks.addQsTileToFrontOrEnd(c, false);
+
+ verify(mQSHost).addTile(c, false);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
index 21c038a..f53fc46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
@@ -20,6 +20,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.satellite.data.prod.FakeDeviceBasedSatelliteRepository
@@ -36,6 +37,7 @@
class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
private lateinit var underTest: DeviceBasedSatelliteViewModel
private lateinit var interactor: DeviceBasedSatelliteInteractor
+ private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
private val repo = FakeDeviceBasedSatelliteRepository()
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
@@ -45,6 +47,7 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ airplaneModeRepository = FakeAirplaneModeRepository()
interactor =
DeviceBasedSatelliteInteractor(
@@ -57,6 +60,7 @@
DeviceBasedSatelliteViewModel(
interactor,
testScope.backgroundScope,
+ airplaneModeRepository,
)
}
@@ -72,6 +76,9 @@
val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
i1.isInService.value = false
+ // GIVEN apm is disabled
+ airplaneModeRepository.setIsAirplaneMode(false)
+
// THEN icon is null because we should not be showing it
assertThat(latest).isNull()
}
@@ -88,11 +95,33 @@
val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
i1.isInService.value = true
+ // GIVEN apm is disabled
+ airplaneModeRepository.setIsAirplaneMode(false)
+
// THEN icon is null because we have service
assertThat(latest).isNull()
}
@Test
+ fun icon_nullWhenShouldNotShow_apmIsEnabled() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ // GIVEN satellite is allowed
+ repo.isSatelliteAllowedForCurrentLocation.value = true
+
+ // GIVEN all icons are OOS
+ val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+ i1.isInService.value = false
+
+ // GIVEN apm is enabled
+ airplaneModeRepository.setIsAirplaneMode(true)
+
+ // THEN icon is null because we should not be showing it
+ assertThat(latest).isNull()
+ }
+
+ @Test
fun icon_satelliteIsOff() =
testScope.runTest {
val latest by collectLastValue(underTest.icon)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
index cd5d5ed..9919c6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy
+import android.app.ActivityOptions
import android.app.Notification
import android.media.projection.MediaProjectionInfo
import android.media.projection.MediaProjectionManager
@@ -23,7 +24,7 @@
import android.service.notification.StatusBarNotification
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags
+import com.android.server.notification.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import org.junit.Assert.assertFalse
@@ -69,6 +70,8 @@
MockitoAnnotations.initMocks(this)
mSetFlagsRule.enableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ setShareFullScreen()
+
controller = SensitiveNotificationProtectionControllerImpl(mediaProjectionManager, handler)
// Obtain useful MediaProjectionCallback
@@ -195,6 +198,14 @@
}
@Test
+ fun isSensitiveStateActive_projectionActive_singleActivity_false() {
+ setShareSingleApp()
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+ assertFalse(controller.isSensitiveStateActive)
+ }
+
+ @Test
fun shouldProtectNotification_projectionInactive_false() {
val notificationEntry = mock(NotificationEntry::class.java)
@@ -202,30 +213,74 @@
}
@Test
- fun shouldProtectNotification_projectionActive_fgsNotification_false() {
+ fun shouldProtectNotification_projectionActive_singleActivity_false() {
+ setShareSingleApp()
mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
- val notificationEntry = mock(NotificationEntry::class.java)
- val sbn = mock(StatusBarNotification::class.java)
- val notification = mock(Notification::class.java)
- `when`(notificationEntry.sbn).thenReturn(sbn)
- `when`(sbn.notification).thenReturn(notification)
- `when`(notification.isFgsOrUij).thenReturn(true)
+ val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME)
assertFalse(controller.shouldProtectNotification(notificationEntry))
}
@Test
+ fun shouldProtectNotification_projectionActive_fgsNotificationFromProjectionApp_false() {
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+ val notificationEntry = setupFgsNotificationEntry(TEST_PROJECTION_PACKAGE_NAME)
+
+ assertFalse(controller.shouldProtectNotification(notificationEntry))
+ }
+
+ @Test
+ fun shouldProtectNotification_projectionActive_fgsNotificationNotFromProjectionApp_true() {
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+ val notificationEntry = setupFgsNotificationEntry(TEST_PACKAGE_NAME)
+
+ assertTrue(controller.shouldProtectNotification(notificationEntry))
+ }
+
+ @Test
fun shouldProtectNotification_projectionActive_notFgsNotification_true() {
mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ val notificationEntry = setupNotificationEntry(TEST_PROJECTION_PACKAGE_NAME)
+
+ assertTrue(controller.shouldProtectNotification(notificationEntry))
+ }
+
+ private fun setShareFullScreen() {
+ `when`(mediaProjectionInfo.packageName).thenReturn(TEST_PROJECTION_PACKAGE_NAME)
+ `when`(mediaProjectionInfo.launchCookie).thenReturn(null)
+ }
+
+ private fun setShareSingleApp() {
+ `when`(mediaProjectionInfo.packageName).thenReturn(TEST_PROJECTION_PACKAGE_NAME)
+ `when`(mediaProjectionInfo.launchCookie).thenReturn(ActivityOptions.LaunchCookie())
+ }
+
+ private fun setupNotificationEntry(
+ packageName: String,
+ isFgs: Boolean = false
+ ): NotificationEntry {
val notificationEntry = mock(NotificationEntry::class.java)
val sbn = mock(StatusBarNotification::class.java)
val notification = mock(Notification::class.java)
`when`(notificationEntry.sbn).thenReturn(sbn)
+ `when`(sbn.packageName).thenReturn(packageName)
`when`(sbn.notification).thenReturn(notification)
- `when`(notification.isFgsOrUij).thenReturn(false)
+ `when`(notification.isFgsOrUij).thenReturn(isFgs)
- assertTrue(controller.shouldProtectNotification(notificationEntry))
+ return notificationEntry
+ }
+
+ private fun setupFgsNotificationEntry(packageName: String): NotificationEntry {
+ return setupNotificationEntry(packageName, /* isFgs= */ true)
+ }
+
+ companion object {
+ private const val TEST_PROJECTION_PACKAGE_NAME =
+ "com.android.systemui.statusbar.policy.projectionpackage"
+ private const val TEST_PACKAGE_NAME = "com.android.systemui.statusbar.policy.testpackage"
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index abfff34..0669cb8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -31,6 +31,7 @@
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.launchIn
@@ -46,6 +47,7 @@
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class UserRepositoryImplTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 7eba3f0..9ea4142 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -494,7 +494,7 @@
mShadeInteractor =
new ShadeInteractorImpl(
mTestScope.getBackgroundScope(),
- deviceProvisioningRepository,
+ mKosmos.getDeviceProvisioningInteractor(),
new FakeDisableFlagsRepository(),
mDozeParameters,
keyguardRepository,
@@ -2098,6 +2098,26 @@
}
@Test
+ public void testShowOrHideAppBubble_addsFromOverflow() {
+ String appBubbleKey = Bubble.getAppBubbleKeyForApp(mAppBubbleIntent.getPackage(), mUser0);
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon);
+
+ // Collapse the stack so we don't need to wait for the dismiss animation in the test
+ mBubbleController.collapseStack();
+
+ // Dismiss the app bubble so it's in the overflow
+ mBubbleController.dismissBubble(appBubbleKey, Bubbles.DISMISS_USER_GESTURE);
+ assertThat(mBubbleData.getOverflowBubbleWithKey(appBubbleKey)).isNotNull();
+
+ // Calling this while collapsed will re-add and expand the app bubble
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon);
+ assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(appBubbleKey);
+ assertThat(mBubbleController.isStackExpanded()).isTrue();
+ assertThat(mBubbleData.getBubbles().size()).isEqualTo(1);
+ assertThat(mBubbleData.getOverflowBubbleWithKey(appBubbleKey)).isNull();
+ }
+
+ @Test
public void testCreateBubbleFromOngoingNotification() {
NotificationEntry notif = new NotificationEntryBuilder()
.setFlag(mContext, Notification.FLAG_ONGOING_EVENT, true)
@@ -2217,7 +2237,8 @@
FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
mBubbleController.registerBubbleStateListener(bubbleStateListener);
- mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(), 500, 1000);
+ mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(),
+ new Rect(500, 1000, 600, 1100));
assertThat(mBubbleController.getLayerView().isExpanded()).isTrue();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
index d91c597..99dfe94 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
@@ -21,11 +21,13 @@
import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor
import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
+import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.shared.flag.sceneContainerFlags
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
import com.android.systemui.user.ui.viewmodel.userSwitcherViewModel
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.time.systemClock
@@ -36,8 +38,10 @@
applicationScope = testScope.backgroundScope,
mainDispatcher = testDispatcher,
bouncerInteractor = bouncerInteractor,
+ inputMethodInteractor = inputMethodInteractor,
simBouncerInteractor = simBouncerInteractor,
authenticationInteractor = authenticationInteractor,
+ selectedUserInteractor = selectedUserInteractor,
flags = sceneContainerFlags,
selectedUser = userSwitcherViewModel.selectedUser,
users = userSwitcherViewModel.users,
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 bc7e7af..7301404 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
@@ -36,6 +36,18 @@
}
}
+ override fun deleteWidgetFromDb(widgetId: Int) {
+ if (_communalWidgets.value.none { it.appWidgetId == widgetId }) {
+ return
+ }
+
+ _communalWidgets.value = _communalWidgets.value.filter { it.appWidgetId != widgetId }
+ }
+
+ override fun deleteWidgetFromHost(widgetId: Int) {
+ deleteWidgetFromDb(widgetId)
+ }
+
private fun onConfigured(id: Int, providerInfo: AppWidgetProviderInfo, priority: Int) {
_communalWidgets.value += listOf(CommunalWidgetContentModel(id, providerInfo, priority))
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index c818e9c..c47f020 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -24,12 +24,15 @@
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.smartspace.data.repository.smartspaceRepository
import com.android.systemui.user.data.repository.userRepository
import com.android.systemui.util.mockito.mock
val Kosmos.communalInteractor by Fixture {
CommunalInteractor(
+ applicationScope = applicationCoroutineScope,
communalRepository = communalRepository,
widgetRepository = communalWidgetRepository,
mediaRepository = communalMediaRepository,
@@ -39,6 +42,8 @@
appWidgetHost = mock(),
keyguardInteractor = keyguardInteractor,
editWidgetsActivityStarter = editWidgetsActivityStarter,
+ logBuffer = logcatLogBuffer("CommunalInteractor"),
+ tableLogBuffer = mock(),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt
index adaea7c..9776b43 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt
@@ -21,6 +21,7 @@
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.util.mockito.mock
val Kosmos.communalTutorialInteractor by
Kosmos.Fixture {
@@ -30,5 +31,6 @@
keyguardInteractor = keyguardInteractor,
communalRepository = communalRepository,
communalInteractor = communalInteractor,
+ tableLogBuffer = mock(),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DeviceStateRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DeviceStateRepositoryKosmos.kt
new file mode 100644
index 0000000..5855060
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DeviceStateRepositoryKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.display.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.deviceStateRepository: DeviceStateRepository by
+ Kosmos.Fixture { fakeDeviceStateRepository }
+val Kosmos.fakeDeviceStateRepository: FakeDeviceStateRepository by
+ Kosmos.Fixture { FakeDeviceStateRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/FakeInputMethodRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/FakeInputMethodRepository.kt
new file mode 100644
index 0000000..8e4461d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/FakeInputMethodRepository.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.inputmethod.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.inputmethod.data.model.InputMethodModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.asFlow
+import kotlinx.coroutines.flow.flowOf
+
+@SysUISingleton
+class FakeInputMethodRepository : InputMethodRepository {
+
+ private var usersToEnabledInputMethods: MutableMap<Int, Flow<InputMethodModel>> = mutableMapOf()
+
+ var selectedInputMethodSubtypes = listOf<InputMethodModel.Subtype>()
+
+ /**
+ * The display ID on which the input method picker dialog was shown, or `null` if the dialog was
+ * not shown.
+ */
+ var inputMethodPickerShownDisplayId: Int? = null
+
+ fun setEnabledInputMethods(userId: Int, vararg enabledInputMethods: InputMethodModel) {
+ usersToEnabledInputMethods[userId] = enabledInputMethods.asFlow()
+ }
+
+ override suspend fun enabledInputMethods(
+ userId: Int,
+ fetchSubtypes: Boolean,
+ ): Flow<InputMethodModel> {
+ return usersToEnabledInputMethods[userId] ?: flowOf()
+ }
+
+ override suspend fun selectedInputMethodSubtypes(): List<InputMethodModel.Subtype> =
+ selectedInputMethodSubtypes
+
+ override suspend fun showInputMethodPicker(displayId: Int, showAuxiliarySubtypes: Boolean) {
+ inputMethodPickerShownDisplayId = displayId
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryKosmos.kt
new file mode 100644
index 0000000..b71b9d8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.inputmethod.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.inputMethodRepository: InputMethodRepository by
+ Kosmos.Fixture { fakeInputMethodRepository }
+val Kosmos.fakeInputMethodRepository by Kosmos.Fixture { FakeInputMethodRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorKosmos.kt
new file mode 100644
index 0000000..da77575
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.inputmethod.domain.interactor
+
+import com.android.systemui.inputmethod.data.repository.inputMethodRepository
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.inputMethodInteractor by
+ Kosmos.Fixture {
+ InputMethodInteractor(
+ repository = inputMethodRepository,
+ )
+ }
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 083de10..b9a3d38 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
@@ -43,6 +43,8 @@
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.statusbar.phone.screenOffAnimationController
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
+import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
import com.android.systemui.util.time.systemClock
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -80,6 +82,8 @@
val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }
val communalInteractor by lazy { kosmos.communalInteractor }
val sceneContainerPlugin by lazy { kosmos.sceneContainerPlugin }
+ val deviceProvisioningInteractor by lazy { kosmos.deviceProvisioningInteractor }
+ val fakeDeviceProvisioningRepository by lazy { kosmos.fakeDeviceProvisioningRepository }
init {
kosmos.applicationContext = testCase.context
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/fontscaling/FontScalingTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/fontscaling/FontScalingTileKosmos.kt
new file mode 100644
index 0000000..9410ce6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/fontscaling/FontScalingTileKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.qs.tiles.impl.fontscaling
+
+import com.android.systemui.accessibility.qs.QSAccessibilityModule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+
+val Kosmos.qsFontScalingTileConfig by
+ Kosmos.Fixture { QSAccessibilityModule.provideFontScalingTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
index afd37b3..2bd76be 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
@@ -29,8 +29,8 @@
import com.android.systemui.statusbar.disableflags.data.repository.disableFlagsRepository
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
import com.android.systemui.statusbar.phone.dozeParameters
-import com.android.systemui.statusbar.policy.data.repository.deviceProvisioningRepository
import com.android.systemui.statusbar.policy.data.repository.userSetupRepository
+import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
import com.android.systemui.user.domain.interactor.userSwitcherInteractor
var Kosmos.baseShadeInteractor: BaseShadeInteractor by
@@ -63,7 +63,7 @@
Kosmos.Fixture {
ShadeInteractorImpl(
scope = applicationCoroutineScope,
- deviceProvisioningRepository = deviceProvisioningRepository,
+ deviceProvisioningInteractor = deviceProvisioningInteractor,
disableFlagsRepository = disableFlagsRepository,
dozeParams = dozeParameters,
keyguardRepository = fakeKeyguardRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt
index ff22ca0..01cac4c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt
@@ -19,12 +19,14 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shared.notifications.domain.interactor.notificationSettingsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
val Kosmos.footerViewModel by Fixture {
FooterViewModel(
activeNotificationsInteractor = activeNotificationsInteractor,
+ notificationSettingsInteractor = notificationSettingsInteractor,
seenNotificationsInteractor = seenNotificationsInteractor,
shadeInteractor = shadeInteractor,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
index 748d04d..489598c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
@@ -23,6 +23,7 @@
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.notificationIconContainerShelfViewBinder
+import com.android.systemui.statusbar.notification.notificationActivityStarter
import com.android.systemui.statusbar.notification.stack.ui.view.notificationStatsLogger
import com.android.systemui.statusbar.notification.stack.displaySwitchNotificationsHiderTracker
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationListViewModel
@@ -36,9 +37,10 @@
configuration = configurationState,
falsingManager = falsingManager,
iconAreaController = notificationIconAreaController,
+ loggerOptional = Optional.of(notificationStatsLogger),
metricsLogger = metricsLogger,
hiderTracker = displaySwitchNotificationsHiderTracker,
nicBinder = notificationIconContainerShelfViewBinder,
- loggerOptional = Optional.of(notificationStatsLogger),
+ notificationActivityStarter = { notificationActivityStarter },
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt
index f2f3a5a..d79633a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt
@@ -18,6 +18,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
@@ -25,5 +26,6 @@
NotificationStackAppearanceViewModel(
stackAppearanceInteractor = notificationStackAppearanceInteractor,
shadeInteractor = shadeInteractor,
+ sceneInteractor = sceneInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index 7c398cd..549a775 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -20,7 +20,9 @@
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
+import com.android.systemui.keyguard.ui.viewmodel.dreamingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.lockscreenToDreamingTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.lockscreenToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.occludedToLockscreenTransitionViewModel
@@ -40,6 +42,8 @@
communalInteractor = communalInteractor,
occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel,
lockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel,
+ dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
+ lockscreenToDreamingTransitionViewModel = lockscreenToDreamingTransitionViewModel,
glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel,
lockscreenToGlanceableHubTransitionViewModel = lockscreenToGlanceableHubTransitionViewModel,
aodBurnInViewModel = aodBurnInViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
index 33ed7e6..d4e9bfb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
@@ -17,8 +17,8 @@
package com.android.systemui.statusbar.policy
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.util.mockito.mock
-val Kosmos.configurationController by Kosmos.Fixture { mock<ConfigurationController>() }
+var Kosmos.configurationController: ConfigurationController by
+ Kosmos.Fixture { fakeConfigurationController }
val Kosmos.fakeConfigurationController: FakeConfigurationController by
Kosmos.Fixture { FakeConfigurationController() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractorKosmos.kt
new file mode 100644
index 0000000..84bd3e8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractorKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.policy.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.policy.data.repository.deviceProvisioningRepository
+
+val Kosmos.deviceProvisioningInteractor by Fixture {
+ DeviceProvisioningInteractor(
+ repository = deviceProvisioningRepository,
+ )
+}
diff --git a/ravenwood/README-ravenwood+mockito.md b/ravenwood/README-ravenwood+mockito.md
deleted file mode 100644
index 6adb6144..0000000
--- a/ravenwood/README-ravenwood+mockito.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Ravenwood and Mockito
-
-Last update: 2023-11-13
-
-- As of 2023-11-13, `external/mockito` is based on version 2.x.
-- Mockito didn't support static mocking before 3.4.0.
- See: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#48
-
-- Latest Mockito is 5.*. According to https://github.com/mockito/mockito:
- `Mockito 3 does not introduce any breaking API changes, but now requires Java 8 over Java 6 for Mockito 2. Mockito 4 removes deprecated API. Mockito 5 switches the default mockmaker to mockito-inline, and now requires Java 11.`
-
-- Mockito now supports Android natively.
- See: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#0.1
- - But it's unclear at this point to omakoto@ how the `mockito-android` module is built.
-
-- Potential plan:
- - Ideal option:
- - If we can update `external/mockito`, that'd be great, but it may not work because
- Mockito has removed the deprecated APIs.
- - Second option:
- - Import the latest mockito as `external/mockito-new`, and require ravenwood
- to use this one.
- - The latest mockito needs be exposed to all of 1) device tests, 2) host tests, and 3) ravenwood tests.
- - This probably will require the latest `bytebuddy` and `objenesis`.
\ No newline at end of file
diff --git a/ravenwood/bulk_enable.py b/ravenwood/bulk_enable.py
new file mode 100644
index 0000000..36d398c
--- /dev/null
+++ b/ravenwood/bulk_enable.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python3
+#
+# 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.
+
+"""
+Tool to bulk-enable tests that are now passing on Ravenwood.
+
+Currently only offers to include classes which are fully passing; ignores
+classes that have partial success.
+
+Typical usage:
+$ ENABLE_PROBE_IGNORED=1 atest MyTestsRavenwood
+$ cd /path/to/tests/root
+$ python bulk_enable.py /path/to/atest/output/host_log.txt
+"""
+
+import collections
+import os
+import re
+import subprocess
+import sys
+
+re_result = re.compile("I/ModuleListener.+?null-device-0 (.+?)#(.+?) ([A-Z_]+)(.*)$")
+
+ANNOTATION = "@android.platform.test.annotations.EnabledOnRavenwood"
+SED_ARG = "s/^((public )?class )/%s\\n\\1/g" % (ANNOTATION)
+
+STATE_PASSED = "PASSED"
+STATE_FAILURE = "FAILURE"
+STATE_ASSUMPTION_FAILURE = "ASSUMPTION_FAILURE"
+STATE_CANDIDATE = "CANDIDATE"
+
+stats_total = collections.defaultdict(int)
+stats_class = collections.defaultdict(lambda: collections.defaultdict(int))
+stats_method = collections.defaultdict()
+
+with open(sys.argv[1]) as f:
+ for line in f.readlines():
+ result = re_result.search(line)
+ if result:
+ clazz, method, state, msg = result.groups()
+ if state == STATE_FAILURE and "actually passed under Ravenwood" in msg:
+ state = STATE_CANDIDATE
+ stats_total[state] += 1
+ stats_class[clazz][state] += 1
+ stats_method[(clazz, method)] = state
+
+# Find classes who are fully "candidates" (would be entirely green if enabled)
+num_enabled = 0
+for clazz in stats_class.keys():
+ stats = stats_class[clazz]
+ if STATE_CANDIDATE in stats and len(stats) == 1:
+ num_enabled += stats[STATE_CANDIDATE]
+ print("Enabling fully-passing class", clazz)
+ clazz_match = re.compile("%s\.(kt|java)" % (clazz.split(".")[-1]))
+ for root, dirs, files in os.walk("."):
+ for f in files:
+ if clazz_match.match(f):
+ path = os.path.join(root, f)
+ subprocess.run(["sed", "-i", "-E", SED_ARG, path])
+
+print("Overall stats", stats_total)
+print("Candidates actually enabled", num_enabled)
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index a797b1d..3670459 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -20,9 +20,14 @@
import android.os.Bundle;
import android.os.HandlerThread;
import android.os.Looper;
+import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.internal.os.RuntimeInit;
+
+import org.junit.runner.Description;
+
import java.io.PrintStream;
import java.util.Map;
import java.util.concurrent.Executors;
@@ -50,6 +55,8 @@
}
public static void init(RavenwoodRule rule) {
+ RuntimeInit.redirectLogStreams();
+
android.os.Process.init$ravenwood(rule.mUid, rule.mPid);
android.os.Binder.init$ravenwood();
android.os.SystemProperties.init$ravenwood(
@@ -92,6 +99,13 @@
android.os.Process.reset$ravenwood();
}
+ public static void logTestRunner(String label, Description description) {
+ // This message string carefully matches the exact format emitted by on-device tests, to
+ // aid developers in debugging raw text logs
+ Log.e("TestRunner", label + ": " + description.getMethodName()
+ + "(" + description.getTestClass().getName() + ")");
+ }
+
private static void dumpStacks() {
final PrintStream out = System.err;
out.println("-----BEGIN ALL THREAD STACKS-----");
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 1e7cbf6..0285b38 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -280,9 +280,14 @@
public void evaluate() throws Throwable {
Assume.assumeTrue(shouldEnableOnRavenwood(description));
+ RavenwoodRuleImpl.logTestRunner("started", description);
RavenwoodRuleImpl.init(RavenwoodRule.this);
try {
base.evaluate();
+ RavenwoodRuleImpl.logTestRunner("finished", description);
+ } catch (Throwable t) {
+ RavenwoodRuleImpl.logTestRunner("failed", description);
+ throw t;
} finally {
RavenwoodRuleImpl.reset(RavenwoodRule.this);
}
@@ -300,6 +305,8 @@
@Override
public void evaluate() throws Throwable {
Assume.assumeFalse(shouldStillIgnoreInProbeIgnoreMode(description));
+
+ RavenwoodRuleImpl.logTestRunner("started", description);
RavenwoodRuleImpl.init(RavenwoodRule.this);
try {
base.evaluate();
@@ -309,6 +316,7 @@
Assume.assumeTrue(shouldEnableOnRavenwood(description));
throw t;
} finally {
+ RavenwoodRuleImpl.logTestRunner("finished", description);
RavenwoodRuleImpl.reset(RavenwoodRule.this);
}
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index d0c2e18..7d172f2 100644
--- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -16,6 +16,8 @@
package android.platform.test.ravenwood;
+import org.junit.runner.Description;
+
public class RavenwoodRuleImpl {
public static boolean isOnRavenwood() {
return false;
@@ -28,4 +30,8 @@
public static void reset(RavenwoodRule rule) {
// No-op when running on a real device
}
+
+ public static void logTestRunner(String label, Description description) {
+ // No-op when running on a real device
+ }
}
diff --git a/ravenwood/list-ravenwood-tests.sh b/ravenwood/list-ravenwood-tests.sh
new file mode 100755
index 0000000..fb9b823
--- /dev/null
+++ b/ravenwood/list-ravenwood-tests.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+# 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.
+
+# List all the ravenwood test modules.
+
+jq -r 'to_entries[] | select( .value.compatibility_suites | index("ravenwood-tests") ) | .key' "$OUT/module-info.json"
diff --git a/ravenwood/mockito/Android.bp b/ravenwood/mockito/Android.bp
index 4135022..a74bca4 100644
--- a/ravenwood/mockito/Android.bp
+++ b/ravenwood/mockito/Android.bp
@@ -7,16 +7,6 @@
default_applicable_licenses: ["frameworks_base_license"],
}
-// Ravenwood tests run on the hostside, so we need mockit of the host variant.
-// But we need to use it in modules of the android variant, so we "wash" the variant with it.
-java_host_for_device {
- name: "mockito_ravenwood",
- libs: [
- "mockito",
- "objenesis",
- ],
-}
-
android_ravenwood_test {
name: "RavenwoodMockitoTest",
@@ -26,8 +16,6 @@
static_libs: [
"junit",
"truth",
-
- "mockito_ravenwood",
],
libs: [
"android.test.mock",
diff --git a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java b/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java
index 364a86a..1284d64 100644
--- a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java
+++ b/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java
@@ -22,6 +22,7 @@
import android.content.Context;
import android.content.Intent;
+import android.os.Parcel;
import android.platform.test.ravenwood.RavenwoodRule;
import org.junit.Rule;
@@ -78,4 +79,13 @@
assertThat(object.getPackageName()).isEqualTo("android");
}
+
+ @Test
+ public void testMockFinalClass() {
+ var object = mock(Parcel.class);
+
+ when(object.readInt()).thenReturn(123);
+
+ assertThat(object.readInt()).isEqualTo(123);
+ }
}
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index b775f9a..e49b64e 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -5,6 +5,7 @@
com.android.internal.logging.MetricsLogger
com.android.internal.logging.testing.FakeMetricsLogger
com.android.internal.logging.testing.UiEventLoggerFake
+com.android.internal.os.AndroidPrintStream
com.android.internal.os.BatteryStatsHistory
com.android.internal.os.BatteryStatsHistory$TraceDelegate
com.android.internal.os.BatteryStatsHistory$VarintParceler
@@ -16,6 +17,7 @@
com.android.internal.os.PowerProfile
com.android.internal.os.PowerStats
com.android.internal.os.PowerStats$Descriptor
+com.android.internal.os.RuntimeInit
com.android.internal.power.ModemPowerProfile
android.util.AtomicFile
diff --git a/ravenwood/run-ravenwood-tests.sh b/ravenwood/run-ravenwood-tests.sh
new file mode 100755
index 0000000..3f4b8a7
--- /dev/null
+++ b/ravenwood/run-ravenwood-tests.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+# 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.
+
+# Run all the ravenwood tests.
+
+# "echo" is to remove the newlines
+all_tests=$(echo $(${0%/*}/list-ravenwood-tests.sh) )
+
+echo "Running tests: $all_tests"
+atest $all_tests
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 63784ba..44144f8 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -5725,10 +5725,10 @@
}
@Override
- public void attachAccessibilityOverlayToDisplay_enforcePermission(
+ public void attachAccessibilityOverlayToDisplay(
int displayId, SurfaceControl sc) {
mContext.enforceCallingPermission(
- INTERNAL_SYSTEM_WINDOW, "attachAccessibilityOverlayToDisplay_enforcePermission");
+ INTERNAL_SYSTEM_WINDOW, "attachAccessibilityOverlayToDisplay");
mMainHandler.sendMessage(
obtainMessage(
AccessibilityManagerService::attachAccessibilityOverlayToDisplayInternal,
diff --git a/services/autofill/features.aconfig b/services/autofill/features.aconfig
index 727721d..532db12 100644
--- a/services/autofill/features.aconfig
+++ b/services/autofill/features.aconfig
@@ -12,4 +12,11 @@
namespace: "autofill"
description: "Guards against Autofill-Credman integration phase 2"
bug: "320730001"
-}
\ No newline at end of file
+}
+
+flag {
+ name: "autofill_credman_dev_integration"
+ namespace: "autofill"
+ description: "Guards against Autofill-Credman Phase1 developer integration via new APIs"
+ bug: "320730001"
+}
diff --git a/services/backup/Android.bp b/services/backup/Android.bp
index d08a97e..2a85eb6 100644
--- a/services/backup/Android.bp
+++ b/services/backup/Android.bp
@@ -21,7 +21,6 @@
libs: ["services.core"],
static_libs: [
"app-compat-annotations",
- "backup_flags_lib",
],
lint: {
baseline_filename: "lint-baseline.xml",
@@ -33,8 +32,3 @@
package: "com.android.server.backup",
srcs: ["flags.aconfig"],
}
-
-java_aconfig_library {
- name: "backup_flags_lib",
- aconfig_declarations: "backup_flags",
-}
diff --git a/services/backup/flags.aconfig b/services/backup/flags.aconfig
index 1416c88..6a63b3a 100644
--- a/services/backup/flags.aconfig
+++ b/services/backup/flags.aconfig
@@ -24,4 +24,12 @@
description: "Enables the write buffer to pipes to be of maximum size."
bug: "265976737"
is_fixed_read_only: true
+}
+
+flag {
+ name: "enable_clear_pipe_after_restore_file"
+ namespace: "onboarding"
+ description: "Enables clearing the pipe buffer after restoring a single file to a BackupAgent."
+ bug: "320633449"
+ is_fixed_read_only: true
}
\ No newline at end of file
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index 0559708..d85dd87 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -732,6 +732,7 @@
BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
monitoringExtras);
EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
+ mStatus = BackupTransport.TRANSPORT_ERROR;
nextState = UnifiedRestoreState.FINAL;
} finally {
executeNextState(nextState);
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 8e35b74..89896c3 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -211,7 +211,6 @@
"com_android_wm_shell_flags_lib",
"com.android.server.utils_aconfig-java",
"service-jobscheduler-deviceidle.flags-aconfig-java",
- "backup_flags_lib",
"policy_flags_lib",
"net_flags_lib",
"stats_flags_lib",
diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java
index a1e6b58f..81c9ee7 100644
--- a/services/core/java/com/android/server/IntentResolver.java
+++ b/services/core/java/com/android/server/IntentResolver.java
@@ -81,7 +81,7 @@
* Returns whether an intent matches the IntentFilter with a pre-resolved type.
*/
public static boolean intentMatchesFilter(
- IntentFilter filter, Intent intent, String resolvedType, boolean defaultOnly) {
+ IntentFilter filter, Intent intent, String resolvedType) {
final boolean debug = localLOGV
|| ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
@@ -97,10 +97,6 @@
int match = filter.match(intent.getAction(), resolvedType, intent.getScheme(),
intent.getData(), intent.getCategories(), TAG);
- if (match >= 0 && defaultOnly && !filter.hasCategory(Intent.CATEGORY_DEFAULT)) {
- match = IntentFilter.NO_MATCH_CATEGORY;
- }
-
if (match >= 0) {
if (debug) {
Slog.v(TAG, "Filter matched! match=0x" + Integer.toHexString(match));
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index cd8be33..82d9377 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -17,6 +17,7 @@
package com.android.server;
import static android.app.Flags.modesApi;
+import static android.app.Flags.enableNightModeCache;
import static android.app.UiModeManager.ContrastUtils.CONTRAST_DEFAULT_VALUE;
import static android.app.UiModeManager.DEFAULT_PRIORITY;
import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
@@ -135,7 +136,23 @@
private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
- private int mNightMode = UiModeManager.MODE_NIGHT_NO;
+
+ private final NightMode mNightMode = new NightMode(){
+ private int mNightModeValue = UiModeManager.MODE_NIGHT_NO;
+
+ @Override
+ public int get() {
+ return mNightModeValue;
+ }
+
+ @Override
+ public void set(int mode) {
+ mNightModeValue = mode;
+ if (enableNightModeCache()) {
+ UiModeManager.invalidateNightModeCache();
+ }
+ }
+ };
private int mNightModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
private int mAttentionModeThemeOverlay = UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
private final LocalTime DEFAULT_CUSTOM_NIGHT_START_TIME = LocalTime.of(22, 0);
@@ -297,7 +314,7 @@
@Override
public void onTwilightStateChanged(@Nullable TwilightState state) {
synchronized (mLock) {
- if (mNightMode == UiModeManager.MODE_NIGHT_AUTO && mSystemReady) {
+ if (mNightMode.get() == UiModeManager.MODE_NIGHT_AUTO && mSystemReady) {
if (shouldApplyAutomaticChangesImmediately()) {
updateLocked(0, 0);
} else {
@@ -392,7 +409,7 @@
private void updateSystemProperties() {
int mode = Secure.getIntForUser(getContext().getContentResolver(), Secure.UI_NIGHT_MODE,
- mNightMode, 0);
+ mNightMode.get(), 0);
if (mode == MODE_NIGHT_AUTO || mode == MODE_NIGHT_CUSTOM) {
mode = MODE_NIGHT_YES;
}
@@ -412,7 +429,7 @@
@Override
public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
mCurrentUser = to.getUserIdentifier();
- if (mNightMode == MODE_NIGHT_AUTO) persistComputedNightMode(from.getUserIdentifier());
+ if (mNightMode.get() == MODE_NIGHT_AUTO) persistComputedNightMode(from.getUserIdentifier());
getContext().getContentResolver().unregisterContentObserver(mSetupWizardObserver);
verifySetupWizardCompleted();
synchronized (mLock) {
@@ -471,12 +488,12 @@
verifySetupWizardCompleted();
final Resources res = context.getResources();
+ mNightMode.set(res.getInteger(
+ com.android.internal.R.integer.config_defaultNightMode));
mStartDreamImmediatelyOnDock = res.getBoolean(
com.android.internal.R.bool.config_startDreamImmediatelyOnDock);
mDreamsDisabledByAmbientModeSuppression = res.getBoolean(
com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig);
- mNightMode = res.getInteger(
- com.android.internal.R.integer.config_defaultNightMode);
mDefaultUiModeType = res.getInteger(
com.android.internal.R.integer.config_defaultUiModeType);
mCarModeKeepsScreenOn = (res.getInteger(
@@ -510,7 +527,7 @@
private final BroadcastReceiver mOnShutdown = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (mNightMode == MODE_NIGHT_AUTO) {
+ if (mNightMode.get() == MODE_NIGHT_AUTO) {
persistComputedNightMode(mCurrentUser);
}
}
@@ -585,7 +602,7 @@
}
private void updateCustomTimeLocked() {
- if (mNightMode != MODE_NIGHT_CUSTOM) return;
+ if (mNightMode.get() != MODE_NIGHT_CUSTOM) return;
if (shouldApplyAutomaticChangesImmediately()) {
updateLocked(0, 0);
} else {
@@ -606,9 +623,9 @@
return;
}
if (mSetupWizardComplete) {
- mNightMode = Secure.getIntForUser(context.getContentResolver(),
+ mNightMode.set(Secure.getIntForUser(context.getContentResolver(),
Secure.UI_NIGHT_MODE, res.getInteger(
- com.android.internal.R.integer.config_defaultNightMode), userId);
+ com.android.internal.R.integer.config_defaultNightMode), userId));
mNightModeCustomType = Secure.getIntForUser(context.getContentResolver(),
Secure.UI_NIGHT_MODE_CUSTOM_TYPE, MODE_NIGHT_CUSTOM_TYPE_UNKNOWN, userId);
mOverrideNightModeOn = Secure.getIntForUser(context.getContentResolver(),
@@ -623,7 +640,7 @@
Secure.getLongForUser(context.getContentResolver(),
Secure.DARK_THEME_CUSTOM_END_TIME,
DEFAULT_CUSTOM_NIGHT_END_TIME.toNanoOfDay() / 1000L, userId) * 1000);
- if (mNightMode == MODE_NIGHT_AUTO) {
+ if (mNightMode.get() == MODE_NIGHT_AUTO) {
mComputedNightMode = Secure.getIntForUser(context.getContentResolver(),
Secure.UI_NIGHT_MODE_LAST_COMPUTED, 0, userId) != 0;
}
@@ -834,21 +851,23 @@
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- if (mNightMode != mode || mNightModeCustomType != customModeType) {
- if (mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM) {
+ if (mNightMode.get() != mode || mNightModeCustomType != customModeType) {
+ if (mNightMode.get() == MODE_NIGHT_AUTO
+ || mNightMode.get() == MODE_NIGHT_CUSTOM) {
unregisterDeviceInactiveListenerLocked();
cancelCustomAlarm();
}
mNightModeCustomType = mode == MODE_NIGHT_CUSTOM
? customModeType
: MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
- mNightMode = mode;
+ mNightMode.set(mode);
//deactivates AttentionMode if user toggles DarkTheme
mAttentionModeThemeOverlay = MODE_ATTENTION_THEME_OVERLAY_OFF;
resetNightModeOverrideLocked();
persistNightMode(user);
// on screen off will update configuration instead
- if ((mNightMode != MODE_NIGHT_AUTO && mNightMode != MODE_NIGHT_CUSTOM)
+ if ((mNightMode.get() != MODE_NIGHT_AUTO
+ && mNightMode.get() != MODE_NIGHT_CUSTOM)
|| shouldApplyAutomaticChangesImmediately()) {
unregisterDeviceInactiveListenerLocked();
updateLocked(0, 0);
@@ -865,7 +884,7 @@
@Override
public int getNightMode() {
synchronized (mLock) {
- return mNightMode;
+ return mNightMode.get();
}
}
@@ -999,18 +1018,19 @@
synchronized (mLock) {
final long ident = Binder.clearCallingIdentity();
try {
- if (mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM) {
+ if (mNightMode.get() == MODE_NIGHT_AUTO
+ || mNightMode.get() == MODE_NIGHT_CUSTOM) {
unregisterDeviceInactiveListenerLocked();
mOverrideNightModeOff = !active;
mOverrideNightModeOn = active;
mOverrideNightModeUser = mCurrentUser;
persistNightModeOverrides(mCurrentUser);
- } else if (mNightMode == UiModeManager.MODE_NIGHT_NO
+ } else if (mNightMode.get() == UiModeManager.MODE_NIGHT_NO
&& active) {
- mNightMode = UiModeManager.MODE_NIGHT_YES;
- } else if (mNightMode == UiModeManager.MODE_NIGHT_YES
+ mNightMode.set(UiModeManager.MODE_NIGHT_YES);
+ } else if (mNightMode.get() == UiModeManager.MODE_NIGHT_YES
&& !active) {
- mNightMode = UiModeManager.MODE_NIGHT_NO;
+ mNightMode.set(UiModeManager.MODE_NIGHT_NO);
}
updateConfigurationLocked();
applyConfigurationExternallyLocked();
@@ -1414,7 +1434,7 @@
private void onCustomTimeUpdated(int user) {
persistNightMode(user);
- if (mNightMode != MODE_NIGHT_CUSTOM) return;
+ if (mNightMode.get() != MODE_NIGHT_CUSTOM) return;
if (shouldApplyAutomaticChangesImmediately()) {
unregisterDeviceInactiveListenerLocked();
updateLocked(0, 0);
@@ -1431,8 +1451,8 @@
pw.print(" mStartDreamImmediatelyOnDock="); pw.print(mStartDreamImmediatelyOnDock);
- pw.print(" mNightMode="); pw.print(mNightMode); pw.print(" (");
- pw.print(Shell.nightModeToStr(mNightMode, mNightModeCustomType)); pw.print(") ");
+ pw.print(" mNightMode="); pw.print(mNightMode.get()); pw.print(" (");
+ pw.print(Shell.nightModeToStr(mNightMode.get(), mNightModeCustomType)); pw.print(") ");
pw.print(" mOverrideOn/Off="); pw.print(mOverrideNightModeOn);
pw.print("/"); pw.print(mOverrideNightModeOff);
pw.print(" mAttentionModeThemeOverlay="); pw.print(mAttentionModeThemeOverlay);
@@ -1623,7 +1643,7 @@
// Only persist setting if not in car mode
if (mCarModeEnabled || mCar) return;
Secure.putIntForUser(getContext().getContentResolver(),
- Secure.UI_NIGHT_MODE, mNightMode, user);
+ Secure.UI_NIGHT_MODE, mNightMode.get(), user);
Secure.putLongForUser(getContext().getContentResolver(),
Secure.UI_NIGHT_MODE_CUSTOM_TYPE, mNightModeCustomType, user);
Secure.putLongForUser(getContext().getContentResolver(),
@@ -1659,11 +1679,11 @@
uiMode = Configuration.UI_MODE_TYPE_VR_HEADSET;
}
- if (mNightMode == MODE_NIGHT_YES || mNightMode == UiModeManager.MODE_NIGHT_NO) {
- updateComputedNightModeLocked(mNightMode == MODE_NIGHT_YES);
+ if (mNightMode.get() == MODE_NIGHT_YES || mNightMode.get() == UiModeManager.MODE_NIGHT_NO) {
+ updateComputedNightModeLocked(mNightMode.get() == MODE_NIGHT_YES);
}
- if (mNightMode == MODE_NIGHT_AUTO) {
+ if (mNightMode.get() == MODE_NIGHT_AUTO) {
boolean activateNightMode = mComputedNightMode;
if (mTwilightManager != null) {
mTwilightManager.registerListener(mTwilightListener, mHandler);
@@ -1677,7 +1697,7 @@
}
}
- if (mNightMode == MODE_NIGHT_CUSTOM) {
+ if (mNightMode.get() == MODE_NIGHT_CUSTOM) {
if (mNightModeCustomType == MODE_NIGHT_CUSTOM_TYPE_BEDTIME) {
updateComputedNightModeLocked(mLastBedtimeRequestedNightMode);
} else {
@@ -2010,7 +2030,7 @@
private void updateComputedNightModeLocked(boolean activate) {
boolean newComputedValue = activate;
- if (mNightMode != MODE_NIGHT_YES && mNightMode != UiModeManager.MODE_NIGHT_NO) {
+ if (mNightMode.get() != MODE_NIGHT_YES && mNightMode.get() != UiModeManager.MODE_NIGHT_NO) {
if (mOverrideNightModeOn && !newComputedValue) {
newComputedValue = true;
} else if (mOverrideNightModeOff && newComputedValue) {
@@ -2029,7 +2049,7 @@
mComputedNightMode = newComputedValue;
}
- if (mNightMode != MODE_NIGHT_AUTO || (mTwilightManager != null
+ if (mNightMode.get() != MODE_NIGHT_AUTO || (mTwilightManager != null
&& mTwilightManager.getLastTwilightState() != null)) {
resetNightModeOverrideLocked();
}
@@ -2279,4 +2299,13 @@
Sandman.startDreamWhenDockedIfAppropriate(context);
}
}
+
+ /**
+ * Interface to contain the value for system night mode. We make the night mode accessible
+ * through this class to ensure that the reassignment of this value invalidates the cache.
+ */
+ private interface NightMode {
+ int get();
+ void set(int mode);
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 374a17a..a5531ae 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2101,7 +2101,8 @@
// Start watching app ops after we and the package manager are up and running.
mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_IN_BACKGROUND, null,
new IAppOpsCallback.Stub() {
- @Override public void opChanged(int op, int uid, String packageName) {
+ @Override public void opChanged(int op, int uid, String packageName,
+ String persistentDeviceId) {
if (op == AppOpsManager.OP_RUN_IN_BACKGROUND && packageName != null) {
if (getAppOpsManager().checkOpNoThrow(op, uid, packageName)
!= AppOpsManager.MODE_ALLOWED) {
@@ -2115,7 +2116,7 @@
mAppOpsService.startWatchingActive(cameraOp, new IAppOpsActiveCallback.Stub() {
@Override
public void opActiveChanged(int op, int uid, String packageName, String attributionTag,
- boolean active, @AttributionFlags int attributionFlags,
+ int virtualDeviceId, boolean active, @AttributionFlags int attributionFlags,
int attributionChainId) {
cameraActiveChanged(uid, active);
}
diff --git a/services/core/java/com/android/server/am/AppPermissionTracker.java b/services/core/java/com/android/server/am/AppPermissionTracker.java
index 18a9153..c641b35 100644
--- a/services/core/java/com/android/server/am/AppPermissionTracker.java
+++ b/services/core/java/com/android/server/am/AppPermissionTracker.java
@@ -393,7 +393,7 @@
private class MyAppOpsCallback extends IAppOpsCallback.Stub {
@Override
- public void opChanged(int op, int uid, String packageName) {
+ public void opChanged(int op, int uid, String packageName, String persistentDeviceId) {
mHandler.obtainMessage(MyHandler.MSG_APPOPS_CHANGED, op, uid, packageName)
.sendToTarget();
}
diff --git a/services/core/java/com/android/server/am/AssistDataRequester.java b/services/core/java/com/android/server/am/AssistDataRequester.java
index 98129ed..856a15f 100644
--- a/services/core/java/com/android/server/am/AssistDataRequester.java
+++ b/services/core/java/com/android/server/am/AssistDataRequester.java
@@ -222,7 +222,7 @@
// Ensure that the current activity supports assist data
boolean isAssistDataAllowed = false;
try {
- isAssistDataAllowed = mActivityTaskManager.isAssistDataAllowedOnCurrentActivity();
+ isAssistDataAllowed = mActivityTaskManager.isAssistDataAllowed();
} catch (RemoteException e) {
// Should never happen
}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 7aafda5..1207616 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -162,6 +162,7 @@
"nfc",
"pdf_viewer",
"pixel_audio_android",
+ "pixel_biometrics_face",
"pixel_bluetooth",
"pixel_connectivity_gps",
"pixel_system_sw_video",
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
index 0916967..9535db8 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
@@ -220,7 +220,7 @@
}
for (int i = 0; i < listenersCopy.size(); i++) {
- listenersCopy.get(i).onUidModeChanged(uid, op, mode);
+ listenersCopy.get(i).onUidModeChanged(uid, op, mode, persistentDeviceId);
}
return true;
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
index f056f6b..8b4ea67 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
@@ -176,8 +176,9 @@
* @param uid The UID whose appop mode was changed.
* @param code The op code that was changed.
* @param mode The new mode.
+ * @param persistentDeviceId the device whose mode was changed
*/
- void onUidModeChanged(int uid, int code, int mode);
+ void onUidModeChanged(int uid, int code, int mode, String persistentDeviceId);
/**
* Invoked when a package's appop mode is changed.
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 2ed217a..5c95d43 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -166,6 +166,7 @@
import com.android.server.LockGuard;
import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemServiceManager;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.pm.PackageList;
import com.android.server.pm.PackageManagerLocal;
import com.android.server.pm.UserManagerInternal;
@@ -275,6 +276,10 @@
= new AppOpsManagerInternalImpl();
@Nullable private final DevicePolicyManagerInternal dpmi =
LocalServices.getService(DevicePolicyManagerInternal.class);
+ @Nullable private VirtualDeviceManagerInternal mVirtualDeviceManagerInternal;
+
+ /** Map of virtual device id -> persistent device id. */
+ private final SparseArray<String> mKnownDeviceIds = new SparseArray<>();
private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface(
ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
@@ -595,45 +600,71 @@
final UidState uidState;
final @NonNull String packageName;
- /** attributionTag -> AttributedOp */
- final ArrayMap<String, AttributedOp> mAttributions = new ArrayMap<>(1);
+ /**
+ * Map to retrieve {@link AttributedOp} for a particular device and attribution tag.
+ *
+ * ArrayMap<Persistent Device Id, ArrayMap<Attribution Tag, AttributedOp>>
+ */
+ final ArrayMap<String, ArrayMap<String, AttributedOp>> mDeviceAttributedOps =
+ new ArrayMap<String, ArrayMap<String, AttributedOp>>(1);
Op(UidState uidState, String packageName, int op, int uid) {
this.op = op;
this.uid = uid;
this.uidState = uidState;
this.packageName = packageName;
+ // We keep an invariant that the persistent device will always have an entry in
+ // mDeviceAttributedOps.
+ mDeviceAttributedOps.put(PERSISTENT_DEVICE_ID_DEFAULT,
+ new ArrayMap<String, AttributedOp>());
}
void removeAttributionsWithNoTime() {
- for (int i = mAttributions.size() - 1; i >= 0; i--) {
- if (!mAttributions.valueAt(i).hasAnyTime()) {
- mAttributions.removeAt(i);
+ for (int deviceIndex = mDeviceAttributedOps.size() - 1; deviceIndex >= 0;
+ deviceIndex--) {
+ ArrayMap<String, AttributedOp> attributedOps = mDeviceAttributedOps.valueAt(
+ deviceIndex);
+ for (int tagIndex = attributedOps.size() - 1; tagIndex >= 0; tagIndex--) {
+ if (!attributedOps.valueAt(tagIndex).hasAnyTime()) {
+ attributedOps.removeAt(tagIndex);
+ }
+ }
+ if (!Objects.equals(PERSISTENT_DEVICE_ID_DEFAULT,
+ mDeviceAttributedOps.keyAt(deviceIndex)) && attributedOps.isEmpty()) {
+ mDeviceAttributedOps.removeAt(deviceIndex);
}
}
}
private @NonNull AttributedOp getOrCreateAttribution(@NonNull Op parent,
- @Nullable String attributionTag) {
- AttributedOp attributedOp;
+ @Nullable String attributionTag, String persistentDeviceId) {
+ ArrayMap<String, AttributedOp> attributedOps = mDeviceAttributedOps.get(
+ persistentDeviceId);
+ if (attributedOps == null) {
+ attributedOps = new ArrayMap<>();
+ mDeviceAttributedOps.put(persistentDeviceId, attributedOps);
+ }
+ AttributedOp attributedOp = attributedOps.get(attributionTag);
- attributedOp = mAttributions.get(attributionTag);
if (attributedOp == null) {
- attributedOp = new AttributedOp(AppOpsService.this, attributionTag, parent);
- mAttributions.put(attributionTag, attributedOp);
+ attributedOp = new AttributedOp(AppOpsService.this, attributionTag,
+ persistentDeviceId, parent);
+ attributedOps.put(attributionTag, attributedOp);
}
return attributedOp;
}
@NonNull OpEntry createEntryLocked() {
- final int numAttributions = mAttributions.size();
-
+ // TODO(b/308201969): Update this method when we introduce disk persistence of events
+ // for accesses on external devices.
+ final ArrayMap<String, AttributedOp> attributedOps = mDeviceAttributedOps.get(
+ PERSISTENT_DEVICE_ID_DEFAULT);
final ArrayMap<String, AppOpsManager.AttributedOpEntry> attributionEntries =
- new ArrayMap<>(numAttributions);
- for (int i = 0; i < numAttributions; i++) {
- attributionEntries.put(mAttributions.keyAt(i),
- mAttributions.valueAt(i).createAttributedOpEntryLocked());
+ new ArrayMap<>(attributedOps.size());
+ for (int i = 0; i < attributedOps.size(); i++) {
+ attributionEntries.put(attributedOps.keyAt(i),
+ attributedOps.valueAt(i).createAttributedOpEntryLocked());
}
return new OpEntry(
@@ -644,17 +675,15 @@
}
@NonNull OpEntry createSingleAttributionEntryLocked(@Nullable String attributionTag) {
- final int numAttributions = mAttributions.size();
-
+ // TODO(b/308201969): Update this method when we introduce disk persistence of events
+ // for accesses on external devices.
+ final ArrayMap<String, AttributedOp> attributedOps = mDeviceAttributedOps.get(
+ PERSISTENT_DEVICE_ID_DEFAULT);
final ArrayMap<String, AttributedOpEntry> attributionEntries = new ArrayMap<>(1);
- for (int i = 0; i < numAttributions; i++) {
- if (Objects.equals(mAttributions.keyAt(i), attributionTag)) {
- attributionEntries.put(mAttributions.keyAt(i),
- mAttributions.valueAt(i).createAttributedOpEntryLocked());
- break;
- }
+ if (attributedOps.get(attributionTag) != null) {
+ attributionEntries.put(attributionTag,
+ attributedOps.get(attributionTag).createAttributedOpEntryLocked());
}
-
return new OpEntry(
op,
mAppOpsCheckingService.getPackageMode(
@@ -663,13 +692,15 @@
}
boolean isRunning() {
- final int numAttributions = mAttributions.size();
- for (int i = 0; i < numAttributions; i++) {
- if (mAttributions.valueAt(i).isRunning()) {
- return true;
+ for (int deviceIndex = 0; deviceIndex < mDeviceAttributedOps.size(); deviceIndex++) {
+ ArrayMap<String, AttributedOp> attributedOps = mDeviceAttributedOps.valueAt(
+ deviceIndex);
+ for (int tagIndex = 0; tagIndex < attributedOps.size(); tagIndex++) {
+ if (attributedOps.valueAt(tagIndex).isRunning()) {
+ return true;
+ }
}
}
-
return false;
}
}
@@ -738,7 +769,15 @@
@Override
public void onOpModeChanged(int op, int uid, String packageName) throws RemoteException {
- mCallback.opChanged(op, uid, packageName);
+ throw new IllegalStateException(
+ "unimplemented onOpModeChanged method called for op: " + op + " uid: " + uid
+ + " packageName: " + packageName);
+ }
+
+ @Override
+ public void onOpModeChanged(int op, int uid, String packageName, String persistentDeviceId)
+ throws RemoteException {
+ mCallback.opChanged(op, uid, packageName, persistentDeviceId);
}
}
@@ -910,6 +949,7 @@
public AppOpsService(File recentAccessesFile, File storageFile, Handler handler,
Context context) {
mContext = context;
+ mKnownDeviceIds.put(Context.DEVICE_ID_DEFAULT, PERSISTENT_DEVICE_ID_DEFAULT);
for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) {
int switchCode = AppOpsManager.opToSwitch(switchedCode);
@@ -927,10 +967,11 @@
mAppOpsCheckingService.addAppOpsModeChangedListener(
new AppOpsCheckingServiceInterface.AppOpsModeChangedListener() {
@Override
- public void onUidModeChanged(int uid, int code, int mode) {
+ public void onUidModeChanged(int uid, int code, int mode,
+ String persistentDeviceId) {
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpChangedForAllPkgsInUid, AppOpsService.this,
- code, uid, false));
+ code, uid, false, persistentDeviceId));
}
@Override
@@ -941,8 +982,9 @@
packageName, code, mode, userId));
}
});
+ // Only notify default device as other devices are unaffected by restriction changes.
mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler,
- code -> notifyWatchersOfChange(code, UID_ANY));
+ code -> notifyWatchersOnDefaultDevice(code, UID_ANY));
LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
mStorageFile = new AtomicFile(storageFile, "appops_legacy");
@@ -1043,25 +1085,28 @@
int numOps = ops.size();
for (int opNum = 0; opNum < numOps; opNum++) {
Op op = ops.valueAt(opNum);
+ for (int deviceIndex = op.mDeviceAttributedOps.size() - 1; deviceIndex >= 0;
+ deviceIndex--) {
+ ArrayMap<String, AttributedOp> attributedOps =
+ op.mDeviceAttributedOps.valueAt(deviceIndex);
+ for (int tagIndex = attributedOps.size() - 1; tagIndex >= 0;
+ tagIndex--) {
+ String tag = attributedOps.keyAt(tagIndex);
+ if (attributionTags.contains(tag)) {
+ // attribution still exist after upgrade
+ continue;
+ }
- int numAttributions = op.mAttributions.size();
- for (int attributionNum = numAttributions - 1; attributionNum >= 0;
- attributionNum--) {
- String attributionTag = op.mAttributions.keyAt(attributionNum);
+ String newAttributionTag = dstAttributionTags.get(tag);
- if (attributionTags.contains(attributionTag)) {
- // attribution still exist after upgrade
- continue;
+ AttributedOp newAttributedOp = op.getOrCreateAttribution(op,
+ newAttributionTag,
+ op.mDeviceAttributedOps.keyAt(deviceIndex));
+ newAttributedOp.add(attributedOps.get(tag));
+ attributedOps.remove(tag);
+
+ scheduleFastWriteLocked();
}
-
- String newAttributionTag = dstAttributionTags.get(attributionTag);
-
- AttributedOp newAttributedOp = op.getOrCreateAttribution(op,
- newAttributionTag);
- newAttributedOp.add(op.mAttributions.valueAt(attributionNum));
- op.mAttributions.removeAt(attributionNum);
-
- scheduleFastWriteLocked();
}
}
}
@@ -1070,6 +1115,8 @@
};
public void systemReady() {
+ mVirtualDeviceManagerInternal = LocalServices.getService(
+ VirtualDeviceManagerInternal.class);
mAppOpsCheckingService.systemReady();
initializeUidStates();
@@ -1144,7 +1191,17 @@
final String changedPkg = changedPkgs[i];
// We trust packagemanager to insert matching uid and packageNames in the
// extras
- notifyOpChanged(onModeChangedListeners, code, changedUid, changedPkg);
+ Set<String> devices;
+ if (mVirtualDeviceManagerInternal != null) {
+ devices = mVirtualDeviceManagerInternal.getAllPersistentDeviceIds();
+ } else {
+ devices = new ArraySet<>();
+ devices.add(PERSISTENT_DEVICE_ID_DEFAULT);
+ }
+ for (String device: devices) {
+ notifyOpChanged(onModeChangedListeners, code, changedUid, changedPkg,
+ device);
+ }
}
}
}
@@ -1295,17 +1352,19 @@
final int numOps = removedOps.size();
for (int opNum = 0; opNum < numOps; opNum++) {
final Op op = removedOps.valueAt(opNum);
+ for (int deviceIndex = 0; deviceIndex < op.mDeviceAttributedOps.size();
+ deviceIndex++) {
+ ArrayMap<String, AttributedOp> attributedOps =
+ op.mDeviceAttributedOps.valueAt(deviceIndex);
+ for (int tagIndex = 0; tagIndex < attributedOps.size(); tagIndex++) {
+ AttributedOp attributedOp = attributedOps.valueAt(tagIndex);
- final int numAttributions = op.mAttributions.size();
- for (int attributionNum = 0; attributionNum < numAttributions;
- attributionNum++) {
- AttributedOp attributedOp = op.mAttributions.valueAt(attributionNum);
-
- while (attributedOp.isRunning()) {
- attributedOp.finished(attributedOp.mInProgressEvents.keyAt(0));
- }
- while (attributedOp.isPaused()) {
- attributedOp.finished(attributedOp.mPausedInProgressEvents.keyAt(0));
+ while (attributedOp.isRunning()) {
+ attributedOp.finished(attributedOp.mInProgressEvents.keyAt(0));
+ }
+ while (attributedOp.isPaused()) {
+ attributedOp.finished(attributedOp.mPausedInProgressEvents.keyAt(0));
+ }
}
}
}
@@ -1380,7 +1439,7 @@
== AppOpsManager.MODE_FOREGROUND) {
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpChangedForAllPkgsInUid,
- this, code, uidState.uid, true));
+ this, code, uidState.uid, true, PERSISTENT_DEVICE_ID_DEFAULT));
} else if (!uidState.pkgOps.isEmpty()) {
final ArraySet<OnOpModeChangedListener> listenerSet =
mOpModeWatchers.get(code);
@@ -1405,7 +1464,8 @@
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpChanged,
this, listenerSet.valueAt(cbi), code, uidState.uid,
- uidState.pkgOps.keyAt(pkgi)));
+ uidState.pkgOps.keyAt(pkgi),
+ PERSISTENT_DEVICE_ID_DEFAULT));
}
}
}
@@ -1422,14 +1482,15 @@
int numOps = ops.size();
for (int opNum = 0; opNum < numOps; opNum++) {
Op op = ops.valueAt(opNum);
-
- int numAttributions = op.mAttributions.size();
- for (int attributionNum = 0; attributionNum < numAttributions;
- attributionNum++) {
- AttributedOp attributedOp = op.mAttributions.valueAt(
- attributionNum);
-
- attributedOp.onUidStateChanged(state);
+ for (int deviceIndex = 0; deviceIndex < op.mDeviceAttributedOps.size();
+ deviceIndex++) {
+ ArrayMap<String, AttributedOp> attributedOps =
+ op.mDeviceAttributedOps.valueAt(deviceIndex);
+ for (int tagIndex = 0; tagIndex < attributedOps.size();
+ tagIndex++) {
+ AttributedOp attributedOp = attributedOps.valueAt(tagIndex);
+ attributedOp.onUidStateChanged(state);
+ }
}
}
}
@@ -1774,7 +1835,7 @@
private void pruneOpLocked(Op op, int uid, String packageName) {
op.removeAttributionsWithNoTime();
- if (op.mAttributions.isEmpty()) {
+ if (op.mDeviceAttributedOps.isEmpty()) {
Ops ops = getOpsLocked(uid, packageName, null, false, null, /* edit */ false);
if (ops != null) {
ops.remove(op.op);
@@ -1870,8 +1931,10 @@
uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, code, mode)) {
return;
}
+ // TODO(b/266164193): Ensure this behavior is device-aware after uid op mode for runtime
+ // permissions is deprecated.
if (mode != MODE_ERRORED && mode != previousMode) {
- updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
+ updateStartedOpModeForUidForDefaultDeviceLocked(code, mode == MODE_IGNORED, uid);
}
}
@@ -1884,8 +1947,10 @@
* @param code The op that changed
* @param uid The uid the op was changed for
* @param onlyForeground Only notify watchers that watch for foreground changes
+ * @param persistentDeviceId device the op was changed for
*/
- private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground) {
+ private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground,
+ String persistentDeviceId) {
String[] uidPackageNames = getPackagesForUid(uid);
ArrayMap<OnOpModeChangedListener, ArraySet<String>> callbackSpecs = null;
synchronized (this) {
@@ -1951,17 +2016,17 @@
final OnOpModeChangedListener callback = callbackSpecs.keyAt(i);
final ArraySet<String> reportedPackageNames = callbackSpecs.valueAt(i);
if (reportedPackageNames == null) {
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyOpChanged,
- this, callback, code, uid, (String) null));
+ mHandler.sendMessage(
+ PooledLambda.obtainMessage(AppOpsService::notifyOpChanged, this,
+ callback, code, uid, (String) null, persistentDeviceId));
} else {
final int reportedPackageCount = reportedPackageNames.size();
for (int j = 0; j < reportedPackageCount; j++) {
final String reportedPackageName = reportedPackageNames.valueAt(j);
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyOpChanged,
- this, callback, code, uid, reportedPackageName));
+ mHandler.sendMessage(
+ PooledLambda.obtainMessage(AppOpsService::notifyOpChanged, this,
+ callback, code, uid, reportedPackageName, persistentDeviceId));
}
}
}
@@ -1999,13 +2064,17 @@
}
scheduleFastWriteLocked();
if (mode != MODE_ERRORED) {
- updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
+ // Notify on PERSISTENT_DEVICE_ID_DEFAULT only as only uid modes are device-aware,
+ // not package modes.
+ updateStartedOpModeForUidForDefaultDeviceLocked(code, mode == MODE_IGNORED, uid);
}
}
if (repCbs != null && uid != -1) {
+ // Notify on PERSISTENT_DEVICE_ID_DEFAULT only as only uid modes are device-aware, not
+ // package modes.
mHandler.sendMessage(PooledLambda.obtainMessage(AppOpsService::notifyOpChanged, this,
- repCbs, code, uid, packageName));
+ repCbs, code, uid, packageName, PERSISTENT_DEVICE_ID_DEFAULT));
}
}
@@ -2161,15 +2230,15 @@
}
private void notifyOpChanged(ArraySet<OnOpModeChangedListener> callbacks, int code,
- int uid, String packageName) {
+ int uid, String packageName, String persistentDeviceId) {
for (int i = 0; i < callbacks.size(); i++) {
final OnOpModeChangedListener callback = callbacks.valueAt(i);
- notifyOpChanged(callback, code, uid, packageName);
+ notifyOpChanged(callback, code, uid, packageName, persistentDeviceId);
}
}
private void notifyOpChanged(OnOpModeChangedListener onModeChangedListener, int code,
- int uid, String packageName) {
+ int uid, String packageName, String persistentDeviceId) {
Objects.requireNonNull(onModeChangedListener);
if (uid != UID_ANY && onModeChangedListener.getWatchingUid() >= 0
@@ -2197,7 +2266,8 @@
onModeChangedListener.getCallingUid())) {
continue;
}
- onModeChangedListener.onOpModeChanged(switchedCode, uid, packageName);
+ onModeChangedListener.onOpModeChanged(switchedCode, uid, packageName,
+ persistentDeviceId);
} catch (RemoteException e) {
/* ignore */
} finally {
@@ -2289,7 +2359,8 @@
boolean changed = false;
for (int i = mUidStates.size() - 1; i >= 0; i--) {
UidState uidState = mUidStates.valueAt(i);
- // TODO(b/299330771): Check non default modes for all devices.
+ // TODO(b/266164193): Ensure this behavior is device-aware after uid op mode for
+ // runtime permissions is deprecated.
SparseIntArray opModes =
mAppOpsCheckingService.getNonDefaultUidModes(
uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT);
@@ -2301,7 +2372,6 @@
int previousMode = opModes.valueAt(j);
int newMode = isUidOpGrantedByRole(uidState.uid, code) ? MODE_ALLOWED :
AppOpsManager.opToDefaultMode(code);
- // TODO(b/299330771): Set mode for all necessary devices.
mAppOpsCheckingService.setUidMode(
uidState.uid,
PERSISTENT_DEVICE_ID_DEFAULT,
@@ -2375,7 +2445,7 @@
allChanges = addChange(allChanges, curOp.op, uid, packageName,
previousMode);
curOp.removeAttributionsWithNoTime();
- if (curOp.mAttributions.isEmpty()) {
+ if (curOp.mDeviceAttributedOps.isEmpty()) {
pkgOps.removeAt(j);
}
}
@@ -2399,9 +2469,18 @@
ArrayList<ChangeRec> reports = ent.getValue();
for (int i=0; i<reports.size(); i++) {
ChangeRec rep = reports.get(i);
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyOpChanged,
- this, cb, rep.op, rep.uid, rep.pkg));
+ Set<String> devices;
+ if (mVirtualDeviceManagerInternal != null) {
+ devices = mVirtualDeviceManagerInternal.getAllPersistentDeviceIds();
+ } else {
+ devices = new ArraySet<>();
+ devices.add(PERSISTENT_DEVICE_ID_DEFAULT);
+ }
+ for (String device: devices) {
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsService::notifyOpChanged,
+ this, cb, rep.op, rep.uid, rep.pkg, device));
+ }
}
}
}
@@ -2595,6 +2674,12 @@
private int checkOperationImpl(int code, int uid, String packageName,
@Nullable String attributionTag, int virtualDeviceId, boolean raw) {
verifyIncomingOp(code);
+ if (!isValidVirtualDeviceId(virtualDeviceId)) {
+ Slog.w(TAG,
+ "checkOperationImpl returned MODE_IGNORED as virtualDeviceId " + virtualDeviceId
+ + " is invalid");
+ return AppOpsManager.MODE_IGNORED;
+ }
if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
return AppOpsManager.opToDefaultMode(code);
}
@@ -2603,7 +2688,8 @@
if (resolvedPackageName == null) {
return AppOpsManager.MODE_IGNORED;
}
- return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag, raw);
+ return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
+ virtualDeviceId, raw);
}
/**
@@ -2617,7 +2703,7 @@
* @return The mode of the op
*/
private @Mode int checkOperationUnchecked(int code, int uid, @NonNull String packageName,
- @Nullable String attributionTag, boolean raw) {
+ @Nullable String attributionTag, int virtualDeviceId, boolean raw) {
PackageVerificationResult pvr;
try {
pvr = verifyAndGetBypass(uid, packageName, null);
@@ -2630,19 +2716,19 @@
return AppOpsManager.MODE_IGNORED;
}
synchronized (this) {
- if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, true)) {
+ if (isOpRestrictedLocked(uid, code, packageName, attributionTag, virtualDeviceId,
+ pvr.bypass, true)) {
return AppOpsManager.MODE_IGNORED;
}
code = AppOpsManager.opToSwitch(code);
UidState uidState = getUidStateLocked(uid, false);
- // TODO(b/299330771): Check mode for the relevant device.
if (uidState != null
&& mAppOpsCheckingService.getUidMode(
- uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, code)
+ uidState.uid, getPersistentId(virtualDeviceId), code)
!= AppOpsManager.opToDefaultMode(code)) {
final int rawMode =
mAppOpsCheckingService.getUidMode(
- uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, code);
+ uidState.uid, getPersistentId(virtualDeviceId), code);
return raw ? rawMode : uidState.evalMode(code, rawMode);
}
Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ false);
@@ -2683,8 +2769,9 @@
mAudioRestrictionManager.setZenModeAudioRestriction(
code, usage, uid, mode, exceptionPackages);
+ // Only notify default device as other devices are unaffected by restriction changes.
mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyWatchersOfChange, this, code, UID_ANY));
+ AppOpsService::notifyWatchersOnDefaultDevice, this, code, UID_ANY));
}
@@ -2694,11 +2781,12 @@
mAudioRestrictionManager.setCameraAudioRestriction(mode);
+ // Only notify default device as other devices are unaffected by restriction changes.
mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyWatchersOfChange, this,
+ AppOpsService::notifyWatchersOnDefaultDevice, this,
AppOpsManager.OP_PLAY_AUDIO, UID_ANY));
mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyWatchersOfChange, this,
+ AppOpsService::notifyWatchersOnDefaultDevice, this,
AppOpsManager.OP_VIBRATE, UID_ANY));
}
@@ -2761,11 +2849,18 @@
final String proxyPackageName = attributionSource.getPackageName();
final String proxyAttributionTag = attributionSource.getAttributionTag();
final int proxiedUid = attributionSource.getNextUid();
+ final int proxyVirtualDeviceId = attributionSource.getDeviceId();
final String proxiedPackageName = attributionSource.getNextPackageName();
final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
verifyIncomingProxyUid(attributionSource);
verifyIncomingOp(code);
+ if (!isValidVirtualDeviceId(proxyVirtualDeviceId)) {
+ Slog.w(TAG, "noteProxyOperationImpl returned MODE_IGNORED as virtualDeviceId "
+ + proxyVirtualDeviceId + " is invalid");
+ return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag,
+ proxiedPackageName);
+ }
if (!isIncomingPackageValid(proxiedPackageName, UserHandle.getUserId(proxiedUid))
|| !isIncomingPackageValid(proxyPackageName, UserHandle.getUserId(proxyUid))) {
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, proxiedAttributionTag,
@@ -2792,8 +2887,9 @@
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
final SyncNotedAppOp proxyReturn = noteOperationUnchecked(code, proxyUid,
- resolveProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
- proxyFlags, !isProxyTrusted, "proxy " + message, shouldCollectMessage);
+ resolveProxyPackageName, proxyAttributionTag, proxyVirtualDeviceId,
+ Process.INVALID_UID, null, null, proxyFlags, !isProxyTrusted,
+ "proxy " + message, shouldCollectMessage);
if (proxyReturn.getOpMode() != AppOpsManager.MODE_ALLOWED) {
return new SyncNotedAppOp(proxyReturn.getOpMode(), code, proxiedAttributionTag,
proxiedPackageName);
@@ -2810,8 +2906,9 @@
final int proxiedFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXIED
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED;
return noteOperationUnchecked(code, proxiedUid, resolveProxiedPackageName,
- proxiedAttributionTag, proxyUid, resolveProxyPackageName, proxyAttributionTag,
- proxiedFlags, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+ proxiedAttributionTag, proxyVirtualDeviceId, proxyUid, resolveProxyPackageName,
+ proxyAttributionTag, proxiedFlags, shouldCollectAsyncNotedOp, message,
+ shouldCollectMessage);
}
@Override
@@ -2838,6 +2935,13 @@
boolean shouldCollectMessage) {
verifyIncomingUid(uid);
verifyIncomingOp(code);
+ if (!isValidVirtualDeviceId(virtualDeviceId)) {
+ Slog.w(TAG,
+ "checkOperationImpl returned MODE_IGNORED as virtualDeviceId " + virtualDeviceId
+ + " is invalid");
+ return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
+ packageName);
+ }
if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
// TODO(b/302609140): Remove extra logging after this issue is diagnosed.
if (code == OP_BLUETOOTH_CONNECT) {
@@ -2854,15 +2958,16 @@
packageName);
}
return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
- Process.INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF,
- shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+ virtualDeviceId, Process.INVALID_UID, null, null,
+ AppOpsManager.OP_FLAG_SELF, shouldCollectAsyncNotedOp, message,
+ shouldCollectMessage);
}
private SyncNotedAppOp noteOperationUnchecked(int code, int uid, @NonNull String packageName,
- @Nullable String attributionTag, int proxyUid, String proxyPackageName,
- @Nullable String proxyAttributionTag, @OpFlags int flags,
- boolean shouldCollectAsyncNotedOp, @Nullable String message,
- boolean shouldCollectMessage) {
+ @Nullable String attributionTag, int virtualDeviceId, int proxyUid,
+ String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags,
+ boolean shouldCollectAsyncNotedOp, @Nullable String message,
+ boolean shouldCollectMessage) {
PackageVerificationResult pvr;
try {
pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
@@ -2891,8 +2996,8 @@
final Ops ops = getOpsLocked(uid, packageName, attributionTag,
pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
if (ops == null) {
- scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- AppOpsManager.MODE_IGNORED);
+ scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag,
+ virtualDeviceId, flags, AppOpsManager.MODE_IGNORED);
if (DEBUG) Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
+ " package " + packageName + "flags: " +
AppOpsManager.flagsToString(flags));
@@ -2911,7 +3016,8 @@
packageName);
}
final Op op = getOpLocked(ops, code, uid, true);
- final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
+ final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag,
+ getPersistentId(virtualDeviceId));
if (attributedOp.isRunning()) {
Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code "
+ code + " startTime of in progress event="
@@ -2920,33 +3026,33 @@
final int switchCode = AppOpsManager.opToSwitch(code);
final UidState uidState = ops.uidState;
- if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, false)) {
+ if (isOpRestrictedLocked(uid, code, packageName, attributionTag, virtualDeviceId,
+ pvr.bypass, false)) {
attributedOp.rejected(uidState.getState(), flags);
- scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- AppOpsManager.MODE_IGNORED);
+ scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag,
+ virtualDeviceId, flags, AppOpsManager.MODE_IGNORED);
return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
packageName);
}
- // TODO(b/299330771): Check mode for the relevant device.
// If there is a non-default per UID policy (we set UID op mode only if
// non-default) it takes over, otherwise use the per package policy.
if (mAppOpsCheckingService.getUidMode(
- uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, switchCode)
+ uidState.uid, getPersistentId(virtualDeviceId), switchCode)
!= AppOpsManager.opToDefaultMode(switchCode)) {
final int uidMode =
uidState.evalMode(
code,
mAppOpsCheckingService.getUidMode(
uidState.uid,
- PERSISTENT_DEVICE_ID_DEFAULT,
+ getPersistentId(virtualDeviceId),
switchCode));
if (uidMode != AppOpsManager.MODE_ALLOWED) {
if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
+ switchCode + " (" + code + ") uid " + uid + " package "
+ packageName + " flags: " + AppOpsManager.flagsToString(flags));
attributedOp.rejected(uidState.getState(), flags);
- scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- uidMode);
+ scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag,
+ virtualDeviceId, flags, uidMode);
// TODO(b/302609140): Remove extra logging after this issue is diagnosed.
if (code == OP_BLUETOOTH_CONNECT && uidMode == MODE_ERRORED) {
Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
@@ -2969,8 +3075,8 @@
+ switchCode + " (" + code + ") uid " + uid + " package "
+ packageName + " flags: " + AppOpsManager.flagsToString(flags));
attributedOp.rejected(uidState.getState(), flags);
- scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- mode);
+ scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag,
+ virtualDeviceId, flags, mode);
// TODO(b/302609140): Remove extra logging after this issue is diagnosed.
if (code == OP_BLUETOOTH_CONNECT && mode == MODE_ERRORED) {
Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
@@ -2986,11 +3092,10 @@
: "." + attributionTag) + " flags: "
+ AppOpsManager.flagsToString(flags));
}
- scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- AppOpsManager.MODE_ALLOWED);
+ scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag,
+ virtualDeviceId, flags, AppOpsManager.MODE_ALLOWED);
attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag,
- uidState.getState(),
- flags);
+ uidState.getState(), flags);
if (shouldCollectAsyncNotedOp) {
collectAsyncNotedOp(uid, packageName, code, attributionTag, flags, message,
@@ -3314,6 +3419,13 @@
int attributionChainId) {
verifyIncomingUid(uid);
verifyIncomingOp(code);
+ if (!isValidVirtualDeviceId(virtualDeviceId)) {
+ Slog.w(TAG,
+ "startOperationImpl returned MODE_IGNORED as virtualDeviceId " + virtualDeviceId
+ + " is invalid");
+ return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
+ packageName);
+ }
if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
@@ -3349,9 +3461,9 @@
}
return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
- Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault,
- shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags,
- attributionChainId);
+ virtualDeviceId, Process.INVALID_UID, null, null, OP_FLAG_SELF,
+ startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+ attributionFlags, attributionChainId);
}
/** @deprecated Use {@link #startProxyOperationWithState} instead. */
@@ -3390,11 +3502,18 @@
final String proxyPackageName = attributionSource.getPackageName();
final String proxyAttributionTag = attributionSource.getAttributionTag();
final int proxiedUid = attributionSource.getNextUid();
+ final int proxyVirtualDeviceId = attributionSource.getDeviceId();
final String proxiedPackageName = attributionSource.getNextPackageName();
final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
verifyIncomingProxyUid(attributionSource);
verifyIncomingOp(code);
+ if (!isValidVirtualDeviceId(proxyVirtualDeviceId)) {
+ Slog.w(TAG, "startProxyOperationImpl returned MODE_IGNORED as virtualDeviceId "
+ + proxyVirtualDeviceId + " is invalid");
+ return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag,
+ proxiedPackageName);
+ }
if (!isIncomingPackageValid(proxyPackageName, UserHandle.getUserId(proxyUid))
|| !isIncomingPackageValid(proxiedPackageName, UserHandle.getUserId(proxiedUid))) {
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, proxiedAttributionTag,
@@ -3435,7 +3554,8 @@
// Test if the proxied operation will succeed before starting the proxy operation
final SyncNotedAppOp testProxiedOp = startOperationDryRun(code,
proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag,
- resolvedProxyPackageName, proxiedFlags, startIfModeDefault);
+ proxyVirtualDeviceId, resolvedProxyPackageName, proxiedFlags,
+ startIfModeDefault);
if (!shouldStartForMode(testProxiedOp.getOpMode(), startIfModeDefault)) {
return testProxiedOp;
@@ -3445,8 +3565,9 @@
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
final SyncNotedAppOp proxyAppOp = startOperationUnchecked(clientId, code, proxyUid,
- resolvedProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
- proxyFlags, startIfModeDefault, !isProxyTrusted, "proxy " + message,
+ resolvedProxyPackageName, proxyAttributionTag, proxyVirtualDeviceId,
+ Process.INVALID_UID, null, null, proxyFlags,
+ startIfModeDefault, !isProxyTrusted, "proxy " + message,
shouldCollectMessage, proxyAttributionFlags, attributionChainId);
if (!shouldStartForMode(proxyAppOp.getOpMode(), startIfModeDefault)) {
return proxyAppOp;
@@ -3454,9 +3575,9 @@
}
return startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
- proxiedAttributionTag, proxyUid, resolvedProxyPackageName, proxyAttributionTag,
- proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage, proxiedAttributionFlags, attributionChainId);
+ proxiedAttributionTag, proxyVirtualDeviceId, proxyUid, resolvedProxyPackageName,
+ proxyAttributionTag, proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp,
+ message, shouldCollectMessage, proxiedAttributionFlags, attributionChainId);
}
private boolean shouldStartForMode(int mode, boolean startIfModeDefault) {
@@ -3464,11 +3585,11 @@
}
private SyncNotedAppOp startOperationUnchecked(IBinder clientId, int code, int uid,
- @NonNull String packageName, @Nullable String attributionTag, int proxyUid,
- String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags,
- boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @Nullable String message,
- boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
- int attributionChainId) {
+ @NonNull String packageName, @Nullable String attributionTag, int virtualDeviceId,
+ int proxyUid, String proxyPackageName, @Nullable String proxyAttributionTag,
+ @OpFlags int flags, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
+ @Nullable String message, boolean shouldCollectMessage,
+ @AttributionFlags int attributionFlags, int attributionChainId) {
PackageVerificationResult pvr;
try {
pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
@@ -3492,8 +3613,8 @@
pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
if (ops == null) {
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
- flags, AppOpsManager.MODE_IGNORED, startType, attributionFlags,
- attributionChainId);
+ virtualDeviceId, flags, AppOpsManager.MODE_IGNORED, startType,
+ attributionFlags, attributionChainId);
if (DEBUG) Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
+ " package " + packageName + " flags: "
+ AppOpsManager.flagsToString(flags));
@@ -3501,23 +3622,23 @@
packageName);
}
final Op op = getOpLocked(ops, code, uid, true);
- final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
+ final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag,
+ getPersistentId(virtualDeviceId));
final UidState uidState = ops.uidState;
- isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass,
- false);
+ isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag,
+ virtualDeviceId, pvr.bypass, false);
final int switchCode = AppOpsManager.opToSwitch(code);
- // TODO(b/299330771): Check mode for the relevant device.
// If there is a non-default per UID policy (we set UID op mode only if
// non-default) it takes over, otherwise use the per package policy.
if (mAppOpsCheckingService.getUidMode(
- uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, switchCode)
+ uidState.uid, getPersistentId(virtualDeviceId), switchCode)
!= AppOpsManager.opToDefaultMode(switchCode)) {
final int uidMode =
uidState.evalMode(
code,
mAppOpsCheckingService.getUidMode(
uidState.uid,
- PERSISTENT_DEVICE_ID_DEFAULT,
+ getPersistentId(virtualDeviceId),
switchCode));
if (!shouldStartForMode(uidMode, startIfModeDefault)) {
if (DEBUG) {
@@ -3527,7 +3648,8 @@
}
attributedOp.rejected(uidState.getState(), flags);
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
- flags, uidMode, startType, attributionFlags, attributionChainId);
+ virtualDeviceId, flags, uidMode, startType, attributionFlags,
+ attributionChainId);
return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
}
} else {
@@ -3547,7 +3669,8 @@
+ packageName + " flags: " + AppOpsManager.flagsToString(flags));
attributedOp.rejected(uidState.getState(), flags);
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
- flags, mode, startType, attributionFlags, attributionChainId);
+ virtualDeviceId, flags, mode, startType, attributionFlags,
+ attributionChainId);
return new SyncNotedAppOp(mode, code, attributionTag, packageName);
}
}
@@ -3557,19 +3680,19 @@
try {
if (isRestricted) {
attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
- proxyAttributionTag, uidState.getState(), flags,
+ proxyAttributionTag, virtualDeviceId, uidState.getState(), flags,
attributionFlags, attributionChainId);
} else {
attributedOp.started(clientId, proxyUid, proxyPackageName,
- proxyAttributionTag, uidState.getState(), flags,
+ proxyAttributionTag, virtualDeviceId, uidState.getState(), flags,
attributionFlags, attributionChainId);
startType = START_TYPE_STARTED;
}
} catch (RemoteException e) {
throw new RuntimeException(e);
}
- scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags,
+ scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, virtualDeviceId,
+ flags, isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags,
attributionChainId);
}
@@ -3590,7 +3713,7 @@
* the proxied app can successfully start the operation.
*/
private SyncNotedAppOp startOperationDryRun(int code, int uid,
- @NonNull String packageName, @Nullable String attributionTag,
+ @NonNull String packageName, @Nullable String attributionTag, int virtualDeviceId,
String proxyPackageName, @OpFlags int flags,
boolean startIfModeDefault) {
PackageVerificationResult pvr;
@@ -3624,21 +3747,20 @@
}
final Op op = getOpLocked(ops, code, uid, true);
final UidState uidState = ops.uidState;
- isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass,
- false);
+ isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag,
+ virtualDeviceId, pvr.bypass, false);
final int switchCode = AppOpsManager.opToSwitch(code);
- // TODO(b/299330771): Check mode for the relevant device.
// If there is a non-default mode per UID policy (we set UID op mode only if
// non-default) it takes over, otherwise use the per package policy.
if (mAppOpsCheckingService.getUidMode(
- uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, switchCode)
+ uidState.uid, getPersistentId(virtualDeviceId), switchCode)
!= AppOpsManager.opToDefaultMode(switchCode)) {
final int uidMode =
uidState.evalMode(
code,
mAppOpsCheckingService.getUidMode(
uidState.uid,
- PERSISTENT_DEVICE_ID_DEFAULT,
+ getPersistentId(virtualDeviceId),
switchCode));
if (!shouldStartForMode(uidMode, startIfModeDefault)) {
if (DEBUG) {
@@ -3697,6 +3819,11 @@
String attributionTag, int virtualDeviceId) {
verifyIncomingUid(uid);
verifyIncomingOp(code);
+ if (!isValidVirtualDeviceId(virtualDeviceId)) {
+ Slog.w(TAG, "finishOperationImpl was a no-op as virtualDeviceId " + virtualDeviceId
+ + " is invalid");
+ return;
+ }
if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
return;
}
@@ -3706,7 +3833,8 @@
return;
}
- finishOperationUnchecked(clientId, code, uid, resolvedPackageName, attributionTag);
+ finishOperationUnchecked(clientId, code, uid, resolvedPackageName, attributionTag,
+ virtualDeviceId);
}
/** @deprecated Use {@link #finishProxyOperationWithState} instead. */
@@ -3731,6 +3859,7 @@
final String proxyPackageName = attributionSource.getPackageName();
final String proxyAttributionTag = attributionSource.getAttributionTag();
final int proxiedUid = attributionSource.getNextUid();
+ final int proxyVirtualDeviceId = attributionSource.getDeviceId();
final String proxiedPackageName = attributionSource.getNextPackageName();
final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
@@ -3739,6 +3868,11 @@
verifyIncomingProxyUid(attributionSource);
verifyIncomingOp(code);
+ if (!isValidVirtualDeviceId(proxyVirtualDeviceId)) {
+ Slog.w(TAG, "finishProxyOperationImpl was a no-op as virtualDeviceId "
+ + proxyVirtualDeviceId + " is invalid");
+ return null;
+ }
if (!isIncomingPackageValid(proxyPackageName, UserHandle.getUserId(proxyUid))
|| !isIncomingPackageValid(proxiedPackageName, UserHandle.getUserId(proxiedUid))) {
return null;
@@ -3752,7 +3886,7 @@
if (!skipProxyOperation) {
finishOperationUnchecked(clientId, code, proxyUid, resolvedProxyPackageName,
- proxyAttributionTag);
+ proxyAttributionTag, proxyVirtualDeviceId);
}
String resolvedProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid,
@@ -3762,13 +3896,13 @@
}
finishOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
- proxiedAttributionTag);
+ proxiedAttributionTag, proxyVirtualDeviceId);
return null;
}
private void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName,
- String attributionTag) {
+ String attributionTag, int virtualDeviceId) {
PackageVerificationResult pvr;
try {
pvr = verifyAndGetBypass(uid, packageName, attributionTag);
@@ -3788,7 +3922,9 @@
+ attributionTag + ") op=" + AppOpsManager.opToName(code));
return;
}
- final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
+ final AttributedOp attributedOp =
+ op.mDeviceAttributedOps.getOrDefault(getPersistentId(virtualDeviceId),
+ new ArrayMap<>()).get(attributionTag);
if (attributedOp == null) {
Slog.e(TAG, "Attribution not found: uid=" + uid + " pkg=" + packageName + "("
+ attributionTag + ") op=" + AppOpsManager.opToName(code));
@@ -3805,8 +3941,8 @@
}
void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull
- String packageName, @Nullable String attributionTag, boolean active, @AttributionFlags
- int attributionFlags, int attributionChainId) {
+ String packageName, @Nullable String attributionTag, int virtualDeviceId,
+ boolean active, @AttributionFlags int attributionFlags, int attributionChainId) {
ArraySet<ActiveCallback> dispatchedCallbacks = null;
final int callbackListCount = mActiveWatchers.size();
for (int i = 0; i < callbackListCount; i++) {
@@ -3827,13 +3963,14 @@
}
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpActiveChanged,
- this, dispatchedCallbacks, code, uid, packageName, attributionTag, active,
- attributionFlags, attributionChainId));
+ this, dispatchedCallbacks, code, uid, packageName, attributionTag,
+ virtualDeviceId, active, attributionFlags, attributionChainId));
}
private void notifyOpActiveChanged(ArraySet<ActiveCallback> callbacks,
int code, int uid, @NonNull String packageName, @Nullable String attributionTag,
- boolean active, @AttributionFlags int attributionFlags, int attributionChainId) {
+ int virtualDeviceId, boolean active, @AttributionFlags int attributionFlags,
+ int attributionChainId) {
// There are features watching for mode changes such as window manager
// and location manager which are in our process. The callbacks in these
// features may require permissions our remote caller does not have.
@@ -3847,7 +3984,7 @@
continue;
}
callback.mCallback.opActiveChanged(code, uid, packageName, attributionTag,
- active, attributionFlags, attributionChainId);
+ virtualDeviceId, active, attributionFlags, attributionChainId);
} catch (RemoteException e) {
/* do nothing */
}
@@ -3858,7 +3995,7 @@
}
void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName,
- String attributionTag, @OpFlags int flags, @Mode int result,
+ String attributionTag, int virtualDeviceId, @OpFlags int flags, @Mode int result,
@AppOpsManager.OnOpStartedListener.StartedType int startedType,
@AttributionFlags int attributionFlags, int attributionChainId) {
ArraySet<StartedCallback> dispatchedCallbacks = null;
@@ -3885,13 +4022,14 @@
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpStarted,
- this, dispatchedCallbacks, code, uid, pkgName, attributionTag, flags,
- result, startedType, attributionFlags, attributionChainId));
+ this, dispatchedCallbacks, code, uid, pkgName, attributionTag, virtualDeviceId,
+ flags, result, startedType, attributionFlags, attributionChainId));
}
private void notifyOpStarted(ArraySet<StartedCallback> callbacks,
- int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
- @Mode int result, @AppOpsManager.OnOpStartedListener.StartedType int startedType,
+ int code, int uid, String packageName, String attributionTag, int virtualDeviceId,
+ @OpFlags int flags, @Mode int result,
+ @AppOpsManager.OnOpStartedListener.StartedType int startedType,
@AttributionFlags int attributionFlags, int attributionChainId) {
final long identity = Binder.clearCallingIdentity();
try {
@@ -3902,8 +4040,9 @@
if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
continue;
}
- callback.mCallback.opStarted(code, uid, packageName, attributionTag, flags,
- result, startedType, attributionFlags, attributionChainId);
+ callback.mCallback.opStarted(code, uid, packageName, attributionTag,
+ virtualDeviceId, flags, result, startedType, attributionFlags,
+ attributionChainId);
} catch (RemoteException e) {
/* do nothing */
}
@@ -3914,7 +4053,7 @@
}
private void scheduleOpNotedIfNeededLocked(int code, int uid, String packageName,
- String attributionTag, @OpFlags int flags, @Mode int result) {
+ String attributionTag, int virtualDeviceId, @OpFlags int flags, @Mode int result) {
ArraySet<NotedCallback> dispatchedCallbacks = null;
final int callbackListCount = mNotedWatchers.size();
for (int i = 0; i < callbackListCount; i++) {
@@ -3935,13 +4074,13 @@
}
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpChecked,
- this, dispatchedCallbacks, code, uid, packageName, attributionTag, flags,
- result));
+ this, dispatchedCallbacks, code, uid, packageName, attributionTag,
+ virtualDeviceId, flags, result));
}
private void notifyOpChecked(ArraySet<NotedCallback> callbacks,
- int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
- @Mode int result) {
+ int code, int uid, String packageName, String attributionTag, int virtualDeviceId,
+ @OpFlags int flags, @Mode int result) {
// There are features watching for checks in our process. The callbacks in
// these features may require permissions our remote caller does not have.
final long identity = Binder.clearCallingIdentity();
@@ -3953,8 +4092,8 @@
if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
continue;
}
- callback.mCallback.opNoted(code, uid, packageName, attributionTag, flags,
- result);
+ callback.mCallback.opNoted(code, uid, packageName, attributionTag,
+ virtualDeviceId, flags, result);
} catch (RemoteException e) {
/* do nothing */
}
@@ -4028,6 +4167,22 @@
watcherPid, watcherUid) != PackageManager.PERMISSION_GRANTED;
}
+ private boolean isValidVirtualDeviceId(int virtualDeviceId) {
+ if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
+ return true;
+ }
+ if (mVirtualDeviceManagerInternal == null) {
+ return true;
+ }
+ if (mVirtualDeviceManagerInternal.isValidVirtualDeviceId(virtualDeviceId)) {
+ mKnownDeviceIds.put(virtualDeviceId,
+ mVirtualDeviceManagerInternal.getPersistentIdForDevice(virtualDeviceId));
+ return true;
+ }
+
+ return false;
+ }
+
private void verifyIncomingOp(int op) {
if (op >= 0 && op < AppOpsManager._NUM_OP) {
// Enforce manage appops permission if it's a restricted read op.
@@ -4488,7 +4643,12 @@
}
private boolean isOpRestrictedLocked(int uid, int code, String packageName,
- String attributionTag, @Nullable RestrictionBypass appBypass, boolean isCheckOp) {
+ String attributionTag, int virtualDeviceId, @Nullable RestrictionBypass appBypass,
+ boolean isCheckOp) {
+ // Restrictions only apply to the default device.
+ if (virtualDeviceId != Context.DEVICE_ID_DEFAULT) {
+ return false;
+ }
int restrictionSetCount = mOpGlobalRestrictions.size();
for (int i = 0; i < restrictionSetCount; i++) {
@@ -4662,7 +4822,10 @@
private void readAttributionOp(TypedXmlPullParser parser, @NonNull Op parent,
@Nullable String attribution)
throws NumberFormatException, IOException, XmlPullParserException {
- final AttributedOp attributedOp = parent.getOrCreateAttribution(parent, attribution);
+ // TODO(b/308201969): Update this method when we introduce disk persistence of events
+ // for accesses on external devices.
+ final AttributedOp attributedOp =
+ parent.getOrCreateAttribution(parent, attribution, PERSISTENT_DEVICE_ID_DEFAULT);
final long key = parser.getAttributeLong(null, "n");
final int uidState = extractUidStateFromKey(key);
@@ -5369,18 +5532,23 @@
private void dumpStatesLocked(@NonNull PrintWriter pw, @Nullable String filterAttributionTag,
@HistoricalOpsRequestFilter int filter, long nowElapsed, @NonNull Op op, long now,
@NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix) {
- final int numAttributions = op.mAttributions.size();
+ // TODO(b/299330771): Dump data for all devices.
+ ArrayMap<String, AttributedOp> defaultDeviceAttributedOps = op.mDeviceAttributedOps.get(
+ PERSISTENT_DEVICE_ID_DEFAULT);
+
+ final int numAttributions = defaultDeviceAttributedOps.size();
for (int i = 0; i < numAttributions; i++) {
if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !Objects.equals(
- op.mAttributions.keyAt(i), filterAttributionTag)) {
+ defaultDeviceAttributedOps.keyAt(i), filterAttributionTag)) {
continue;
}
- pw.print(prefix + op.mAttributions.keyAt(i) + "=[\n");
- dumpStatesLocked(pw, nowElapsed, op, op.mAttributions.keyAt(i), now, sdf, date,
- prefix + " ");
+ pw.print(prefix + defaultDeviceAttributedOps.keyAt(i) + "=[\n");
+ dumpStatesLocked(pw, nowElapsed, op, defaultDeviceAttributedOps.keyAt(i), now, sdf,
+ date, prefix + " ");
pw.print(prefix + "]\n");
}
+
}
private void dumpStatesLocked(@NonNull PrintWriter pw, long nowElapsed, @NonNull Op op,
@@ -5462,8 +5630,11 @@
pw.println();
}
}
+ // TODO(b/299330771): Dump running starts for all devices.
+ final AttributedOp attributedOp =
+ op.mDeviceAttributedOps.getOrDefault(PERSISTENT_DEVICE_ID_DEFAULT,
+ new ArrayMap<>()).get(attributionTag);
- final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
if (attributedOp.isRunning()) {
long earliestElapsedTime = Long.MAX_VALUE;
long maxNumStarts = 0;
@@ -5828,7 +5999,7 @@
}
for (int i=0; i<mUidStates.size(); i++) {
UidState uidState = mUidStates.valueAt(i);
- // TODO(b/299330771): Get modes for all devices.
+ // TODO(b/299330771): Dump modes for all devices.
final SparseIntArray opModes =
mAppOpsCheckingService.getNonDefaultUidModes(
uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT);
@@ -6043,11 +6214,13 @@
if (restrictionState.setRestriction(code, restricted, excludedPackageTags,
userHandle)) {
+ // Notify on PERSISTENT_DEVICE_ID_DEFAULT only as only the default device is
+ // affected by restrictions.
mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyWatchersOfChange, this, code, UID_ANY));
+ AppOpsService::notifyWatchersOnDefaultDevice, this, code, UID_ANY));
mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::updateStartedOpModeForUser, this, code, restricted,
- userHandle));
+ AppOpsService::updateStartedOpModeForUserForDefaultDevice, this, code,
+ restricted, userHandle));
}
if (restrictionState.isDefault()) {
@@ -6057,7 +6230,8 @@
}
}
- private void updateStartedOpModeForUser(int code, boolean restricted, int userId) {
+ private void updateStartedOpModeForUserForDefaultDevice(int code, boolean restricted,
+ int userId) {
synchronized (AppOpsService.this) {
int numUids = mUidStates.size();
for (int uidNum = 0; uidNum < numUids; uidNum++) {
@@ -6065,12 +6239,13 @@
if (userId != UserHandle.USER_ALL && UserHandle.getUserId(uid) != userId) {
continue;
}
- updateStartedOpModeForUidLocked(code, restricted, uid);
+ updateStartedOpModeForUidForDefaultDeviceLocked(code, restricted, uid);
}
}
}
- private void updateStartedOpModeForUidLocked(int code, boolean restricted, int uid) {
+ private void updateStartedOpModeForUidForDefaultDeviceLocked(int code, boolean restricted,
+ int uid) {
UidState uidState = mUidStates.get(uid);
if (uidState == null) {
return;
@@ -6089,9 +6264,11 @@
if (mode != MODE_ALLOWED && mode != MODE_FOREGROUND) {
continue;
}
- int numAttrTags = op.mAttributions.size();
- for (int attrNum = 0; attrNum < numAttrTags; attrNum++) {
- AttributedOp attrOp = op.mAttributions.valueAt(attrNum);
+ ArrayMap<String, AttributedOp> defaultDeviceAttributedOps = op.mDeviceAttributedOps.get(
+ PERSISTENT_DEVICE_ID_DEFAULT);
+ for (int tagIndex = 0; tagIndex < defaultDeviceAttributedOps.size();
+ tagIndex++) {
+ AttributedOp attrOp = defaultDeviceAttributedOps.valueAt(tagIndex);
if (restricted && attrOp.isRunning()) {
attrOp.pause();
} else if (attrOp.isPaused()) {
@@ -6101,7 +6278,7 @@
}
}
- private void notifyWatchersOfChange(int code, int uid) {
+ private void notifyWatchersOnDefaultDevice(int code, int uid) {
final ArraySet<OnOpModeChangedListener> modeChangedListenerSet;
synchronized (this) {
modeChangedListenerSet = mOpModeWatchers.get(code);
@@ -6109,8 +6286,7 @@
return;
}
}
-
- notifyOpChanged(modeChangedListenerSet, code, uid, null);
+ notifyOpChanged(modeChangedListenerSet, code, uid, null, PERSISTENT_DEVICE_ID_DEFAULT);
}
@Override
@@ -6582,6 +6758,25 @@
return packageNames;
}
+ @NonNull private String getPersistentId(int virtualDeviceId) {
+ if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
+ return PERSISTENT_DEVICE_ID_DEFAULT;
+ }
+ if (mVirtualDeviceManagerInternal == null) {
+ return PERSISTENT_DEVICE_ID_DEFAULT;
+ }
+ String persistentId =
+ mVirtualDeviceManagerInternal.getPersistentIdForDevice(virtualDeviceId);
+ if (persistentId == null) {
+ persistentId = mKnownDeviceIds.get(virtualDeviceId);
+ }
+ if (persistentId != null) {
+ return persistentId;
+ }
+ throw new IllegalStateException(
+ "Requested persistentId for invalid virtualDeviceId: " + virtualDeviceId);
+ }
+
private final class ClientUserRestrictionState implements DeathRecipient {
private final IBinder token;
@@ -6713,12 +6908,14 @@
}
if (restrictionState.setRestriction(code, restricted)) {
+ // Notify on PERSISTENT_DEVICE_ID_DEFAULT only as only the default device is
+ // affected by restrictions.
mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyWatchersOfChange, AppOpsService.this, code,
- UID_ANY));
+ AppOpsService::notifyWatchersOnDefaultDevice, AppOpsService.this,
+ code, UID_ANY));
mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::updateStartedOpModeForUser, AppOpsService.this,
- code, restricted, UserHandle.USER_ALL));
+ AppOpsService::updateStartedOpModeForUserForDefaultDevice,
+ AppOpsService.this, code, restricted, UserHandle.USER_ALL));
}
if (restrictionState.isDefault()) {
diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java
index 0ded75a..94baf88 100644
--- a/services/core/java/com/android/server/appop/AttributedOp.java
+++ b/services/core/java/com/android/server/appop/AttributedOp.java
@@ -42,6 +42,7 @@
final class AttributedOp {
private final @NonNull AppOpsService mAppOpsService;
public final @Nullable String tag;
+ public final @NonNull String persistentDeviceId;
public final @NonNull AppOpsService.Op parent;
/**
@@ -81,9 +82,10 @@
@Nullable ArrayMap<IBinder, InProgressStartOpEvent> mPausedInProgressEvents;
AttributedOp(@NonNull AppOpsService appOpsService, @Nullable String tag,
- @NonNull AppOpsService.Op parent) {
+ @NonNull String persistentDeviceId, @NonNull AppOpsService.Op parent) {
mAppOpsService = appOpsService;
this.tag = tag;
+ this.persistentDeviceId = persistentDeviceId;
this.parent = parent;
}
@@ -196,23 +198,26 @@
*/
public void started(@NonNull IBinder clientId, int proxyUid,
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
- @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
- @AppOpsManager.AttributionFlags
- int attributionFlags, int attributionChainId) throws RemoteException {
+ int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState,
+ @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags,
+ int attributionChainId) throws RemoteException {
startedOrPaused(clientId, proxyUid, proxyPackageName,
- proxyAttributionTag, uidState, flags, /* triggeredByUidStateChange */ false,
- /* isStarted */ true, attributionFlags, attributionChainId);
+ proxyAttributionTag, proxyVirtualDeviceId, uidState, flags,
+ /* triggeredByUidStateChange */ false, /* isStarted */ true, attributionFlags,
+ attributionChainId);
}
@SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
private void startedOrPaused(@NonNull IBinder clientId, int proxyUid,
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
- @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
- boolean triggeredByUidStateChange, boolean isStarted, @AppOpsManager.AttributionFlags
- int attributionFlags, int attributionChainId) throws RemoteException {
+ int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState,
+ @AppOpsManager.OpFlags int flags, boolean triggeredByUidStateChange,
+ boolean isStarted, @AppOpsManager.AttributionFlags int attributionFlags,
+ int attributionChainId) throws RemoteException {
if (!triggeredByUidStateChange && !parent.isRunning() && isStarted) {
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
- parent.packageName, tag, true, attributionFlags, attributionChainId);
+ parent.packageName, tag, proxyVirtualDeviceId, true, attributionFlags,
+ attributionChainId);
}
if (isStarted && mInProgressEvents == null) {
@@ -227,7 +232,7 @@
InProgressStartOpEvent event = events.get(clientId);
if (event == null) {
event = mAppOpsService.mInProgressStartOpEventPool.acquire(startTime,
- SystemClock.elapsedRealtime(), clientId, tag,
+ SystemClock.elapsedRealtime(), clientId, tag, proxyVirtualDeviceId,
PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId),
proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags,
attributionFlags, attributionChainId);
@@ -320,8 +325,8 @@
// TODO ntmyren: Also callback for single attribution tag activity changes
if (!triggeredByUidStateChange && !parent.isRunning()) {
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op,
- parent.uid, parent.packageName, tag, false,
- event.getAttributionFlags(), event.getAttributionChainId());
+ parent.uid, parent.packageName, tag, event.getVirtualDeviceId(),
+ false, event.getAttributionFlags(), event.getAttributionChainId());
}
}
}
@@ -362,11 +367,13 @@
*/
public void createPaused(@NonNull IBinder clientId, int proxyUid,
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
- @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
- @AppOpsManager.AttributionFlags
- int attributionFlags, int attributionChainId) throws RemoteException {
+ int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState,
+ @AppOpsManager.OpFlags int flags,
+ @AppOpsManager.AttributionFlags int attributionFlags,
+ int attributionChainId) throws RemoteException {
startedOrPaused(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
- uidState, flags, false, false, attributionFlags, attributionChainId);
+ proxyVirtualDeviceId, uidState, flags, false, false,
+ attributionFlags, attributionChainId);
}
/**
@@ -387,7 +394,7 @@
finishOrPause(event.getClientId(), false, true);
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
- parent.packageName, tag, false,
+ parent.packageName, tag, event.getVirtualDeviceId(), false,
event.getAttributionFlags(), event.getAttributionChainId());
}
mInProgressEvents = null;
@@ -419,14 +426,15 @@
event.getAttributionFlags(), event.getAttributionChainId());
if (shouldSendActive) {
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
- parent.packageName, tag, true, event.getAttributionFlags(),
- event.getAttributionChainId());
+ parent.packageName, tag, event.getVirtualDeviceId(), true,
+ event.getAttributionFlags(), event.getAttributionChainId());
}
// Note: this always sends MODE_ALLOWED, even if the mode is FOREGROUND
// TODO ntmyren: figure out how to get the real mode.
mAppOpsService.scheduleOpStartedIfNeededLocked(parent.op, parent.uid,
- parent.packageName, tag, event.getFlags(), MODE_ALLOWED, START_TYPE_RESUMED,
- event.getAttributionFlags(), event.getAttributionChainId());
+ parent.packageName, tag, event.getVirtualDeviceId(), event.getFlags(),
+ MODE_ALLOWED, START_TYPE_RESUMED, event.getAttributionFlags(),
+ event.getAttributionChainId());
}
mPausedInProgressEvents = null;
}
@@ -488,13 +496,15 @@
// previously removed unfinished start counts back
if (proxy != null) {
startedOrPaused(event.getClientId(), proxy.getUid(),
- proxy.getPackageName(), proxy.getAttributionTag(), newState,
- event.getFlags(), true, isRunning,
+ proxy.getPackageName(), proxy.getAttributionTag(),
+ event.getVirtualDeviceId(), newState, event.getFlags(),
+ true, isRunning,
event.getAttributionFlags(), event.getAttributionChainId());
} else {
startedOrPaused(event.getClientId(), Process.INVALID_UID, null, null,
- newState, event.getFlags(), true, isRunning,
- event.getAttributionFlags(), event.getAttributionChainId());
+ event.getVirtualDeviceId(), newState, event.getFlags(), true,
+ isRunning, event.getAttributionFlags(),
+ event.getAttributionChainId());
}
events = isRunning ? mInProgressEvents : mPausedInProgressEvents;
@@ -508,7 +518,7 @@
"Cannot switch to new uidState " + newState);
}
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op,
- parent.uid, parent.packageName, tag, false,
+ parent.uid, parent.packageName, tag, event.getVirtualDeviceId(), false,
eventAttributionFlags, eventAttributionChainId);
}
}
@@ -644,6 +654,9 @@
/** Id of the client that started the event */
private @NonNull IBinder mClientId;
+ /** virtual device id */
+ private int mVirtualDeviceId;
+
/** The attribution tag for this operation */
private @Nullable String mAttributionTag;
@@ -685,7 +698,7 @@
* @throws RemoteException If the client is dying
*/
InProgressStartOpEvent(long startTime, long startElapsedTime,
- @NonNull IBinder clientId, @Nullable String attributionTag,
+ @NonNull IBinder clientId, int virtualDeviceId, @Nullable String attributionTag,
@NonNull Runnable onDeath, @AppOpsManager.UidState int uidState,
@Nullable AppOpsManager.OpEventProxyInfo proxy, @AppOpsManager.OpFlags int flags,
@AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId)
@@ -693,6 +706,7 @@
mStartTime = startTime;
mStartElapsedTime = startElapsedTime;
mClientId = clientId;
+ mVirtualDeviceId = virtualDeviceId;
mAttributionTag = attributionTag;
mOnDeath = onDeath;
mUidState = uidState;
@@ -737,7 +751,7 @@
* @throws RemoteException If the client is dying
*/
public void reinit(long startTime, long startElapsedTime, @NonNull IBinder clientId,
- @Nullable String attributionTag, @NonNull Runnable onDeath,
+ @Nullable String attributionTag, int virtualDeviceId, @NonNull Runnable onDeath,
@AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
@Nullable AppOpsManager.OpEventProxyInfo proxy,
@AppOpsManager.AttributionFlags int attributionFlags,
@@ -749,6 +763,7 @@
mClientId = clientId;
mAttributionTag = attributionTag;
mOnDeath = onDeath;
+ mVirtualDeviceId = virtualDeviceId;
mUidState = uidState;
mFlags = flags;
@@ -802,6 +817,11 @@
return mAttributionChainId;
}
+ /** @return virtual device id for the access */
+ public int getVirtualDeviceId() {
+ return mVirtualDeviceId;
+ }
+
public void setStartTime(long startTime) {
mStartTime = startTime;
}
@@ -824,11 +844,11 @@
}
InProgressStartOpEvent acquire(long startTime, long elapsedTime, @NonNull IBinder clientId,
- @Nullable String attributionTag, @NonNull Runnable onDeath, int proxyUid,
- @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
- @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
- @AppOpsManager.AttributionFlags
- int attributionFlags, int attributionChainId) throws RemoteException {
+ @Nullable String attributionTag, int virtualDeviceId, @NonNull Runnable onDeath,
+ int proxyUid, @Nullable String proxyPackageName,
+ @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState,
+ @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags
+ int attributionFlags, int attributionChainId) throws RemoteException {
InProgressStartOpEvent recycled = acquire();
@@ -839,14 +859,15 @@
}
if (recycled != null) {
- recycled.reinit(startTime, elapsedTime, clientId, attributionTag, onDeath,
- uidState, flags, proxyInfo, attributionFlags, attributionChainId,
+ recycled.reinit(startTime, elapsedTime, clientId, attributionTag, virtualDeviceId,
+ onDeath, uidState, flags, proxyInfo, attributionFlags, attributionChainId,
mOpEventProxyInfoPool);
return recycled;
}
- return new InProgressStartOpEvent(startTime, elapsedTime, clientId, attributionTag,
- onDeath, uidState, proxyInfo, flags, attributionFlags, attributionChainId);
+ return new InProgressStartOpEvent(startTime, elapsedTime, clientId, virtualDeviceId,
+ attributionTag, onDeath, uidState, proxyInfo, flags, attributionFlags,
+ attributionChainId);
}
}
diff --git a/services/core/java/com/android/server/appop/OnOpModeChangedListener.java b/services/core/java/com/android/server/appop/OnOpModeChangedListener.java
index 1d1a9e7..f5f34c1 100644
--- a/services/core/java/com/android/server/appop/OnOpModeChangedListener.java
+++ b/services/core/java/com/android/server/appop/OnOpModeChangedListener.java
@@ -16,8 +16,11 @@
package com.android.server.appop;
+import android.companion.virtual.VirtualDeviceManager;
import android.os.RemoteException;
+import java.util.Objects;
+
/**
* Listener for mode changes, encapsulates methods that should be triggered in the event of a mode
* change.
@@ -95,6 +98,20 @@
throws RemoteException;
/**
+ * Method that should be triggered when the app-op's mode is changed.
+ * @param op app-op whose mode-change is being listened to.
+ * @param uid user-is associated with the app-op.
+ * @param packageName package name associated with the app-op.
+ * @param persistentDeviceId device associated with the app-op.
+ */
+ public void onOpModeChanged(int op, int uid, String packageName, String persistentDeviceId)
+ throws RemoteException {
+ if (Objects.equals(persistentDeviceId, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)) {
+ onOpModeChanged(op, uid, packageName);
+ }
+ }
+
+ /**
* Return human readable string representing the listener.
*/
public abstract String toString();
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index cd295b5..f1eea72 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -2820,6 +2820,10 @@
return mDeviceInventory.isBluetoothAudioDeviceCategoryFixed(address);
}
+ /*package*/ boolean isSADevice(AdiDeviceState deviceState) {
+ return mAudioService.isSADevice(deviceState);
+ }
+
//------------------------------------------------
// for testing purposes only
void clearDeviceInventory() {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 690c37a..102a960 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -307,9 +307,12 @@
&& di.mPeerDeviceAddress.equals(ads2.getDeviceAddress()))) {
continue;
}
- ads2.setHasHeadTracker(updatedDevice.hasHeadTracker());
- ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
- ads2.setSAEnabled(updatedDevice.isSAEnabled());
+ if (mDeviceBroker.isSADevice(updatedDevice)
+ == mDeviceBroker.isSADevice(ads2)) {
+ ads2.setHasHeadTracker(updatedDevice.hasHeadTracker());
+ ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
+ ads2.setSAEnabled(updatedDevice.isSAEnabled());
+ }
ads2.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
mDeviceBroker.postUpdatedAdiDeviceState(ads2);
@@ -325,9 +328,12 @@
&& di.mDeviceAddress.equals(ads2.getDeviceAddress()))) {
continue;
}
- ads2.setHasHeadTracker(updatedDevice.hasHeadTracker());
- ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
- ads2.setSAEnabled(updatedDevice.isSAEnabled());
+ if (mDeviceBroker.isSADevice(updatedDevice)
+ == mDeviceBroker.isSADevice(ads2)) {
+ ads2.setHasHeadTracker(updatedDevice.hasHeadTracker());
+ ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
+ ads2.setSAEnabled(updatedDevice.isSAEnabled());
+ }
ads2.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
mDeviceBroker.postUpdatedAdiDeviceState(ads2);
@@ -348,10 +354,11 @@
|| !updatedDevice.getDeviceAddress().equals(ads.getDeviceAddress())) {
continue;
}
-
- ads.setHasHeadTracker(updatedDevice.hasHeadTracker());
- ads.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
- ads.setSAEnabled(updatedDevice.isSAEnabled());
+ if (mDeviceBroker.isSADevice(updatedDevice) == mDeviceBroker.isSADevice(ads)) {
+ ads.setHasHeadTracker(updatedDevice.hasHeadTracker());
+ ads.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
+ ads.setSAEnabled(updatedDevice.isSAEnabled());
+ }
ads.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
mDeviceBroker.postUpdatedAdiDeviceState(ads);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 9f7c07e..bbbba26 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -7855,7 +7855,7 @@
sDeviceLogger.enqueue(new EventLogger.StringEvent("BlutoothActiveDeviceChanged for "
+ BluetoothProfile.getProfileName(profile) + ", device update " + previousDevice
- + " -> " + newDevice));
+ + " -> " + newDevice).printLog(TAG));
AudioDeviceBroker.BtDeviceChangedData data =
new AudioDeviceBroker.BtDeviceChangedData(newDevice, previousDevice, info,
"AudioService");
@@ -10638,6 +10638,10 @@
mSpatializerHelper.setFeatureEnabled(mHasSpatializerEffect);
}
+ /*package*/ boolean isSADevice(AdiDeviceState deviceState) {
+ return mSpatializerHelper.isSADevice(deviceState);
+ }
+
private boolean isBluetoothPrividged() {
if (!bluetoothMacAddressAnonymization()) {
return true;
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 8428f12..8d76731 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -813,7 +813,7 @@
return false;
}
- private boolean isSADevice(AdiDeviceState deviceState) {
+ /*package*/ boolean isSADevice(AdiDeviceState deviceState) {
return deviceState.getDeviceType() == getCanonicalDeviceType(deviceState.getDeviceType(),
deviceState.getInternalDeviceType()) && isDeviceCompatibleWithSpatializationModes(
deviceState.getAudioDeviceAttributes());
diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
index aa6a0f1..fbd32a6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
@@ -112,10 +112,8 @@
getLogger().logOnError(getContext(), getOperationContext(),
errorCode, vendorCode, getTargetUserId());
try {
- if (getListener() != null) {
- mShouldSendErrorToClient = false;
- getListener().onError(getSensorId(), getCookie(), errorCode, vendorCode);
- }
+ mShouldSendErrorToClient = false;
+ getListener().onError(getSensorId(), getCookie(), errorCode, vendorCode);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to invoke sendError", e);
}
@@ -147,9 +145,7 @@
final int errorCode = BiometricConstants.BIOMETRIC_ERROR_CANCELED;
try {
- if (getListener() != null) {
- getListener().onError(getSensorId(), getCookie(), errorCode, 0 /* vendorCode */);
- }
+ getListener().onError(getSensorId(), getCookie(), errorCode, 0 /* vendorCode */);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to invoke sendError", e);
}
@@ -181,7 +177,7 @@
}
try {
- if (getListener() != null && shouldSend) {
+ if (shouldSend) {
getListener().onAcquired(getSensorId(), acquiredInfo, vendorCode);
}
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index f9568ea..506b456 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -265,17 +265,13 @@
Slog.d(TAG, "Skipping addAuthToken");
}
try {
- if (listener != null) {
- if (!mIsRestricted) {
- listener.onAuthenticationSucceeded(getSensorId(), identifier, byteToken,
- getTargetUserId(), mIsStrongBiometric);
- } else {
- listener.onAuthenticationSucceeded(getSensorId(), null /* identifier */,
- byteToken,
- getTargetUserId(), mIsStrongBiometric);
- }
+ if (!mIsRestricted) {
+ listener.onAuthenticationSucceeded(getSensorId(), identifier, byteToken,
+ getTargetUserId(), mIsStrongBiometric);
} else {
- Slog.e(TAG, "Received successful auth, but client was not listening");
+ listener.onAuthenticationSucceeded(getSensorId(), null /* identifier */,
+ byteToken,
+ getTargetUserId(), mIsStrongBiometric);
}
} catch (RemoteException e) {
Slog.e(TAG, "Unable to notify listener", e);
@@ -301,11 +297,7 @@
}
try {
- if (listener != null) {
- listener.onAuthenticationFailed(getSensorId());
- } else {
- Slog.e(TAG, "Received failed auth, but client was not listening");
- }
+ listener.onAuthenticationFailed(getSensorId());
} catch (RemoteException e) {
Slog.e(TAG, "Unable to notify listener", e);
mCallback.onClientFinished(this, false);
diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
index 0216e49..a408852 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
@@ -22,6 +22,7 @@
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.IBiometricSensorReceiver;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
@@ -55,7 +56,7 @@
@Nullable private IBinder mToken;
private long mRequestId;
- @Nullable private ClientMonitorCallbackConverter mListener;
+ @NonNull private ClientMonitorCallbackConverter mListener;
// Currently only used for authentication client. The cookie generated by BiometricService
// is never 0.
private final int mCookie;
@@ -95,7 +96,8 @@
mContext = context;
mToken = token;
mRequestId = -1;
- mListener = listener;
+ mListener = listener == null ? new ClientMonitorCallbackConverter(
+ new IBiometricSensorReceiver.Default()) : listener;
mTargetUserId = userId;
mOwner = owner;
mCookie = cookie;
@@ -199,7 +201,7 @@
}
mToken = null;
if (clearListener) {
- mListener = null;
+ mListener = new ClientMonitorCallbackConverter(new IBiometricSensorReceiver.Default());
}
}
@@ -233,8 +235,8 @@
return mOwner;
}
- @Nullable
- public final ClientMonitorCallbackConverter getListener() {
+ @NonNull
+ protected ClientMonitorCallbackConverter getListener() {
return mListener;
}
@@ -312,9 +314,7 @@
final int errorCode = BiometricConstants.BIOMETRIC_ERROR_CANCELED;
try {
ClientMonitorCallbackConverter listener = getListener();
- if (listener != null) {
- listener.onError(getSensorId(), getCookie(), errorCode, 0 /* vendorCode */);
- }
+ listener.onError(getSensorId(), getCookie(), errorCode, 0 /* vendorCode */);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to invoke sendError", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
index 2c4d30b..8e7004d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
@@ -82,9 +82,7 @@
final ClientMonitorCallbackConverter listener = getListener();
try {
- if (listener != null) {
- listener.onEnrollResult(identifier, remaining);
- }
+ listener.onEnrollResult(identifier, remaining);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
index 45ffa23..d2ef278 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
@@ -74,13 +74,9 @@
if (identifier == null) {
Slog.e(TAG, "identifier was null, skipping onRemove()");
try {
- if (getListener() != null) {
- getListener().onError(getSensorId(), getCookie(),
- BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_REMOVE,
- 0 /* vendorCode */);
- } else {
- Slog.e(TAG, "Error, listener was null, not sending onError callback");
- }
+ getListener().onError(getSensorId(), getCookie(),
+ BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_REMOVE,
+ 0 /* vendorCode */);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to send error to client for onRemoved", e);
}
@@ -93,9 +89,7 @@
identifier.getBiometricId());
try {
- if (getListener() != null) {
- getListener().onRemoved(identifier, remaining);
- }
+ getListener().onRemoved(identifier, remaining);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to notify Removed:", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index f35de93..415d294 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -337,7 +337,7 @@
onAcquiredInternal(acquireInfo, vendorCode, false /* shouldSend */);
final boolean shouldSend = shouldSendAcquiredMessage(acquireInfo, vendorCode);
- if (shouldSend && getListener() != null) {
+ if (shouldSend) {
try {
getListener().onAuthenticationFrame(frame);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index f5c4529..5f370f2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -152,7 +152,7 @@
onAcquiredInternal(acquireInfo, vendorCode, false /* shouldSend */);
final boolean shouldSend = shouldSendAcquiredMessage(acquireInfo, vendorCode);
- if (shouldSend && getListener() != null) {
+ if (shouldSend) {
try {
getListener().onEnrollmentFrame(frame);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
index e404bd2..cf45eb8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
@@ -58,11 +58,6 @@
void onChallengeGenerated(int sensorId, int userId, long challenge) {
try {
final ClientMonitorCallbackConverter listener = getListener();
- if (listener == null) {
- Slog.e(TAG, "Listener is null in onChallengeGenerated");
- mCallback.onClientFinished(this, false /* success */);
- return;
- }
listener.onChallengeGenerated(sensorId, userId, challenge);
mCallback.onClientFinished(this, true /* success */);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
index 9812536..47aaeec 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
@@ -61,9 +61,7 @@
@Override
public void unableToStart() {
try {
- if (getListener() != null) {
- getListener().onFeatureGet(false /* success */, new int[0], new boolean[0]);
- }
+ getListener().onFeatureGet(false /* success */, new int[0], new boolean[0]);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to send error", e);
}
@@ -85,9 +83,7 @@
featureState[0] = result.value;
mValue = result.value;
- if (getListener() != null) {
- getListener().onFeatureGet(result.status == Status.OK, features, featureState);
- }
+ getListener().onFeatureGet(result.status == Status.OK, features, featureState);
mCallback.onClientFinished(this, true /* success */);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to getFeature", e);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 6912961..e0fd44b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -450,9 +450,7 @@
pc.major);
}
- if (getListener() != null) {
- getListener().onUdfpsPointerDown(getSensorId());
- }
+ getListener().onUdfpsPointerDown(getSensorId());
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
@@ -471,9 +469,7 @@
session.getSession().onPointerUp(pc.pointerId);
}
- if (getListener() != null) {
- getListener().onUdfpsPointerUp(getSensorId());
- }
+ getListener().onUdfpsPointerUp(getSensorId());
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index a7fb774..cb220b9e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -175,11 +175,7 @@
vibrateSuccess();
try {
- if (getListener() != null) {
- getListener().onDetected(getSensorId(), getTargetUserId(), mIsStrongBiometric);
- } else {
- Slog.e(TAG, "Listener is null!");
- }
+ getListener().onDetected(getSensorId(), getTargetUserId(), mIsStrongBiometric);
mCallback.onClientFinished(this, true /* success */);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when sending onDetected", e);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index 3fb9223..225bd59 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -305,9 +305,7 @@
pc.major);
}
- if (getListener() != null) {
- getListener().onUdfpsPointerDown(getSensorId());
- }
+ getListener().onUdfpsPointerDown(getSensorId());
} catch (RemoteException e) {
Slog.e(TAG, "Unable to send pointer down", e);
}
@@ -325,9 +323,7 @@
session.getSession().onPointerUp(pc.pointerId);
}
- if (getListener() != null) {
- getListener().onUdfpsPointerUp(getSensorId());
- }
+ getListener().onUdfpsPointerUp(getSensorId());
} catch (RemoteException e) {
Slog.e(TAG, "Unable to send pointer up", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
index ce693ff..d2f36ce 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
@@ -59,11 +59,6 @@
void onChallengeGenerated(int sensorId, int userId, long challenge) {
try {
final ClientMonitorCallbackConverter listener = getListener();
- if (listener == null) {
- Slog.e(TAG, "Listener is null in onChallengeGenerated");
- mCallback.onClientFinished(this, false /* success */);
- return;
- }
listener.onChallengeGenerated(sensorId, userId, challenge);
mCallback.onClientFinished(this, true /* success */);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index 9232e11..f857946 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -28,6 +28,7 @@
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Handler;
import android.os.IBinder;
@@ -367,7 +368,8 @@
final IBinder token = client.getToken();
final long operationId = authClient.getOperationId();
final int cookie = client.getCookie();
- final ClientMonitorCallbackConverter listener = client.getListener();
+ final ClientMonitorCallbackConverter listener = new ClientMonitorCallbackConverter(
+ new IFingerprintServiceReceiver.Default());
final boolean restricted = authClient.isRestricted();
final int statsClient = client.getLogger().getStatsClient();
final boolean isKeyguard = authClient.isKeyguard();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 7a329e9..60c532c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -279,12 +279,10 @@
mALSProbeCallback.getProbe().enable();
UdfpsHelper.onFingerDown(getFreshDaemon(), (int) pc.x, (int) pc.y, pc.minor, pc.major);
- if (getListener() != null) {
- try {
- getListener().onUdfpsPointerDown(getSensorId());
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- }
+ try {
+ getListener().onUdfpsPointerDown(getSensorId());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
}
}
@@ -295,12 +293,10 @@
mALSProbeCallback.getProbe().disable();
UdfpsHelper.onFingerUp(getFreshDaemon());
- if (getListener() != null) {
- try {
- getListener().onUdfpsPointerUp(getSensorId());
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- }
+ try {
+ getListener().onUdfpsPointerUp(getSensorId());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index 6e029d2..50e48fe 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -153,12 +153,10 @@
final PerformanceTracker pm = PerformanceTracker.getInstanceForSensorId(getSensorId());
pm.incrementAuthForUser(getTargetUserId(), authenticated);
- if (getListener() != null) {
- try {
- getListener().onDetected(getSensorId(), getTargetUserId(), mIsStrongBiometric);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when sending onDetected", e);
- }
+ try {
+ getListener().onDetected(getSensorId(), getTargetUserId(), mIsStrongBiometric);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when sending onDetected", e);
}
}
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index a796544..458fd82 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -1292,7 +1292,7 @@
return;
}
if (DEBUG) Slog.v(TAG, "Setting NFC reader mode. enablePolling: " + enablePolling);
- nfcAdapter.setReaderMode(enablePolling);
+ nfcAdapter.setReaderModePollingEnabled(enablePolling);
}
private static int[] toArray(Collection<Integer> c) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 93addcd..e930627 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -872,14 +872,15 @@
}
@VisibleForTesting
- void performTraversalInternal(SurfaceControl.Transaction t) {
+ void performTraversalInternal(SurfaceControl.Transaction t,
+ SparseArray<SurfaceControl.Transaction> displayTransactions) {
synchronized (mSyncRoot) {
if (!mPendingTraversal) {
return;
}
mPendingTraversal = false;
- performTraversalLocked(t);
+ performTraversalLocked(t, displayTransactions);
}
// List is self-synchronized copy-on-write.
@@ -2593,7 +2594,8 @@
}
}
- private void performTraversalLocked(SurfaceControl.Transaction t) {
+ private void performTraversalLocked(SurfaceControl.Transaction t,
+ SparseArray<SurfaceControl.Transaction> displayTransactions) {
// Clear all viewports before configuring displays so that we can keep
// track of which ones we have configured.
clearViewportsLocked();
@@ -2601,9 +2603,11 @@
// Configure each display device.
mLogicalDisplayMapper.forEachLocked((LogicalDisplay display) -> {
final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
+ final SurfaceControl.Transaction displayTransaction =
+ displayTransactions.get(display.getDisplayIdLocked(), t);
if (device != null) {
- configureDisplayLocked(t, device);
- device.performTraversalLocked(t);
+ configureDisplayLocked(displayTransaction, device);
+ device.performTraversalLocked(displayTransaction);
}
});
@@ -4680,8 +4684,9 @@
}
@Override
- public void performTraversal(SurfaceControl.Transaction t) {
- performTraversalInternal(t);
+ public void performTraversal(SurfaceControl.Transaction t,
+ SparseArray<SurfaceControl.Transaction> displayTransactions) {
+ performTraversalInternal(t, displayTransactions);
}
@Override
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index 0f40ca0..1715254 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -16,8 +16,6 @@
package com.android.server.graphics.fonts;
-import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE;
-
import static com.android.server.graphics.fonts.FontManagerService.SystemFontException;
import android.annotation.NonNull;
@@ -580,11 +578,11 @@
return null;
}
resolvedFonts.add(new FontConfig.Font(info.mFile, null, info.getPostScriptName(),
- font.getFontStyle(), font.getIndex(), font.getFontVariationSettings(), null));
+ font.getFontStyle(), font.getIndex(), font.getFontVariationSettings(),
+ null /* family name */, FontConfig.Font.VAR_TYPE_AXES_NONE));
}
FontConfig.FontFamily family = new FontConfig.FontFamily(resolvedFonts,
- LocaleList.getEmptyLocaleList(), FontConfig.FontFamily.VARIANT_DEFAULT,
- VARIABLE_FONT_FAMILY_TYPE_NONE);
+ LocaleList.getEmptyLocaleList(), FontConfig.FontFamily.VARIANT_DEFAULT);
return new FontConfig.NamedFamilyList(Collections.singletonList(family),
fontFamily.getName());
}
diff --git a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
index 30a4f31..3c3cfe6 100644
--- a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
+++ b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
@@ -45,6 +45,13 @@
@VisibleForTesting
static final int STATE_WAITING_FOR_REPORT_POWER_STATUS = 1;
+ // State in which the action is delayed. If the action starts and
+ // {@link PowerManager#isInteractive} returns false, it could indicate the beginning of a
+ // standby process. In this scenario, the action will be removed when
+ // {@link HdmiCecLocalDeviceSource#disableDevice} is called, therefore we delay the action.
+ @VisibleForTesting
+ static final int STATE_CHECK_STANDBY_PROCESS_STARTED = 2;
+
// The maximum number of times we send <Give Device Power Status> before we give up.
// We wait up to RESPONSE_TIMEOUT_MS * LOOP_COUNTER_MAX = 20 seconds.
private static final int LOOP_COUNTER_MAX = 10;
@@ -87,6 +94,22 @@
boolean start() {
// Because only source device can create this action, it's safe to cast.
mSource = source();
+
+ if (!mSource.mService.getPowerManager().isInteractive()) {
+ Slog.d(TAG, "PowerManager is not interactive. Delay the action to check if standby"
+ + " started!");
+ mState = STATE_CHECK_STANDBY_PROCESS_STARTED;
+ addTimer(mState, HdmiConfig.TIMEOUT_MS);
+ } else {
+ startAction();
+ }
+
+ return true;
+ }
+
+ private void startAction() {
+ Slog.i(TAG, "Start action.");
+
sendCommand(HdmiCecMessageBuilder.buildTextViewOn(getSourceAddress(), mTargetAddress));
boolean is20TargetOnBefore = mIsCec20 && getTargetDevicePowerStatus(mSource, mTargetAddress,
@@ -116,12 +139,11 @@
maySendActiveSource();
}
finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
- return true;
+ return;
}
}
mState = STATE_WAITING_FOR_REPORT_POWER_STATUS;
addTimer(mState, HdmiConfig.TIMEOUT_MS);
- return true;
}
private void setAndBroadcastActiveSource() {
@@ -174,14 +196,22 @@
if (mState != state) {
return;
}
- if (state == STATE_WAITING_FOR_REPORT_POWER_STATUS) {
- if (mPowerStatusCounter++ < LOOP_COUNTER_MAX) {
- queryDevicePowerStatus();
- addTimer(mState, HdmiConfig.TIMEOUT_MS);
- } else {
- // Couldn't wake up the TV for whatever reason. Report failure.
- finishWithCallback(HdmiControlManager.RESULT_TIMEOUT);
- }
+ switch (state) {
+ case STATE_WAITING_FOR_REPORT_POWER_STATUS:
+ if (mPowerStatusCounter++ < LOOP_COUNTER_MAX) {
+ queryDevicePowerStatus();
+ addTimer(mState, HdmiConfig.TIMEOUT_MS);
+ } else {
+ // Couldn't wake up the TV for whatever reason. Report failure.
+ finishWithCallback(HdmiControlManager.RESULT_TIMEOUT);
+ }
+ return;
+ case STATE_CHECK_STANDBY_PROCESS_STARTED:
+ Slog.d(TAG, "Action was not removed, start the action.");
+ startAction();
+ return;
+ default:
+ return;
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index f031b7b..2ea662c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3185,6 +3185,24 @@
}
}
}
+
+ if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
+ String ime = SecureSettingsWrapper.getString(
+ Settings.Secure.DEFAULT_INPUT_METHOD, null, mSettings.getUserId());
+ String defaultDeviceIme = SecureSettingsWrapper.getString(
+ Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, mSettings.getUserId());
+ if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
+ if (DEBUG) {
+ Slog.v(TAG, "Current input method " + ime + " differs from the stored default"
+ + " device input method for user " + mSettings.getUserId()
+ + " - restoring " + defaultDeviceIme);
+ }
+ SecureSettingsWrapper.putString(
+ Settings.Secure.DEFAULT_INPUT_METHOD, defaultDeviceIme,
+ mSettings.getUserId());
+ }
+ }
+
// We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
// ENABLED_INPUT_METHODS is taking care of keeping them correctly in
// sync, so we will never have a DEFAULT_INPUT_METHOD that is not
@@ -3659,6 +3677,15 @@
Slog.e(TAG, "windowToken cannot be null.");
return InputBindResult.NULL;
}
+ // The user represented by userId, must be running.
+ if (!mUserManagerInternal.isUserRunning(userId)) {
+ // There is a chance that we hit here because of race condition. Let's just
+ // return an error code instead of crashing the caller process, which at
+ // least has INTERACT_ACROSS_USERS_FULL permission thus is likely to be an
+ // important process.
+ Slog.w(TAG, "User #" + userId + " is not running.");
+ return InputBindResult.INVALID_USER;
+ }
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
"IMMS.startInputOrWindowGainedFocus");
@@ -3666,20 +3693,43 @@
"InputMethodManagerService#startInputOrWindowGainedFocus");
final InputBindResult result;
synchronized (ImfLock.class) {
+ // If the system is not yet ready, we shouldn't be running third party code.
if (!mSystemReady) {
- // If the system is not yet ready, we shouldn't be running third arty code.
return new InputBindResult(
InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY,
null /* method */, null /* accessibilitySessions */, null /* channel */,
getSelectedMethodIdLocked(), getSequenceNumberLocked(),
false /* isInputMethodSuppressingSpellChecker */);
}
+ final ClientState cs = mClientController.getClient(client.asBinder());
+ if (cs == null) {
+ throw new IllegalArgumentException("Unknown client " + client.asBinder());
+ }
final long ident = Binder.clearCallingIdentity();
try {
+ // Verify if IMMS is in the process of switching user.
+ if (mUserSwitchHandlerTask != null) {
+ // There is already an on-going pending user switch task.
+ final int nextUserId = mUserSwitchHandlerTask.mToUserId;
+ if (userId == nextUserId) {
+ scheduleSwitchUserTaskLocked(userId, cs.mClient);
+ return InputBindResult.USER_SWITCHING;
+ }
+ final int[] profileIdsWithDisabled = mUserManagerInternal.getProfileIds(
+ mSettings.getUserId(), false /* enabledOnly */);
+ for (int profileId : profileIdsWithDisabled) {
+ if (profileId == userId) {
+ scheduleSwitchUserTaskLocked(userId, cs.mClient);
+ return InputBindResult.USER_SWITCHING;
+ }
+ }
+ return InputBindResult.INVALID_USER;
+ }
+
result = startInputOrWindowGainedFocusInternalLocked(startInputReason,
client, windowToken, startInputFlags, softInputMode, windowFlags,
editorInfo, inputConnection, remoteAccessibilityInputConnection,
- unverifiedTargetSdkVersion, userId, imeDispatcher);
+ unverifiedTargetSdkVersion, userId, imeDispatcher, cs);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -3708,7 +3758,7 @@
IRemoteInputConnection inputContext,
@Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, @UserIdInt int userId,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, @NonNull ClientState cs) {
if (DEBUG) {
Slog.v(TAG, "startInputOrWindowGainedFocusInternalLocked: reason="
+ InputMethodDebug.startInputReasonToString(startInputReason)
@@ -3721,23 +3771,9 @@
+ " windowFlags=#" + Integer.toHexString(windowFlags)
+ " unverifiedTargetSdkVersion=" + unverifiedTargetSdkVersion
+ " userId=" + userId
- + " imeDispatcher=" + imeDispatcher);
+ + " imeDispatcher=" + imeDispatcher
+ + " cs=" + cs);
}
-
- if (!mUserManagerInternal.isUserRunning(userId)) {
- // There is a chance that we hit here because of race condition. Let's just
- // return an error code instead of crashing the caller process, which at
- // least has INTERACT_ACROSS_USERS_FULL permission thus is likely to be an
- // important process.
- Slog.w(TAG, "User #" + userId + " is not running.");
- return InputBindResult.INVALID_USER;
- }
-
- final ClientState cs = mClientController.getClient(client.asBinder());
- if (cs == null) {
- throw new IllegalArgumentException("Unknown client " + client.asBinder());
- }
-
final int imeClientFocus = mWindowManagerInternal.hasInputMethodClientFocus(
windowToken, cs.mUid, cs.mPid, cs.mSelfReportedDisplayId);
switch (imeClientFocus) {
@@ -3759,24 +3795,6 @@
return InputBindResult.INVALID_DISPLAY_ID;
}
- if (mUserSwitchHandlerTask != null) {
- // There is already an on-going pending user switch task.
- final int nextUserId = mUserSwitchHandlerTask.mToUserId;
- if (userId == nextUserId) {
- scheduleSwitchUserTaskLocked(userId, cs.mClient);
- return InputBindResult.USER_SWITCHING;
- }
- final int[] profileIdsWithDisabled = mUserManagerInternal.getProfileIds(
- mSettings.getUserId(), false /* enabledOnly */);
- for (int profileId : profileIdsWithDisabled) {
- if (profileId == userId) {
- scheduleSwitchUserTaskLocked(userId, cs.mClient);
- return InputBindResult.USER_SWITCHING;
- }
- }
- return InputBindResult.INVALID_USER;
- }
-
final boolean shouldClearFlag = mImePlatformCompatUtils.shouldClearShowForcedFlag(cs.mUid);
// In case mShowForced flag affects the next client to keep IME visible, when the current
// client is leaving due to the next focused client, we clear mShowForced flag when the
@@ -5366,7 +5384,11 @@
if (!setSubtypeOnly) {
// Set InputMethod here
- mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
+ final String imeId = imi != null ? imi.getId() : "";
+ mSettings.putSelectedInputMethod(imeId);
+ if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
+ mSettings.putSelectedDefaultDeviceInputMethod(imeId);
+ }
}
}
@@ -5509,6 +5531,9 @@
return false; // IME is not found or not enabled.
}
settings.putSelectedInputMethod(imeId);
+ if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
+ settings.putSelectedDefaultDeviceInputMethod(imeId);
+ }
settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
return true;
}
@@ -6555,6 +6580,9 @@
// Reset selected IME.
settings.putSelectedInputMethod(nextIme);
+ if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
+ settings.putSelectedDefaultDeviceInputMethod(nextIme);
+ }
settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
}
out.println("Reset current and enabled IMEs for user #" + userId);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
index a51002b..e444db1 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
@@ -36,7 +36,6 @@
import java.util.ArrayList;
import java.util.List;
-import java.util.Objects;
import java.util.function.Predicate;
/**
@@ -88,12 +87,6 @@
mMethodMap = methodMap;
mMethodList = methodMap.values();
mUserId = userId;
- String ime = getSelectedInputMethod();
- String defaultDeviceIme = getSelectedDefaultDeviceInputMethod();
- if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
- putSelectedInputMethod(defaultDeviceIme);
- putSelectedDefaultDeviceInputMethod(null);
- }
}
@AnyThread
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 4da2cc9..1c9bbab 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2049,7 +2049,9 @@
if (!mUserProfiles.isProfileUser(userId)) {
mConditionProviders.onUserUnlocked(userId);
mListeners.onUserUnlocked(userId);
- mZenModeHelper.onUserUnlocked(userId);
+ if (!android.app.Flags.modesApi()) {
+ mZenModeHelper.onUserUnlocked(userId);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index fb95632..2f20bbe 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -210,6 +210,9 @@
mDefaultConfig = readDefaultConfig(mContext.getResources());
updateDefaultAutomaticRuleNames();
+ if (Flags.modesApi()) {
+ updateDefaultAutomaticRulePolicies();
+ }
mConfig = mDefaultConfig.copy();
synchronized (mConfigsArrayLock) {
mConfigs.put(UserHandle.USER_SYSTEM, mConfig);
@@ -325,6 +328,7 @@
}
}
+ // TODO: b/310620812 - Remove when MODES_API is inlined (no more callers).
public void onUserUnlocked(int user) {
loadConfigForUser(user, "onUserUnlocked");
}
@@ -1966,6 +1970,21 @@
}
}
+ // Updates the policies in the default automatic rules (provided via default XML config) to
+ // be fully filled in default values.
+ private void updateDefaultAutomaticRulePolicies() {
+ if (!Flags.modesApi()) {
+ // Should be checked before calling, but just in case.
+ return;
+ }
+ ZenPolicy defaultPolicy = mDefaultConfig.toZenPolicy();
+ for (ZenRule rule : mDefaultConfig.automaticRules.values()) {
+ if (ZenModeConfig.DEFAULT_RULE_IDS.contains(rule.id) && rule.zenPolicy == null) {
+ rule.zenPolicy = defaultPolicy.copy();
+ }
+ }
+ }
+
@VisibleForTesting
protected void applyRestrictions() {
final boolean zenOn = mZenMode != Global.ZEN_MODE_OFF;
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index 47967db..722654a 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -43,6 +43,13 @@
}
flag {
+ name: "screenshare_notification_hiding"
+ namespace: "systemui"
+ description: "Enable hiding of notifications during screenshare"
+ bug: "312784809"
+}
+
+flag {
name: "sensitive_notification_app_protection"
namespace: "systemui"
description: "This flag controls the sensitive notification app protections while screen sharing"
@@ -63,4 +70,11 @@
namespace: "systemui"
description: "Timing test, no functionality"
bug: "316931130"
+}
+
+flag {
+ name: "notification_custom_view_uri_restriction"
+ namespace: "systemui"
+ description: "This flag enables memory restriction of notifications holding custom views with Uri Bitmaps"
+ bug: "270553691"
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java
index 86d05d9..25a39cc 100644
--- a/services/core/java/com/android/server/om/IdmapManager.java
+++ b/services/core/java/com/android/server/om/IdmapManager.java
@@ -257,7 +257,7 @@
private boolean matchesActorSignature(@NonNull AndroidPackage targetPackage,
@NonNull AndroidPackage overlayPackage, int userId) {
String targetOverlayableName = overlayPackage.getOverlayTargetOverlayableName();
- if (targetOverlayableName != null && !mPackageManager.getNamedActors().isEmpty()) {
+ if (targetOverlayableName != null) {
try {
OverlayableInfo overlayableInfo = mPackageManager.getOverlayableForTarget(
targetPackage.getPackageName(), targetOverlayableName, userId);
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index a61b03f..b9464d9 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -32,7 +32,6 @@
import static android.os.Trace.TRACE_TAG_RRO;
import static android.os.Trace.traceBegin;
import static android.os.Trace.traceEnd;
-
import static com.android.server.om.OverlayManagerServiceImpl.OperationFailedException;
import android.annotation.NonNull;
@@ -363,7 +362,7 @@
defaultPackages.add(packageName);
}
}
- return defaultPackages.toArray(new String[0]);
+ return defaultPackages.toArray(new String[defaultPackages.size()]);
}
private final class OverlayManagerPackageMonitor extends PackageMonitor {
@@ -1144,10 +1143,9 @@
};
private static final class PackageManagerHelperImpl implements PackageManagerHelper {
- private static final class PackageStateUsers {
+ private static class PackageStateUsers {
private PackageState mPackageState;
- private Boolean mDefinesOverlayable = null;
- private final ArraySet<Integer> mInstalledUsers = new ArraySet<>();
+ private final Set<Integer> mInstalledUsers = new ArraySet<>();
private PackageStateUsers(@NonNull PackageState packageState) {
this.mPackageState = packageState;
}
@@ -1162,7 +1160,7 @@
// state may lead to contradictions within OMS. Better then to lag
// behind until all pending intents have been processed.
private final ArrayMap<String, PackageStateUsers> mCache = new ArrayMap<>();
- private final ArraySet<Integer> mInitializedUsers = new ArraySet<>();
+ private final Set<Integer> mInitializedUsers = new ArraySet<>();
PackageManagerHelperImpl(Context context) {
mContext = context;
@@ -1178,7 +1176,8 @@
*/
@NonNull
public ArrayMap<String, PackageState> initializeForUser(final int userId) {
- if (mInitializedUsers.add(userId)) {
+ if (!mInitializedUsers.contains(userId)) {
+ mInitializedUsers.add(userId);
mPackageManagerInternal.forEachPackageState((packageState -> {
if (packageState.getPkg() != null
&& packageState.getUserStateOrDefault(userId).isInstalled()) {
@@ -1197,11 +1196,13 @@
return userPackages;
}
- private PackageStateUsers getRawPackageStateForUser(@NonNull final String packageName,
+ @Override
+ @Nullable
+ public PackageState getPackageStateForUser(@NonNull final String packageName,
final int userId) {
final PackageStateUsers pkg = mCache.get(packageName);
if (pkg != null && pkg.mInstalledUsers.contains(userId)) {
- return pkg;
+ return pkg.mPackageState;
}
try {
if (!mPackageManager.isPackageAvailable(packageName, userId)) {
@@ -1215,14 +1216,8 @@
return addPackageUser(packageName, userId);
}
- @Override
- public PackageState getPackageStateForUser(@NonNull final String packageName,
- final int userId) {
- final PackageStateUsers pkg = getRawPackageStateForUser(packageName, userId);
- return pkg != null ? pkg.mPackageState : null;
- }
-
- private PackageStateUsers addPackageUser(@NonNull final String packageName,
+ @NonNull
+ private PackageState addPackageUser(@NonNull final String packageName,
final int user) {
final PackageState pkg = mPackageManagerInternal.getPackageStateInternal(packageName);
if (pkg == null) {
@@ -1234,20 +1229,20 @@
}
@NonNull
- private PackageStateUsers addPackageUser(@NonNull final PackageState pkg,
+ private PackageState addPackageUser(@NonNull final PackageState pkg,
final int user) {
PackageStateUsers pkgUsers = mCache.get(pkg.getPackageName());
if (pkgUsers == null) {
pkgUsers = new PackageStateUsers(pkg);
mCache.put(pkg.getPackageName(), pkgUsers);
- } else if (pkgUsers.mPackageState != pkg) {
+ } else {
pkgUsers.mPackageState = pkg;
- pkgUsers.mDefinesOverlayable = null;
}
pkgUsers.mInstalledUsers.add(user);
- return pkgUsers;
+ return pkgUsers.mPackageState;
}
+
@NonNull
private void removePackageUser(@NonNull final String packageName, final int user) {
final PackageStateUsers pkgUsers = mCache.get(packageName);
@@ -1265,15 +1260,15 @@
}
}
+ @Nullable
public PackageState onPackageAdded(@NonNull final String packageName, final int userId) {
- final var pu = addPackageUser(packageName, userId);
- return pu != null ? pu.mPackageState : null;
+ return addPackageUser(packageName, userId);
}
+ @Nullable
public PackageState onPackageUpdated(@NonNull final String packageName,
final int userId) {
- final var pu = addPackageUser(packageName, userId);
- return pu != null ? pu.mPackageState : null;
+ return addPackageUser(packageName, userId);
}
public void onPackageRemoved(@NonNull final String packageName, final int userId) {
@@ -1313,30 +1308,22 @@
return (pkgs.length == 0) ? null : pkgs[0];
}
+ @Nullable
@Override
public OverlayableInfo getOverlayableForTarget(@NonNull String packageName,
@NonNull String targetOverlayableName, int userId)
throws IOException {
- final var psu = getRawPackageStateForUser(packageName, userId);
- final var pkg = (psu == null || psu.mPackageState == null)
- ? null : psu.mPackageState.getAndroidPackage();
+ var packageState = getPackageStateForUser(packageName, userId);
+ var pkg = packageState == null ? null : packageState.getAndroidPackage();
if (pkg == null) {
throw new IOException("Unable to get target package");
}
- if (Boolean.FALSE.equals(psu.mDefinesOverlayable)) {
- return null;
- }
-
ApkAssets apkAssets = null;
try {
apkAssets = ApkAssets.loadFromPath(pkg.getSplits().get(0).getPath(),
ApkAssets.PROPERTY_ONLY_OVERLAYABLES);
- if (psu.mDefinesOverlayable == null) {
- psu.mDefinesOverlayable = apkAssets.definesOverlayable();
- }
- return Boolean.FALSE.equals(psu.mDefinesOverlayable)
- ? null : apkAssets.getOverlayableInfo(targetOverlayableName);
+ return apkAssets.getOverlayableInfo(targetOverlayableName);
} finally {
if (apkAssets != null) {
try {
@@ -1350,29 +1337,24 @@
@Override
public boolean doesTargetDefineOverlayable(String targetPackageName, int userId)
throws IOException {
- final var psu = getRawPackageStateForUser(targetPackageName, userId);
- var pkg = (psu == null || psu.mPackageState == null)
- ? null : psu.mPackageState.getAndroidPackage();
+ var packageState = getPackageStateForUser(targetPackageName, userId);
+ var pkg = packageState == null ? null : packageState.getAndroidPackage();
if (pkg == null) {
throw new IOException("Unable to get target package");
}
- if (psu.mDefinesOverlayable == null) {
- ApkAssets apkAssets = null;
- try {
- apkAssets = ApkAssets.loadFromPath(pkg.getSplits().get(0).getPath(),
- ApkAssets.PROPERTY_ONLY_OVERLAYABLES);
- psu.mDefinesOverlayable = apkAssets.definesOverlayable();
- } finally {
- if (apkAssets != null) {
- try {
- apkAssets.close();
- } catch (Throwable ignored) {
- }
+ ApkAssets apkAssets = null;
+ try {
+ apkAssets = ApkAssets.loadFromPath(pkg.getSplits().get(0).getPath());
+ return apkAssets.definesOverlayable();
+ } finally {
+ if (apkAssets != null) {
+ try {
+ apkAssets.close();
+ } catch (Throwable ignored) {
}
}
}
- return psu.mDefinesOverlayable;
}
@Override
@@ -1563,7 +1545,8 @@
final OverlayPaths frameworkOverlays =
mImpl.getEnabledOverlayPaths("android", userId, false);
for (final String targetPackageName : targetPackageNames) {
- final var list = new OverlayPaths.Builder(frameworkOverlays);
+ final OverlayPaths.Builder list = new OverlayPaths.Builder();
+ list.addAll(frameworkOverlays);
if (!"android".equals(targetPackageName)) {
list.addAll(mImpl.getEnabledOverlayPaths(targetPackageName, userId, true));
}
@@ -1575,21 +1558,17 @@
final HashSet<String> invalidPackages = new HashSet<>();
pm.setEnabledOverlayPackages(userId, pendingChanges, updatedPackages, invalidPackages);
- if (DEBUG || !invalidPackages.isEmpty()) {
- for (final String targetPackageName : targetPackageNames) {
- if (DEBUG) {
- Slog.d(TAG,
- "-> Updating overlay: target=" + targetPackageName + " overlays=["
- + pendingChanges.get(targetPackageName)
- + "] userId=" + userId);
- }
+ for (final String targetPackageName : targetPackageNames) {
+ if (DEBUG) {
+ Slog.d(TAG, "-> Updating overlay: target=" + targetPackageName + " overlays=["
+ + pendingChanges.get(targetPackageName)
+ + "] userId=" + userId);
+ }
- if (invalidPackages.contains(targetPackageName)) {
- Slog.e(TAG, TextUtils.formatSimple(
- "Failed to change enabled overlays for %s user %d",
- targetPackageName,
- userId));
- }
+ if (invalidPackages.contains(targetPackageName)) {
+ Slog.e(TAG, TextUtils.formatSimple(
+ "Failed to change enabled overlays for %s user %d", targetPackageName,
+ userId));
}
}
return new ArrayList<>(updatedPackages);
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index c1b6ccc..972c78d 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -772,20 +772,24 @@
OverlayPaths getEnabledOverlayPaths(@NonNull final String targetPackageName,
final int userId, boolean includeImmutableOverlays) {
- final var paths = new OverlayPaths.Builder();
- mSettings.forEachMatching(userId, null, targetPackageName, oi -> {
+ final List<OverlayInfo> overlays = mSettings.getOverlaysForTarget(targetPackageName,
+ userId);
+ final OverlayPaths.Builder paths = new OverlayPaths.Builder();
+ final int n = overlays.size();
+ for (int i = 0; i < n; i++) {
+ final OverlayInfo oi = overlays.get(i);
if (!oi.isEnabled()) {
- return;
+ continue;
}
if (!includeImmutableOverlays && !oi.isMutable) {
- return;
+ continue;
}
if (oi.isFabricated()) {
paths.addNonApkPath(oi.baseCodePath);
} else {
paths.addApkPath(oi.baseCodePath);
}
- });
+ }
return paths.build();
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
index b8b49f3e..eae614a 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -47,7 +47,6 @@
import java.util.List;
import java.util.Objects;
import java.util.Set;
-import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
@@ -183,23 +182,6 @@
return CollectionUtils.map(items, SettingsItem::getOverlayInfo);
}
- void forEachMatching(int userId, String overlayName, String targetPackageName,
- @NonNull Consumer<OverlayInfo> consumer) {
- for (int i = 0, n = mItems.size(); i < n; i++) {
- final SettingsItem item = mItems.get(i);
- if (item.getUserId() != userId) {
- continue;
- }
- if (overlayName != null && !item.mOverlay.getPackageName().equals(overlayName)) {
- continue;
- }
- if (targetPackageName != null && !item.mTargetPackageName.equals(targetPackageName)) {
- continue;
- }
- consumer.accept(item.getOverlayInfo());
- }
- }
-
ArrayMap<String, List<OverlayInfo>> getOverlaysForUser(final int userId) {
final List<SettingsItem> items = selectWhereUser(userId);
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 0555d90..0be8e6e 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -586,7 +586,7 @@
list.add(ri);
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
mInjector.getCompatibility(), mComponentResolver,
- list, false, intent, resolvedType, flags, filterCallingUid);
+ list, false, intent, resolvedType, filterCallingUid);
}
}
} else {
@@ -616,7 +616,7 @@
// We also have to ensure all components match the original intent
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
mInjector.getCompatibility(), mComponentResolver,
- list, false, originalIntent, resolvedType, flags, filterCallingUid);
+ list, false, originalIntent, resolvedType, filterCallingUid);
}
return skipPostResolution ? list : applyPostResolutionFilter(
@@ -700,7 +700,7 @@
list.add(ri);
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
mInjector.getCompatibility(), mComponentResolver,
- list, false, intent, resolvedType, flags, callingUid);
+ list, false, intent, resolvedType, callingUid);
}
}
} else {
@@ -712,7 +712,7 @@
// We also have to ensure all components match the original intent
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
mInjector.getCompatibility(), mComponentResolver,
- list, false, originalIntent, resolvedType, flags, callingUid);
+ list, false, originalIntent, resolvedType, callingUid);
}
return list;
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index a10bae9..3abf3a5 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -16,7 +16,10 @@
package com.android.server.pm;
+import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_RESTORE;
+import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_SETUP;
import static android.os.Trace.TRACE_TAG_DALVIK;
+import static android.os.incremental.IncrementalManager.isIncrementalPath;
import static com.android.server.LocalManagerRegistry.ManagerNotFoundException;
import static com.android.server.pm.ApexManager.ActiveApexInfo;
@@ -27,6 +30,8 @@
import static com.android.server.pm.PackageManagerService.REASON_BOOT_AFTER_OTA;
import static com.android.server.pm.PackageManagerService.REASON_CMDLINE;
import static com.android.server.pm.PackageManagerService.REASON_FIRST_BOOT;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_APEX;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_INSTANT_APP;
import static com.android.server.pm.PackageManagerService.STUB_SUFFIX;
import static com.android.server.pm.PackageManagerService.TAG;
import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
@@ -45,6 +50,8 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.Flags;
+import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.dex.ArtManager;
@@ -54,6 +61,7 @@
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
+import android.provider.Settings.Global;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
@@ -1091,4 +1099,73 @@
+ " has unsupported status " + status);
}
}
+
+ /**
+ * Returns DexoptOptions by the given InstallRequest.
+ */
+ static DexoptOptions getDexoptOptionsByInstallRequest(InstallRequest installRequest,
+ DexManager dexManager) {
+ final PackageSetting ps = installRequest.getScannedPackageSetting();
+ final String packageName = ps.getPackageName();
+ final boolean isBackupOrRestore =
+ installRequest.getInstallReason() == INSTALL_REASON_DEVICE_RESTORE
+ || installRequest.getInstallReason() == INSTALL_REASON_DEVICE_SETUP;
+ final int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE
+ | DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
+ | DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE
+ | (isBackupOrRestore ? DexoptOptions.DEXOPT_FOR_RESTORE : 0);
+ // Compute the compilation reason from the installation scenario.
+ final int compilationReason =
+ dexManager.getCompilationReasonForInstallScenario(
+ installRequest.getInstallScenario());
+ return new DexoptOptions(packageName, compilationReason, dexoptFlags);
+ }
+
+ /**
+ * Use ArtService to perform dexopt by the given InstallRequest.
+ */
+ static DexoptResult dexoptPackageUsingArtService(InstallRequest installRequest,
+ DexoptOptions dexoptOptions) {
+ final PackageSetting ps = installRequest.getScannedPackageSetting();
+ final String packageName = ps.getPackageName();
+
+ PackageManagerLocal packageManagerLocal =
+ LocalManagerRegistry.getManager(PackageManagerLocal.class);
+ try (PackageManagerLocal.FilteredSnapshot snapshot =
+ packageManagerLocal.withFilteredSnapshot()) {
+ boolean ignoreDexoptProfile =
+ (installRequest.getInstallFlags()
+ & PackageManager.INSTALL_IGNORE_DEXOPT_PROFILE)
+ != 0;
+ /*@DexoptFlags*/ int extraFlags =
+ ignoreDexoptProfile && Flags.useArtServiceV2()
+ ? ArtFlags.FLAG_IGNORE_PROFILE
+ : 0;
+ DexoptParams params = dexoptOptions.convertToDexoptParams(extraFlags);
+ DexoptResult dexOptResult = getArtManagerLocal().dexoptPackage(
+ snapshot, packageName, params);
+
+ return dexOptResult;
+ }
+ }
+
+ /**
+ * Returns whether to perform dexopt by the given InstallRequest.
+ */
+ static boolean shouldPerformDexopt(InstallRequest installRequest, DexoptOptions dexoptOptions,
+ Context context) {
+ final boolean isApex = ((installRequest.getScanFlags() & SCAN_AS_APEX) != 0);
+ final boolean instantApp = ((installRequest.getScanFlags() & SCAN_AS_INSTANT_APP) != 0);
+ final PackageSetting ps = installRequest.getScannedPackageSetting();
+ final AndroidPackage pkg = ps.getPkg();
+ final boolean onIncremental = isIncrementalPath(ps.getPathString());
+
+ return (!instantApp || Global.getInt(context.getContentResolver(),
+ Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0)
+ && pkg != null
+ && !pkg.isDebuggable()
+ && (!onIncremental)
+ && dexoptOptions.isCompilationEnabled()
+ && !isApex;
+ }
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index e70c5ea..28f3d59 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -32,8 +32,6 @@
import static android.content.pm.PackageManager.INSTALL_FAILED_TEST_ONLY;
import static android.content.pm.PackageManager.INSTALL_FAILED_UID_CHANGED;
import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
-import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_RESTORE;
-import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_SETUP;
import static android.content.pm.PackageManager.INSTALL_STAGED;
import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
import static android.content.pm.PackageManager.UNINSTALL_REASON_UNKNOWN;
@@ -170,10 +168,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.server.EventLogTags;
-import com.android.server.LocalManagerRegistry;
import com.android.server.SystemConfig;
-import com.android.server.art.model.ArtFlags;
-import com.android.server.art.model.DexoptParams;
import com.android.server.art.model.DexoptResult;
import com.android.server.criticalevents.CriticalEventLog;
import com.android.server.pm.Installer.LegacyDexoptDisabledException;
@@ -800,10 +795,27 @@
"restoreAndPostInstall userId=" + userId + " package=" + request.getPkg());
}
- // A restore should be requested at this point if (a) the install
- // succeeded, (b) the operation is not an update.
+ PackageSetting packageSetting = null;
+
final boolean update = request.isUpdate();
- boolean doRestore = !update && request.getPkg() != null;
+ boolean doRestore = false;
+ if (request.getPkg() != null && !request.isArchived()) {
+ // A restore should be requested at this point:
+ // if the install succeeded and it's not an archived install
+ if (!update) {
+ // AND the operation is not an update,
+ doRestore = true;
+ } else {
+ // OR the package has never been restored.
+ String packageName = request.getPkg().getPackageName();
+ synchronized (mPm.mLock) {
+ packageSetting = mPm.mSettings.getPackageLPr(packageName);
+ if (packageSetting != null && packageSetting.isPendingRestore()) {
+ doRestore = true;
+ }
+ }
+ }
+ }
// Set up the post-install work request bookkeeping. This will be used
// and cleaned up by the post-install event handling regardless of whether
@@ -833,7 +845,13 @@
doRestore = performRollbackManagerRestore(userId, token, request);
}
- if (!doRestore) {
+ if (doRestore) {
+ if (packageSetting != null) {
+ synchronized (mPm.mLock) {
+ packageSetting.setPendingRestore(false);
+ }
+ }
+ } else {
// No restore possible, or the Backup Manager was mysteriously not
// available -- just fire the post-install work request directly.
if (DEBUG_INSTALL) Log.v(TAG, "No restore - queue post-install for " + token);
@@ -2464,8 +2482,6 @@
final ArraySet<IncrementalStorage> incrementalStorages = new ArraySet<>();
for (ReconciledPackage reconciledPkg : reconciledPackages) {
final InstallRequest installRequest = reconciledPkg.mInstallRequest;
- final boolean instantApp = ((installRequest.getScanFlags() & SCAN_AS_INSTANT_APP) != 0);
- final boolean isApex = ((installRequest.getScanFlags() & SCAN_AS_APEX) != 0);
final PackageSetting ps = installRequest.getScannedPackageSetting();
final String packageName = ps.getPackageName();
final String codePath = ps.getPathString();
@@ -2507,28 +2523,14 @@
}
}
- // Compute the compilation reason from the installation scenario.
- final int compilationReason =
- mDexManager.getCompilationReasonForInstallScenario(
- installRequest.getInstallScenario());
-
// Construct the DexoptOptions early to see if we should skip running dexopt.
//
// Do not run PackageDexOptimizer through the local performDexOpt
// method because `pkg` may not be in `mPackages` yet.
//
// Also, don't fail application installs if the dexopt step fails.
- final boolean isBackupOrRestore =
- installRequest.getInstallReason() == INSTALL_REASON_DEVICE_RESTORE
- || installRequest.getInstallReason() == INSTALL_REASON_DEVICE_SETUP;
-
- final int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE
- | DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
- | DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE
- | (isBackupOrRestore ? DexoptOptions.DEXOPT_FOR_RESTORE : 0);
- DexoptOptions dexoptOptions =
- new DexoptOptions(packageName, compilationReason, dexoptFlags);
-
+ DexoptOptions dexoptOptions = DexOptHelper.getDexoptOptionsByInstallRequest(
+ installRequest, mDexManager);
// Check whether we need to dexopt the app.
//
// NOTE: it is IMPORTANT to call dexopt:
@@ -2555,16 +2557,9 @@
//
// TODO(b/174695087): instantApp and onIncremental should be removed and their install
// path moved to SCENARIO_FAST.
- final boolean performDexopt =
- (!instantApp || android.provider.Settings.Global.getInt(
- mContext.getContentResolver(),
- android.provider.Settings.Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0)
- && pkg != null
- && !pkg.isDebuggable()
- && (!onIncremental)
- && dexoptOptions.isCompilationEnabled()
- && !isApex;
+ final boolean performDexopt = DexOptHelper.shouldPerformDexopt(installRequest,
+ dexoptOptions, mContext);
if (performDexopt) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
@@ -2579,23 +2574,9 @@
realPkgSetting.getPkgState().setUpdatedSystemApp(isUpdatedSystemApp);
if (useArtService()) {
- PackageManagerLocal packageManagerLocal =
- LocalManagerRegistry.getManager(PackageManagerLocal.class);
- try (PackageManagerLocal.FilteredSnapshot snapshot =
- packageManagerLocal.withFilteredSnapshot()) {
- boolean ignoreDexoptProfile =
- (installRequest.getInstallFlags()
- & PackageManager.INSTALL_IGNORE_DEXOPT_PROFILE)
- != 0;
- /*@DexoptFlags*/ int extraFlags =
- ignoreDexoptProfile && Flags.useArtServiceV2()
- ? ArtFlags.FLAG_IGNORE_PROFILE
- : 0;
- DexoptParams params = dexoptOptions.convertToDexoptParams(extraFlags);
- DexoptResult dexOptResult = DexOptHelper.getArtManagerLocal().dexoptPackage(
- snapshot, packageName, params);
- installRequest.onDexoptFinished(dexOptResult);
- }
+ DexoptResult dexOptResult = DexOptHelper.dexoptPackageUsingArtService(
+ installRequest, dexoptOptions);
+ installRequest.onDexoptFinished(dexOptResult);
} else {
try {
mPackageDexOptimizer.performDexOpt(pkg, realPkgSetting,
@@ -2919,7 +2900,7 @@
mPm.scheduleDeferredNoKillPostDelete(args);
if (Flags.improveInstallDontKill()) {
synchronized (mPm.mInstallLock) {
- PackageManagerServiceUtils.linkSplitsToOldDirs(mPm.mInstaller,
+ PackageManagerServiceUtils.linkFilesToOldDirs(mPm.mInstaller,
packageName, pkgSetting.getPath(), pkgSetting.getOldPaths());
}
}
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index bc0173a..43328fc 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -1726,7 +1726,8 @@
.scheme("android-app")
.authority(callingPackage)
.build())
- .setPackage(installerPackageName);
+ .setPackage(installerPackageName)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 474b590..339b1e7 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -19,7 +19,6 @@
import static android.app.ActivityManager.START_ABORTED;
import static android.app.ActivityManager.START_CLASS_NOT_FOUND;
import static android.app.ActivityManager.START_PERMISSION_DENIED;
-import static android.app.ActivityManager.START_SUCCESS;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
@@ -286,10 +285,16 @@
Slog.e(TAG, TextUtils.formatSimple(
"Unexpected error occurred while unarchiving package %s: %s.", packageName,
t.getLocalizedMessage()));
- return START_ABORTED;
}
- return START_SUCCESS;
+ // We return STATUS_ABORTED because:
+ // 1. Archived App is not actually present during activity start. Hence the unarchival
+ // start should be treated as an error code.
+ // 2. STATUS_ABORTED is not visible to the end consumers. Hence, it will not change user
+ // experience.
+ // 3. Returning STATUS_ABORTED helps us avoid manually handling of different cases like
+ // aborting activity options, animations etc in the Windows Manager.
+ return START_ABORTED;
}
/**
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index dfe705a7..b705e84 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -598,6 +598,8 @@
static final int DEFAULT_FILE_ACCESS_MODE = 0644;
+ static final int DEFAULT_NATIVE_LIBRARY_FILE_ACCESS_MODE = 0755;
+
final Handler mHandler;
final Handler mBackgroundHandler;
@@ -1550,6 +1552,8 @@
}
pkgSetting
.setPkg(null)
+ // This package was installed as archived. Need to mark it for later restore.
+ .setPendingRestore(true)
.modifyUserState(userId)
.setInstalled(false)
.setArchiveState(archiveState);
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 8531692..4f9ed03 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -23,6 +23,7 @@
import static android.system.OsConstants.O_CREAT;
import static android.system.OsConstants.O_RDWR;
+import static com.android.internal.content.NativeLibraryHelper.LIB64_DIR_NAME;
import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME;
import static com.android.internal.util.FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__EXPLICIT_INTENT_FILTER_UNMATCH;
import static com.android.server.LocalManagerRegistry.ManagerNotFoundException;
@@ -31,6 +32,7 @@
import static com.android.server.pm.PackageManagerService.DEBUG_INTENT_MATCHING;
import static com.android.server.pm.PackageManagerService.DEBUG_PREFERRED;
import static com.android.server.pm.PackageManagerService.DEFAULT_FILE_ACCESS_MODE;
+import static com.android.server.pm.PackageManagerService.DEFAULT_NATIVE_LIBRARY_FILE_ACCESS_MODE;
import static com.android.server.pm.PackageManagerService.RANDOM_CODEPATH_PREFIX;
import static com.android.server.pm.PackageManagerService.RANDOM_DIR_PREFIX;
import static com.android.server.pm.PackageManagerService.SHELL_PACKAGE_NAME;
@@ -44,7 +46,7 @@
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.compat.annotation.ChangeId;
-import android.compat.annotation.Disabled;
+import android.compat.annotation.EnabledAfter;
import android.compat.annotation.Overridable;
import android.content.Context;
import android.content.Intent;
@@ -198,7 +200,7 @@
*/
@Overridable
@ChangeId
- @Disabled /* Enforcement reverted in T: b/274147456 */
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
private static final long ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS = 161252188;
/**
@@ -1192,8 +1194,7 @@
public static void applyEnforceIntentFilterMatching(
PlatformCompat compat, ComponentResolverApi resolver,
List<ResolveInfo> resolveInfos, boolean isReceiver,
- Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
- int filterCallingUid) {
+ Intent intent, String resolvedType, int filterCallingUid) {
if (DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.get()) return;
// Do not enforce filter matching when the caller is system or root
@@ -1203,10 +1204,9 @@
? new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM)
: null;
- final boolean defaultOnly = (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0;
-
- final boolean enforce = compat.isChangeEnabledByUidInternal(
- ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS, filterCallingUid);
+ final boolean enforce = android.security.Flags.enforceIntentFilterMatch()
+ && compat.isChangeEnabledByUidInternal(
+ ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS, filterCallingUid);
for (int i = resolveInfos.size() - 1; i >= 0; --i) {
final ComponentInfo info = resolveInfos.get(i).getComponentInfo();
@@ -1237,8 +1237,7 @@
boolean match = false;
for (int j = 0, size = comp.getIntents().size(); j < size; ++j) {
IntentFilter intentFilter = comp.getIntents().get(j).getIntentFilter();
- if (IntentResolver.intentMatchesFilter(
- intentFilter, intent, resolvedType, defaultOnly)) {
+ if (IntentResolver.intentMatchesFilter(intentFilter, intent, resolvedType)) {
match = true;
break;
}
@@ -1557,7 +1556,7 @@
return initiatingPackageName == null || SHELL_PACKAGE_NAME.equals(initiatingPackageName);
}
- public static void linkSplitsToOldDirs(@NonNull Installer installer,
+ public static void linkFilesToOldDirs(@NonNull Installer installer,
@NonNull String packageName,
@NonNull File newPath,
@Nullable Set<File> oldPaths) {
@@ -1572,56 +1571,108 @@
if (filesInNewPath == null || filesInNewPath.length == 0) {
return;
}
- final List<String> splitApkNames = new ArrayList<String>();
- for (int i = 0; i < filesInNewPath.length; i++) {
- if (!filesInNewPath[i].isDirectory() && filesInNewPath[i].toString().endsWith(".apk")) {
- splitApkNames.add(filesInNewPath[i].getName());
+ final List<File> splitApks = new ArrayList<>();
+ for (File file : filesInNewPath) {
+ if (!file.isDirectory() && file.toString().endsWith(".apk")) {
+ splitApks.add(file);
}
}
- final int numSplits = splitApkNames.size();
- if (numSplits == 0) {
+ if (splitApks.isEmpty()) {
return;
}
+ final File[] splitApkNames = splitApks.toArray(new File[0]);
for (File oldPath : oldPaths) {
if (!oldPath.exists()) {
continue;
}
- for (int i = 0; i < numSplits; i++) {
- final String splitApkName = splitApkNames.get(i);
- final File linkedSplit = new File(oldPath, splitApkName);
- if (linkedSplit.exists()) {
- if (DEBUG) {
- Slog.d(PackageManagerService.TAG, "Skipping existing linked split <"
- + linkedSplit + ">");
- }
- continue;
- }
- final File sourceSplit = new File(newPath, splitApkName);
- try {
- installer.linkFile(packageName, splitApkName,
- newPath.getAbsolutePath(), oldPath.getAbsolutePath());
- if (DEBUG) {
- Slog.d(PackageManagerService.TAG, "Linked <"
- + sourceSplit + "> to <" + linkedSplit + ">");
- }
- } catch (Installer.InstallerException e) {
- Slog.w(PackageManagerService.TAG, "Failed to link split <"
- + sourceSplit + " > to <" + linkedSplit + ">", e);
- continue;
- }
- try {
- Os.chmod(linkedSplit.getAbsolutePath(), DEFAULT_FILE_ACCESS_MODE);
- } catch (ErrnoException e) {
- Slog.w(PackageManagerService.TAG, "Failed to set mode for linked split <"
- + linkedSplit + ">", e);
- continue;
- }
- if (!SELinux.restorecon(linkedSplit)) {
- Slog.w(PackageManagerService.TAG, "Failed to restorecon for linked split <"
- + linkedSplit + ">");
- }
+ linkFilesAndSetModes(installer, packageName, newPath, oldPath, splitApkNames,
+ DEFAULT_FILE_ACCESS_MODE);
+ linkNativeLibraries(installer, packageName, newPath, oldPath, LIB_DIR_NAME);
+ linkNativeLibraries(installer, packageName, newPath, oldPath, LIB64_DIR_NAME);
+ }
+
+ }
+
+ private static void linkNativeLibraries(@NonNull Installer installer,
+ @NonNull String packageName,
+ @NonNull File sourcePath, @NonNull File targetPath,
+ @NonNull String libDirName) {
+ final File sourceLibDir = new File(sourcePath, libDirName);
+ if (!sourceLibDir.exists()) {
+ return;
+ }
+ final File targetLibDir = new File(targetPath, libDirName);
+ if (!targetLibDir.exists()) {
+ try {
+ NativeLibraryHelper.createNativeLibrarySubdir(targetLibDir);
+ } catch (IOException e) {
+ Slog.w(PackageManagerService.TAG, "Failed to create native library dir at <"
+ + targetLibDir + ">", e);
+ return;
}
}
- //TODO(b/291212866): support native libs
+ final File[] archs = sourceLibDir.listFiles();
+ if (archs == null) {
+ return;
+ }
+ for (File arch : archs) {
+ final File targetArchDir = new File(targetLibDir, arch.getName());
+ if (!targetArchDir.exists()) {
+ try {
+ NativeLibraryHelper.createNativeLibrarySubdir(targetArchDir);
+ } catch (IOException e) {
+ Slog.w(PackageManagerService.TAG, "Failed to create native library subdir at <"
+ + targetArchDir + ">", e);
+ continue;
+ }
+ }
+ final File sourceArchDir = new File(sourceLibDir, arch.getName());
+ final File[] files = sourceArchDir.listFiles();
+ if (files == null || files.length == 0) {
+ continue;
+ }
+ linkFilesAndSetModes(installer, packageName, sourceArchDir, targetArchDir, files,
+ DEFAULT_NATIVE_LIBRARY_FILE_ACCESS_MODE);
+ }
+ }
+
+ // Link the files with specified names from under the sourcePath to be under the targetPath
+ private static void linkFilesAndSetModes(@NonNull Installer installer, String packageName,
+ @NonNull File sourcePath, @NonNull File targetPath, @NonNull File[] files, int mode) {
+ for (File file : files) {
+ final String fileName = file.getName();
+ final File sourceFile = new File(sourcePath, fileName);
+ final File targetFile = new File(targetPath, fileName);
+ if (targetFile.exists()) {
+ if (DEBUG) {
+ Slog.d(PackageManagerService.TAG, "Skipping existing linked file <"
+ + targetFile + ">");
+ }
+ continue;
+ }
+ try {
+ installer.linkFile(packageName, fileName,
+ sourcePath.getAbsolutePath(), targetPath.getAbsolutePath());
+ if (DEBUG) {
+ Slog.d(PackageManagerService.TAG, "Linked <"
+ + sourceFile + "> to <" + targetFile + ">");
+ }
+ } catch (Installer.InstallerException e) {
+ Slog.w(PackageManagerService.TAG, "Failed to link native library <"
+ + sourceFile + "> to <" + targetFile + ">", e);
+ continue;
+ }
+ try {
+ Os.chmod(targetFile.getAbsolutePath(), mode);
+ } catch (ErrnoException e) {
+ Slog.w(PackageManagerService.TAG, "Failed to set mode for linked file <"
+ + targetFile + ">", e);
+ continue;
+ }
+ if (!SELinux.restorecon(targetFile)) {
+ Slog.w(PackageManagerService.TAG, "Failed to restorecon for linked file <"
+ + targetFile + ">");
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 74c482b..f474d32 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -94,7 +94,8 @@
INSTALL_PERMISSION_FIXED,
UPDATE_AVAILABLE,
FORCE_QUERYABLE_OVERRIDE,
- SCANNED_AS_STOPPED_SYSTEM_APP
+ SCANNED_AS_STOPPED_SYSTEM_APP,
+ PENDING_RESTORE,
})
public @interface Flags {
}
@@ -102,6 +103,7 @@
private static final int UPDATE_AVAILABLE = 1 << 1;
private static final int FORCE_QUERYABLE_OVERRIDE = 1 << 2;
private static final int SCANNED_AS_STOPPED_SYSTEM_APP = 1 << 3;
+ private static final int PENDING_RESTORE = 1 << 4;
}
private int mBooleans;
@@ -543,6 +545,20 @@
return mSharedUserAppId > 0;
}
+ /**
+ * @see PackageState#isPendingRestore()
+ */
+ public PackageSetting setPendingRestore(boolean value) {
+ setBoolean(Booleans.PENDING_RESTORE, value);
+ onChanged();
+ return this;
+ }
+
+ @Override
+ public boolean isPendingRestore() {
+ return getBoolean(Booleans.PENDING_RESTORE);
+ }
+
@Override
public String toString() {
return "PackageSetting{"
diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
index 203e1de..b664e39 100644
--- a/services/core/java/com/android/server/pm/ResolveIntentHelper.java
+++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
@@ -459,7 +459,7 @@
list.add(ri);
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
mPlatformCompat, componentResolver, list, true, intent,
- resolvedType, flags, filterCallingUid);
+ resolvedType, filterCallingUid);
}
}
} else {
@@ -485,7 +485,7 @@
// We also have to ensure all components match the original intent
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
mPlatformCompat, componentResolver,
- list, true, originalIntent, resolvedType, flags, filterCallingUid);
+ list, true, originalIntent, resolvedType, filterCallingUid);
}
return computer.applyPostResolutionFilter(list, instantAppPkgName, false, queryingUid,
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 7cf1d33..c7ee649 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4537,6 +4537,8 @@
t.traceBegin("createNewUser-" + userHandle);
Installer.Batch batch = new Installer.Batch();
final boolean skipPackageAllowList = userTypeInstallablePackages == null;
+ // Use the same timestamp for all system apps that are to be installed on the new user
+ final long currentTimeMillis = System.currentTimeMillis();
synchronized (mLock) {
final int size = mPackages.size();
for (int i = 0; i < size; i++) {
@@ -4552,6 +4554,9 @@
ps.getPackageName()));
// Only system apps are initially installed.
ps.setInstalled(shouldReallyInstall, userHandle);
+ if (Flags.fixSystemAppsFirstInstallTime() && shouldReallyInstall) {
+ ps.setFirstInstallTime(currentTimeMillis, userHandle);
+ }
// Non-Apex system apps, that are not included in the allowlist in
// initialNonStoppedSystemPackages, should be marked as stopped by default.
@@ -4878,7 +4883,7 @@
@NeverCompile // Avoid size overhead of debugging code.
void dumpPackageLPr(PrintWriter pw, String prefix, String checkinTag,
- ArraySet<String> permissionNames, PackageSetting ps,
+ ArraySet<String> permissionNames, @NonNull PackageSetting ps,
LegacyPermissionState permissionsState, SimpleDateFormat sdf, Date date,
List<UserInfo> users, boolean dumpAll, boolean dumpAllComponents) {
AndroidPackage pkg = ps.getPkg();
@@ -5017,6 +5022,10 @@
pw.print(prefix); pw.print(" privateFlags="); printFlags(pw,
privateFlags, PRIVATE_FLAG_DUMP_SPEC); pw.println();
}
+ if (ps.isPendingRestore()) {
+ pw.print(prefix); pw.print(" pendingRestore=true");
+ pw.println();
+ }
if (!pkg.isUpdatableSystem()) {
pw.print(prefix); pw.print(" updatableSystem=false");
pw.println();
@@ -5227,6 +5236,9 @@
pw.print(prefix); pw.print(" privatePkgFlags="); printFlags(pw, ps.getPrivateFlags(),
PRIVATE_FLAG_DUMP_SPEC);
pw.println();
+ if (ps.isPendingRestore()) {
+ pw.print(prefix); pw.println(" pendingRestore=true");
+ }
pw.print(prefix); pw.print(" apexModuleName="); pw.println(ps.getApexModuleName());
if (pkg != null && pkg.getOverlayTarget() != null) {
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index a7ae4eb..e0ee199 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -265,6 +265,14 @@
*/
boolean hasSharedUser();
+
+ /**
+ * Whether this app needs to be restore during next install/update.
+ * E.g. if an app was installed as archived and never had a chance to restore its data.
+ * @hide
+ */
+ boolean isPendingRestore();
+
/**
* Retrieves the shared user app ID. Note that the actual shared user data is not available here
* and must be queried separately.
diff --git a/services/core/java/com/android/server/pm/verify/domain/OWNERS b/services/core/java/com/android/server/pm/verify/domain/OWNERS
index c669112..b451fe4 100644
--- a/services/core/java/com/android/server/pm/verify/domain/OWNERS
+++ b/services/core/java/com/android/server/pm/verify/domain/OWNERS
@@ -1,5 +1,4 @@
# Bug component: 36137
+include /PACKAGE_MANAGER_OWNERS
-chiuwinson@google.com
-patb@google.com
-toddke@google.com
\ No newline at end of file
+wloh@google.com
\ No newline at end of file
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index 61348b5..f15646f 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -43,6 +43,7 @@
import android.app.KeyguardManager;
import android.app.TaskInfo;
import android.app.compat.CompatChanges;
+import android.companion.virtual.VirtualDeviceManager;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.content.BroadcastReceiver;
@@ -234,7 +235,12 @@
this::synchronizeUidPermissionsAndAppOpsAsync);
mAppOpsCallback = new IAppOpsCallback.Stub() {
- public void opChanged(int op, int uid, @Nullable String packageName) {
+ public void opChanged(int op, int uid, @Nullable String packageName,
+ String persistentDeviceId) {
+ if (Objects.equals(persistentDeviceId,
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)) {
+ return;
+ }
if (packageName != null) {
synchronizeUidPermissionsAndAppOpsAsync(uid);
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 1fdcc64..b947aa3 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -75,6 +75,7 @@
import static android.view.WindowManagerGlobal.ADD_PERMISSION_DENIED;
import static android.view.contentprotection.flags.Flags.createAccessibilityOverlayAppOpEnabled;
+import static com.android.hardware.input.Flags.emojiAndScreenshotKeycodesAvailable;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT;
@@ -3779,6 +3780,11 @@
sendSystemKeyToStatusBarAsync(event);
return true;
}
+ case KeyEvent.KEYCODE_SCREENSHOT:
+ if (emojiAndScreenshotKeycodesAvailable() && down && repeatCount == 0) {
+ interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
+ }
+ return true;
}
if (isValidGlobalKey(keyCode)
&& mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) {
@@ -5010,6 +5016,8 @@
case KeyEvent.KEYCODE_STYLUS_BUTTON_SECONDARY:
case KeyEvent.KEYCODE_STYLUS_BUTTON_TERTIARY:
case KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL: {
+ Slog.i(TAG, "Stylus buttons event: " + keyCode + " received. Should handle event? "
+ + mStylusButtonsEnabled);
if (mStylusButtonsEnabled) {
sendSystemKeyToStatusBarAsync(event);
}
@@ -5022,6 +5030,12 @@
case KeyEvent.KEYCODE_MACRO_4:
result &= ~ACTION_PASS_TO_USER;
break;
+ case KeyEvent.KEYCODE_EMOJI_PICKER:
+ if (!emojiAndScreenshotKeycodesAvailable()) {
+ // Don't allow EMOJI_PICKER key to be dispatched until flag is released.
+ result &= ~ACTION_PASS_TO_USER;
+ }
+ break;
}
if (useHapticFeedback) {
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index b271a03..a4c6959 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -18,6 +18,7 @@
import android.annotation.Nullable;
import android.app.ITransientNotificationCallback;
+import android.content.ComponentName;
import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.os.Bundle;
import android.os.IBinder;
@@ -241,4 +242,17 @@
* @see com.android.internal.statusbar.IStatusBar#showMediaOutputSwitcher
*/
void showMediaOutputSwitcher(String packageName);
+
+ /**
+ * Add a tile to the Quick Settings Panel
+ * @param tile the ComponentName of the {@link android.service.quicksettings.TileService}
+ * @param end if true, the tile will be added at the end. If false, at the beginning.
+ */
+ void addQsTileToFrontOrEnd(ComponentName tile, boolean end);
+
+ /**
+ * Remove the tile from the Quick Settings Panel
+ * @param tile the ComponentName of the {@link android.service.quicksettings.TileService}
+ */
+ void removeQsTile(ComponentName tile);
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index b21721a..4955358 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -89,6 +89,7 @@
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
+import android.view.accessibility.Flags;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -834,6 +835,20 @@
}
}
}
+
+ @Override
+ public void addQsTileToFrontOrEnd(ComponentName tile, boolean end) {
+ if (Flags.a11yQsShortcut()) {
+ StatusBarManagerService.this.addQsTileToFrontOrEnd(tile, end);
+ }
+ }
+
+ @Override
+ public void removeQsTile(ComponentName tile) {
+ if (Flags.a11yQsShortcut()) {
+ StatusBarManagerService.this.remTile(tile);
+ }
+ }
};
private final GlobalActionsProvider mGlobalActionsProvider = new GlobalActionsProvider() {
@@ -934,11 +949,26 @@
}
public void addTile(ComponentName component) {
+ if (Flags.a11yQsShortcut()) {
+ addQsTileToFrontOrEnd(component, false);
+ } else {
+ enforceStatusBarOrShell();
+
+ if (mBar != null) {
+ try {
+ mBar.addQsTile(component);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+ }
+
+ private void addQsTileToFrontOrEnd(ComponentName tile, boolean end) {
enforceStatusBarOrShell();
if (mBar != null) {
try {
- mBar.addQsTile(component);
+ mBar.addQsTileToFrontOrEnd(tile, end);
} catch (RemoteException ex) {
}
}
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index 3abebf8..d102054 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -443,6 +443,8 @@
mPendingSuccessfulUnlock = false;
}
+ // It's okay to use the "Inner" version of isDeviceLocked since they differ only for
+ // profiles, which cannot be switched to and thus don't support trust agents anyway.
if (mTrustManagerService.isDeviceLockedInner(mUserId)) {
onDeviceLocked();
} else {
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index dbf777f..2b05993 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -188,25 +188,30 @@
new SparseArray<>();
/**
- * Stores the locked state for users on the device. There are three different type of users
+ * Stores the locked state for users on the device. There are several different types of users
* which are handled slightly differently:
* <ul>
- * <li> Users with real keyguard
+ * <li> Users with real keyguard:
* These are users who can be switched to ({@link UserInfo#supportsSwitchToByUser()}). Their
* locked state is derived by a combination of user secure state, keyguard state, trust agent
* decision and biometric authentication result. These are updated via
* {@link #refreshDeviceLockedForUser(int)} and result stored in {@link #mDeviceLockedForUser}.
- * <li> Managed profiles with unified challenge
- * Managed profile with unified challenge always shares the same locked state as their parent,
+ * <li> Profiles with unified challenge:
+ * Profiles with a unified challenge always share the same locked state as their parent,
* so their locked state is not recorded in {@link #mDeviceLockedForUser}. Instead,
* {@link ITrustManager#isDeviceLocked(int)} always resolves their parent user handle and
* queries its locked state instead.
- * <li> Managed profiles with separate challenge
- * Locked state for profile with separate challenge is determined by other parts of the
- * framework (mostly PowerManager) and pushed to TrustManagerService via
- * {@link ITrustManager#setDeviceLockedForUser(int, boolean)}. Although in a corner case when
- * the profile has a separate but empty challenge, setting its {@link #mDeviceLockedForUser} to
- * {@code false} is actually done by {@link #refreshDeviceLockedForUser(int)}.
+ * <li> Profiles without unified challenge:
+ * The locked state for profiles that do not have a unified challenge (e.g. they have a
+ * separate challenge from their parent, or they have no parent at all) is determined by other
+ * parts of the framework (mostly PowerManager) and pushed to TrustManagerService via
+ * {@link ITrustManager#setDeviceLockedForUser(int, boolean)}.
+ * However, in the case where such a profile has an empty challenge, setting its
+ * {@link #mDeviceLockedForUser} to {@code false} is actually done by
+ * {@link #refreshDeviceLockedForUser(int)}.
+ * (This serves as a corner case for managed profiles with a separate but empty challenge. It
+ * is always currently the case for Communal profiles, for which having a non-empty challenge
+ * is not currently supported.)
* </ul>
* TODO: Rename {@link ITrustManager#setDeviceLockedForUser(int, boolean)} to
* {@code setDeviceLockedForProfile} to better reflect its purpose. Unifying
@@ -794,7 +799,7 @@
/**
* Update the user's locked state. Only applicable to users with a real keyguard
- * ({@link UserInfo#supportsSwitchToByUser}) and unsecured managed profiles.
+ * ({@link UserInfo#supportsSwitchToByUser}) and unsecured profiles.
*
* If this is called due to an unlock operation set unlockedUser to prevent the lock from
* being prematurely reset for that user while keyguard is still in the process of going away.
@@ -826,7 +831,11 @@
boolean secure = mLockPatternUtils.isSecure(id);
if (!info.supportsSwitchToByUser()) {
- if (info.isManagedProfile() && !secure) {
+ if (info.isProfile() && !secure
+ && !mLockPatternUtils.isProfileWithUnifiedChallenge(id)) {
+ // Unsecured profiles need to be explicitly set to false.
+ // However, Unified challenge profiles officially shouldn't have a presence in
+ // mDeviceLockedForUser at all, since that's not how they're tracked.
setDeviceLockedForUser(id, false);
}
continue;
@@ -1853,6 +1862,7 @@
}
}
+ /** If the userId has a parent, returns that parent's userId. Otherwise userId is returned. */
private int resolveProfileParent(int userId) {
final long identity = Binder.clearCallingIdentity();
try {
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index e434df7..089a886 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -2791,6 +2791,29 @@
}
@Override
+ public void notifyTvAdSessionData(
+ IBinder sessionToken, String type, Bundle data, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ final int resolvedUserId = resolveCallingUserId(callingPid, callingUid,
+ userId, "notifyTvAdSessionData");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getSessionLocked(sessionState).notifyTvAdSessionData(type, data);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slog.e(TAG, "error in notifyTvAdSessionData", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public int getClientPid(String sessionId) {
ensureTunerResourceAccessPermission();
final long identity = Binder.clearCallingIdentity();
@@ -4322,6 +4345,23 @@
}
}
}
+
+ @Override
+ public void onTvInputSessionData(String type, Bundle data) {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slog.d(TAG, "onTvInputSessionData()");
+ }
+ if (mSessionState.session == null || mSessionState.client == null) {
+ return;
+ }
+ try {
+ mSessionState.client.onTvInputSessionData(type, data, mSessionState.seq);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in onTvInputSessionData", e);
+ }
+ }
+ }
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index eacd3f8..ffce50e 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -937,6 +937,41 @@
}
@Override
+ public void sendAppLinkCommand(String serviceId, Bundle command, int userId) {
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, "sendAppLinkCommand");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+ TvAdServiceState adServiceState = userState.mAdServiceMap.get(serviceId);
+ if (adServiceState == null) {
+ Slogf.e(TAG, "failed to sendAppLinkCommand - unknown service id "
+ + serviceId);
+ return;
+ }
+ ComponentName componentName = adServiceState.mInfo.getComponent();
+ AdServiceState serviceState = userState.mAdServiceStateMap.get(componentName);
+ if (serviceState == null) {
+ serviceState = new AdServiceState(componentName, serviceId, resolvedUserId);
+ serviceState.addPendingAppLinkCommand(command);
+ userState.mAdServiceStateMap.put(componentName, serviceState);
+ updateAdServiceConnectionLocked(componentName, resolvedUserId);
+ } else if (serviceState.mService != null) {
+ serviceState.mService.sendAppLinkCommand(command);
+ } else {
+ serviceState.addPendingAppLinkCommand(command);
+ updateAdServiceConnectionLocked(componentName, resolvedUserId);
+ }
+ }
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in sendAppLinkCommand", e);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void createSession(final ITvAdClient client, final String serviceId, String type,
int seq, int userId) {
final int callingUid = Binder.getCallingUid();
@@ -1320,6 +1355,32 @@
}
@Override
+ public void notifyTvInputSessionData(
+ IBinder sessionToken, String type, Bundle data, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "notifyTvInputSessionData(type=%d)", type);
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+ "notifyTvInputSessionData");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ AdSessionState sessionState =
+ getAdSessionStateLocked(sessionToken, callingUid, resolvedUserId);
+ getAdSessionLocked(sessionState).notifyTvInputSessionData(type, data);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in notifyTvInputSessionData", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void registerCallback(final ITvAdManagerCallback callback, int userId) {
int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
@@ -4358,6 +4419,110 @@
}
}
+ @Override
+ public void onRequestCurrentVideoBounds() {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onRequestCurrentVideoBounds");
+ }
+ if (mSessionState.mSession == null || mSessionState.mClient == null) {
+ return;
+ }
+ try {
+ mSessionState.mClient.onRequestCurrentVideoBounds(mSessionState.mSeq);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in onRequestCurrentVideoBounds", e);
+ }
+ }
+ }
+
+ @Override
+ public void onRequestCurrentChannelUri() {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onRequestCurrentChannelUri");
+ }
+ if (mSessionState.mSession == null || mSessionState.mClient == null) {
+ return;
+ }
+ try {
+ mSessionState.mClient.onRequestCurrentChannelUri(mSessionState.mSeq);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in onRequestCurrentChannelUri", e);
+ }
+ }
+ }
+
+ @Override
+ public void onRequestTrackInfoList() {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onRequestTrackInfoList");
+ }
+ if (mSessionState.mSession == null || mSessionState.mClient == null) {
+ return;
+ }
+ try {
+ mSessionState.mClient.onRequestTrackInfoList(mSessionState.mSeq);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in onRequestTrackInfoList", e);
+ }
+ }
+ }
+
+ @Override
+ public void onRequestCurrentTvInputId() {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onRequestCurrentTvInputId");
+ }
+ if (mSessionState.mSession == null || mSessionState.mClient == null) {
+ return;
+ }
+ try {
+ mSessionState.mClient.onRequestCurrentTvInputId(mSessionState.mSeq);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in onRequestCurrentTvInputId", e);
+ }
+ }
+ }
+
+
+ @Override
+ public void onRequestSigning(String id, String algorithm, String alias, byte[] data) {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onRequestSigning");
+ }
+ if (mSessionState.mSession == null || mSessionState.mClient == null) {
+ return;
+ }
+ try {
+ mSessionState.mClient.onRequestSigning(
+ id, algorithm, alias, data, mSessionState.mSeq);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in onRequestSigning", e);
+ }
+ }
+ }
+
+ @Override
+ public void onTvAdSessionData(String type, Bundle data) {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onTvAdSessionData");
+ }
+ if (mSessionState.mSession == null || mSessionState.mClient == null) {
+ return;
+ }
+ try {
+ mSessionState.mClient.onTvAdSessionData(type, data, mSessionState.mSeq);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in onTvAdSessionData", e);
+ }
+ }
+ }
+
@GuardedBy("mLock")
private boolean addAdSessionTokenToClientStateLocked(ITvAdSession session) {
try {
diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java
index ed04e5f..1383708 100644
--- a/services/core/java/com/android/server/vcn/VcnContext.java
+++ b/services/core/java/com/android/server/vcn/VcnContext.java
@@ -34,7 +34,7 @@
@NonNull private final Looper mLooper;
@NonNull private final VcnNetworkProvider mVcnNetworkProvider;
@NonNull private final FeatureFlags mFeatureFlags;
- @NonNull private final com.android.net.flags.FeatureFlags mCoreNetFeatureFlags;
+ @NonNull private final android.net.platform.flags.FeatureFlags mCoreNetFeatureFlags;
private final boolean mIsInTestMode;
public VcnContext(
@@ -49,7 +49,7 @@
// Auto-generated class
mFeatureFlags = new FeatureFlagsImpl();
- mCoreNetFeatureFlags = new com.android.net.flags.FeatureFlagsImpl();
+ mCoreNetFeatureFlags = new android.net.platform.flags.FeatureFlagsImpl();
}
@NonNull
diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java
index 0a7872f..c9805c7 100644
--- a/services/core/java/com/android/server/vibrator/VibrationScaler.java
+++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java
@@ -17,10 +17,10 @@
package com.android.server.vibrator;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.Context;
import android.hardware.vibrator.V1_0.EffectStrength;
import android.os.IExternalVibratorService;
+import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.vibrator.Flags;
@@ -57,8 +57,7 @@
private final SparseArray<ScaleLevel> mScaleLevels;
private final VibrationSettings mSettingsController;
private final int mDefaultVibrationAmplitude;
-
- private SparseArray<Float> mAdaptiveHapticsScales;
+ private final SparseArray<Float> mAdaptiveHapticsScales = new SparseArray<>();
VibrationScaler(Context context, VibrationSettings settingsController) {
mSettingsController = settingsController;
@@ -147,7 +146,7 @@
// If adaptive haptics scaling is available for this usage, apply it to the segment.
if (Flags.adaptiveHapticsEnabled()
- && mAdaptiveHapticsScales != null && mAdaptiveHapticsScales.size() > 0
+ && mAdaptiveHapticsScales.size() > 0
&& mAdaptiveHapticsScales.contains(usageHint)) {
float adaptiveScale = mAdaptiveHapticsScales.get(usageHint);
segment = segment.scale(adaptiveScale);
@@ -187,13 +186,35 @@
}
/**
- * Updates the adaptive haptics scales.
- * @param scales the new vibration scales to apply.
+ * Updates the adaptive haptics scales list by adding or modifying the scale for this usage.
+ *
+ * @param usageHint one of VibrationAttributes.USAGE_*.
+ * @param scale The scaling factor that should be applied to vibrations of this usage.
*
* @hide
*/
- public void updateAdaptiveHapticsScales(@Nullable SparseArray<Float> scales) {
- mAdaptiveHapticsScales = scales;
+ public void updateAdaptiveHapticsScale(@VibrationAttributes.Usage int usageHint, float scale) {
+ mAdaptiveHapticsScales.put(usageHint, scale);
+ }
+
+ /**
+ * Removes the usage from the cached adaptive haptics scales list.
+ *
+ * @param usageHint one of VibrationAttributes.USAGE_*.
+ *
+ * @hide
+ */
+ public void removeAdaptiveHapticsScale(@VibrationAttributes.Usage int usageHint) {
+ mAdaptiveHapticsScales.remove(usageHint);
+ }
+
+ /**
+ * Removes all cached adaptive haptics scales.
+ *
+ * @hide
+ */
+ public void clearAdaptiveHapticsScales() {
+ mAdaptiveHapticsScales.clear();
}
/** Mapping of Vibrator.VIBRATION_INTENSITY_* values to {@link EffectStrength}. */
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 839c207..fab0430 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -376,6 +376,25 @@
}
/**
+ * Returns the duration, in milliseconds, that the vibrator control service will wait for new
+ * vibration params.
+ * @return The request vibration params timeout in milliseconds.
+ * @hide
+ */
+ public int getRequestVibrationParamsTimeoutMs() {
+ return mVibrationConfig.getRequestVibrationParamsTimeoutMs();
+ }
+
+ /**
+ * The list of usages that should request vibration params before they are played. These
+ * usages don't have strong latency requirements, e.g. ringtone and notification, and can be
+ * slightly delayed.
+ */
+ public int[] getRequestVibrationParamsForUsages() {
+ return mVibrationConfig.getRequestVibrationParamsForUsages();
+ }
+
+ /**
* Return a {@link VibrationEffect} that should be played if the device do not support given
* {@code effectId}.
*
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index 624da80..9cf942e 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -21,7 +21,9 @@
import android.os.Build;
import android.os.CombinedVibration;
import android.os.IBinder;
+import android.os.VibrationAttributes;
import android.os.VibrationEffect;
+import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.RampSegment;
@@ -38,6 +40,8 @@
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
/**
* Creates and manages a queue of steps for performing a VibrationEffect, as well as coordinating
@@ -65,15 +69,18 @@
public final VibrationThread.VibratorManagerHooks vibratorManagerHooks;
private final DeviceAdapter mDeviceAdapter;
+ private final VibrationScaler mVibrationScaler;
// Not guarded by lock because it's mostly used to read immutable fields by this conductor.
// This is only modified here at the prepareToStart method which always runs at the vibration
// thread, to update the adapted effect and report start time.
private final HalVibration mVibration;
-
private final PriorityQueue<Step> mNextSteps = new PriorityQueue<>();
private final Queue<Step> mPendingOnVibratorCompleteSteps = new LinkedList<>();
+ @Nullable
+ private final CompletableFuture<Void> mRequestVibrationParamsFuture;
+
// Signalling fields.
// Note that vibrator callback signals may happen inside vibrator HAL calls made by the
// VibrationThread, or on an external executor, so this lock should not be held for anything
@@ -97,10 +104,14 @@
VibrationStepConductor(HalVibration vib, VibrationSettings vibrationSettings,
DeviceAdapter deviceAdapter,
+ VibrationScaler vibrationScaler,
+ CompletableFuture<Void> requestVibrationParamsFuture,
VibrationThread.VibratorManagerHooks vibratorManagerHooks) {
this.mVibration = vib;
this.vibrationSettings = vibrationSettings;
this.mDeviceAdapter = deviceAdapter;
+ mVibrationScaler = vibrationScaler;
+ mRequestVibrationParamsFuture = requestVibrationParamsFuture;
this.vibratorManagerHooks = vibratorManagerHooks;
this.mSignalVibratorsComplete =
new IntArray(mDeviceAdapter.getAvailableVibratorIds().length);
@@ -143,7 +154,15 @@
if (Build.IS_DEBUGGABLE) {
expectIsVibrationThread(true);
}
- // Scaling happened before the effect was dispatched to this conductor (or to input devices)
+
+ if (!mVibration.callerInfo.attrs.isFlagSet(
+ VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)) {
+ if (Flags.adaptiveHapticsEnabled()) {
+ waitForVibrationParamsIfRequired();
+ }
+ mVibration.scaleEffects(mVibrationScaler::scale);
+ }
+
mVibration.adaptToDevice(mDeviceAdapter);
CombinedVibration.Sequential sequentialEffect = toSequential(mVibration.getEffectToPlay());
mPendingVibrateSteps++;
@@ -361,6 +380,9 @@
+ mSignalCancelImmediate);
}
}
+ if (mRequestVibrationParamsFuture != null) {
+ mRequestVibrationParamsFuture.cancel(/* mayInterruptIfRunning= */true);
+ }
mLock.notify();
}
}
@@ -420,6 +442,30 @@
}
}
+ /**
+ * Blocks until the vibration params future is complete.
+ *
+ * This should be called by the VibrationThread and may be interrupted by calling
+ * `notifyCancelled` from outside it.
+ */
+ private void waitForVibrationParamsIfRequired() {
+ if (Build.IS_DEBUGGABLE) {
+ expectIsVibrationThread(true);
+ }
+
+ if (mRequestVibrationParamsFuture == null) {
+ return;
+ }
+
+ try {
+ mRequestVibrationParamsFuture.orTimeout(
+ vibrationSettings.getRequestVibrationParamsTimeoutMs(),
+ TimeUnit.MILLISECONDS).get();
+ } catch (Throwable e) {
+ Slog.w(TAG, "Failed to retrieve vibration params.", e);
+ }
+ }
+
@GuardedBy("mLock")
private boolean hasPendingNotifySignalLocked() {
if (Build.IS_DEBUGGABLE) {
diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java
index 9d75249..8f8fe3c 100644
--- a/services/core/java/com/android/server/vibrator/VibratorControlService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java
@@ -16,11 +16,13 @@
package com.android.server.vibrator;
+import static android.os.VibrationAttributes.USAGE_ACCESSIBILITY;
import static android.os.VibrationAttributes.USAGE_ALARM;
import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
import static android.os.VibrationAttributes.USAGE_HARDWARE_FEEDBACK;
import static android.os.VibrationAttributes.USAGE_MEDIA;
import static android.os.VibrationAttributes.USAGE_NOTIFICATION;
+import static android.os.VibrationAttributes.USAGE_PHYSICAL_EMULATION;
import static android.os.VibrationAttributes.USAGE_RINGTONE;
import static android.os.VibrationAttributes.USAGE_TOUCH;
import static android.os.VibrationAttributes.USAGE_UNKNOWN;
@@ -32,12 +34,18 @@
import android.frameworks.vibrator.IVibratorController;
import android.frameworks.vibrator.ScaleParam;
import android.frameworks.vibrator.VibrationParam;
+import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.VibrationAttributes;
import android.util.Slog;
-import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
/**
* Implementation of {@link IVibratorControlService} which allows the registration of
@@ -47,16 +55,25 @@
*/
public final class VibratorControlService extends IVibratorControlService.Stub {
private static final String TAG = "VibratorControlService";
+ private static final int UNRECOGNIZED_VIBRATION_TYPE = -1;
+ private static final int NO_SCALE = -1;
private final VibratorControllerHolder mVibratorControllerHolder;
private final VibrationScaler mVibrationScaler;
private final Object mLock;
+ private final int[] mRequestVibrationParamsForUsages;
+
+ @GuardedBy("mLock")
+ private CompletableFuture<Void> mRequestVibrationParamsFuture = null;
+ @GuardedBy("mLock")
+ private IBinder mRequestVibrationParamsToken;
public VibratorControlService(VibratorControllerHolder vibratorControllerHolder,
- VibrationScaler vibrationScaler, Object lock) {
+ VibrationScaler vibrationScaler, VibrationSettings vibrationSettings, Object lock) {
mVibratorControllerHolder = vibratorControllerHolder;
mVibrationScaler = vibrationScaler;
mLock = lock;
+ mRequestVibrationParamsForUsages = vibrationSettings.getRequestVibrationParamsForUsages();
}
@Override
@@ -85,8 +102,9 @@
+ "controller doesn't match the registered one. " + this);
return;
}
- updateAdaptiveHapticsScales(/* params= */ null);
+ mVibrationScaler.clearAdaptiveHapticsScales();
mVibratorControllerHolder.setVibratorController(null);
+ endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ true);
}
}
@@ -131,10 +149,8 @@
+ "controller doesn't match the registered one. " + this);
return;
}
- //TODO(305942827): Update this method to only clear the specified vibration types.
- // Perhaps look into whether it makes more sense to have this clear all scales and
- // rely on setVibrationParams for clearing the scales for specific vibrations.
- updateAdaptiveHapticsScales(/* params= */ null);
+
+ updateAdaptiveHapticsScales(types, NO_SCALE);
}
}
@@ -142,7 +158,26 @@
public void onRequestVibrationParamsComplete(
@NonNull IBinder requestToken, @SuppressLint("ArrayReturn") VibrationParam[] result)
throws RemoteException {
- // TODO(305942827): Cache the vibration params in VibrationScaler
+ Objects.requireNonNull(requestToken);
+
+ synchronized (mLock) {
+ if (mRequestVibrationParamsToken == null) {
+ Slog.wtf(TAG,
+ "New vibration params received but no token was cached in the service. "
+ + "New vibration params ignored.");
+ return;
+ }
+
+ if (!Objects.equals(requestToken, mRequestVibrationParamsToken)) {
+ Slog.w(TAG,
+ "New vibration params received but the provided token does not match the "
+ + "cached one. New vibration params ignored.");
+ return;
+ }
+
+ updateAdaptiveHapticsScales(result);
+ endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ false);
+ }
}
@Override
@@ -156,50 +191,190 @@
}
/**
- * Extracts the vibration scales and caches them in {@link VibrationScaler}.
+ * If an {@link IVibratorController} is registered to the service, it will request the latest
+ * vibration params and return a {@link CompletableFuture} that completes when the request is
+ * fulfilled. Otherwise, ignores the call and returns null.
*
- * @param params the new vibration params to cache.
+ * @param usage a {@link android.os.VibrationAttributes} usage.
+ * @param timeoutInMillis the request's timeout in millis.
+ * @return a {@link CompletableFuture} to track the completion of the vibration param
+ * request, or null if no {@link IVibratorController} is registered.
*/
- private void updateAdaptiveHapticsScales(@Nullable VibrationParam[] params) {
- if (params == null || params.length == 0) {
- mVibrationScaler.updateAdaptiveHapticsScales(null);
- return;
- }
+ @Nullable
+ public CompletableFuture<Void> triggerVibrationParamsRequest(
+ @VibrationAttributes.Usage int usage, int timeoutInMillis) {
+ synchronized (mLock) {
+ IVibratorController vibratorController =
+ mVibratorControllerHolder.getVibratorController();
+ if (vibratorController == null) {
+ Slog.d(TAG, "Unable to request vibration params. There is no registered "
+ + "IVibrationController.");
+ return null;
+ }
- SparseArray<Float> vibrationScales = new SparseArray<>();
- for (int i = 0; i < params.length; i++) {
- ScaleParam scaleParam = params[i].getScale();
- extractVibrationScales(scaleParam, vibrationScales);
+ int vibrationType = mapToAdaptiveVibrationType(usage);
+ if (vibrationType == UNRECOGNIZED_VIBRATION_TYPE) {
+ Slog.d(TAG, "Unable to request vibration params. The provided usage " + usage
+ + " is unrecognized.");
+ return null;
+ }
+
+ try {
+ endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ true);
+ mRequestVibrationParamsFuture = new CompletableFuture<>();
+ mRequestVibrationParamsToken = new Binder();
+ vibratorController.requestVibrationParams(vibrationType, timeoutInMillis,
+ mRequestVibrationParamsToken);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to request vibration params.", e);
+ endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ true);
+ }
+
+ return mRequestVibrationParamsFuture;
}
- mVibrationScaler.updateAdaptiveHapticsScales(vibrationScales);
}
/**
- * Extracts the vibration scales and map them to their corresponding
- * {@link android.os.VibrationAttributes} usages.
+ * If an {@link IVibratorController} is registered to the service, then it checks whether to
+ * request new vibration params before playing the vibration. Returns true if the
+ * usage is for high latency vibrations, e.g. ringtone and notification, and can be delayed
+ * slightly. Otherwise, returns false.
+ *
+ * @param usage a {@link android.os.VibrationAttributes} usage.
+ * @return true if usage is for high latency vibrations, false otherwise.
*/
- private void extractVibrationScales(ScaleParam scaleParam, SparseArray<Float> vibrationScales) {
- if ((ScaleParam.TYPE_ALARM & scaleParam.typesMask) != 0) {
- vibrationScales.put(USAGE_ALARM, scaleParam.scale);
+ public boolean shouldRequestVibrationParams(@VibrationAttributes.Usage int usage) {
+ synchronized (mLock) {
+ IVibratorController vibratorController =
+ mVibratorControllerHolder.getVibratorController();
+ if (vibratorController == null) {
+ Slog.d(TAG, "Unable to check if should request vibration params. "
+ + "There is no registered IVibrationController.");
+ return false;
+ }
+
+ return ArrayUtils.contains(mRequestVibrationParamsForUsages, usage);
+ }
+ }
+
+ /**
+ * Returns the {@link #mRequestVibrationParamsToken} which is used to validate
+ * {@link #onRequestVibrationParamsComplete(IBinder, VibrationParam[])} calls.
+ */
+ @VisibleForTesting
+ public IBinder getRequestVibrationParamsToken() {
+ synchronized (mLock) {
+ return mRequestVibrationParamsToken;
+ }
+ }
+
+ /**
+ * Completes or cancels the vibration params request future and resets the future and token
+ * to null.
+ * @param wasCancelled specifies whether the future should be ended by being cancelled or not.
+ */
+ @GuardedBy("mLock")
+ private void endOngoingRequestVibrationParamsLocked(boolean wasCancelled) {
+ mRequestVibrationParamsToken = null;
+ if (mRequestVibrationParamsFuture == null) {
+ return;
}
- if ((ScaleParam.TYPE_NOTIFICATION & scaleParam.typesMask) != 0) {
- vibrationScales.put(USAGE_NOTIFICATION, scaleParam.scale);
- vibrationScales.put(USAGE_COMMUNICATION_REQUEST, scaleParam.scale);
+ if (wasCancelled) {
+ mRequestVibrationParamsFuture.cancel(/* mayInterruptIfRunning= */ true);
+ } else {
+ mRequestVibrationParamsFuture.complete(null);
}
- if ((ScaleParam.TYPE_RINGTONE & scaleParam.typesMask) != 0) {
- vibrationScales.put(USAGE_RINGTONE, scaleParam.scale);
+ mRequestVibrationParamsFuture = null;
+ }
+
+ private static int mapToAdaptiveVibrationType(@VibrationAttributes.Usage int usage) {
+ switch (usage) {
+ case USAGE_ALARM -> {
+ return ScaleParam.TYPE_ALARM;
+ }
+ case USAGE_NOTIFICATION, USAGE_COMMUNICATION_REQUEST -> {
+ return ScaleParam.TYPE_NOTIFICATION;
+ }
+ case USAGE_RINGTONE -> {
+ return ScaleParam.TYPE_RINGTONE;
+ }
+ case USAGE_MEDIA, USAGE_UNKNOWN -> {
+ return ScaleParam.TYPE_MEDIA;
+ }
+ case USAGE_TOUCH, USAGE_HARDWARE_FEEDBACK, USAGE_ACCESSIBILITY,
+ USAGE_PHYSICAL_EMULATION -> {
+ return ScaleParam.TYPE_INTERACTIVE;
+ }
+ default -> {
+ Slog.w(TAG, "Unrecognized vibration usage " + usage);
+ return UNRECOGNIZED_VIBRATION_TYPE;
+ }
+ }
+ }
+
+ /**
+ * Updates the adaptive haptics scales cached in {@link VibrationScaler} with the
+ * provided params.
+ *
+ * @param params the new vibration params.
+ */
+ private void updateAdaptiveHapticsScales(@Nullable VibrationParam[] params) {
+ for (VibrationParam param : params) {
+ ScaleParam scaleParam = param.getScale();
+ updateAdaptiveHapticsScales(scaleParam.typesMask, scaleParam.scale);
+ }
+ }
+
+ /**
+ * Updates the adaptive haptics scales, cached in {@link VibrationScaler}, for the provided
+ * vibration types.
+ *
+ * @param types The type of vibrations.
+ * @param scale The scaling factor that should be applied to the vibrations.
+ */
+ private void updateAdaptiveHapticsScales(int types, float scale) {
+ if ((ScaleParam.TYPE_ALARM & types) != 0) {
+ updateOrRemoveAdaptiveHapticsScale(USAGE_ALARM, scale);
}
- if ((ScaleParam.TYPE_MEDIA & scaleParam.typesMask) != 0) {
- vibrationScales.put(USAGE_MEDIA, scaleParam.scale);
- vibrationScales.put(USAGE_UNKNOWN, scaleParam.scale);
+ if ((ScaleParam.TYPE_NOTIFICATION & types) != 0) {
+ updateOrRemoveAdaptiveHapticsScale(USAGE_NOTIFICATION, scale);
+ updateOrRemoveAdaptiveHapticsScale(USAGE_COMMUNICATION_REQUEST, scale);
}
- if ((ScaleParam.TYPE_INTERACTIVE & scaleParam.typesMask) != 0) {
- vibrationScales.put(USAGE_TOUCH, scaleParam.scale);
- vibrationScales.put(USAGE_HARDWARE_FEEDBACK, scaleParam.scale);
+ if ((ScaleParam.TYPE_RINGTONE & types) != 0) {
+ updateOrRemoveAdaptiveHapticsScale(USAGE_RINGTONE, scale);
}
+
+ if ((ScaleParam.TYPE_MEDIA & types) != 0) {
+ updateOrRemoveAdaptiveHapticsScale(USAGE_MEDIA, scale);
+ updateOrRemoveAdaptiveHapticsScale(USAGE_UNKNOWN, scale);
+ }
+
+ if ((ScaleParam.TYPE_INTERACTIVE & types) != 0) {
+ updateOrRemoveAdaptiveHapticsScale(USAGE_TOUCH, scale);
+ updateOrRemoveAdaptiveHapticsScale(USAGE_HARDWARE_FEEDBACK, scale);
+ }
+ }
+
+ /**
+ * Updates or removes the adaptive haptics scale for the specified usage. If the scale is set
+ * to {@link #NO_SCALE} then it will be removed from the cached usage scales in
+ * {@link VibrationScaler}. Otherwise, the cached usage scale will be updated by the new value.
+ *
+ * @param usageHint one of VibrationAttributes.USAGE_*.
+ * @param scale The scaling factor that should be applied to the vibrations. If set to
+ * {@link #NO_SCALE} then the scale will be removed.
+ */
+ private void updateOrRemoveAdaptiveHapticsScale(@VibrationAttributes.Usage int usageHint,
+ float scale) {
+ if (scale == NO_SCALE) {
+ mVibrationScaler.removeAdaptiveHapticsScale(usageHint);
+ return;
+ }
+
+ mVibrationScaler.updateAdaptiveHapticsScale(usageHint, scale);
}
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java b/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java
index 63e69db..79a99b3 100644
--- a/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java
+++ b/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java
@@ -57,7 +57,7 @@
@Override
public void binderDied(@NonNull IBinder deadBinder) {
- if (deadBinder == mVibratorController.asBinder()) {
+ if (mVibratorController != null && deadBinder == mVibratorController.asBinder()) {
setVibratorController(null);
}
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 2c1ab95..759450b 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -53,6 +53,7 @@
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.VibratorInfo;
+import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.VibrationEffectSegment;
import android.os.vibrator.VibratorInfoFactory;
@@ -84,6 +85,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -161,6 +163,7 @@
private final VibrationSettings mVibrationSettings;
private final VibrationScaler mVibrationScaler;
+ private final VibratorControlService mVibratorControlService;
private final InputDeviceDelegate mInputDeviceDelegate;
private final DeviceAdapter mDeviceAdapter;
@@ -212,6 +215,9 @@
mVibrationSettings = new VibrationSettings(mContext, mHandler);
mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings);
+ mVibratorControlService = new VibratorControlService(
+ injector.createVibratorControllerHolder(), mVibrationScaler, mVibrationSettings,
+ mLock);
mInputDeviceDelegate = new InputDeviceDelegate(mContext, mHandler);
VibrationCompleteListener listener = new VibrationCompleteListener(this);
@@ -272,9 +278,7 @@
injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService());
if (ServiceManager.isDeclared(VIBRATOR_CONTROL_SERVICE)) {
- injector.addService(VIBRATOR_CONTROL_SERVICE,
- new VibratorControlService(new VibratorControllerHolder(), mVibrationScaler,
- mLock));
+ injector.addService(VIBRATOR_CONTROL_SERVICE, mVibratorControlService);
}
}
@@ -783,19 +787,12 @@
private Vibration.EndInfo startVibrationLocked(HalVibration vib) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
try {
- if (!vib.callerInfo.attrs.isFlagSet(
- VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)) {
- // Scale effect before dispatching it to the input devices or the vibration thread.
- vib.scaleEffects(mVibrationScaler::scale);
- }
- boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable(
- vib.callerInfo, vib.getEffectToPlay());
- if (inputDevicesAvailable) {
- return new Vibration.EndInfo(Vibration.Status.FORWARDED_TO_INPUT_DEVICES);
+ if (mInputDeviceDelegate.isAvailable()) {
+ return startVibrationOnInputDevicesLocked(vib);
}
- VibrationStepConductor conductor = new VibrationStepConductor(vib, mVibrationSettings,
- mDeviceAdapter, mVibrationThreadCallbacks);
+ VibrationStepConductor conductor = createVibrationStepConductor(vib);
+
if (mCurrentVibration == null) {
return startVibrationOnThreadLocked(conductor);
}
@@ -866,6 +863,34 @@
vib.getStatsInfo(/* completionUptimeMillis= */ SystemClock.uptimeMillis()));
}
+ private VibrationStepConductor createVibrationStepConductor(HalVibration vib) {
+ CompletableFuture<Void> requestVibrationParamsFuture = null;
+
+ if (Flags.adaptiveHapticsEnabled() && !vib.callerInfo.attrs.isFlagSet(
+ VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)
+ && mVibratorControlService.shouldRequestVibrationParams(
+ vib.callerInfo.attrs.getUsage())) {
+ requestVibrationParamsFuture =
+ mVibratorControlService.triggerVibrationParamsRequest(
+ vib.callerInfo.attrs.getUsage(),
+ mVibrationSettings.getRequestVibrationParamsTimeoutMs());
+ }
+
+ return new VibrationStepConductor(vib, mVibrationSettings, mDeviceAdapter, mVibrationScaler,
+ requestVibrationParamsFuture, mVibrationThreadCallbacks);
+ }
+
+ private Vibration.EndInfo startVibrationOnInputDevicesLocked(HalVibration vib) {
+ if (!vib.callerInfo.attrs.isFlagSet(
+ VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)) {
+ // Scale effect before dispatching it to the input devices.
+ vib.scaleEffects(mVibrationScaler::scale);
+ }
+ mInputDeviceDelegate.vibrateIfAvailable(vib.callerInfo, vib.getEffectToPlay());
+
+ return new Vibration.EndInfo(Vibration.Status.FORWARDED_TO_INPUT_DEVICES);
+ }
+
private void logVibrationStatus(int uid, VibrationAttributes attrs,
Vibration.Status status) {
switch (status) {
@@ -1395,6 +1420,10 @@
void addService(String name, IBinder service) {
ServiceManager.addService(name, service);
}
+
+ VibratorControllerHolder createVibratorControllerHolder() {
+ return new VibratorControllerHolder();
+ }
}
/**
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index 8549957..37f3825 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -347,7 +347,7 @@
for (Rect crop : relativeCropHints) {
Rect originalRect = new Rect(crop);
originalRect.scale(wallpaper.mSampleSize);
- originalRect.offset(wallpaper.cropHint.left, wallpaper.cropHint.right);
+ originalRect.offset(wallpaper.cropHint.left, wallpaper.cropHint.top);
result.add(originalRect);
}
return result;
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index fa8c35a..19ea9f9 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -626,6 +626,8 @@
private boolean mForceShowMagnifiableBounds = false;
+ private final MagnificationSpec mMagnificationSpec = new MagnificationSpec();
+
DisplayMagnifier(WindowManagerService windowManagerService,
DisplayContent displayContent,
Display display,
@@ -655,13 +657,28 @@
mAccessibilityTracing.logTrace(LOG_TAG + ".setMagnificationSpec",
FLAGS_MAGNIFICATION_CALLBACK, "spec={" + spec + "}");
}
- mMagnifedViewport.updateMagnificationSpec(spec);
+ updateMagnificationSpec(spec);
mMagnifedViewport.recomputeBounds();
mService.applyMagnificationSpecLocked(mDisplay.getDisplayId(), spec);
mService.scheduleAnimationLocked();
}
+ void updateMagnificationSpec(MagnificationSpec spec) {
+ if (spec != null) {
+ mMagnificationSpec.initialize(spec.scale, spec.offsetX, spec.offsetY);
+ } else {
+ mMagnificationSpec.clear();
+ }
+ // If this message is pending we are in a rotation animation and do not want
+ // to show the border. We will do so when the pending message is handled.
+ if (!mHandler.hasMessages(
+ MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)) {
+ mMagnifedViewport.setMagnifiedRegionBorderShown(
+ isForceShowingMagnifiableBounds(), true);
+ }
+ }
+
void setForceShowMagnifiableBounds(boolean show) {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
mAccessibilityTracing.logTrace(LOG_TAG + ".setForceShowMagnifiableBounds",
@@ -800,8 +817,7 @@
case WindowManager.LayoutParams.TYPE_QS_DIALOG:
case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL: {
Rect magnifiedRegionBounds = mTempRect2;
- mMagnifedViewport.getMagnifiedFrameInContentCoords(
- magnifiedRegionBounds);
+ getMagnifiedFrameInContentCoords(magnifiedRegionBounds);
Rect touchableRegionBounds = mTempRect1;
windowState.getTouchableRegion(mTempRegion1);
mTempRegion1.getBounds(touchableRegionBounds);
@@ -818,6 +834,14 @@
}
}
+ void getMagnifiedFrameInContentCoords(Rect rect) {
+ Region magnificationRegion = new Region();
+ mMagnifedViewport.getMagnificationRegion(magnificationRegion);
+ magnificationRegion.getBounds(rect);
+ rect.offset((int) -mMagnificationSpec.offsetX, (int) -mMagnificationSpec.offsetY);
+ rect.scale(1.0f / mMagnificationSpec.scale);
+ }
+
void notifyImeWindowVisibilityChanged(boolean shown) {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
mAccessibilityTracing.logTrace(LOG_TAG + ".notifyImeWindowVisibilityChanged",
@@ -832,13 +856,13 @@
mAccessibilityTracing.logTrace(LOG_TAG + ".getMagnificationSpecForWindow",
FLAGS_MAGNIFICATION_CALLBACK, "windowState={" + windowState + "}");
}
- MagnificationSpec spec = mMagnifedViewport.getMagnificationSpec();
- if (spec != null && !spec.isNop()) {
+
+ if (mMagnificationSpec != null && !mMagnificationSpec.isNop()) {
if (!windowState.shouldMagnify()) {
return null;
}
}
- return spec;
+ return mMagnificationSpec;
}
void getMagnificationRegion(Region outMagnificationRegion) {
@@ -852,6 +876,10 @@
mMagnifedViewport.getMagnificationRegion(outMagnificationRegion);
}
+ boolean isMagnifying() {
+ return mMagnificationSpec.scale > 1.0f;
+ }
+
void destroy() {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
mAccessibilityTracing.logTrace(LOG_TAG + ".destroy", FLAGS_MAGNIFICATION_CALLBACK);
@@ -897,8 +925,6 @@
private final Path mCircularPath;
- private final MagnificationSpec mMagnificationSpec = new MagnificationSpec();
-
private final float mBorderWidth;
private final int mHalfBorderWidth;
private final int mDrawBorderInset;
@@ -932,20 +958,6 @@
outMagnificationRegion.set(mMagnificationRegion);
}
- void updateMagnificationSpec(MagnificationSpec spec) {
- if (spec != null) {
- mMagnificationSpec.initialize(spec.scale, spec.offsetX, spec.offsetY);
- } else {
- mMagnificationSpec.clear();
- }
- // If this message is pending we are in a rotation animation and do not want
- // to show the border. We will do so when the pending message is handled.
- if (!mHandler.hasMessages(
- MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)) {
- setMagnifiedRegionBorderShown(isForceShowingMagnifiableBounds(), true);
- }
- }
-
void recomputeBounds() {
getDisplaySizeLocked(mScreenSize);
final int screenWidth = mScreenSize.x;
@@ -1127,21 +1139,6 @@
}
}
- void getMagnifiedFrameInContentCoords(Rect rect) {
- MagnificationSpec spec = mMagnificationSpec;
- mMagnificationRegion.getBounds(rect);
- rect.offset((int) -spec.offsetX, (int) -spec.offsetY);
- rect.scale(1.0f / spec.scale);
- }
-
- boolean isMagnifying() {
- return mMagnificationSpec.scale > 1.0f;
- }
-
- MagnificationSpec getMagnificationSpec() {
- return mMagnificationSpec;
- }
-
void drawWindowIfNeeded() {
recomputeBounds();
mWindow.postDrawIfNeeded();
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 2e0546e..1128d0c 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -1319,9 +1319,33 @@
try {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
- if (r != null) {
+ if (r == null) return;
+ final TransitionController controller = r.mTransitionController;
+ if (!controller.isShellTransitionsEnabled()) {
r.setShowWhenLocked(showWhenLocked);
+ return;
}
+ if (controller.isCollecting()
+ && !mService.mKeyguardController.isKeyguardLocked(r.getDisplayId())) {
+ // Keyguard isn't locked, so this can be done as part of the collecting
+ // transition.
+ r.setShowWhenLocked(showWhenLocked);
+ return;
+ }
+ final Transition transition = new Transition(
+ showWhenLocked ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK,
+ 0 /* flags */, controller, mService.mWindowManager.mSyncEngine);
+ r.mTransitionController.startCollectOrQueue(transition, (deferred) -> {
+ transition.collect(r);
+ r.setShowWhenLocked(showWhenLocked);
+ if (transition.isNoOp()) {
+ transition.abort();
+ return;
+ }
+ controller.requestStartTransition(transition, null /* trigger */,
+ null /* remoteTransition */, null /* displayChange */);
+ transition.setReady(r, true);
+ });
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -1334,9 +1358,34 @@
try {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
- if (r != null) {
+ if (r == null) return;
+ final TransitionController controller = r.mTransitionController;
+ // If shell transitions is not enabled just set it directly.
+ if (!controller.isShellTransitionsEnabled()) {
r.setInheritShowWhenLocked(inheritShowWhenLocked);
+ return;
}
+ if (controller.isCollecting()
+ && !mService.mKeyguardController.isKeyguardLocked(r.getDisplayId())) {
+ // Keyguard isn't locked, so this can be done as part of the collecting
+ // transition.
+ r.setInheritShowWhenLocked(inheritShowWhenLocked);
+ return;
+ }
+ final Transition transition = new Transition(
+ inheritShowWhenLocked ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK,
+ 0 /* flags */, controller, mService.mWindowManager.mSyncEngine);
+ r.mTransitionController.startCollectOrQueue(transition, (deferred) -> {
+ transition.collect(r);
+ r.setInheritShowWhenLocked(inheritShowWhenLocked);
+ if (transition.isNoOp()) {
+ transition.abort();
+ return;
+ }
+ controller.requestStartTransition(transition, null /* trigger */,
+ null /* remoteTransition */, null /* displayChange */);
+ transition.setReady(r, true);
+ });
}
} finally {
Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index d6f52b8..85580ac 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1034,20 +1034,19 @@
}
if (err == ActivityManager.START_SUCCESS && aInfo == null) {
+ // We couldn't find the specific class specified in the Intent.
+ err = ActivityManager.START_CLASS_NOT_FOUND;
+
if (isArchivingEnabled()) {
PackageArchiver packageArchiver = mService
.getPackageManagerInternalLocked()
.getPackageArchiver();
if (packageArchiver.isIntentResolvedToArchivedApp(intent, mRequest.userId)) {
- return packageArchiver
+ err = packageArchiver
.requestUnarchiveOnActivityStart(
intent, callingPackage, mRequest.userId, realCallingUid);
}
}
-
- // We couldn't find the specific class specified in the Intent.
- // Also the end of the line.
- err = ActivityManager.START_CLASS_NOT_FOUND;
}
if (err == ActivityManager.START_SUCCESS && sourceRecord != null
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 3397a3d..0def5a1 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3505,8 +3505,9 @@
}
@Override
- public boolean isAssistDataAllowedOnCurrentActivity() {
+ public boolean isAssistDataAllowed() {
int userId;
+ boolean hasRestrictedWindow;
synchronized (mGlobalLock) {
final Task focusedRootTask = getTopDisplayFocusedRootTask();
if (focusedRootTask == null || focusedRootTask.isActivityTypeAssistant()) {
@@ -3518,8 +3519,17 @@
return false;
}
userId = activity.mUserId;
+ DisplayContent displayContent = activity.getDisplayContent();
+ if (displayContent == null) {
+ return false;
+ }
+ hasRestrictedWindow = displayContent.forAllWindows(windowState -> {
+ return windowState.isOnScreen() && UserManager.isUserTypePrivateProfile(
+ getUserManager().getProfileType(windowState.mShowUserId));
+ }, true /* traverseTopToBottom */);
}
- return DevicePolicyCache.getInstance().isScreenCaptureAllowed(userId);
+ return DevicePolicyCache.getInstance().isScreenCaptureAllowed(userId)
+ && !hasRestrictedWindow;
}
private void onLocalVoiceInteractionStartedLocked(IBinder activity,
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 82dbf8d..716aee3 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1429,14 +1429,6 @@
return mTokenMap.get(binder);
}
- ActivityRecord getActivityRecord(IBinder binder) {
- final WindowToken token = getWindowToken(binder);
- if (token == null) {
- return null;
- }
- return token.asActivityRecord();
- }
-
void addWindowToken(IBinder binder, WindowToken token) {
final DisplayContent dc = mWmService.mRoot.getWindowTokenDisplay(token);
if (dc != null) {
@@ -2250,7 +2242,6 @@
}
}
- mWmService.mDisplayManagerInternal.performTraversal(transaction);
if (shellTransitions) {
// Before setDisplayProjection is applied by the start transaction of transition,
// set the transform hint to avoid using surface in old rotation.
@@ -6985,7 +6976,7 @@
@Override
public void onAppTransitionFinishedLocked(IBinder token) {
- final ActivityRecord r = getActivityRecord(token);
+ final ActivityRecord r = ActivityRecord.forTokenLocked(token);
// Ignore the animating recents so the fixed rotation transform won't be switched twice
// by finishing the recents animation and moving it to top. That also avoids flickering
// due to wait for previous activity to be paused if it supports PiP that ignores the
diff --git a/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java b/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java
index eae9951..d08f043 100644
--- a/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java
+++ b/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.internal.perfetto.protos.PerfettoTrace;
import android.os.SystemClock;
+import android.os.Trace;
import android.tracing.perfetto.DataSourceParams;
import android.tracing.perfetto.InitArguments;
import android.tracing.perfetto.Producer;
@@ -57,6 +58,16 @@
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logSentTransition");
+ try {
+ doLogSentTransition(transition, targets);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogSentTransition(
+ Transition transition, ArrayList<Transition.ChangeInfo> targets) {
mDataSource.trace((ctx) -> {
final ProtoOutputStream os = ctx.newTracePacket();
@@ -91,6 +102,15 @@
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logFinishedTransition");
+ try {
+ doLogFinishTransition(transition);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogFinishTransition(Transition transition) {
mDataSource.trace((ctx) -> {
final ProtoOutputStream os = ctx.newTracePacket();
@@ -114,6 +134,15 @@
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logAbortedTransition");
+ try {
+ doLogAbortedTransition(transition);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogAbortedTransition(Transition transition) {
mDataSource.trace((ctx) -> {
final ProtoOutputStream os = ctx.newTracePacket();
@@ -131,6 +160,15 @@
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logRemovingStartingWindow");
+ try {
+ doLogRemovingStartingWindow(startingData);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ public void doLogRemovingStartingWindow(@NonNull StartingData startingData) {
mDataSource.trace((ctx) -> {
final ProtoOutputStream os = ctx.newTracePacket();
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index 7b23004..7a3124d 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -160,7 +160,8 @@
// Invisible activity should be stopped. If the recents activity is alive and its doesn't
// need to relaunch by current configuration, then it may be already in stopped state.
- if (!targetActivity.isState(STOPPING, STOPPED)) {
+ if (!targetActivity.finishing && targetActivity.isAttached()
+ && !targetActivity.isState(STOPPING, STOPPED)) {
// Add to stopping instead of stop immediately. So the client has the chance to perform
// traversal in non-stopped state (ViewRootImpl.mStopped) that would initialize more
// things (e.g. the measure can be done earlier). The actual stop will be performed when
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 587cc74..25646f1 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -249,6 +249,8 @@
/** Reference to default display so we can quickly look it up. */
private DisplayContent mDefaultDisplay;
private final SparseArray<IntArray> mDisplayAccessUIDs = new SparseArray<>();
+ private final SparseArray<SurfaceControl.Transaction> mDisplayTransactions =
+ new SparseArray<>();
/** The current user */
int mCurrentUser;
@@ -569,22 +571,6 @@
}, true /* traverseTopToBottom */);
}
- /**
- * Returns the app window token for the input binder if it exist in the system.
- * NOTE: Only one AppWindowToken is allowed to exist in the system for a binder token, since
- * AppWindowToken represents an activity which can only exist on one display.
- */
- ActivityRecord getActivityRecord(IBinder binder) {
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final DisplayContent dc = mChildren.get(i);
- final ActivityRecord activity = dc.getActivityRecord(binder);
- if (activity != null) {
- return activity;
- }
- }
- return null;
- }
-
/** Returns the window token for the input binder if it exist in the system. */
WindowToken getWindowToken(IBinder binder) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
@@ -991,11 +977,13 @@
for (int j = 0; j < count; ++j) {
final DisplayContent dc = mChildren.get(j);
dc.applySurfaceChangesTransaction();
+ mDisplayTransactions.append(dc.mDisplayId, dc.getSyncTransaction());
}
// Give the display manager a chance to adjust properties like display rotation if it needs
// to.
- mWmService.mDisplayManagerInternal.performTraversal(t);
+ mWmService.mDisplayManagerInternal.performTraversal(t, mDisplayTransactions);
+ mDisplayTransactions.clear();
}
/**
diff --git a/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
index bdb4588..967f415 100644
--- a/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
+++ b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
@@ -36,8 +36,7 @@
import com.android.internal.protolog.common.ProtoLog;
import java.io.PrintWriter;
-import java.util.Map;
-import java.util.Set;
+import java.util.ArrayList;
public class ScreenRecordingCallbackController {
@@ -57,10 +56,10 @@
}
@GuardedBy("WindowManagerService.mGlobalLock")
- private final Map<IBinder, Callback> mCallbacks = new ArrayMap<>();
+ private final ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<>();
@GuardedBy("WindowManagerService.mGlobalLock")
- private final Map<Integer /*UID*/, Boolean> mLastInvokedStateByUid = new ArrayMap<>();
+ private final ArrayMap<Integer /*UID*/, Boolean> mLastInvokedStateByUid = new ArrayMap<>();
private final WindowManagerService mWms;
@@ -108,8 +107,8 @@
}
IBinder binder = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
- IMediaProjectionManager mediaProjectionManager =
- IMediaProjectionManager.Stub.asInterface(binder);
+ IMediaProjectionManager mediaProjectionManager = IMediaProjectionManager.Stub.asInterface(
+ binder);
long identityToken = Binder.clearCallingIdentity();
MediaProjectionInfo mediaProjectionInfo = null;
@@ -157,11 +156,14 @@
synchronized (mWms.mGlobalLock) {
IBinder binder = callback.asBinder();
Callback callbackInfo = mCallbacks.remove(binder);
+ if (callbackInfo == null) {
+ return;
+ }
binder.unlinkToDeath(callbackInfo, 0);
boolean uidHasCallback = false;
- for (Callback cb : mCallbacks.values()) {
- if (cb.mUid == callbackInfo.mUid) {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ if (mCallbacks.valueAt(i).mUid == callbackInfo.mUid) {
uidHasCallback = true;
break;
}
@@ -213,7 +215,9 @@
return;
}
- dispatchCallbacks(Set.of(uid), processVisible);
+ ArraySet<Integer> uidSet = new ArraySet<>();
+ uidSet.add(uid);
+ dispatchCallbacks(uidSet, processVisible);
}
@GuardedBy("WindowManagerService.mGlobalLock")
@@ -233,8 +237,8 @@
}
@GuardedBy("WindowManagerService.mGlobalLock")
- private Set<Integer> getRecordedUids() {
- Set<Integer> result = new ArraySet<>();
+ private ArraySet<Integer> getRecordedUids() {
+ ArraySet<Integer> result = new ArraySet<>();
if (mRecordedWC == null) {
return result;
}
@@ -248,37 +252,43 @@
}
@GuardedBy("WindowManagerService.mGlobalLock")
- private void dispatchCallbacks(Set<Integer> uids, boolean visibleInScreenRecording) {
+ private void dispatchCallbacks(ArraySet<Integer> uids, boolean visibleInScreenRecording) {
if (uids.isEmpty()) {
return;
}
- for (Integer uid : uids) {
- mLastInvokedStateByUid.put(uid, visibleInScreenRecording);
+ for (int i = 0; i < uids.size(); i++) {
+ mLastInvokedStateByUid.put(uids.valueAt(i), visibleInScreenRecording);
}
- for (Callback callback : mCallbacks.values()) {
- if (!uids.contains(callback.mUid)) {
- continue;
- }
- try {
- callback.mCallback.onScreenRecordingStateChanged(visibleInScreenRecording);
- } catch (RemoteException e) {
- // Client has died. Cleanup is handled via DeathRecipient.
+ ArrayList<IScreenRecordingCallback> callbacks = new ArrayList<>();
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ if (uids.contains(mCallbacks.valueAt(i).mUid)) {
+ callbacks.add(mCallbacks.valueAt(i).mCallback);
}
}
+
+ mWms.mH.post(() -> {
+ for (int i = 0; i < callbacks.size(); i++) {
+ try {
+ callbacks.get(i).onScreenRecordingStateChanged(visibleInScreenRecording);
+ } catch (RemoteException e) {
+ // Client has died. Cleanup is handled via DeathRecipient.
+ }
+ }
+ });
}
void dump(PrintWriter pw) {
pw.format("ScreenRecordingCallbackController:\n");
pw.format(" Registered callbacks:\n");
- for (Map.Entry<IBinder, Callback> entry : mCallbacks.entrySet()) {
- pw.format(" callback=%s uid=%s\n", entry.getKey(), entry.getValue().mUid);
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ pw.format(" callback=%s uid=%s\n", mCallbacks.keyAt(i), mCallbacks.valueAt(i).mUid);
}
pw.format(" Last invoked states:\n");
- for (Map.Entry<Integer, Boolean> entry : mLastInvokedStateByUid.entrySet()) {
- pw.format(" uid=%s isVisibleInScreenRecording=%s\n", entry.getKey(),
- entry.getValue());
+ for (int i = 0; i < mLastInvokedStateByUid.size(); i++) {
+ pw.format(" uid=%s isVisibleInScreenRecording=%s\n", mLastInvokedStateByUid.keyAt(i),
+ mLastInvokedStateByUid.valueAt(i));
}
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 8f9ed83..5b51776 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5070,7 +5070,7 @@
mHandler.removeMessages(TRANSLUCENT_TIMEOUT_MSG);
if (waitingActivity != null) {
- mWmService.setWindowOpaqueLocked(waitingActivity.token, false);
+ waitingActivity.setMainWindowOpaque(false);
if (waitingActivity.attachedToProcess()) {
try {
waitingActivity.app.getThread().scheduleTranslucentConversionComplete(
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 2accf9a..1e58306 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2917,6 +2917,26 @@
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_WINDOW_MANAGER, TAG, cookie);
}
+ /**
+ * Get whether the transition, in its current state, is a no-op. This should be avoided. It is
+ * only here for legacy usages where we can't tell ahead-of-time whether something will
+ * generate a change.
+ */
+ boolean isNoOp() {
+ for (int i = mParticipants.size() - 1; i >= 0; --i) {
+ // This is the same criteria as the rejection logic in calculateTargets
+ final WindowContainer<?> wc = mParticipants.valueAt(i);
+ if (!wc.isAttached()) continue;
+ // The level of transition target should be at least window token.
+ if (wc.asWindowState() != null) continue;
+ final ChangeInfo changeInfo = mChanges.get(wc);
+ // Reject no-ops
+ if (!changeInfo.hasChanged()) continue;
+ return false;
+ }
+ return true;
+ }
+
@VisibleForTesting
static class ChangeInfo {
private static final int FLAG_NONE = 0;
diff --git a/services/core/java/com/android/server/wm/TrustedOverlayHost.java b/services/core/java/com/android/server/wm/TrustedOverlayHost.java
index f8edc2b..debe794 100644
--- a/services/core/java/com/android/server/wm/TrustedOverlayHost.java
+++ b/services/core/java/com/android/server/wm/TrustedOverlayHost.java
@@ -88,7 +88,17 @@
void addOverlay(SurfaceControlViewHost.SurfacePackage p, SurfaceControl currentParent) {
requireOverlaySurfaceControl();
- mOverlays.add(p);
+
+ boolean hasExistingOverlay = false;
+ for (int i = mOverlays.size() - 1; i >= 0; i--) {
+ SurfaceControlViewHost.SurfacePackage l = mOverlays.get(i);
+ if (l.getSurfaceControl().isSameSurface(p.getSurfaceControl())) {
+ hasExistingOverlay = true;
+ }
+ }
+ if (!hasExistingOverlay) {
+ mOverlays.add(p);
+ }
SurfaceControl.Transaction t = mWmService.mTransactionFactory.get();
t.reparent(p.getSurfaceControl(), mSurfaceControl)
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 426694d..554cbce 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -158,6 +158,7 @@
import android.Manifest;
import android.Manifest.permission;
import android.animation.ValueAnimator;
+import android.annotation.EnforcePermission;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -1073,7 +1074,7 @@
@Override
public void onAppTransitionFinishedLocked(IBinder token) {
- final ActivityRecord atoken = mRoot.getActivityRecord(token);
+ final ActivityRecord atoken = ActivityRecord.forTokenLocked(token);
if (atoken == null) {
return;
}
@@ -3105,13 +3106,6 @@
return mRecentsAnimationController != null && mRecentsAnimationController.isTargetApp(r);
}
- void setWindowOpaqueLocked(IBinder token, boolean isOpaque) {
- final ActivityRecord wtoken = mRoot.getActivityRecord(token);
- if (wtoken != null) {
- wtoken.setMainWindowOpaque(isOpaque);
- }
- }
-
boolean isValidPictureInPictureAspectRatio(DisplayContent displayContent, float aspectRatio) {
return displayContent.getPinnedTaskController().isValidPictureInPictureAspectRatio(
aspectRatio);
@@ -3292,7 +3286,7 @@
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.DISABLE_KEYGUARD)
+ @EnforcePermission(android.Manifest.permission.DISABLE_KEYGUARD)
/**
* @see android.app.KeyguardManager#exitKeyguardSecurely
*/
@@ -4520,7 +4514,7 @@
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
+ @EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
@Override
public SurfaceControl addShellRoot(int displayId, IWindow client,
@WindowManager.ShellRootLayer int shellRootLayer) {
@@ -4539,7 +4533,7 @@
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
+ @EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
@Override
public void setShellRootAccessibilityWindow(int displayId,
@WindowManager.ShellRootLayer int shellRootLayer, IWindow target) {
@@ -4562,7 +4556,7 @@
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
+ @EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
@Override
public void setDisplayWindowInsetsController(
int displayId, IDisplayWindowInsetsController insetsController) {
@@ -4581,7 +4575,7 @@
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
+ @EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
@Override
public void updateDisplayWindowRequestedVisibleTypes(
int displayId, @InsetsType int requestedVisibleTypes) {
@@ -5841,7 +5835,7 @@
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
@Override
public void setForcedDisplaySize(int displayId, int width, int height) {
setForcedDisplaySize_enforcePermission();
@@ -5859,7 +5853,7 @@
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
@Override
public void setForcedDisplayScalingMode(int displayId, int mode) {
setForcedDisplayScalingMode_enforcePermission();
@@ -5947,7 +5941,7 @@
return changed;
}
- @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
@Override
public void clearForcedDisplaySize(int displayId) {
clearForcedDisplaySize_enforcePermission();
@@ -6010,7 +6004,7 @@
return -1;
}
- @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
@Override
public void setForcedDisplayDensityForUser(int displayId, int density, int userId) {
setForcedDisplayDensityForUser_enforcePermission();
@@ -6036,7 +6030,7 @@
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
@Override
public void clearForcedDisplayDensityForUser(int displayId, int userId) {
clearForcedDisplayDensityForUser_enforcePermission();
@@ -6536,7 +6530,7 @@
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.STATUS_BAR)
+ @EnforcePermission(android.Manifest.permission.STATUS_BAR)
public void setNavBarVirtualKeyHapticFeedbackEnabled(boolean enabled) {
setNavBarVirtualKeyHapticFeedbackEnabled_enforcePermission();
@@ -6578,7 +6572,7 @@
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
+ @EnforcePermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
@Override
public Region getCurrentImeTouchRegion() {
getCurrentImeTouchRegion_enforcePermission();
@@ -8468,12 +8462,13 @@
SurfaceControlViewHost.SurfacePackage overlay) {
if (overlay == null) {
throw new IllegalArgumentException("Invalid overlay passed in for task=" + taskId);
- } else if (overlay.getSurfaceControl() == null
- || !overlay.getSurfaceControl().isValid()) {
- throw new IllegalArgumentException(
- "Invalid overlay surfacecontrol passed in for task=" + taskId);
}
synchronized (mGlobalLock) {
+ if (overlay.getSurfaceControl() == null
+ || !overlay.getSurfaceControl().isValid()) {
+ throw new IllegalArgumentException(
+ "Invalid overlay surfacecontrol passed in for task=" + taskId);
+ }
final Task task = mRoot.getRootTask(taskId);
if (task == null) {
throw new IllegalArgumentException("no task with taskId" + taskId);
@@ -9956,13 +9951,17 @@
mTrustedPresentationListenerController.unregisterListener(listener, id);
}
+ @EnforcePermission(android.Manifest.permission.DETECT_SCREEN_RECORDING)
@Override
public boolean registerScreenRecordingCallback(IScreenRecordingCallback callback) {
+ registerScreenRecordingCallback_enforcePermission();
return mScreenRecordingCallbackController.register(callback);
}
+ @EnforcePermission(android.Manifest.permission.DETECT_SCREEN_RECORDING)
@Override
public void unregisterScreenRecordingCallback(IScreenRecordingCallback callback) {
+ unregisterScreenRecordingCallback_enforcePermission();
mScreenRecordingCallbackController.unregister(callback);
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 3f889c0..8cd399f 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1471,12 +1471,11 @@
final int index = task.mChildren.indexOf(topTaskFragment);
task.mChildren.remove(taskFragment);
task.mChildren.add(index, taskFragment);
- if (taskFragment.hasChild()) {
- effects |= TRANSACT_EFFECTS_LIFECYCLE;
- } else {
+ if (!taskFragment.hasChild()) {
// Ensure that the child layers are updated if the TaskFragment is empty
task.assignChildLayers();
}
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
}
break;
@@ -1491,12 +1490,11 @@
if (task != null) {
task.mChildren.remove(taskFragment);
task.mChildren.add(0, taskFragment);
- if (taskFragment.hasChild()) {
- effects |= TRANSACT_EFFECTS_LIFECYCLE;
- } else {
+ if (!taskFragment.hasChild()) {
// Ensure that the child layers are updated if the TaskFragment is empty.
task.assignChildLayers();
}
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
break;
}
@@ -1505,12 +1503,11 @@
if (task != null) {
task.mChildren.remove(taskFragment);
task.mChildren.add(taskFragment);
- if (taskFragment.hasChild()) {
- effects |= TRANSACT_EFFECTS_LIFECYCLE;
- } else {
+ if (!taskFragment.hasChild()) {
// Ensure that the child layers are updated if the TaskFragment is empty.
task.assignChildLayers();
}
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
break;
}
diff --git a/services/incremental/OWNERS b/services/incremental/OWNERS
index 7ebb962..c18a9e5 100644
--- a/services/incremental/OWNERS
+++ b/services/incremental/OWNERS
@@ -1,8 +1,4 @@
# Bug component: 554432
-include /services/core/java/com/android/server/pm/OWNERS
+include /PACKAGE_MANAGER_OWNERS
-alexbuy@google.com
-schfan@google.com
-toddke@google.com
-zyy@google.com
-patb@google.com
+zyy@google.com
\ No newline at end of file
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
index 8f464d4..fc2eb26 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
@@ -17,6 +17,7 @@
package com.android.server.permission.access.appop
import android.app.AppOpsManager
+import android.companion.virtual.VirtualDeviceManager
import android.os.Handler
import android.os.UserHandle
import android.util.ArrayMap
@@ -213,7 +214,10 @@
val uid = key.first
val appOpCode = key.second
- listener.onUidModeChanged(uid, appOpCode, mode)
+ listener.onUidModeChanged(uid,
+ appOpCode,
+ mode,
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)
}
}
diff --git a/services/profcollect/OWNERS b/services/profcollect/OWNERS
index b380e39..be9e61f 100644
--- a/services/profcollect/OWNERS
+++ b/services/profcollect/OWNERS
@@ -1,3 +1 @@
-srhines@google.com
-yabinc@google.com
-yikong@google.com
+include platform/prebuilts/clang/host/linux-x86:/OWNERS
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index 582b712..fb0fbe8 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -62,7 +62,7 @@
private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG);
private static final String INTENT_UPLOAD_PROFILES =
"com.android.server.profcollect.UPLOAD_PROFILES";
- private static final long BG_PROCESS_PERIOD = TimeUnit.HOURS.toMillis(4); // every 4 hours.
+ private static final long BG_PROCESS_INTERVAL = TimeUnit.HOURS.toMillis(4); // every 4 hours.
private IProfCollectd mIProfcollect;
private static ProfcollectForwardingService sSelfService;
@@ -226,7 +226,7 @@
js.schedule(new JobInfo.Builder(JOB_IDLE_PROCESS, JOB_SERVICE_NAME)
.setRequiresDeviceIdle(true)
.setRequiresCharging(true)
- .setPeriodic(BG_PROCESS_PERIOD)
+ .setPeriodic(BG_PROCESS_INTERVAL)
.setPriority(JobInfo.PRIORITY_MIN)
.build());
}
diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp
index 56423b9..897afbf 100644
--- a/services/tests/InputMethodSystemServerTests/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/Android.bp
@@ -73,7 +73,6 @@
"androidx.annotation_annotation",
"androidx.test.rules",
"framework",
- "mockito_ravenwood",
"ravenwood-runtime",
"ravenwood-utils",
"services",
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 75febd9..42bcb33 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -103,6 +103,7 @@
import android.os.RemoteException;
import android.os.SystemProperties;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.util.SparseArray;
import android.view.ContentRecordingSession;
import android.view.Display;
import android.view.DisplayAdjustments;
@@ -432,7 +433,7 @@
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
@@ -507,7 +508,7 @@
assertTrue(expectedDisplayTypeToViewPortTypeMapping.keySet().contains(info.type));
}
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
@@ -559,7 +560,7 @@
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
@@ -594,7 +595,7 @@
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
@@ -632,7 +633,7 @@
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
@@ -667,7 +668,7 @@
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
@@ -947,7 +948,7 @@
PACKAGE_NAME);
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
@@ -1439,7 +1440,7 @@
PACKAGE_NAME);
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
@@ -1694,7 +1695,7 @@
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
@@ -1728,7 +1729,7 @@
null /* projection */, PACKAGE_NAME);
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
assertNotNull(ddi);
@@ -1797,7 +1798,7 @@
mock(DisplayWindowPolicyController.class), PACKAGE_NAME);
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
assertNotNull(ddi);
@@ -2912,6 +2913,11 @@
assertEquals(expectedMode, displayInfo.getMode());
}
+ private void performTraversalInternal(DisplayManagerService displayManager) {
+ displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class),
+ new SparseArray<>());
+ }
+
private int getDisplayIdForDisplayDevice(
DisplayManagerService displayManager,
DisplayManagerService.BinderService displayManagerBinderService,
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 321d945..d928306 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -46,7 +46,6 @@
"androidx.test.espresso.core",
"androidx.test.espresso.contrib",
"androidx.test.ext.truth",
- "backup_flags_lib",
"flag-junit",
"frameworks-base-testutils",
"hamcrest-library",
diff --git a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
index 47ae97f..0e85626 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
@@ -44,6 +44,7 @@
import android.app.IActivityManager;
import android.app.IUidObserver;
import android.app.usage.UsageStatsManager;
+import android.companion.virtual.VirtualDeviceManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -309,7 +310,8 @@
mRestrictedPackages.remove(p);
}
if (mAppOpsCallback != null) {
- mAppOpsCallback.opChanged(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName);
+ mAppOpsCallback.opChanged(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName,
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index b39cd04..a476155 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -125,6 +125,7 @@
import android.app.compat.CompatChanges;
import android.app.tare.EconomyManager;
import android.app.usage.UsageStatsManagerInternal;
+import android.companion.virtual.VirtualDeviceManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -2980,7 +2981,8 @@
mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ALLOWED);
mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
- mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
+ mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE,
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
assertAndHandleMessageSync(REMOVE_EXACT_ALARMS);
verify(mService).removeExactAlarmsOnPermissionRevoked(TEST_CALLING_UID,
TEST_CALLING_PACKAGE, true);
@@ -2993,7 +2995,8 @@
mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ALLOWED);
mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
- mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
+ mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE,
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
verify(mService.mHandler, never()).sendMessageAtTime(
argThat(m -> m.what == REMOVE_EXACT_ALARMS), anyLong());
@@ -3008,7 +3011,8 @@
mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ERRORED);
mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
- mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
+ mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE,
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp
index f49f638..64fef68 100644
--- a/services/tests/powerstatstests/Android.bp
+++ b/services/tests/powerstatstests/Android.bp
@@ -81,7 +81,6 @@
"androidx.annotation_annotation",
"androidx.test.rules",
"truth",
- "mockito_ravenwood",
],
srcs: [
":power_stats_ravenwood_tests",
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index e22d99d..ad6e2c6 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -84,6 +84,7 @@
"flag-junit",
"ravenwood-junit",
"net_flags_lib",
+ "CtsVirtualDeviceCommonLib",
],
libs: [
@@ -152,7 +153,6 @@
static_libs: [
"androidx.annotation_annotation",
"androidx.test.rules",
- "mockito_ravenwood",
"services.core",
],
srcs: [
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
index a2aaccc..b487dc6 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
@@ -16,6 +16,8 @@
package com.android.server.appop;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertFalse;
@@ -31,17 +33,23 @@
import android.app.AppOpsManager;
import android.app.AppOpsManager.OnOpActiveChangedListener;
+import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceParams;
+import android.content.AttributionSource;
import android.content.Context;
import android.os.Process;
+import android.virtualdevice.cts.common.FakeAssociationRule;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* Tests app ops version upgrades
@@ -50,6 +58,8 @@
@RunWith(AndroidJUnit4.class)
public class AppOpsActiveWatcherTest {
+ @Rule
+ public FakeAssociationRule mFakeAssociationRule = new FakeAssociationRule();
private static final long NOTIFICATION_TIMEOUT_MILLIS = 5000;
@Test
@@ -69,7 +79,7 @@
verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpActiveChanged(eq(AppOpsManager.OPSTR_CAMERA),
eq(Process.myUid()), eq(getContext().getPackageName()),
- isNull(), eq(true), anyInt(), anyInt());
+ isNull(), eq(Context.DEVICE_ID_DEFAULT), eq(true), anyInt(), anyInt());
// This should be the only callback we got
verifyNoMoreInteractions(listener);
@@ -88,7 +98,7 @@
verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpActiveChanged(eq(AppOpsManager.OPSTR_CAMERA),
eq(Process.myUid()), eq(getContext().getPackageName()), isNull(),
- eq(false), anyInt(), anyInt());
+ eq(Context.DEVICE_ID_DEFAULT), eq(false), anyInt(), anyInt());
// Verify that the op is not active
assertThat(appOpsManager.isOperationActive(AppOpsManager.OP_CAMERA,
@@ -126,7 +136,7 @@
verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpActiveChanged(eq(AppOpsManager.OPSTR_CAMERA),
eq(Process.myUid()), eq(getContext().getPackageName()), isNull(),
- eq(true), anyInt(), anyInt());
+ eq(Context.DEVICE_ID_DEFAULT), eq(true), anyInt(), anyInt());
// Finish up
appOpsManager.finishOp(AppOpsManager.OP_CAMERA);
@@ -134,6 +144,64 @@
}
@Test
+ public void testWatchActiveOpsForExternalDevice() {
+ final VirtualDeviceManager virtualDeviceManager = getContext().getSystemService(
+ VirtualDeviceManager.class);
+ AtomicInteger virtualDeviceId = new AtomicInteger();
+ runWithShellPermissionIdentity(() -> {
+ final VirtualDeviceManager.VirtualDevice virtualDevice =
+ virtualDeviceManager.createVirtualDevice(
+ mFakeAssociationRule.getAssociationInfo().getId(),
+ new VirtualDeviceParams.Builder().setName("virtual_device").build());
+ virtualDeviceId.set(virtualDevice.getDeviceId());
+ });
+ final OnOpActiveChangedListener listener = mock(OnOpActiveChangedListener.class);
+ AttributionSource attributionSource = new AttributionSource(Process.myUid(),
+ getContext().getOpPackageName(), getContext().getAttributionTag(),
+ virtualDeviceId.get());
+
+ final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
+ appOpsManager.startWatchingActive(new String[]{AppOpsManager.OPSTR_CAMERA,
+ AppOpsManager.OPSTR_RECORD_AUDIO}, getContext().getMainExecutor(), listener);
+
+ appOpsManager.startOpNoThrow(getContext().getAttributionSource().getToken(),
+ AppOpsManager.OP_CAMERA, attributionSource, false, "",
+ AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
+
+ verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
+ .times(1)).onOpActiveChanged(eq(AppOpsManager.OPSTR_CAMERA),
+ eq(Process.myUid()), eq(getContext().getOpPackageName()),
+ eq(getContext().getAttributionTag()), eq(virtualDeviceId.get()), eq(true),
+ eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
+ eq(AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE));
+ verifyNoMoreInteractions(listener);
+
+ appOpsManager.finishOp(getContext().getAttributionSource().getToken(),
+ AppOpsManager.OP_CAMERA, attributionSource);
+
+ verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
+ .times(1)).onOpActiveChanged(eq(AppOpsManager.OPSTR_CAMERA),
+ eq(Process.myUid()), eq(getContext().getOpPackageName()),
+ eq(getContext().getAttributionTag()), eq(virtualDeviceId.get()), eq(false),
+ eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
+ eq(AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE));
+ verifyNoMoreInteractions(listener);
+
+ appOpsManager.stopWatchingActive(listener);
+
+ appOpsManager.startOpNoThrow(getContext().getAttributionSource().getToken(),
+ AppOpsManager.OP_CAMERA, attributionSource, false, "",
+ AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
+
+ verifyNoMoreInteractions(listener);
+
+ appOpsManager.finishOp(getContext().getAttributionSource().getToken(),
+ AppOpsManager.OP_CAMERA, attributionSource);
+
+ verifyNoMoreInteractions(listener);
+ }
+
+ @Test
public void testIsRunning() throws Exception {
final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
// Start the op
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
index b5229d8..1abd4eb 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
@@ -22,27 +22,36 @@
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import android.app.AppOpsManager;
import android.app.AppOpsManager.OnOpNotedListener;
+import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceParams;
+import android.content.AttributionSource;
import android.content.Context;
import android.os.Process;
+import android.virtualdevice.cts.common.FakeAssociationRule;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
+import java.util.concurrent.atomic.AtomicInteger;
+
/**
* Tests watching noted ops.
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
public class AppOpsNotedWatcherTest {
-
+ @Rule
+ public FakeAssociationRule mFakeAssociationRule = new FakeAssociationRule();
private static final long NOTIFICATION_TIMEOUT_MILLIS = 5000;
@Test
@@ -66,12 +75,14 @@
inOrder.verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpNoted(eq(AppOpsManager.OPSTR_FINE_LOCATION),
eq(Process.myUid()), eq(getContext().getPackageName()),
- eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
+ eq(getContext().getAttributionTag()), eq(Context.DEVICE_ID_DEFAULT),
+ eq(AppOpsManager.OP_FLAG_SELF),
eq(AppOpsManager.MODE_ALLOWED));
inOrder.verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpNoted(eq(AppOpsManager.OPSTR_CAMERA),
eq(Process.myUid()), eq(getContext().getPackageName()),
- eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
+ eq(getContext().getAttributionTag()), eq(Context.DEVICE_ID_DEFAULT),
+ eq(AppOpsManager.OP_FLAG_SELF),
eq(AppOpsManager.MODE_ALLOWED));
// Stop watching
@@ -96,13 +107,54 @@
verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(2)).onOpNoted(eq(AppOpsManager.OPSTR_FINE_LOCATION),
eq(Process.myUid()), eq(getContext().getPackageName()),
- eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
+ eq(getContext().getAttributionTag()), eq(Context.DEVICE_ID_DEFAULT),
+ eq(AppOpsManager.OP_FLAG_SELF),
eq(AppOpsManager.MODE_ALLOWED));
// Finish up
appOpsManager.stopWatchingNoted(listener);
}
+ @Test
+ public void testWatchNotedOpsForExternalDevice() {
+ final AppOpsManager.OnOpNotedListener listener = mock(
+ AppOpsManager.OnOpNotedListener.class);
+ final VirtualDeviceManager virtualDeviceManager = getContext().getSystemService(
+ VirtualDeviceManager.class);
+ AtomicInteger virtualDeviceId = new AtomicInteger();
+ runWithShellPermissionIdentity(() -> {
+ final VirtualDeviceManager.VirtualDevice virtualDevice =
+ virtualDeviceManager.createVirtualDevice(
+ mFakeAssociationRule.getAssociationInfo().getId(),
+ new VirtualDeviceParams.Builder().setName("virtual_device").build());
+ virtualDeviceId.set(virtualDevice.getDeviceId());
+ });
+ AttributionSource attributionSource = new AttributionSource(Process.myUid(),
+ getContext().getOpPackageName(), getContext().getAttributionTag(),
+ virtualDeviceId.get());
+
+ final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
+ appOpsManager.startWatchingNoted(new int[]{AppOpsManager.OP_FINE_LOCATION,
+ AppOpsManager.OP_CAMERA}, listener);
+
+ appOpsManager.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, attributionSource, "message");
+
+ verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
+ .times(1)).onOpNoted(eq(AppOpsManager.OPSTR_FINE_LOCATION),
+ eq(Process.myUid()), eq(getContext().getOpPackageName()),
+ eq(getContext().getAttributionTag()), eq(virtualDeviceId.get()),
+ eq(AppOpsManager.OP_FLAG_SELF), eq(AppOpsManager.MODE_ALLOWED));
+
+ appOpsManager.finishOp(getContext().getAttributionSource().getToken(),
+ AppOpsManager.OP_FINE_LOCATION, attributionSource);
+
+ verifyNoMoreInteractions(listener);
+
+ appOpsManager.stopWatchingNoted(listener);
+
+ verifyNoMoreInteractions(listener);
+ }
+
private static Context getContext() {
return InstrumentationRegistry.getContext();
}
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
index e98a4dd..2890078 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
@@ -16,6 +16,8 @@
package com.android.server.appop;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
@@ -25,22 +27,31 @@
import android.app.AppOpsManager;
import android.app.AppOpsManager.OnOpStartedListener;
+import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceParams;
+import android.content.AttributionSource;
import android.content.Context;
import android.os.Process;
+import android.virtualdevice.cts.common.FakeAssociationRule;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
+import java.util.concurrent.atomic.AtomicInteger;
+
/** Tests watching started ops. */
@SmallTest
@RunWith(AndroidJUnit4.class)
public class AppOpsStartedWatcherTest {
+ @Rule
+ public FakeAssociationRule mFakeAssociationRule = new FakeAssociationRule();
private static final long NOTIFICATION_TIMEOUT_MILLIS = 5000;
@Test
@@ -63,15 +74,17 @@
inOrder.verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpStarted(eq(AppOpsManager.OP_FINE_LOCATION),
eq(Process.myUid()), eq(getContext().getPackageName()),
- eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
- eq(AppOpsManager.MODE_ALLOWED), eq(OnOpStartedListener.START_TYPE_STARTED),
+ eq(getContext().getAttributionTag()), eq(Context.DEVICE_ID_DEFAULT),
+ eq(AppOpsManager.OP_FLAG_SELF), eq(AppOpsManager.MODE_ALLOWED),
+ eq(OnOpStartedListener.START_TYPE_STARTED),
eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
eq(AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE));
inOrder.verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpStarted(eq(AppOpsManager.OP_CAMERA),
eq(Process.myUid()), eq(getContext().getPackageName()),
- eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
- eq(AppOpsManager.MODE_ALLOWED), eq(OnOpStartedListener.START_TYPE_STARTED),
+ eq(getContext().getAttributionTag()), eq(Context.DEVICE_ID_DEFAULT),
+ eq(AppOpsManager.OP_FLAG_SELF), eq(AppOpsManager.MODE_ALLOWED),
+ eq(OnOpStartedListener.START_TYPE_STARTED),
eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
eq(AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE));
@@ -97,8 +110,9 @@
verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(2)).onOpStarted(eq(AppOpsManager.OP_CAMERA),
eq(Process.myUid()), eq(getContext().getPackageName()),
- eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
- eq(AppOpsManager.MODE_ALLOWED), eq(OnOpStartedListener.START_TYPE_STARTED),
+ eq(getContext().getAttributionTag()), eq(Context.DEVICE_ID_DEFAULT),
+ eq(AppOpsManager.OP_FLAG_SELF), eq(AppOpsManager.MODE_ALLOWED),
+ eq(OnOpStartedListener.START_TYPE_STARTED),
eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
eq(AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE));
verifyNoMoreInteractions(listener);
@@ -109,6 +123,50 @@
appOpsManager.stopWatchingStarted(listener);
}
+ @Test
+ public void testWatchStartedOpsForExternalDevice() {
+ final VirtualDeviceManager virtualDeviceManager = getContext().getSystemService(
+ VirtualDeviceManager.class);
+ AtomicInteger virtualDeviceId = new AtomicInteger();
+ runWithShellPermissionIdentity(() -> {
+ final VirtualDeviceManager.VirtualDevice virtualDevice =
+ virtualDeviceManager.createVirtualDevice(
+ mFakeAssociationRule.getAssociationInfo().getId(),
+ new VirtualDeviceParams.Builder().setName("virtual_device").build());
+ virtualDeviceId.set(virtualDevice.getDeviceId());
+ });
+ final OnOpStartedListener listener = mock(OnOpStartedListener.class);
+ AttributionSource attributionSource = new AttributionSource(Process.myUid(),
+ getContext().getOpPackageName(), getContext().getAttributionTag(),
+ virtualDeviceId.get());
+
+ final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
+ appOpsManager.startWatchingStarted(new int[]{AppOpsManager.OP_FINE_LOCATION,
+ AppOpsManager.OP_CAMERA}, listener);
+
+ appOpsManager.startOpNoThrow(getContext().getAttributionSource().getToken(),
+ AppOpsManager.OP_FINE_LOCATION, attributionSource, false,
+ "message", 0, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
+
+ verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
+ .times(1)).onOpStarted(eq(AppOpsManager.OP_FINE_LOCATION),
+ eq(Process.myUid()), eq(getContext().getOpPackageName()),
+ eq(getContext().getAttributionTag()), eq(virtualDeviceId.get()),
+ eq(AppOpsManager.OP_FLAG_SELF),
+ eq(AppOpsManager.MODE_ALLOWED), eq(OnOpStartedListener.START_TYPE_STARTED),
+ eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
+ eq(AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE));
+
+ appOpsManager.finishOp(getContext().getAttributionSource().getToken(),
+ AppOpsManager.OP_FINE_LOCATION, attributionSource);
+
+ verifyNoMoreInteractions(listener);
+
+ appOpsManager.stopWatchingStarted(listener);
+
+ verifyNoMoreInteractions(listener);
+ }
+
private static Context getContext() {
return InstrumentationRegistry.getContext();
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java
index 3a9c0f0..a1f0dbd 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java
@@ -70,7 +70,7 @@
mClientMonitor.binderDied();
assertThat(mClientMonitor.mCanceled).isTrue();
- assertThat(mClientMonitor.getListener()).isNull();
+ assertThat(mClientMonitor.getListener()).isNotNull();
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java
index c8bfaa9..5b81277 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java
@@ -87,14 +87,6 @@
verify(mCallback).onClientFinished(mClient, true);
}
- @Test
- public void generateChallenge_nullListener() {
- createClient(null);
- mClient.start(mCallback);
-
- verify(mCallback).onClientFinished(mClient, false);
- }
-
private void createClient(ClientMonitorCallbackConverter listener) {
mClient = new FaceGenerateChallengeClient(mContext, () -> mAidlSession, mToken, listener,
USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext);
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
index 07dd59d2..a4628ee 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
@@ -18,6 +18,8 @@
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
import static android.content.Context.DEVICE_ID_DEFAULT;
import static android.content.Context.DEVICE_ID_INVALID;
@@ -135,4 +137,34 @@
when(mVirtualDevice.getDevicePolicy(POLICY_TYPE_SENSORS)).thenReturn(DEVICE_POLICY_CUSTOM);
assertThat(virtualDevice.hasCustomSensorSupport()).isTrue();
}
+
+ @Test
+ public void virtualDevice_hasCustomAudioInputSupport() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_VDM_PUBLIC_APIS);
+
+ VirtualDevice virtualDevice =
+ new VirtualDevice(
+ mVirtualDevice, VIRTUAL_DEVICE_ID, /*persistentId=*/null, /*name=*/null);
+
+ when(mVirtualDevice.getDevicePolicy(POLICY_TYPE_AUDIO)).thenReturn(DEVICE_POLICY_DEFAULT);
+ assertThat(virtualDevice.hasCustomAudioInputSupport()).isFalse();
+
+ when(mVirtualDevice.getDevicePolicy(POLICY_TYPE_AUDIO)).thenReturn(DEVICE_POLICY_CUSTOM);
+ assertThat(virtualDevice.hasCustomAudioInputSupport()).isTrue();
+ }
+
+ @Test
+ public void virtualDevice_hasCustomCameraSupport() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_VDM_PUBLIC_APIS);
+
+ VirtualDevice virtualDevice =
+ new VirtualDevice(
+ mVirtualDevice, VIRTUAL_DEVICE_ID, /*persistentId=*/null, /*name=*/null);
+
+ when(mVirtualDevice.getDevicePolicy(POLICY_TYPE_CAMERA)).thenReturn(DEVICE_POLICY_DEFAULT);
+ assertThat(virtualDevice.hasCustomCameraSupport()).isFalse();
+
+ when(mVirtualDevice.getDevicePolicy(POLICY_TYPE_CAMERA)).thenReturn(DEVICE_POLICY_CUSTOM);
+ assertThat(virtualDevice.hasCustomCameraSupport()).isTrue();
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index 3de167e..dacff4c 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -23,7 +23,6 @@
import android.content.Context;
import android.graphics.FontListParser;
-import android.graphics.fonts.FontFamily;
import android.graphics.fonts.FontManager;
import android.graphics.fonts.FontStyle;
import android.graphics.fonts.FontUpdateRequest;
@@ -324,15 +323,16 @@
Function<Map<String, File>, FontConfig> configSupplier = (map) -> {
FontConfig.Font fooFont = new FontConfig.Font(
new File(mPreinstalledFontDirs.get(0), "foo.ttf"), null, "foo",
- new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null, null);
+ new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null, null,
+ FontConfig.Font.VAR_TYPE_AXES_NONE);
FontConfig.Font barFont = new FontConfig.Font(
new File(mPreinstalledFontDirs.get(1), "bar.ttf"), null, "bar",
- new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null, null);
+ new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null, null,
+ FontConfig.Font.VAR_TYPE_AXES_NONE);
FontConfig.FontFamily family = new FontConfig.FontFamily(
Arrays.asList(fooFont, barFont), null,
- FontConfig.FontFamily.VARIANT_DEFAULT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE);
+ FontConfig.FontFamily.VARIANT_DEFAULT);
return new FontConfig(Collections.emptyList(),
Collections.emptyList(),
Collections.singletonList(new FontConfig.NamedFamilyList(
@@ -492,10 +492,9 @@
mConfigFile, mCurrentTimeSupplier, (map) -> {
FontConfig.Font font = new FontConfig.Font(
file, null, "bar", new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT),
- 0, null, null);
+ 0, null, null, FontConfig.Font.VAR_TYPE_AXES_NONE);
FontConfig.FontFamily family = new FontConfig.FontFamily(
- Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE);
+ Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT);
return new FontConfig(
Collections.emptyList(),
Collections.emptyList(),
@@ -647,10 +646,9 @@
mConfigFile, mCurrentTimeSupplier, (map) -> {
FontConfig.Font font = new FontConfig.Font(
file, null, "test", new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null,
- null);
+ null, FontConfig.Font.VAR_TYPE_AXES_NONE);
FontConfig.FontFamily family = new FontConfig.FontFamily(
- Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE);
+ Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT);
return new FontConfig(Collections.emptyList(), Collections.emptyList(),
Collections.singletonList(new FontConfig.NamedFamilyList(
Collections.singletonList(family), "sans-serif")),
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
index 1172a87..4641802 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
@@ -19,6 +19,7 @@
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_TV;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
+import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON;
import static com.android.server.hdmi.OneTouchPlayAction.STATE_WAITING_FOR_REPORT_POWER_STATUS;
import static com.google.common.truth.Truth.assertThat;
@@ -46,6 +47,7 @@
import java.util.ArrayList;
import java.util.Collections;
+import java.util.concurrent.TimeUnit;
/** Tests for {@link OneTouchPlayAction} */
@SmallTest
@@ -70,6 +72,7 @@
private Context mContextSpy;
private HdmiControlService mHdmiControlService;
+ private HdmiCecController mHdmiCecController;
private FakeNativeWrapper mNativeWrapper;
private FakePowerManagerWrapper mPowerManager;
private FakeHdmiCecConfig mHdmiCecConfig;
@@ -108,10 +111,10 @@
mHdmiControlService.setHdmiCecConfig(mHdmiCecConfig);
setHdmiControlEnabled(hdmiControlEnabled);
mNativeWrapper = new FakeNativeWrapper();
- HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
+ mHdmiCecController = HdmiCecController.createWithNativeWrapper(
this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
- mHdmiControlService.setCecController(hdmiCecController);
+ mHdmiControlService.setCecController(mHdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.initService();
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
@@ -618,6 +621,53 @@
assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
}
+ @Test
+ public void onWakeUp_notInteractive_startOneTouchPlay() throws Exception {
+ setUp(true);
+
+ mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+ mPowerManager.setInteractive(false);
+ mTestLooper.dispatchAll();
+
+ assertThat(mPowerManager.isInteractive()).isFalse();
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage textViewOn =
+ HdmiCecMessageBuilder.buildTextViewOn(
+ mHdmiControlService.playback().getDeviceInfo().getLogicalAddress(),
+ ADDR_TV);
+ assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn);
+ }
+
+
+ @Test
+ public void onWakeUp_interruptedByOnStandby_notInteractive_OneTouchPlayNotStarted()
+ throws Exception {
+ setUp(true);
+ long allocationDelay = TimeUnit.SECONDS.toMillis(1);
+ mHdmiCecController.setLogicalAddressAllocationDelay(allocationDelay);
+ mTestLooper.dispatchAll();
+
+ mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+ mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+ mPowerManager.setInteractive(false);
+ mTestLooper.dispatchAll();
+
+ assertThat(mPowerManager.isInteractive()).isFalse();
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage textViewOn =
+ HdmiCecMessageBuilder.buildTextViewOn(
+ mHdmiControlService.playback().getDeviceInfo().getLogicalAddress(),
+ ADDR_TV);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(textViewOn);
+
+ }
+
private static class TestActionTimer implements ActionTimer {
private int mState;
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
index 1c6d36b..ea84eb2 100644
--- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -36,6 +36,7 @@
import android.os.IDumpstateListener;
import android.os.Process;
import android.os.RemoteException;
+import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -48,6 +49,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -118,6 +120,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED)
public void testBugreportFileManagerFileExists() {
Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
mBugreportFileManager.addBugreportFileForCaller(
@@ -137,6 +140,7 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED)
+ @Ignore
public void testBugreportFileManagerKeepFilesOnRetrieval() {
Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
mBugreportFileManager.addBugreportFileForCaller(
@@ -150,6 +154,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED)
public void testBugreportFileManagerMultipleFiles() {
Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
mBugreportFileManager.addBugreportFileForCaller(
diff --git a/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java b/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
index 398148f..2e5feff 100644
--- a/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
@@ -273,7 +273,7 @@
final CountDownLatch latch = new CountDownLatch(1);
final IAppOpsCallback watcher = new IAppOpsCallback.Stub() {
@Override
- public void opChanged(int op, int uid, String packageName) {
+ public void opChanged(int op, int uid, String packageName, String persistentDeviceId) {
if (op == code && packageName.equals(TEST_APP_PACKAGE_NAME)) {
latch.countDown();
}
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index 839cf7c..c247c08 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -82,6 +82,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.IpcDataCache;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
@@ -174,6 +175,11 @@
@Before
+ public void disableProcessCaches() {
+ IpcDataCache.disableForTestMode();
+ }
+
+ @Before
public void setUp() {
// The AIDL stub will use PermissionEnforcer to check permission from the caller.
mPermissionEnforcer = new FakePermissionEnforcer();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 9c2cba8..ef0ac33 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -227,8 +227,6 @@
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
-import android.platform.test.rule.DeniedDevices;
-import android.platform.test.rule.DeviceProduct;
import android.platform.test.rule.LimitDevicesRule;
import android.provider.DeviceConfig;
import android.provider.MediaStore;
@@ -336,7 +334,6 @@
@RunWith(AndroidTestingRunner.class)
@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
@RunWithLooper
-@DeniedDevices(denied = {DeviceProduct.CF_AUTO})
public class NotificationManagerServiceTest extends UiServiceTestCase {
private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
private static final String TEST_PACKAGE = "The.name.is.Package.Test.Package";
@@ -593,7 +590,7 @@
when(mAtm.getTaskToShowPermissionDialogOn(anyString(), anyInt()))
.thenReturn(INVALID_TASK_ID);
mContext.addMockSystemService(AppOpsManager.class, mock(AppOpsManager.class));
- when(mUm.getProfileIds(0, false)).thenReturn(new int[]{0});
+ when(mUm.getProfileIds(eq(mUserId), eq(false))).thenReturn(new int[] { mUserId });
when(mPackageManagerClient.hasSystemFeature(FEATURE_TELECOM)).thenReturn(true);
@@ -881,9 +878,7 @@
private void simulatePackageRemovedBroadcast(String pkg, int uid) {
// mimics receive broadcast that package is removed, but doesn't remove the package.
final Bundle extras = new Bundle();
- extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST,
- new String[]{pkg});
- extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, new int[]{uid});
+ extras.putInt(Intent.EXTRA_UID, uid);
final Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
intent.setData(Uri.parse("package:" + pkg));
@@ -1031,7 +1026,7 @@
private NotificationRecord generateNotificationRecord(NotificationChannel channel,
long postTime) {
- final StatusBarNotification sbn = generateSbn(PKG, mUid, postTime, 0);
+ final StatusBarNotification sbn = generateSbn(PKG, mUid, postTime, mUserId);
return new NotificationRecord(mContext, sbn, channel);
}
@@ -1766,7 +1761,7 @@
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"testEnqueueNotification_appBlocked", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
waitForIdle();
verify(mWorkerHandler, never()).post(
any(NotificationManagerService.EnqueueNotificationRunnable.class));
@@ -1776,7 +1771,7 @@
public void testEnqueueNotificationWithTag_PopulatesGetActiveNotifications() throws Exception {
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"testEnqueueNotificationWithTag_PopulatesGetActiveNotifications", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
waitForIdle();
StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
assertEquals(1, notifs.length);
@@ -1787,7 +1782,7 @@
public void testEnqueueNotificationWithTag_WritesExpectedLogs() throws Exception {
final String tag = "testEnqueueNotificationWithTag_WritesExpectedLog";
mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
waitForIdle();
assertEquals(1, mNotificationRecordLogger.numCalls());
@@ -1828,12 +1823,12 @@
Notification original = new Notification.Builder(mContext,
mTestNotificationChannel.getId())
.setSmallIcon(android.R.drawable.sym_def_app_icon).build();
- mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, original, 0);
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, original, mUserId);
Notification update = new Notification.Builder(mContext,
mTestNotificationChannel.getId())
.setSmallIcon(android.R.drawable.sym_def_app_icon)
.setCategory(Notification.CATEGORY_ALARM).build();
- mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, update, 0);
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, update, mUserId);
waitForIdle();
assertEquals(2, mNotificationRecordLogger.numCalls());
@@ -1853,9 +1848,9 @@
public void testEnqueueNotificationWithTag_DoesNotLogOnMinorUpdate() throws Exception {
final String tag = "testEnqueueNotificationWithTag_DoesNotLogOnMinorUpdate";
mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
waitForIdle();
assertEquals(2, mNotificationRecordLogger.numCalls());
assertTrue(mNotificationRecordLogger.get(0).wasLogged);
@@ -1869,10 +1864,10 @@
final String tag = "testEnqueueNotificationWithTag_DoesNotLogOnTitleUpdate";
mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0,
generateNotificationRecord(null).getNotification(),
- 0);
+ mUserId);
final Notification notif = generateNotificationRecord(null).getNotification();
notif.extras.putString(Notification.EXTRA_TITLE, "Changed title");
- mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, notif, 0);
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, notif, mUserId);
waitForIdle();
assertEquals(2, mNotificationRecordLogger.numCalls());
assertEquals(NOTIFICATION_POSTED, mNotificationRecordLogger.event(0));
@@ -1885,11 +1880,11 @@
Notification notification = new Notification.Builder(mContext,
mTestNotificationChannel.getId())
.setSmallIcon(android.R.drawable.sym_def_app_icon).build();
- mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, notification, 0);
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, notification, mUserId);
waitForIdle();
- mBinderService.cancelNotificationWithTag(PKG, PKG, tag, 0, 0);
+ mBinderService.cancelNotificationWithTag(PKG, PKG, tag, 0, mUserId);
waitForIdle();
- mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, notification, 0);
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, notification, mUserId);
waitForIdle();
assertEquals(3, mNotificationRecordLogger.numCalls());
@@ -1949,7 +1944,7 @@
.build();
n.actions[1] = null;
- mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, n, 0);
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, n, mUserId);
waitForIdle();
StatusBarNotification[] posted = mBinderService.getActiveNotifications(PKG);
@@ -1970,7 +1965,7 @@
n.actions[0] = null;
n.actions[1] = null;
- mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, n, 0);
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, n, mUserId);
waitForIdle();
StatusBarNotification[] posted = mBinderService.getActiveNotifications(PKG);
@@ -1982,7 +1977,7 @@
public void enqueueNotificationWithTag_usesAndFinishesTracker() throws Exception {
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"testEnqueueNotificationWithTag_PopulatesGetActiveNotifications", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
assertThat(mPostNotificationTrackerFactory.mCreatedTrackers).hasSize(1);
assertThat(mPostNotificationTrackerFactory.mCreatedTrackers.get(0).isOngoing()).isTrue();
@@ -2000,7 +1995,7 @@
assertThrows(Exception.class,
() -> mBinderService.enqueueNotificationWithTag(PKG, PKG,
"testEnqueueNotificationWithTag_PopulatesGetActiveNotifications", 0,
- /* notification= */ null, 0));
+ /* notification= */ null, mUserId));
waitForIdle();
@@ -2017,7 +2012,7 @@
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"testEnqueueNotificationWithTag_PopulatesGetActiveNotifications", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
waitForIdle();
assertThat(mBinderService.getActiveNotifications(PKG)).hasLength(0);
@@ -2032,7 +2027,7 @@
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"testEnqueueNotificationWithTag_PopulatesGetActiveNotifications", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
waitForIdle();
assertThat(mBinderService.getActiveNotifications(PKG)).hasLength(0);
@@ -2044,7 +2039,7 @@
public void enqueueNotification_acquiresAndReleasesWakeLock() throws Exception {
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"enqueueNotification_acquiresAndReleasesWakeLock", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
assertThat(mAcquiredWakeLocks).hasSize(1);
@@ -2062,7 +2057,7 @@
assertThrows(Exception.class,
() -> mBinderService.enqueueNotificationWithTag(PKG, PKG,
"enqueueNotification_throws_acquiresAndReleasesWakeLock", 0,
- /* notification= */ null, 0));
+ /* notification= */ null, mUserId));
verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
assertThat(mAcquiredWakeLocks).hasSize(1);
@@ -2077,7 +2072,7 @@
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"enqueueNotification_notEnqueued_acquiresAndReleasesWakeLock", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
assertThat(mAcquiredWakeLocks).hasSize(1);
@@ -2098,7 +2093,7 @@
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"enqueueNotification_notPosted_acquiresAndReleasesWakeLock", 0,
- notif, 0);
+ notif, mUserId);
verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
assertThat(mAcquiredWakeLocks).hasSize(1);
@@ -2123,7 +2118,7 @@
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"enqueueNotification_setsWakeLockWorkSource", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
waitForIdle();
InOrder inOrder = inOrder(mPowerManager, wakeLock);
@@ -2137,7 +2132,7 @@
@Test
public void testCancelNonexistentNotification() throws Exception {
mBinderService.cancelNotificationWithTag(PKG, PKG,
- "testCancelNonexistentNotification", 0, 0);
+ "testCancelNonexistentNotification", 0, mUserId);
waitForIdle();
// The notification record logger doesn't even get called when a nonexistent notification
// is cancelled, because that happens very frequently and is not interesting.
@@ -2148,9 +2143,9 @@
public void testCancelNotificationImmediatelyAfterEnqueue() throws Exception {
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"testCancelNotificationImmediatelyAfterEnqueue", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
mBinderService.cancelNotificationWithTag(PKG, PKG,
- "testCancelNotificationImmediatelyAfterEnqueue", 0, 0);
+ "testCancelNotificationImmediatelyAfterEnqueue", 0, mUserId);
waitForIdle();
StatusBarNotification[] notifs =
mBinderService.getActiveNotifications(PKG);
@@ -2185,13 +2180,13 @@
public void testCancelNotificationWhilePostedAndEnqueued() throws Exception {
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"testCancelNotificationWhilePostedAndEnqueued", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
waitForIdle();
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"testCancelNotificationWhilePostedAndEnqueued", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
mBinderService.cancelNotificationWithTag(PKG, PKG,
- "testCancelNotificationWhilePostedAndEnqueued", 0, 0);
+ "testCancelNotificationWhilePostedAndEnqueued", 0, mUserId);
waitForIdle();
StatusBarNotification[] notifs =
mBinderService.getActiveNotifications(PKG);
@@ -3406,12 +3401,12 @@
@Test
public void testPostNotification_appPermissionFixed() throws Exception {
when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
- when(mPermissionHelper.isPermissionFixed(PKG, 0)).thenReturn(true);
+ when(mPermissionHelper.isPermissionFixed(PKG, mUserId)).thenReturn(true);
NotificationRecord temp = generateNotificationRecord(mTestNotificationChannel);
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"testPostNotification_appPermissionFixed", 0,
- temp.getNotification(), 0);
+ temp.getNotification(), mUserId);
waitForIdle();
assertThat(mService.getNotificationRecordCount()).isEqualTo(1);
StatusBarNotification[] notifs =
@@ -3443,7 +3438,7 @@
Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_onTv", 0,
- generateNotificationRecord(null, tv).getNotification(), 0);
+ generateNotificationRecord(null, tv).getNotification(), mUserId);
verify(mPreferencesHelper, times(1)).getConversationNotificationChannel(
anyString(), anyInt(), eq("foo"), eq(null), anyBoolean(), anyBoolean());
}
@@ -3458,7 +3453,7 @@
Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_notOnTv",
- 0, generateNotificationRecord(null, tv).getNotification(), 0);
+ 0, generateNotificationRecord(null, tv).getNotification(), mUserId);
verify(mPreferencesHelper, times(1)).getConversationNotificationChannel(
anyString(), anyInt(), eq(mTestNotificationChannel.getId()), eq(null),
anyBoolean(), anyBoolean());
@@ -11859,10 +11854,10 @@
@Test
public void testGetActiveNotification_filtersUsers() throws Exception {
- when(mUm.getProfileIds(0, false)).thenReturn(new int[]{0, 10});
+ when(mUm.getProfileIds(mUserId, false)).thenReturn(new int[]{mUserId, 10});
NotificationRecord nr0 =
- generateNotificationRecord(mTestNotificationChannel, 0);
+ generateNotificationRecord(mTestNotificationChannel, mUserId);
mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag0",
nr0.getSbn().getId(), nr0.getSbn().getNotification(), nr0.getSbn().getUserId());
@@ -12316,7 +12311,7 @@
.setFullScreenIntent(mock(PendingIntent.class), true)
.build();
- mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
+ mService.fixNotification(n, PKG, "tag", 9, mUserId, mUid, NOT_FOREGROUND_SERVICE, true);
final int stickyFlag = n.flags & Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 87e822c..7462201 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -2123,6 +2123,25 @@
}
@Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void testDefaultRulesFromConfig_modesApi_getPolicies() {
+ // After mZenModeHelper was created, set some things in the policy so it's changed from
+ // default.
+ setupZenConfig();
+
+ // Find default rules; check they have non-null policies; check that they match the default
+ // and not whatever has been set up in setupZenConfig.
+ ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
+ for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) {
+ assertThat(rules).containsKey(defaultId);
+ ZenRule rule = rules.get(defaultId);
+ assertThat(rule.zenPolicy).isNotNull();
+
+ assertThat(rule.zenPolicy).isEqualTo(mZenModeHelper.getDefaultZenPolicy());
+ }
+ }
+
+ @Test
public void testAddAutomaticZenRule_beyondSystemLimit() {
for (int i = 0; i < RULE_LIMIT_PER_PACKAGE; i++) {
ScheduleInfo si = new ScheduleInfo();
@@ -5183,7 +5202,7 @@
// rules for a missing package, created a long time ago and deleted a long time ago
config.deletedRules.put("del6", newZenRule("bad_pkg", twoMonthsAgo, twoMonthsAgo));
- mZenModeHelper.onUserUnlocked(42); // copies config and cleans it up.
+ mZenModeHelper.onUserSwitched(42); // copies config and cleans it up.
assertThat(mZenModeHelper.mConfig.automaticRules.keySet())
.containsExactly("ar1", "ar2", "ar3", "ar4");
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
index f9fe6a9..b431888 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
@@ -53,7 +53,6 @@
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
-import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
@@ -270,10 +269,8 @@
setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_HIGH);
setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_HIGH);
- SparseArray<Float> adaptiveHapticsScales = new SparseArray<>();
- adaptiveHapticsScales.put(USAGE_RINGTONE, 0.5f);
- adaptiveHapticsScales.put(USAGE_NOTIFICATION, 0.5f);
- mVibrationScaler.updateAdaptiveHapticsScales(adaptiveHapticsScales);
+ mVibrationScaler.updateAdaptiveHapticsScale(USAGE_RINGTONE, 0.5f);
+ mVibrationScaler.updateAdaptiveHapticsScale(USAGE_NOTIFICATION, 0.5f);
StepSegment scaled = getFirstSegment(mVibrationScaler.scale(
VibrationEffect.createOneShot(128, 128), USAGE_RINGTONE));
@@ -287,6 +284,50 @@
assertTrue(scaled.getAmplitude() < 0.5);
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ public void scale_clearAdaptiveHapticsScales_clearsAllCachedScales() {
+ setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_HIGH);
+ setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_HIGH);
+
+ mVibrationScaler.updateAdaptiveHapticsScale(USAGE_RINGTONE, 0.5f);
+ mVibrationScaler.updateAdaptiveHapticsScale(USAGE_NOTIFICATION, 0.5f);
+ mVibrationScaler.clearAdaptiveHapticsScales();
+
+ StepSegment scaled = getFirstSegment(mVibrationScaler.scale(
+ VibrationEffect.createOneShot(128, 128), USAGE_RINGTONE));
+ // Ringtone scales up.
+ assertTrue(scaled.getAmplitude() > 0.5);
+
+ scaled = getFirstSegment(mVibrationScaler.scale(
+ VibrationEffect.createWaveform(new long[]{128}, new int[]{128}, -1),
+ USAGE_NOTIFICATION));
+ // Notification scales up.
+ assertTrue(scaled.getAmplitude() > 0.5);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ public void scale_removeAdaptiveHapticsScale_removesCachedScale() {
+ setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_HIGH);
+ setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_HIGH);
+
+ mVibrationScaler.updateAdaptiveHapticsScale(USAGE_RINGTONE, 0.5f);
+ mVibrationScaler.updateAdaptiveHapticsScale(USAGE_NOTIFICATION, 0.5f);
+ mVibrationScaler.removeAdaptiveHapticsScale(USAGE_NOTIFICATION);
+
+ StepSegment scaled = getFirstSegment(mVibrationScaler.scale(
+ VibrationEffect.createOneShot(128, 128), USAGE_RINGTONE));
+ // Ringtone scales down.
+ assertTrue(scaled.getAmplitude() < 0.5);
+
+ scaled = getFirstSegment(mVibrationScaler.scale(
+ VibrationEffect.createWaveform(new long[]{128}, new int[]{128}, -1),
+ USAGE_NOTIFICATION));
+ // Notification scales up.
+ assertTrue(scaled.getAmplitude() > 0.5);
+ }
+
private void setDefaultIntensity(@VibrationAttributes.Usage int usage,
@Vibrator.VibrationIntensity int intensity) {
when(mVibrationConfigMock.getDefaultVibrationIntensity(eq(usage))).thenReturn(intensity);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index b0aef47..6e478d8 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -16,6 +16,7 @@
package com.android.server.vibrator;
+import static android.os.VibrationAttributes.USAGE_RINGTONE;
import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
@@ -54,12 +55,16 @@
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.test.TestLooper;
+import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
@@ -81,6 +86,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.CompletableFuture;
import java.util.function.BooleanSupplier;
import java.util.stream.Collectors;
@@ -95,6 +101,9 @@
private static final int TEST_RAMP_STEP_DURATION = 5;
@Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
@Mock private PackageManagerInternal mPackageManagerInternalMock;
@Mock private VibrationThread.VibratorManagerHooks mManagerHooks;
@@ -104,6 +113,7 @@
private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
private VibrationSettings mVibrationSettings;
+ private VibrationScaler mVibrationScaler;
private TestLooper mTestLooper;
private TestLooperAutoDispatcher mCustomTestLooperDispatcher;
private VibrationThread mThread;
@@ -132,6 +142,7 @@
Context context = InstrumentationRegistry.getContext();
mVibrationSettings = new VibrationSettings(context, new Handler(mTestLooper.getLooper()),
mVibrationConfigMock);
+ mVibrationScaler = new VibrationScaler(context, mVibrationSettings);
mockVibrators(VIBRATOR_ID);
@@ -231,6 +242,45 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ public void vibrate_singleWaveformWithAdaptiveHapticsScaling_scalesAmplitudesProperly() {
+ mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ new long[]{5, 5, 5}, new int[]{1, 1, 1}, -1);
+ CompletableFuture<Void> mRequestVibrationParamsFuture = CompletableFuture.runAsync(() -> {
+ mVibrationScaler.updateAdaptiveHapticsScale(USAGE_RINGTONE, 0.5f);
+ });
+ long vibrationId = startThreadAndDispatcher(effect, mRequestVibrationParamsFuture,
+ USAGE_RINGTONE);
+ waitForCompletion();
+
+ assertEquals(Arrays.asList(expectedOneShot(15)),
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
+ List<Float> amplitudes = mVibratorProviders.get(VIBRATOR_ID).getAmplitudes();
+ for (int i = 0; i < amplitudes.size(); i++) {
+ assertTrue(amplitudes.get(i) < 1 / 255f);
+ }
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ public void vibrate_withVibrationParamsRequestStalling_timeoutRequestAndApplyNoScaling() {
+ mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ new long[]{5, 5, 5}, new int[]{1, 1, 1}, -1);
+
+ CompletableFuture<Void> neverCompletingFuture = new CompletableFuture<>();
+ long vibrationId = startThreadAndDispatcher(effect, neverCompletingFuture, USAGE_RINGTONE);
+ waitForCompletion();
+
+ assertEquals(Arrays.asList(expectedOneShot(15)),
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
+ assertEquals(expectedAmplitudes(1, 1, 1),
+ mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
+ }
+
+ @Test
public void vibrate_singleVibratorRepeatingWaveform_runsVibrationUntilThreadCancelled()
throws Exception {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
@@ -1610,10 +1660,26 @@
}
private long startThreadAndDispatcher(HalVibration vib) {
+ return startThreadAndDispatcher(vib, /* requestVibrationParamsFuture= */ null);
+ }
+
+ private long startThreadAndDispatcher(VibrationEffect effect,
+ CompletableFuture<Void> requestVibrationParamsFuture, int usage) {
+ VibrationAttributes attrs = new VibrationAttributes.Builder()
+ .setUsage(usage)
+ .build();
+ HalVibration vib = new HalVibration(mVibrationToken,
+ CombinedVibration.createParallel(effect),
+ new Vibration.CallerInfo(attrs, UID, DEVICE_ID, PACKAGE_NAME, "reason"));
+ return startThreadAndDispatcher(vib, requestVibrationParamsFuture);
+ }
+
+ private long startThreadAndDispatcher(HalVibration vib,
+ CompletableFuture<Void> requestVibrationParamsFuture) {
mControllers = createVibratorControllers();
DeviceAdapter deviceAdapter = new DeviceAdapter(mVibrationSettings, mControllers);
- mVibrationConductor =
- new VibrationStepConductor(vib, mVibrationSettings, deviceAdapter, mManagerHooks);
+ mVibrationConductor = new VibrationStepConductor(vib, mVibrationSettings, deviceAdapter,
+ mVibrationScaler, requestVibrationParamsFuture, mManagerHooks);
assertTrue(mThread.runVibrationOnVibrationThread(mVibrationConductor));
return mVibrationConductor.getVibration().id;
}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
index 1e0b1df..2823223 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
@@ -18,29 +18,47 @@
import static android.os.VibrationAttributes.USAGE_ALARM;
import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
+import static android.os.VibrationAttributes.USAGE_HARDWARE_FEEDBACK;
+import static android.os.VibrationAttributes.USAGE_MEDIA;
import static android.os.VibrationAttributes.USAGE_NOTIFICATION;
+import static android.os.VibrationAttributes.USAGE_RINGTONE;
+import static android.os.VibrationAttributes.USAGE_TOUCH;
+import static android.os.VibrationAttributes.USAGE_UNKNOWN;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+import android.content.ComponentName;
+import android.content.pm.PackageManagerInternal;
import android.frameworks.vibrator.ScaleParam;
import android.frameworks.vibrator.VibrationParam;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
import android.os.RemoteException;
+import android.os.test.TestLooper;
import android.util.SparseArray;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.server.LocalServices;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
public class VibratorControlServiceTest {
@@ -49,35 +67,45 @@
@Mock
private VibrationScaler mMockVibrationScaler;
- @Captor
- private ArgumentCaptor<SparseArray<Float>> mVibrationScalesCaptor;
+ @Mock
+ private PackageManagerInternal mPackageManagerInternalMock;
+ private FakeVibratorController mFakeVibratorController;
private VibratorControlService mVibratorControlService;
+ private VibrationSettings mVibrationSettings;
private final Object mLock = new Object();
@Before
public void setUp() throws Exception {
+ when(mPackageManagerInternalMock.getSystemUiServiceComponent())
+ .thenReturn(new ComponentName("", ""));
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock);
+
+ TestLooper testLooper = new TestLooper();
+ mVibrationSettings = new VibrationSettings(
+ ApplicationProvider.getApplicationContext(), new Handler(testLooper.getLooper()));
+
+ mFakeVibratorController = new FakeVibratorController();
mVibratorControlService = new VibratorControlService(new VibratorControllerHolder(),
- mMockVibrationScaler, mLock);
+ mMockVibrationScaler, mVibrationSettings, mLock);
}
@Test
public void testRegisterVibratorController() throws RemoteException {
- FakeVibratorController fakeController = new FakeVibratorController();
- mVibratorControlService.registerVibratorController(fakeController);
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
- assertThat(fakeController.isLinkedToDeath).isTrue();
+ assertThat(mFakeVibratorController.isLinkedToDeath).isTrue();
}
@Test
public void testUnregisterVibratorController_providingTheRegisteredController_performsRequest()
throws RemoteException {
- FakeVibratorController fakeController = new FakeVibratorController();
- mVibratorControlService.registerVibratorController(fakeController);
- mVibratorControlService.unregisterVibratorController(fakeController);
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
+ mVibratorControlService.unregisterVibratorController(mFakeVibratorController);
- verify(mMockVibrationScaler).updateAdaptiveHapticsScales(null);
- assertThat(fakeController.isLinkedToDeath).isFalse();
+ verify(mMockVibrationScaler).clearAdaptiveHapticsScales();
+ assertThat(mFakeVibratorController.isLinkedToDeath).isFalse();
}
@Test
@@ -93,41 +121,80 @@
}
@Test
+ public void testOnRequestVibrationParamsComplete_cachesAdaptiveHapticsScalesCorrectly()
+ throws RemoteException {
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
+ int timeoutInMillis = 10;
+ CompletableFuture<Void> future =
+ mVibratorControlService.triggerVibrationParamsRequest(USAGE_RINGTONE,
+ timeoutInMillis);
+ IBinder token = mVibratorControlService.getRequestVibrationParamsToken();
+
+ SparseArray<Float> vibrationScales = new SparseArray<>();
+ vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f);
+ vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
+
+ mVibratorControlService.onRequestVibrationParamsComplete(token,
+ generateVibrationParams(vibrationScales));
+
+ verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_ALARM, 0.7f);
+ verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_NOTIFICATION, 0.4f);
+ // Setting ScaleParam.TYPE_NOTIFICATION will update vibration scaling for both
+ // notification and communication request usages.
+ verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_COMMUNICATION_REQUEST, 0.4f);
+ verifyNoMoreInteractions(mMockVibrationScaler);
+
+ assertThat(future.isDone()).isTrue();
+ assertThat(future.isCompletedExceptionally()).isFalse();
+ }
+
+ @Test
+ public void testOnRequestVibrationParamsComplete_withIncorrectToken_ignoresRequest()
+ throws RemoteException, InterruptedException {
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
+ int timeoutInMillis = 10;
+ CompletableFuture<Void> unusedFuture =
+ mVibratorControlService.triggerVibrationParamsRequest(USAGE_RINGTONE,
+ timeoutInMillis);
+
+ SparseArray<Float> vibrationScales = new SparseArray<>();
+ vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f);
+ vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
+
+ mVibratorControlService.onRequestVibrationParamsComplete(new Binder(),
+ generateVibrationParams(vibrationScales));
+
+ verifyZeroInteractions(mMockVibrationScaler);
+ }
+
+ @Test
public void testSetVibrationParams_cachesAdaptiveHapticsScalesCorrectly()
throws RemoteException {
- FakeVibratorController fakeController = new FakeVibratorController();
- mVibratorControlService.registerVibratorController(fakeController);
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
SparseArray<Float> vibrationScales = new SparseArray<>();
vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f);
vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
mVibratorControlService.setVibrationParams(generateVibrationParams(vibrationScales),
- fakeController);
+ mFakeVibratorController);
- verify(mMockVibrationScaler).updateAdaptiveHapticsScales(mVibrationScalesCaptor.capture());
- SparseArray<Float> cachedVibrationScales = mVibrationScalesCaptor.getValue();
- assertThat(cachedVibrationScales.size()).isEqualTo(3);
- assertThat(cachedVibrationScales.keyAt(0)).isEqualTo(USAGE_ALARM);
- assertThat(cachedVibrationScales.valueAt(0)).isEqualTo(0.7f);
- assertThat(cachedVibrationScales.keyAt(1)).isEqualTo(USAGE_NOTIFICATION);
- assertThat(cachedVibrationScales.valueAt(1)).isEqualTo(0.4f);
+ verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_ALARM, 0.7f);
+ verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_NOTIFICATION, 0.4f);
// Setting ScaleParam.TYPE_NOTIFICATION will update vibration scaling for both
// notification and communication request usages.
- assertThat(cachedVibrationScales.keyAt(2)).isEqualTo(USAGE_COMMUNICATION_REQUEST);
- assertThat(cachedVibrationScales.valueAt(2)).isEqualTo(0.4f);
+ verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_COMMUNICATION_REQUEST, 0.4f);
+ verifyNoMoreInteractions(mMockVibrationScaler);
}
@Test
public void testSetVibrationParams_withUnregisteredController_ignoresRequest()
throws RemoteException {
- FakeVibratorController fakeController = new FakeVibratorController();
-
SparseArray<Float> vibrationScales = new SparseArray<>();
vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f);
vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
mVibratorControlService.setVibrationParams(generateVibrationParams(vibrationScales),
- fakeController);
+ mFakeVibratorController);
verifyZeroInteractions(mMockVibrationScaler);
}
@@ -135,23 +202,72 @@
@Test
public void testClearVibrationParams_clearsCachedAdaptiveHapticsScales()
throws RemoteException {
- FakeVibratorController fakeController = new FakeVibratorController();
- mVibratorControlService.registerVibratorController(fakeController);
- mVibratorControlService.clearVibrationParams(ScaleParam.TYPE_ALARM, fakeController);
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
+ int types = buildVibrationTypesMask(ScaleParam.TYPE_ALARM, ScaleParam.TYPE_NOTIFICATION);
- verify(mMockVibrationScaler).updateAdaptiveHapticsScales(null);
+ mVibratorControlService.clearVibrationParams(types, mFakeVibratorController);
+
+ verify(mMockVibrationScaler).removeAdaptiveHapticsScale(USAGE_ALARM);
+ verify(mMockVibrationScaler).removeAdaptiveHapticsScale(USAGE_NOTIFICATION);
+ // Clearing ScaleParam.TYPE_NOTIFICATION will clear vibration scaling for both
+ // notification and communication request usages.
+ verify(mMockVibrationScaler).removeAdaptiveHapticsScale(USAGE_COMMUNICATION_REQUEST);
}
@Test
public void testClearVibrationParams_withUnregisteredController_ignoresRequest()
throws RemoteException {
- FakeVibratorController fakeController = new FakeVibratorController();
-
- mVibratorControlService.clearVibrationParams(ScaleParam.TYPE_ALARM, fakeController);
+ mVibratorControlService.clearVibrationParams(ScaleParam.TYPE_ALARM,
+ mFakeVibratorController);
verifyZeroInteractions(mMockVibrationScaler);
}
+ @Test
+ public void testRequestVibrationParams_createsFutureRequestProperly()
+ throws RemoteException {
+ int timeoutInMillis = 10;
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
+ CompletableFuture<Void> future =
+ mVibratorControlService.triggerVibrationParamsRequest(USAGE_RINGTONE,
+ timeoutInMillis);
+ try {
+ future.orTimeout(timeoutInMillis, TimeUnit.MILLISECONDS).get();
+ } catch (Throwable ignored) {
+ }
+ assertThat(mFakeVibratorController.didRequestVibrationParams).isTrue();
+ assertThat(mFakeVibratorController.requestVibrationType).isEqualTo(
+ ScaleParam.TYPE_RINGTONE);
+ assertThat(mFakeVibratorController.requestTimeoutInMillis).isEqualTo(timeoutInMillis);
+ }
+
+ @Test
+ public void testShouldRequestVibrationParams_returnsTrueForVibrationsThatShouldRequestParams()
+ throws RemoteException {
+ int[] vibrations =
+ new int[]{USAGE_ALARM, USAGE_RINGTONE, USAGE_MEDIA, USAGE_TOUCH, USAGE_NOTIFICATION,
+ USAGE_HARDWARE_FEEDBACK, USAGE_UNKNOWN, USAGE_COMMUNICATION_REQUEST};
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
+
+ for (int vibration : vibrations) {
+ assertThat(mVibratorControlService.shouldRequestVibrationParams(vibration))
+ .isEqualTo(ArrayUtils.contains(
+ mVibrationSettings.getRequestVibrationParamsForUsages(), vibration));
+ }
+ }
+
+ @Test
+ public void testShouldRequestVibrationParams_unregisteredVibratorController_returnsFalse()
+ throws RemoteException {
+ int[] vibrations =
+ new int[]{USAGE_ALARM, USAGE_RINGTONE, USAGE_MEDIA, USAGE_TOUCH, USAGE_NOTIFICATION,
+ USAGE_HARDWARE_FEEDBACK, USAGE_UNKNOWN, USAGE_COMMUNICATION_REQUEST};
+
+ for (int vibration : vibrations) {
+ assertThat(mVibratorControlService.shouldRequestVibrationParams(vibration)).isFalse();
+ }
+ }
+
private VibrationParam[] generateVibrationParams(SparseArray<Float> vibrationScales) {
List<VibrationParam> vibrationParamList = new ArrayList<>();
for (int i = 0; i < vibrationScales.size(); i++) {
@@ -173,4 +289,12 @@
return vibrationParam;
}
+
+ private int buildVibrationTypesMask(int... types) {
+ int typesMask = 0;
+ for (int type : types) {
+ typesMask |= type;
+ }
+ return typesMask;
+ }
}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index d6b2116..bdbb6c6 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -318,6 +318,12 @@
return new HapticFeedbackVibrationProvider(
resources, vibratorInfo, mHapticFeedbackVibrationMap);
}
+
+ VibratorControllerHolder createVibratorControllerHolder() {
+ VibratorControllerHolder holder = new VibratorControllerHolder();
+ holder.setVibratorController(new FakeVibratorController());
+ return holder;
+ }
});
return mService;
}
@@ -965,8 +971,9 @@
new long[]{10, 10_000}, new int[]{255, 0}, 1);
vibrate(service, repeatingEffect, ALARM_ATTRS);
- // VibrationThread will start this vibration async, wait until the off waveform step.
- assertTrue(waitUntil(s -> fakeVibrator.getOffCount() > 0, service, TEST_TIMEOUT_MILLIS));
+ // VibrationThread will start this vibration async, wait until it has started.
+ assertTrue(waitUntil(s -> !fakeVibrator.getAllEffectSegments().isEmpty(), service,
+ TEST_TIMEOUT_MILLIS));
vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
ALARM_ATTRS);
@@ -1019,8 +1026,9 @@
new long[]{10, 10_000}, new int[]{255, 0}, -1);
vibrate(service, alarmEffect, ALARM_ATTRS);
- // VibrationThread will start this vibration async, wait until the off waveform step.
- assertTrue(waitUntil(s -> fakeVibrator.getOffCount() > 0, service, TEST_TIMEOUT_MILLIS));
+ // VibrationThread will start this vibration async, wait until it has started.
+ assertTrue(waitUntil(s -> !fakeVibrator.getAllEffectSegments().isEmpty(), service,
+ TEST_TIMEOUT_MILLIS));
vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
UNKNOWN_ATTRS);
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java
index 7e23587..3912206 100644
--- a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java
@@ -20,6 +20,7 @@
import android.frameworks.vibrator.IVibratorController;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.VibrationAttributes;
/**
* Provides a fake implementation of {@link android.frameworks.vibrator.IVibratorController} for
@@ -28,10 +29,16 @@
public final class FakeVibratorController extends IVibratorController.Stub {
public boolean isLinkedToDeath = false;
+ public boolean didRequestVibrationParams = false;
+ public int requestVibrationType = VibrationAttributes.USAGE_UNKNOWN;
+ public long requestTimeoutInMillis = 0;
@Override
- public void requestVibrationParams(int i, long l, IBinder iBinder) throws RemoteException {
-
+ public void requestVibrationParams(int vibrationType, long timeoutInMillis, IBinder iBinder)
+ throws RemoteException {
+ didRequestVibrationParams = true;
+ requestVibrationType = vibrationType;
+ requestTimeoutInMillis = timeoutInMillis;
}
@Override
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
index 0382ca0..e21388e 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
@@ -34,6 +34,7 @@
import static android.view.KeyEvent.KEYCODE_SLASH;
import static android.view.KeyEvent.KEYCODE_SPACE;
import static android.view.KeyEvent.KEYCODE_TAB;
+import static android.view.KeyEvent.KEYCODE_SCREENSHOT;
import static android.view.KeyEvent.KEYCODE_U;
import static android.view.KeyEvent.KEYCODE_Z;
@@ -193,4 +194,26 @@
mPhoneWindowManager.verifyNewBrightness(newBrightness[i]);
}
}
+
+ /**
+ * Sends a KEYCODE_SCREENSHOT and validates screenshot is taken if flag is enabled
+ */
+ @Test
+ public void testTakeScreenshot_flagEnabled() {
+ mSetFlagsRule.enableFlags(com.android.hardware.input.Flags
+ .FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE);
+ sendKeyCombination(new int[]{KEYCODE_SCREENSHOT}, 0);
+ mPhoneWindowManager.assertTakeScreenshotCalled();
+ }
+
+ /**
+ * Sends a KEYCODE_SCREENSHOT and validates screenshot is not taken if flag is disabled
+ */
+ @Test
+ public void testTakeScreenshot_flagDisabled() {
+ mSetFlagsRule.disableFlags(com.android.hardware.input.Flags
+ .FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE);
+ sendKeyCombination(new int[]{KEYCODE_SCREENSHOT}, 0);
+ mPhoneWindowManager.assertTakeScreenshotNotCalled();
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
index 157d162..fee6582 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -46,6 +46,7 @@
import static java.util.Collections.unmodifiableMap;
import android.content.Context;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArrayMap;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
@@ -57,11 +58,17 @@
import org.junit.After;
import org.junit.Rule;
+import org.junit.rules.RuleChain;
import java.util.Map;
class ShortcutKeyTestBase {
- @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ public final FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+
+ @Rule
+ public RuleChain rules = RuleChain.outerRule(mSettingsProviderRule).around(mSetFlagsRule);
TestPhoneWindowManager mPhoneWindowManager;
DispatchedKeyHandler mDispatchedKeyHandler = event -> false;
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index c8abd8d..2904c03 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -596,6 +596,11 @@
verify(mDisplayPolicy).takeScreenshot(anyInt(), anyInt());
}
+ void assertTakeScreenshotNotCalled() {
+ mTestLooper.dispatchAll();
+ verify(mDisplayPolicy, never()).takeScreenshot(anyInt(), anyInt());
+ }
+
void assertShowGlobalActionsCalled() {
mTestLooper.dispatchAll();
verify(mPhoneWindowManager).showGlobalActions();
diff --git a/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java b/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
index 56c3ec0..c2a5824 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
@@ -146,7 +146,7 @@
private void setupMocks(boolean currentActivityAssistAllowed, boolean assistStructureAllowed,
boolean assistScreenshotAllowed) throws Exception {
- doReturn(currentActivityAssistAllowed).when(mAtm).isAssistDataAllowedOnCurrentActivity();
+ doReturn(currentActivityAssistAllowed).when(mAtm).isAssistDataAllowed();
doReturn(assistStructureAllowed ? MODE_ALLOWED : MODE_ERRORED).when(mAppOpsManager)
.noteOpNoThrow(eq(OP_ASSIST_STRUCTURE), anyInt(), anyString(), any(), any());
doReturn(assistScreenshotAllowed ? MODE_ALLOWED : MODE_ERRORED).when(mAppOpsManager)
diff --git a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
index 09f677e7..46e14d51 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
@@ -16,8 +16,6 @@
package com.android.server.wm;
-import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
-
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -38,13 +36,12 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.filters.SmallTest;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
@@ -56,13 +53,8 @@
*/
@SmallTest
@Presubmit
-public class ClientLifecycleManagerTests {
-
- @Rule(order = 0)
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
-
- @Rule(order = 1)
- public final SystemServicesTestRule mSystemServices = new SystemServicesTestRule();
+@RunWith(WindowTestRunner.class)
+public class ClientLifecycleManagerTests extends SystemServiceTestsBase {
@Mock
private IBinder mClientBinder;
@@ -86,7 +78,7 @@
public void setup() {
MockitoAnnotations.initMocks(this);
- mWms = mSystemServices.getWindowManagerService();
+ mWms = mSystemServicesTestRule.getWindowManagerService();
mLifecycleManager = spy(new ClientLifecycleManager());
mLifecycleManager.setWindowManager(mWms);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index be83744..0ee78e3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1802,8 +1802,7 @@
@Override
public void addStartingWindow(StartingWindowInfo info) {
synchronized (mWMService.mGlobalLock) {
- final ActivityRecord activity = mWMService.mRoot.getActivityRecord(
- info.appToken);
+ final ActivityRecord activity = ActivityRecord.forTokenLocked(info.appToken);
IWindow iWindow = mock(IWindow.class);
doReturn(mock(IBinder.class)).when(iWindow).asBinder();
final WindowState window = WindowTestsBase.createWindow(null,
@@ -1825,8 +1824,7 @@
final IBinder appToken = mTaskAppMap.get(removalInfo.taskId);
if (appToken != null) {
mTaskAppMap.remove(removalInfo.taskId);
- final ActivityRecord activity = mWMService.mRoot.getActivityRecord(
- appToken);
+ final ActivityRecord activity = ActivityRecord.forTokenLocked(appToken);
WindowState win = mAppWindowMap.remove(appToken);
activity.removeChild(win);
activity.mStartingWindow = null;
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index 7ccc27e..0c324e6 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -649,12 +649,19 @@
}
/**
- * @return Reason for denial if the registration state is {@link #REGISTRATION_STATE_DENIED}.
- * Depending on {@code accessNetworkTechnology}, the values are defined in 3GPP TS 24.008
- * 10.5.3.6 for UMTS, 3GPP TS 24.301 9.9.3.9 for LTE, and 3GPP2 A.S0001 6.2.2.44 for CDMA
- * @hide
+ * Get the 3GPP/3GPP2 reason code indicating why registration failed.
+ *
+ * Returns the reason code for non-transient registration failures. Typically this method will
+ * only return the last reason code received during a network selection procedure. The reason
+ * code is system-specific; however, the reason codes for both 3GPP and 3GPP2 systems are
+ * largely equivalent across generations.
+ *
+ * @return registration reject cause if available, otherwise 0. Depending on
+ * {@link #getAccessNetworkTechnology}, the values are defined in 3GPP TS 24.008 10.5.3.6 for
+ * WCDMA/UMTS, 3GPP TS 24.301 9.9.3.9 for LTE/EPS, 3GPP 24.501 Annex A for NR/5GS, or 3GPP2
+ * A.S0001 6.2.2.44 for CDMA.
*/
- @SystemApi
+ @FlaggedApi(Flags.FLAG_NETWORK_REGISTRATION_INFO_REJECT_CAUSE)
public int getRejectCause() {
return mRejectCause;
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index a0f0338..89661a4 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -6638,11 +6638,7 @@
}
}
- /**
- * @hide
- */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
- private ITelephony getITelephony() {
+ private static ITelephony getITelephony() {
// Keeps cache disabled until test fixes are checked into AOSP.
if (!sServiceHandleCacheEnabled) {
return ITelephony.Stub.asInterface(
@@ -18696,11 +18692,7 @@
@SimState
public static int getSimStateForSlotIndex(int slotIndex) {
try {
- ITelephony telephony = ITelephony.Stub.asInterface(
- TelephonyFrameworkInitializer
- .getTelephonyServiceManager()
- .getTelephonyServiceRegisterer()
- .get());
+ ITelephony telephony = getITelephony();
if (telephony != null) {
return telephony.getSimStateForSlotIndex(slotIndex);
}
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 09d2108..9b8e62f 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -43,6 +43,7 @@
import android.util.Log;
import com.android.internal.telephony.euicc.IEuiccController;
+import com.android.internal.telephony.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -897,9 +898,7 @@
/** @hide */
public EuiccManager(Context context) {
mContext = context;
- TelephonyManager tm = (TelephonyManager)
- context.getSystemService(Context.TELEPHONY_SERVICE);
- mCardId = tm.getCardIdForDefaultEuicc();
+ mCardId = getCardIdForDefaultEuicc();
}
/** @hide */
@@ -1646,14 +1645,9 @@
private boolean refreshCardIdIfUninitialized() {
// Refresh mCardId if its UNINITIALIZED_CARD_ID
if (mCardId == TelephonyManager.UNINITIALIZED_CARD_ID) {
- TelephonyManager tm = (TelephonyManager)
- mContext.getSystemService(Context.TELEPHONY_SERVICE);
- mCardId = tm.getCardIdForDefaultEuicc();
+ mCardId = getCardIdForDefaultEuicc();
}
- if (mCardId == TelephonyManager.UNINITIALIZED_CARD_ID) {
- return false;
- }
- return true;
+ return mCardId != TelephonyManager.UNINITIALIZED_CARD_ID;
}
private static void sendUnavailableError(PendingIntent callbackIntent) {
@@ -1672,6 +1666,23 @@
.get());
}
+ private int getCardIdForDefaultEuicc() {
+ int cardId = TelephonyManager.UNINITIALIZED_CARD_ID;
+
+ if (Flags.enforceTelephonyFeatureMappingForPublicApis()) {
+ PackageManager pm = mContext.getPackageManager();
+ if (pm != null && pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_EUICC)) {
+ TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+ cardId = tm.getCardIdForDefaultEuicc();
+ }
+ } else {
+ TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+ cardId = tm.getCardIdForDefaultEuicc();
+ }
+
+ return cardId;
+ }
+
/**
* Returns whether the passing portIndex is available.
* A port is available if it is active without enabled profile on it or
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
index 6015e931..381c574 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
@@ -101,7 +101,7 @@
@Mock protected Context mContext;
@Mock protected Network mNetwork;
@Mock protected FeatureFlags mFeatureFlags;
- @Mock protected com.android.net.flags.FeatureFlags mCoreNetFeatureFlags;
+ @Mock protected android.net.platform.flags.FeatureFlags mCoreNetFeatureFlags;
@Mock protected TelephonySubscriptionSnapshot mSubscriptionSnapshot;
@Mock protected TelephonyManager mTelephonyManager;
@Mock protected IPowerManager mPowerManagerService;
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 275a0e2..938a5ed 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -232,3 +232,39 @@
],
},
}
+
+cc_genrule {
+ name: "aapt2_results",
+ srcs: [
+ ":aapt2_tests",
+ "integration-tests/CompileTest/**/*",
+ "integration-tests/CommandTests/**/*",
+ "integration-tests/ConvertTest/**/*",
+ "integration-tests/DumpTest/**/*",
+ ],
+ host_supported: true,
+ device_supported: false,
+ target: {
+ windows: {
+ compile_multilib: "64",
+ },
+ },
+ out: ["result.xml"],
+ cmd: "mkdir -p $(genDir)/integration-tests/CompileTest/ && " +
+ "cp $(locations integration-tests/CompileTest/**/*) $(genDir)/integration-tests/CompileTest/ && " +
+ "mkdir -p $(genDir)/integration-tests/CommandTests/ && " +
+ "cp $(locations integration-tests/CommandTests/**/*) $(genDir)/integration-tests/CompileTest/ && " +
+ "mkdir -p $(genDir)/integration-tests/ConvertTest/ && " +
+ "cp $(locations integration-tests/ConvertTest/**/*) $(genDir)/integration-tests/ConvertTest/ && " +
+ "mkdir -p $(genDir)/integration-tests/DumpTest/ && " +
+ "cp $(locations integration-tests/DumpTest/**/*) $(genDir)/integration-tests/DumpTest/ && " +
+ "cp $(locations :aapt2_tests) $(genDir)/ && " +
+ "$(genDir)/aapt2_tests " +
+ "--gtest_output=xml:$(out) " +
+ ">/dev/null 2>&1 ; true",
+}
+
+phony_rule {
+ name: "aapt2_run_host_unit_tests",
+ phony_deps: ["aapt2_results"],
+}
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index 34a1b11..15ae2ba 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -1,22 +1,4 @@
-LOCAL_PATH := $(call my-dir)
-
include $(CLEAR_VARS)
-
-aapt2_results := $(call intermediates-dir-for,PACKAGING,aapt2_run_host_unit_tests)/result.xml
-
-# Target for running host unit tests on post/pre-submit.
-.PHONY: aapt2_run_host_unit_tests
-aapt2_run_host_unit_tests: $(aapt2_results)
-
-$(call dist-for-goals,aapt2_run_host_unit_tests,$(aapt2_results):gtest/aapt2_host_unit_tests_result.xml)
-
-# Always run the tests again, even if they haven't changed
-$(aapt2_results): .KATI_IMPLICIT_OUTPUTS := $(aapt2_results)-nocache
-$(aapt2_results): $(HOST_OUT_NATIVE_TESTS)/aapt2_tests/aapt2_tests
- -$(HOST_OUT_NATIVE_TESTS)/aapt2_tests/aapt2_tests --gtest_output=xml:$@ > /dev/null 2>&1
-
+aapt2_results := ./out/soong/.intermediates/frameworks/base/tools/aapt2/aapt2_results
$(call declare-1p-target,$(aapt2_results))
-
aapt2_results :=
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/EventLog_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/EventLog_host.java
index 292e8da..6480cfc 100644
--- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/EventLog_host.java
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/EventLog_host.java
@@ -15,9 +15,9 @@
*/
package com.android.hoststubgen.nativesubstitution;
-import android.util.Log;
-import android.util.Log.Level;
+import com.android.internal.os.RuntimeInit;
+import java.io.PrintStream;
import java.util.Collection;
public class EventLog_host {
@@ -54,7 +54,7 @@
}
}
sb.append(']');
- System.out.println(sb.toString());
+ getRealOut().println(sb.toString());
return sb.length();
}
@@ -66,4 +66,16 @@
Collection<android.util.EventLog.Event> output) {
throw new UnsupportedOperationException();
}
+
+ /**
+ * Return the "real" {@code System.out} if it's been swapped by {@code RavenwoodRuleImpl}, so
+ * that we don't end up in a recursive loop.
+ */
+ private static PrintStream getRealOut() {
+ if (RuntimeInit.sOut$ravenwood != null) {
+ return RuntimeInit.sOut$ravenwood;
+ } else {
+ return System.out;
+ }
+ }
}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Log_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Log_host.java
index ee55c7a..cdfa302 100644
--- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Log_host.java
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Log_host.java
@@ -18,6 +18,8 @@
import android.util.Log;
import android.util.Log.Level;
+import com.android.internal.os.RuntimeInit;
+
import java.io.PrintStream;
public class Log_host {
@@ -27,7 +29,6 @@
}
public static int println_native(int bufID, int priority, String tag, String msg) {
- final PrintStream out = System.out;
final String buffer;
switch (bufID) {
case Log.LOG_ID_MAIN: buffer = "main"; break;
@@ -50,7 +51,7 @@
};
for (String s : msg.split("\\n")) {
- out.println(String.format("logd: [%s] %s %s: %s", buffer, prio, tag, s));
+ getRealOut().println(String.format("logd: [%s] %s %s: %s", buffer, prio, tag, s));
}
return msg.length();
}
@@ -58,4 +59,16 @@
public static int logger_entry_max_payload_native() {
return 4068; // [ravenwood] This is what people use in various places.
}
+
+ /**
+ * Return the "real" {@code System.out} if it's been swapped by {@code RavenwoodRuleImpl}, so
+ * that we don't end up in a recursive loop.
+ */
+ private static PrintStream getRealOut() {
+ if (RuntimeInit.sOut$ravenwood != null) {
+ return RuntimeInit.sOut$ravenwood;
+ } else {
+ return System.out;
+ }
+ }
}
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
index 7c6aa25..60eb47ee 100644
--- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
@@ -63,7 +63,10 @@
*/
public static void onThrowMethodCalled() {
// TODO: Maybe add call tracking?
- throw new RuntimeException("This method is not supported on the host side");
+ throw new RuntimeException(
+ "This method is not yet supported under the Ravenwood deviceless testing "
+ + "environment; consider requesting support from the API owner or "
+ + "consider using Mockito; more details at go/ravenwood-docs");
}
/**
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
index fc6b862..ba17c75 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
@@ -68,7 +68,7 @@
TinyFrameworkForTextPolicy tfc = new TinyFrameworkForTextPolicy();
thrown.expect(RuntimeException.class);
- thrown.expectMessage("This method is not supported on the host side");
+ thrown.expectMessage("not yet supported");
tfc.visibleButUsesUnsupportedMethod();
}
@@ -182,7 +182,7 @@
} catch (java.lang.reflect.InvocationTargetException e) {
var inner = e.getCause();
- assertThat(inner.getMessage()).contains("not supported on the host side");
+ assertThat(inner.getMessage()).contains("not yet supported");
}
}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithAnnotTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithAnnotTest.java
index 20cc2ec..288c716 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithAnnotTest.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithAnnotTest.java
@@ -60,7 +60,7 @@
TinyFrameworkClassAnnotations tfc = new TinyFrameworkClassAnnotations();
thrown.expect(RuntimeException.class);
- thrown.expectMessage("This method is not supported on the host side");
+ thrown.expectMessage("not yet supported");
tfc.visibleButUsesUnsupportedMethod();
}
}
diff --git a/wifi/java/src/android/net/wifi/nl80211/DeviceWiphyCapabilities.java b/wifi/java/src/android/net/wifi/nl80211/DeviceWiphyCapabilities.java
index 21700d5..6d57a87 100644
--- a/wifi/java/src/android/net/wifi/nl80211/DeviceWiphyCapabilities.java
+++ b/wifi/java/src/android/net/wifi/nl80211/DeviceWiphyCapabilities.java
@@ -16,11 +16,13 @@
package android.net.wifi.nl80211;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiAnnotations.ChannelWidth;
import android.net.wifi.WifiAnnotations.WifiStandard;
+import android.net.wifi.flags.Flags;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
@@ -48,6 +50,7 @@
private boolean mChannelWidth320MhzSupported;
private int mMaxNumberTxSpatialStreams;
private int mMaxNumberRxSpatialStreams;
+ private int mMaxNumberAkms;
/** public constructor */
@@ -61,6 +64,7 @@
mChannelWidth320MhzSupported = false;
mMaxNumberTxSpatialStreams = 1;
mMaxNumberRxSpatialStreams = 1;
+ mMaxNumberAkms = 1;
}
/**
@@ -199,6 +203,25 @@
}
/**
+ * Get the maximum number of AKM suites supported in the connection request to the driver.
+ *
+ * @return maximum number of AKMs
+ */
+ @FlaggedApi(Flags.FLAG_GET_DEVICE_CROSS_AKM_ROAMING_SUPPORT)
+ public int getMaxNumberAkms() {
+ return mMaxNumberAkms;
+ }
+
+ /**
+ * Set the maximum number of AKM suites supported in the connection request to the driver.
+ *
+ * @hide
+ */
+ public void setMaxNumberAkms(int akms) {
+ mMaxNumberAkms = akms;
+ }
+
+ /**
* Set maximum number of receive spatial streams
*
* @param streams number of streams
@@ -226,7 +249,8 @@
&& mChannelWidth80p80MhzSupported == capa.mChannelWidth80p80MhzSupported
&& mChannelWidth320MhzSupported == capa.mChannelWidth320MhzSupported
&& mMaxNumberTxSpatialStreams == capa.mMaxNumberTxSpatialStreams
- && mMaxNumberRxSpatialStreams == capa.mMaxNumberRxSpatialStreams;
+ && mMaxNumberRxSpatialStreams == capa.mMaxNumberRxSpatialStreams
+ && mMaxNumberAkms == capa.mMaxNumberAkms;
}
/** override hash code */
@@ -235,7 +259,7 @@
return Objects.hash(m80211nSupported, m80211acSupported, m80211axSupported,
m80211beSupported, mChannelWidth160MhzSupported, mChannelWidth80p80MhzSupported,
mChannelWidth320MhzSupported, mMaxNumberTxSpatialStreams,
- mMaxNumberRxSpatialStreams);
+ mMaxNumberRxSpatialStreams, mMaxNumberAkms);
}
/** implement Parcelable interface */
@@ -259,6 +283,7 @@
out.writeBoolean(mChannelWidth320MhzSupported);
out.writeInt(mMaxNumberTxSpatialStreams);
out.writeInt(mMaxNumberRxSpatialStreams);
+ out.writeInt(mMaxNumberAkms);
}
@Override
@@ -276,6 +301,7 @@
.append(mChannelWidth320MhzSupported ? "Yes" : "No");
sb.append("mMaxNumberTxSpatialStreams: ").append(mMaxNumberTxSpatialStreams);
sb.append("mMaxNumberRxSpatialStreams: ").append(mMaxNumberRxSpatialStreams);
+ sb.append("mMaxNumberAkms: ").append(mMaxNumberAkms);
return sb.toString();
}
@@ -298,6 +324,7 @@
capabilities.mChannelWidth320MhzSupported = in.readBoolean();
capabilities.mMaxNumberTxSpatialStreams = in.readInt();
capabilities.mMaxNumberRxSpatialStreams = in.readInt();
+ capabilities.mMaxNumberAkms = in.readInt();
return capabilities;
}
diff --git a/wifi/tests/src/android/net/wifi/nl80211/DeviceWiphyCapabilitiesTest.java b/wifi/tests/src/android/net/wifi/nl80211/DeviceWiphyCapabilitiesTest.java
index 7b900fe..5a3ca2b 100644
--- a/wifi/tests/src/android/net/wifi/nl80211/DeviceWiphyCapabilitiesTest.java
+++ b/wifi/tests/src/android/net/wifi/nl80211/DeviceWiphyCapabilitiesTest.java
@@ -49,6 +49,7 @@
capa.setChannelWidthSupported(ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ, false);
capa.setMaxNumberTxSpatialStreams(2);
capa.setMaxNumberRxSpatialStreams(1);
+ capa.setMaxNumberAkms(2);
Parcel parcel = Parcel.obtain();
capa.writeToParcel(parcel, 0);
diff --git a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
index 362eb14..02da835 100644
--- a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
@@ -1130,6 +1130,7 @@
capaExpected.setChannelWidthSupported(ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ, false);
capaExpected.setMaxNumberTxSpatialStreams(2);
capaExpected.setMaxNumberRxSpatialStreams(1);
+ capaExpected.setMaxNumberAkms(2);
when(mWificond.getDeviceWiphyCapabilities(TEST_INTERFACE_NAME))
.thenReturn(capaExpected);
diff --git a/wifi/wifi.aconfig b/wifi/wifi.aconfig
new file mode 100644
index 0000000..6ac986e
--- /dev/null
+++ b/wifi/wifi.aconfig
@@ -0,0 +1,9 @@
+package: "android.net.wifi.flags"
+
+flag {
+ name: "get_device_cross_akm_roaming_support"
+ namespace: "wifi"
+ description: "Add new API to get the device support for CROSS-AKM roaming"
+ bug: "313038031"
+ is_fixed_read_only: true
+}