Merge "Remove bouncer root view whenever it is not needed." into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 0ca9789..dd919ca 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -93,6 +93,7 @@
"com.android.media.flags.performance-aconfig-java",
"com.android.media.flags.projection-aconfig-java",
"com.android.net.thread.platform.flags-aconfig-java",
+ "com.android.ranging.flags.ranging-aconfig-java",
"com.android.server.contextualsearch.flags-java",
"com.android.server.flags.services-aconfig-java",
"com.android.text.flags-aconfig-java",
@@ -1549,6 +1550,13 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+// Ranging
+java_aconfig_library {
+ name: "com.android.ranging.flags.ranging-aconfig-java",
+ aconfig_declarations: "ranging_aconfig_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// System Server
aconfig_declarations {
name: "android.systemserver.flags-aconfig",
diff --git a/api/Android.bp b/api/Android.bp
index 533f9f6..3f2316f 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -102,6 +102,11 @@
"framework-crashrecovery",
],
default: [],
+ }) + select(release_flag("RELEASE_RANGING_STACK"), {
+ true: [
+ "framework-ranging",
+ ],
+ default: [],
}),
system_server_classpath: [
"service-art",
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index b3a674f..d1aa23c 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -466,6 +466,32 @@
}
java_library {
+ name: "android-non-updatable.stubs.system_server",
+ defaults: ["android-non-updatable_defaults"],
+ static_libs: [
+ "android-non-updatable.stubs.system_server.from-source",
+ ],
+ product_variables: {
+ build_from_text_stub: {
+ static_libs: [
+ "android-non-updatable.stubs.system_server.from-text",
+ ],
+ exclude_static_libs: [
+ "android-non-updatable.stubs.system_server.from-source",
+ ],
+ },
+ },
+}
+
+java_library {
+ name: "android-non-updatable.stubs.exportable.system_server",
+ defaults: ["android-non-updatable_defaults"],
+ static_libs: [
+ "android-non-updatable.stubs.exportable.system_server.from-source",
+ ],
+}
+
+java_library {
name: "android-non-updatable.stubs.from-source",
defaults: [
"android-non-updatable_defaults",
@@ -561,6 +587,30 @@
},
}
+java_library {
+ name: "android-non-updatable.stubs.system_server.from-source",
+ defaults: [
+ "android-non-updatable_defaults",
+ "android-non-updatable_from_source_defaults",
+ ],
+ srcs: [":services-non-updatable-stubs"],
+ libs: non_updatable_api_deps_on_modules,
+}
+
+java_library {
+ name: "android-non-updatable.stubs.exportable.system_server.from-source",
+ defaults: [
+ "android-non-updatable_defaults",
+ "android-non-updatable_from_source_defaults",
+ "android-non-updatable_exportable_from_source_defaults",
+ ],
+ srcs: [":services-non-updatable-stubs{.exportable}"],
+ libs: non_updatable_api_deps_on_modules,
+ dist: {
+ dir: "apistubs/android/system-server",
+ },
+}
+
java_defaults {
name: "android-non-updatable_from_text_defaults",
defaults: ["android-non-updatable-stubs-libs-defaults"],
@@ -662,6 +712,25 @@
libs: ["all-modules-system-stubs"],
}
+java_api_library {
+ name: "android-non-updatable.stubs.system_server.from-text",
+ api_surface: "system_server",
+ api_contributions: [
+ "api-stubs-docs-non-updatable.api.contribution",
+ "system-api-stubs-docs-non-updatable.api.contribution",
+ "module-lib-api-stubs-docs-non-updatable.api.contribution",
+ "services-non-updatable-stubs.api.contribution",
+ ],
+ defaults: [
+ "module-classpath-java-defaults",
+ "android-non-updatable_everything_from_text_defaults",
+ ],
+
+ // Use full Android API not just the non-updatable API as the latter is incomplete
+ // and can result in incorrect behavior.
+ previous_api: ":android.api.combined.system-server.latest",
+}
+
java_defaults {
name: "android_stubs_dists_default",
dist: {
@@ -813,9 +882,9 @@
defaults: [
"android.jar_defaults",
],
- srcs: [":services-non-updatable-stubs"],
installable: false,
static_libs: [
+ "android-non-updatable.stubs.system_server",
"android_module_lib_stubs_current",
],
visibility: ["//frameworks/base/services"],
@@ -827,9 +896,9 @@
"android.jar_defaults",
"android_stubs_dists_default",
],
- srcs: [":services-non-updatable-stubs{.exportable}"],
installable: false,
static_libs: [
+ "android-non-updatable.stubs.exportable.system_server",
"android_module_lib_stubs_current_exportable",
],
dist: {
diff --git a/cmds/uiautomator/library/Android.bp b/cmds/uiautomator/library/Android.bp
index 966bf13..5c5b220 100644
--- a/cmds/uiautomator/library/Android.bp
+++ b/cmds/uiautomator/library/Android.bp
@@ -28,9 +28,9 @@
"testrunner-src/**/*.java",
],
libs: [
- "android.test.runner",
+ "android.test.runner.stubs.system",
"junit",
- "android.test.base",
+ "android.test.base.stubs.system",
"unsupportedappusage",
],
installable: false,
@@ -56,9 +56,9 @@
":uiautomator-stubs",
],
libs: [
- "android.test.runner",
+ "android.test.runner.stubs",
"junit",
- "android.test.base",
+ "android.test.base.stubs",
],
sdk_version: "current",
installable: false,
diff --git a/core/api/current.txt b/core/api/current.txt
index b2539ce..542543d 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -16126,24 +16126,30 @@
ctor public Gainmap(@NonNull android.graphics.Bitmap);
ctor @FlaggedApi("com.android.graphics.hwui.flags.gainmap_constructor_with_metadata") public Gainmap(@NonNull android.graphics.Gainmap, @NonNull android.graphics.Bitmap);
method public int describeContents();
+ method @FlaggedApi("com.android.graphics.hwui.flags.iso_gainmap_apis") @Nullable public android.graphics.ColorSpace getAlternativeImagePrimaries();
method @NonNull public float getDisplayRatioForFullHdr();
method @NonNull public float[] getEpsilonHdr();
method @NonNull public float[] getEpsilonSdr();
method @NonNull public android.graphics.Bitmap getGainmapContents();
+ method @FlaggedApi("com.android.graphics.hwui.flags.iso_gainmap_apis") public int getGainmapDirection();
method @NonNull public float[] getGamma();
method @NonNull public float getMinDisplayRatioForHdrTransition();
method @NonNull public float[] getRatioMax();
method @NonNull public float[] getRatioMin();
+ method @FlaggedApi("com.android.graphics.hwui.flags.iso_gainmap_apis") public void setAlternativeImagePrimaries(@Nullable android.graphics.ColorSpace);
method public void setDisplayRatioForFullHdr(@FloatRange(from=1.0f) float);
method public void setEpsilonHdr(float, float, float);
method public void setEpsilonSdr(float, float, float);
method public void setGainmapContents(@NonNull android.graphics.Bitmap);
+ method @FlaggedApi("com.android.graphics.hwui.flags.iso_gainmap_apis") public void setGainmapDirection(int);
method public void setGamma(float, float, float);
method public void setMinDisplayRatioForHdrTransition(@FloatRange(from=1.0f) float);
method public void setRatioMax(float, float, float);
method public void setRatioMin(float, float, float);
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.graphics.Gainmap> CREATOR;
+ field @FlaggedApi("com.android.graphics.hwui.flags.iso_gainmap_apis") public static final int GAINMAP_DIRECTION_HDR_TO_SDR = 1; // 0x1
+ field @FlaggedApi("com.android.graphics.hwui.flags.iso_gainmap_apis") public static final int GAINMAP_DIRECTION_SDR_TO_HDR = 0; // 0x0
}
public class HardwareBufferRenderer implements java.lang.AutoCloseable {
@@ -19207,10 +19213,10 @@
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> FLASH_INFO_AVAILABLE;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> FLASH_INFO_STRENGTH_DEFAULT_LEVEL;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> FLASH_INFO_STRENGTH_MAXIMUM_LEVEL;
- field @FlaggedApi("com.android.internal.camera.flags.camera_manual_flash_strength_control") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> FLASH_SINGLE_STRENGTH_DEFAULT_LEVEL;
- field @FlaggedApi("com.android.internal.camera.flags.camera_manual_flash_strength_control") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> FLASH_SINGLE_STRENGTH_MAX_LEVEL;
- field @FlaggedApi("com.android.internal.camera.flags.camera_manual_flash_strength_control") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> FLASH_TORCH_STRENGTH_DEFAULT_LEVEL;
- field @FlaggedApi("com.android.internal.camera.flags.camera_manual_flash_strength_control") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> FLASH_TORCH_STRENGTH_MAX_LEVEL;
+ field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> FLASH_SINGLE_STRENGTH_DEFAULT_LEVEL;
+ field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> FLASH_SINGLE_STRENGTH_MAX_LEVEL;
+ field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> FLASH_TORCH_STRENGTH_DEFAULT_LEVEL;
+ field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> FLASH_TORCH_STRENGTH_MAX_LEVEL;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.DeviceStateSensorOrientationMap> INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> INFO_SESSION_CONFIGURATION_QUERY_VERSION;
@@ -19370,11 +19376,9 @@
method @NonNull public java.util.List<java.lang.Integer> getSupportedExtensions();
method public boolean isCaptureProcessProgressAvailable(int);
method public boolean isPostviewAvailable(int);
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Float>> EFV_PADDING_ZOOM_FACTOR_RANGE;
field public static final int EXTENSION_AUTOMATIC = 0; // 0x0
field @Deprecated public static final int EXTENSION_BEAUTY = 1; // 0x1
field public static final int EXTENSION_BOKEH = 2; // 0x2
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public static final int EXTENSION_EYES_FREE_VIDEOGRAPHY = 5; // 0x5
field public static final int EXTENSION_FACE_RETOUCH = 1; // 0x1
field public static final int EXTENSION_HDR = 3; // 0x3
field public static final int EXTENSION_NIGHT = 4; // 0x4
@@ -19800,7 +19804,7 @@
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> EDGE_MODE;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> EXTENSION_STRENGTH;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> FLASH_MODE;
- field @FlaggedApi("com.android.internal.camera.flags.camera_manual_flash_strength_control") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> FLASH_STRENGTH_LEVEL;
+ field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> FLASH_STRENGTH_LEVEL;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> HOT_PIXEL_MODE;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<android.location.Location> JPEG_GPS_LOCATION;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> JPEG_ORIENTATION;
@@ -19897,7 +19901,7 @@
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EXTENSION_STRENGTH;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> FLASH_MODE;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> FLASH_STATE;
- field @FlaggedApi("com.android.internal.camera.flags.camera_manual_flash_strength_control") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> FLASH_STRENGTH_LEVEL;
+ field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> FLASH_STRENGTH_LEVEL;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> HOT_PIXEL_MODE;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.location.Location> JPEG_GPS_LOCATION;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> JPEG_ORIENTATION;
@@ -19974,30 +19978,6 @@
field public static final int MAX_THUMBNAIL_DIMENSION = 256; // 0x100
}
- @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public final class ExtensionCaptureRequest {
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Boolean> EFV_AUTO_ZOOM;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_MAX_PADDING_ZOOM_FACTOR;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_PADDING_ZOOM_FACTOR;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_ROTATE_VIEWPORT;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> EFV_STABILIZATION_MODE;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public static final int EFV_STABILIZATION_MODE_GIMBAL = 1; // 0x1
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public static final int EFV_STABILIZATION_MODE_LOCKED = 2; // 0x2
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public static final int EFV_STABILIZATION_MODE_OFF = 0; // 0x0
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<android.util.Pair<java.lang.Integer,java.lang.Integer>> EFV_TRANSLATE_VIEWPORT;
- }
-
- @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public final class ExtensionCaptureResult {
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> EFV_AUTO_ZOOM;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<int[]> EFV_AUTO_ZOOM_PADDING_REGION;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_MAX_PADDING_ZOOM_FACTOR;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<int[]> EFV_PADDING_REGION;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_PADDING_ZOOM_FACTOR;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_ROTATE_VIEWPORT;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EFV_STABILIZATION_MODE;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.graphics.PointF[]> EFV_TARGET_COORDINATES;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.util.Pair<java.lang.Integer,java.lang.Integer>> EFV_TRANSLATE_VIEWPORT;
- }
-
public class MultiResolutionImageReader implements java.lang.AutoCloseable {
ctor public MultiResolutionImageReader(@NonNull java.util.Collection<android.hardware.camera2.params.MultiResolutionStreamInfo>, int, @IntRange(from=1) int);
method public void close();
@@ -32633,6 +32613,13 @@
method public boolean isCharging();
field public static final String ACTION_CHARGING = "android.os.action.CHARGING";
field public static final String ACTION_DISCHARGING = "android.os.action.DISCHARGING";
+ field @FlaggedApi("android.os.battery_part_status_api") public static final int BATTERY_CAPACITY_LEVEL_CRITICAL = 1; // 0x1
+ field @FlaggedApi("android.os.battery_part_status_api") public static final int BATTERY_CAPACITY_LEVEL_FULL = 5; // 0x5
+ field @FlaggedApi("android.os.battery_part_status_api") public static final int BATTERY_CAPACITY_LEVEL_HIGH = 4; // 0x4
+ field @FlaggedApi("android.os.battery_part_status_api") public static final int BATTERY_CAPACITY_LEVEL_LOW = 2; // 0x2
+ field @FlaggedApi("android.os.battery_part_status_api") public static final int BATTERY_CAPACITY_LEVEL_NORMAL = 3; // 0x3
+ field @FlaggedApi("android.os.battery_part_status_api") public static final int BATTERY_CAPACITY_LEVEL_UNKNOWN = 0; // 0x0
+ field @FlaggedApi("android.os.battery_part_status_api") public static final int BATTERY_CAPACITY_LEVEL_UNSUPPORTED = -1; // 0xffffffff
field public static final int BATTERY_HEALTH_COLD = 7; // 0x7
field public static final int BATTERY_HEALTH_DEAD = 4; // 0x4
field public static final int BATTERY_HEALTH_GOOD = 2; // 0x2
@@ -32657,6 +32644,7 @@
field public static final int BATTERY_STATUS_NOT_CHARGING = 4; // 0x4
field public static final int BATTERY_STATUS_UNKNOWN = 1; // 0x1
field public static final String EXTRA_BATTERY_LOW = "battery_low";
+ field @FlaggedApi("android.os.battery_part_status_api") public static final String EXTRA_CAPACITY_LEVEL = "android.os.extra.CAPACITY_LEVEL";
field public static final String EXTRA_CHARGING_STATUS = "android.os.extra.CHARGING_STATUS";
field public static final String EXTRA_CYCLE_COUNT = "android.os.extra.CYCLE_COUNT";
field public static final String EXTRA_HEALTH = "health";
@@ -33248,6 +33236,7 @@
}
public interface IBinder {
+ method @FlaggedApi("android.os.binder_frozen_state_change_callback") public default void addFrozenStateChangeCallback(@NonNull android.os.IBinder.FrozenStateChangeCallback) throws android.os.RemoteException;
method public void dump(@NonNull java.io.FileDescriptor, @Nullable String[]) throws android.os.RemoteException;
method public void dumpAsync(@NonNull java.io.FileDescriptor, @Nullable String[]) throws android.os.RemoteException;
method @Nullable public String getInterfaceDescriptor() throws android.os.RemoteException;
@@ -33256,6 +33245,7 @@
method public void linkToDeath(@NonNull android.os.IBinder.DeathRecipient, int) throws android.os.RemoteException;
method public boolean pingBinder();
method @Nullable public android.os.IInterface queryLocalInterface(@NonNull String);
+ method @FlaggedApi("android.os.binder_frozen_state_change_callback") public default boolean removeFrozenStateChangeCallback(@NonNull android.os.IBinder.FrozenStateChangeCallback);
method public boolean transact(int, @NonNull android.os.Parcel, @Nullable android.os.Parcel, int) throws android.os.RemoteException;
method public boolean unlinkToDeath(@NonNull android.os.IBinder.DeathRecipient, int);
field public static final int DUMP_TRANSACTION = 1598311760; // 0x5f444d50
@@ -33273,6 +33263,12 @@
method public default void binderDied(@NonNull android.os.IBinder);
}
+ @FlaggedApi("android.os.binder_frozen_state_change_callback") public static interface IBinder.FrozenStateChangeCallback {
+ method public void onFrozenStateChanged(@NonNull android.os.IBinder, int);
+ field public static final int STATE_FROZEN = 0; // 0x0
+ field public static final int STATE_UNFROZEN = 1; // 0x1
+ }
+
public interface IInterface {
method public android.os.IBinder asBinder();
}
@@ -36932,13 +36928,17 @@
field public static final String CONTENT_DIRECTORY = "data";
}
- @FlaggedApi("android.provider.new_default_account_api_enabled") public static final class ContactsContract.RawContacts.DefaultAccountAndState {
- ctor public ContactsContract.RawContacts.DefaultAccountAndState(int, @Nullable android.accounts.Account);
+ @FlaggedApi("android.provider.new_default_account_api_enabled") public static final class ContactsContract.RawContacts.DefaultAccount {
+ ctor public ContactsContract.RawContacts.DefaultAccount();
+ }
+
+ @FlaggedApi("android.provider.new_default_account_api_enabled") public static final class ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState {
+ ctor public ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState(int, @Nullable android.accounts.Account);
method @Nullable public android.accounts.Account getCloudAccount();
method public int getState();
- method @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccountAndState ofCloud(@NonNull android.accounts.Account);
- method @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccountAndState ofLocal();
- method @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccountAndState ofNotSet();
+ method @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState ofCloud(@NonNull android.accounts.Account);
+ method @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState ofLocal();
+ method @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState ofNotSet();
field public static final int DEFAULT_ACCOUNT_STATE_CLOUD = 3; // 0x3
field public static final int DEFAULT_ACCOUNT_STATE_LOCAL = 2; // 0x2
field public static final int DEFAULT_ACCOUNT_STATE_NOT_SET = 1; // 0x1
@@ -44086,7 +44086,7 @@
}
public static final class CarrierConfigManager.Gps {
- field @FlaggedApi("android.location.flags.enable_ni_supl_message_injection_by_carrier_config") public static final String KEY_ENABLE_NI_SUPL_MESSAGE_INJECTION_BOOL = "gps.enable_ni_supl_message_injection_bool";
+ field @FlaggedApi("android.location.flags.enable_ni_supl_message_injection_by_carrier_config_bugfix") public static final String KEY_ENABLE_NI_SUPL_MESSAGE_INJECTION_BOOL = "gps.enable_ni_supl_message_injection_bool";
field public static final String KEY_PERSIST_LPP_MODE_BOOL = "gps.persist_lpp_mode_bool";
field public static final String KEY_PREFIX = "gps.";
}
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 24962a3..8447a7f 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -321,7 +321,10 @@
package android.net.wifi {
public final class WifiMigration {
- method @FlaggedApi("android.net.wifi.flags.legacy_keystore_to_wifi_blobstore_migration_read_only") public static void migrateLegacyKeystoreToWifiBlobstore();
+ method @FlaggedApi("android.net.wifi.flags.legacy_keystore_to_wifi_blobstore_migration_read_only") public static int migrateLegacyKeystoreToWifiBlobstore();
+ field @FlaggedApi("android.net.wifi.flags.legacy_keystore_to_wifi_blobstore_migration_read_only") public static final int KEYSTORE_MIGRATION_FAILURE_ENCOUNTERED_EXCEPTION = 2; // 0x2
+ field @FlaggedApi("android.net.wifi.flags.legacy_keystore_to_wifi_blobstore_migration_read_only") public static final int KEYSTORE_MIGRATION_SUCCESS_MIGRATION_COMPLETE = 0; // 0x0
+ field @FlaggedApi("android.net.wifi.flags.legacy_keystore_to_wifi_blobstore_migration_read_only") public static final int KEYSTORE_MIGRATION_SUCCESS_MIGRATION_NOT_NEEDED = 1; // 0x1
}
}
@@ -384,6 +387,12 @@
field public static final int DEVICE_INITIAL_SDK_INT;
}
+ public class Handler {
+ method @FlaggedApi("android.os.mainline_vcn_platform_api") public final boolean hasMessagesOrCallbacks();
+ method @FlaggedApi("android.os.mainline_vcn_platform_api") public final void removeCallbacksAndEqualMessages(@Nullable Object);
+ method @FlaggedApi("android.os.mainline_vcn_platform_api") public final void removeEqualMessages(int, @Nullable Object);
+ }
+
public class IpcDataCache<Query, Result> {
ctor public IpcDataCache(int, @NonNull String, @NonNull String, @NonNull String, @NonNull android.os.IpcDataCache.QueryHandler<Query,Result>);
method public void disableForCurrentProcess();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 7a8e829..b2a49e1 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3835,6 +3835,7 @@
field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String ON_DEVICE_INTELLIGENCE_SERVICE = "on_device_intelligence";
field public static final String PERMISSION_CONTROLLER_SERVICE = "permission_controller";
field public static final String PERMISSION_SERVICE = "permission";
+ field @FlaggedApi("com.android.ranging.flags.ranging_stack_enabled") public static final String RANGING_SERVICE = "ranging";
field public static final String REBOOT_READINESS_SERVICE = "reboot_readiness";
field public static final String ROLLBACK_SERVICE = "rollback";
field public static final String SAFETY_CENTER_SERVICE = "safety_center";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index caf6992..72a68f8 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1797,18 +1797,20 @@
public class InputSettings {
method @FlaggedApi("com.android.hardware.input.keyboard_a11y_bounce_keys_flag") public static int getAccessibilityBounceKeysThreshold(@NonNull android.content.Context);
- method @FlaggedApi("com.android.hardware.input.keyboard_repeat_keys") public static int getAccessibilityRepeatKeysDelay(@NonNull android.content.Context);
- method @FlaggedApi("com.android.hardware.input.keyboard_repeat_keys") public static int getAccessibilityRepeatKeysTimeout(@NonNull android.content.Context);
method @FlaggedApi("com.android.hardware.input.keyboard_a11y_slow_keys_flag") public static int getAccessibilitySlowKeysThreshold(@NonNull android.content.Context);
+ method @FlaggedApi("com.android.input.flags.keyboard_repeat_keys") public static int getRepeatKeysDelay(@NonNull android.content.Context);
+ method @FlaggedApi("com.android.input.flags.keyboard_repeat_keys") public static int getRepeatKeysTimeout(@NonNull android.content.Context);
method @FlaggedApi("com.android.hardware.input.keyboard_a11y_mouse_keys") public static boolean isAccessibilityMouseKeysEnabled(@NonNull android.content.Context);
method @FlaggedApi("com.android.hardware.input.keyboard_a11y_sticky_keys_flag") public static boolean isAccessibilityStickyKeysEnabled(@NonNull android.content.Context);
+ method @FlaggedApi("com.android.input.flags.keyboard_repeat_keys") public static boolean isRepeatKeysEnabled(@NonNull android.content.Context);
method @FlaggedApi("com.android.hardware.input.keyboard_a11y_bounce_keys_flag") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilityBounceKeysThreshold(@NonNull android.content.Context, int);
method @FlaggedApi("com.android.hardware.input.keyboard_a11y_mouse_keys") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilityMouseKeysEnabled(@NonNull android.content.Context, boolean);
- method @FlaggedApi("com.android.hardware.input.keyboard_repeat_keys") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilityRepeatKeysDelay(@NonNull android.content.Context, int);
- method @FlaggedApi("com.android.hardware.input.keyboard_repeat_keys") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilityRepeatKeysTimeout(@NonNull android.content.Context, int);
method @FlaggedApi("com.android.hardware.input.keyboard_a11y_slow_keys_flag") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilitySlowKeysThreshold(@NonNull android.content.Context, int);
method @FlaggedApi("com.android.hardware.input.keyboard_a11y_sticky_keys_flag") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilityStickyKeysEnabled(@NonNull android.content.Context, boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static void setMaximumObscuringOpacityForTouch(@NonNull android.content.Context, @FloatRange(from=0, to=1) float);
+ method @FlaggedApi("com.android.input.flags.keyboard_repeat_keys") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setRepeatKeysDelay(@NonNull android.content.Context, int);
+ method @FlaggedApi("com.android.input.flags.keyboard_repeat_keys") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setRepeatKeysEnabled(@NonNull android.content.Context, boolean);
+ method @FlaggedApi("com.android.input.flags.keyboard_repeat_keys") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setRepeatKeysTimeout(@NonNull android.content.Context, int);
field public static final int DEFAULT_POINTER_SPEED = 0; // 0x0
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 4350545..5db79fe 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -8288,12 +8288,12 @@
}
Context c = null;
ApplicationInfo ai = info.applicationInfo;
- if (context.getPackageName().equals(ai.packageName)) {
+ if (context != null && context.getPackageName().equals(ai.packageName)) {
c = context;
} else if (mInitialApplication != null &&
mInitialApplication.getPackageName().equals(ai.packageName)) {
c = mInitialApplication;
- } else {
+ } else if (context != null) {
try {
c = context.createPackageContext(ai.packageName,
Context.CONTEXT_INCLUDE_CODE);
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index dbf9afd..ed6b851 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -16,6 +16,7 @@
package android.app;
+import static android.app.PropertyInvalidatedCache.createSystemCacheKey;
import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED;
import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_NOT_COLORED;
import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
@@ -817,7 +818,7 @@
private final static PropertyInvalidatedCache<HasSystemFeatureQuery, Boolean>
mHasSystemFeatureCache =
new PropertyInvalidatedCache<HasSystemFeatureQuery, Boolean>(
- 256, "cache_key.has_system_feature") {
+ 256, createSystemCacheKey("has_system_feature")) {
@Override
public Boolean recompute(HasSystemFeatureQuery query) {
try {
@@ -1127,7 +1128,7 @@
}
private static final String CACHE_KEY_PACKAGES_FOR_UID_PROPERTY =
- "cache_key.get_packages_for_uid";
+ createSystemCacheKey("get_packages_for_uid");
private static final PropertyInvalidatedCache<Integer, GetPackagesForUidResult>
mGetPackagesForUidCache =
new PropertyInvalidatedCache<Integer, GetPackagesForUidResult>(
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index 0c786cb..0e761fc 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
+import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -283,6 +284,12 @@
*/
/**
+ * The well-known key prefix.
+ * @hide
+ */
+ private static final String CACHE_KEY_PREFIX = "cache_key";
+
+ /**
* The module used for unit tests and cts tests. It is expected that no process in
* the system has permissions to write properties with this module.
* @hide
@@ -366,7 +373,44 @@
}
}
- return "cache_key." + module + "." + new String(suffix);
+ return CACHE_KEY_PREFIX + "." + module + "." + new String(suffix);
+ }
+
+ /**
+ * All legal keys start with one of the following strings.
+ */
+ private static final String[] sValidKeyPrefix = {
+ CACHE_KEY_PREFIX + "." + MODULE_SYSTEM + ".",
+ CACHE_KEY_PREFIX + "." + MODULE_BLUETOOTH + ".",
+ CACHE_KEY_PREFIX + "." + MODULE_TELEPHONY + ".",
+ CACHE_KEY_PREFIX + "." + MODULE_TEST + ".",
+ };
+
+ /**
+ * Verify that the property name conforms to the standard. Log a warning if this is not true.
+ * Note that this is done once in the cache constructor; it does not have to be very fast.
+ */
+ private void validateCacheKey(String name) {
+ if (Build.IS_USER) {
+ // Do not bother checking keys in user builds. The keys will have been tested in
+ // eng/userdebug builds already.
+ return;
+ }
+ for (int i = 0; i < sValidKeyPrefix.length; i++) {
+ if (name.startsWith(sValidKeyPrefix[i])) return;
+ }
+ Log.w(TAG, "invalid cache name: " + name);
+ }
+
+ /**
+ * Create a cache key for the system module. The parameter is the API name. This reduces
+ * some of the boilerplate in system caches. It is not needed in other modules because other
+ * modules must use the {@link IpcDataCache} interfaces.
+ * @hide
+ */
+ @NonNull
+ public static String createSystemCacheKey(@NonNull String api) {
+ return createPropertyName(MODULE_SYSTEM, api);
}
/**
@@ -561,6 +605,7 @@
public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName,
@NonNull String cacheName) {
mPropertyName = propertyName;
+ validateCacheKey(mPropertyName);
mCacheName = cacheName;
mMaxEntries = maxEntries;
mComputer = new DefaultComputer<>(this);
@@ -584,6 +629,7 @@
public PropertyInvalidatedCache(int maxEntries, @NonNull String module, @NonNull String api,
@NonNull String cacheName, @NonNull QueryHandler<Query, Result> computer) {
mPropertyName = createPropertyName(module, api);
+ validateCacheKey(mPropertyName);
mCacheName = cacheName;
mMaxEntries = maxEntries;
mComputer = computer;
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 337939f..03bec71 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -1667,11 +1667,20 @@
@Override
public WearableSensingManager createService(ContextImpl ctx)
throws ServiceNotFoundException {
- IBinder iBinder = ServiceManager.getServiceOrThrow(
+ IBinder iBinder = ServiceManager.getService(
Context.WEARABLE_SENSING_SERVICE);
- IWearableSensingManager manager =
- IWearableSensingManager.Stub.asInterface(iBinder);
- return new WearableSensingManager(ctx.getOuterContext(), manager);
+ if (iBinder != null) {
+ IWearableSensingManager manager =
+ IWearableSensingManager.Stub.asInterface(iBinder);
+ return new WearableSensingManager(ctx.getOuterContext(), manager);
+ }
+ // Wear intentionally removes the service, so do not throw a
+ // ServiceNotFoundException when the service is not absent.
+ if (ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
+ && android.server.Flags.removeWearableSensingServiceFromWear()) {
+ return null;
+ }
+ throw new ServiceNotFoundException(Context.WEARABLE_SENSING_SERVICE);
}});
registerService(Context.ON_DEVICE_INTELLIGENCE_SERVICE, OnDeviceIntelligenceManager.class,
diff --git a/core/java/android/app/appfunctions/AppFunctionService.java b/core/java/android/app/appfunctions/AppFunctionService.java
index c27141a..0d981ea 100644
--- a/core/java/android/app/appfunctions/AppFunctionService.java
+++ b/core/java/android/app/appfunctions/AppFunctionService.java
@@ -26,6 +26,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Service;
+import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
@@ -60,29 +61,53 @@
@NonNull
public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
- private final Binder mBinder =
- new IAppFunctionService.Stub() {
- @Override
- public void executeAppFunction(
- @NonNull ExecuteAppFunctionRequest request,
- @NonNull IExecuteAppFunctionCallback callback) {
- if (AppFunctionService.this.checkCallingPermission(BIND_APP_FUNCTION_SERVICE)
- == PERMISSION_DENIED) {
- throw new SecurityException("Can only be called by the system server.");
- }
- SafeOneTimeExecuteAppFunctionCallback safeCallback =
- new SafeOneTimeExecuteAppFunctionCallback(callback);
- try {
- AppFunctionService.this.onExecuteFunction(request, safeCallback::onResult);
- } catch (Exception ex) {
- // Apps should handle exceptions. But if they don't, report the error on
- // behalf of them.
- safeCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- getResultCode(ex), ex.getMessage(), /* extras= */ null));
- }
+ /**
+ * Functional interface to represent the execution logic of an app function.
+ *
+ * @hide
+ */
+ @FunctionalInterface
+ public interface OnExecuteFunction {
+ /**
+ * Performs the semantic of executing the function specified by the provided request and
+ * return the response through the provided callback.
+ */
+ void perform(
+ @NonNull ExecuteAppFunctionRequest request,
+ @NonNull Consumer<ExecuteAppFunctionResponse> callback);
+ }
+
+ /** @hide */
+ @NonNull
+ public static Binder createBinder(
+ @NonNull Context context, @NonNull OnExecuteFunction onExecuteFunction) {
+ return new IAppFunctionService.Stub() {
+ @Override
+ public void executeAppFunction(
+ @NonNull ExecuteAppFunctionRequest request,
+ @NonNull IExecuteAppFunctionCallback callback) {
+ if (context.checkCallingPermission(BIND_APP_FUNCTION_SERVICE)
+ == PERMISSION_DENIED) {
+ throw new SecurityException("Can only be called by the system server.");
}
- };
+ SafeOneTimeExecuteAppFunctionCallback safeCallback =
+ new SafeOneTimeExecuteAppFunctionCallback(callback);
+ try {
+ onExecuteFunction.perform(request, safeCallback::onResult);
+ } catch (Exception ex) {
+ // Apps should handle exceptions. But if they don't, report the error on
+ // behalf of them.
+ safeCallback.onResult(
+ ExecuteAppFunctionResponse.newFailure(
+ getResultCode(ex), ex.getMessage(), /* extras= */ null));
+ }
+ }
+ };
+ }
+
+ private final Binder mBinder = createBinder(
+ AppFunctionService.this,
+ AppFunctionService.this::onExecuteFunction);
@NonNull
@Override
diff --git a/core/java/android/app/compat/ChangeIdStateCache.java b/core/java/android/app/compat/ChangeIdStateCache.java
index 7948cec..db663f8 100644
--- a/core/java/android/app/compat/ChangeIdStateCache.java
+++ b/core/java/android/app/compat/ChangeIdStateCache.java
@@ -16,6 +16,8 @@
package android.app.compat;
+import static android.app.PropertyInvalidatedCache.createSystemCacheKey;
+
import android.annotation.NonNull;
import android.app.PropertyInvalidatedCache;
import android.content.Context;
@@ -31,7 +33,7 @@
*/
public final class ChangeIdStateCache
extends PropertyInvalidatedCache<ChangeIdStateQuery, Boolean> {
- private static final String CACHE_KEY = "cache_key.is_compat_change_enabled";
+ private static final String CACHE_KEY = createSystemCacheKey("is_compat_change_enabled");
private static final int MAX_ENTRIES = 2048;
private static boolean sDisabled = false;
private volatile IPlatformCompat mPlatformCompat;
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 9891e89..9b06adf 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -236,4 +236,12 @@
namespace: "systemui"
description: "Guards new android.app.richongoingnotification api"
bug: "337261753"
+}
+
+flag {
+ name: "ui_rich_ongoing"
+ is_exported: true
+ namespace: "systemui"
+ description: "Guards new android.app.richongoingnotification promotion and new uis"
+ bug: "337261753"
}
\ No newline at end of file
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 12c5d07..91f7a8b 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4324,6 +4324,7 @@
SECURITY_STATE_SERVICE,
//@hide: ECM_ENHANCED_CONFIRMATION_SERVICE,
CONTACT_KEYS_SERVICE,
+ RANGING_SERVICE,
})
@Retention(RetentionPolicy.SOURCE)
@@ -6402,6 +6403,17 @@
/**
* Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.ranging.RangingManager}.
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ @FlaggedApi(com.android.ranging.flags.Flags.FLAG_RANGING_STACK_ENABLED)
+ @SystemApi
+ public static final String RANGING_SERVICE = "ranging";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
* {@link android.app.DreamManager} for controlling Dream states.
*
* @see #getSystemService(String)
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 52c84dc..26f919f 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -778,8 +778,18 @@
public List<LauncherActivityInfo> getActivityList(String packageName, UserHandle user) {
logErrorForInvalidProfileAccess(user);
try {
- return convertToActivityList(mService.getLauncherActivities(mContext.getPackageName(),
- packageName, user), user);
+ final List<LauncherActivityInfo> activityList = convertToActivityList(
+ mService.getLauncherActivities(
+ mContext.getPackageName(),
+ packageName,
+ user
+ ), user);
+ if (activityList.isEmpty()) {
+ // b/350144057
+ Log.d(TAG, "getActivityList: No launchable activities found for"
+ + "packageName=" + packageName + ", user=" + user);
+ }
+ return activityList;
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index ffadd1e..2cdae21 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -206,6 +206,17 @@
]
},
{
+ "name": "CtsPackageInstallerCUJDeviceAdminTestCases",
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ }
+ ]
+ },
+ {
"name": "CtsPackageInstallerCUJInstallationTestCases",
"options":[
{
@@ -217,6 +228,17 @@
]
},
{
+ "name": "CtsPackageInstallerCUJMultiUsersTestCases",
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ }
+ ]
+ },
+ {
"name": "CtsPackageInstallerCUJUninstallationTestCases",
"options":[
{
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 9eec7a4..34f3b61 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -241,6 +241,16 @@
is_fixed_read_only: true
}
+flag {
+ name: "caches_not_invalidated_at_start_read_only"
+ namespace: "multiuser"
+ description: "PIC need to be invalidated at start in order to work properly."
+ bug: "356167673"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+ is_fixed_read_only: true
+}
# This flag guards the private space feature and all its implementations excluding the APIs. APIs are guarded by android.os.Flags.allow_private_profile.
flag {
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 7a8a16f..37983df 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1484,7 +1484,6 @@
*/
@PublicKey
@NonNull
- @FlaggedApi(Flags.FLAG_CAMERA_MANUAL_FLASH_STRENGTH_CONTROL)
public static final Key<Integer> FLASH_SINGLE_STRENGTH_MAX_LEVEL =
new Key<Integer>("android.flash.singleStrengthMaxLevel", int.class);
@@ -1500,7 +1499,6 @@
*/
@PublicKey
@NonNull
- @FlaggedApi(Flags.FLAG_CAMERA_MANUAL_FLASH_STRENGTH_CONTROL)
public static final Key<Integer> FLASH_SINGLE_STRENGTH_DEFAULT_LEVEL =
new Key<Integer>("android.flash.singleStrengthDefaultLevel", int.class);
@@ -1524,7 +1522,6 @@
*/
@PublicKey
@NonNull
- @FlaggedApi(Flags.FLAG_CAMERA_MANUAL_FLASH_STRENGTH_CONTROL)
public static final Key<Integer> FLASH_TORCH_STRENGTH_MAX_LEVEL =
new Key<Integer>("android.flash.torchStrengthMaxLevel", int.class);
@@ -1540,7 +1537,6 @@
*/
@PublicKey
@NonNull
- @FlaggedApi(Flags.FLAG_CAMERA_MANUAL_FLASH_STRENGTH_CONTROL)
public static final Key<Integer> FLASH_TORCH_STRENGTH_DEFAULT_LEVEL =
new Key<Integer>("android.flash.torchStrengthDefaultLevel", int.class);
@@ -5976,28 +5972,6 @@
public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> JPEGR_AVAILABLE_JPEG_R_STALL_DURATIONS_MAXIMUM_RESOLUTION =
new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.jpegr.availableJpegRStallDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class);
- /**
- * <p>Minimum and maximum padding zoom factors supported by this camera device for
- * android.efv.paddingZoomFactor used for the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension.</p>
- * <p>The minimum and maximum padding zoom factors supported by the device for
- * android.efv.paddingZoomFactor used as part of the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension feature. This extension specific camera characteristic can be queried using
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#get }.</p>
- * <p><b>Units</b>: A pair of padding zoom factors in floating-points:
- * (minPaddingZoomFactor, maxPaddingZoomFactor)</p>
- * <p><b>Range of valid values:</b><br></p>
- * <p>1.0 < minPaddingZoomFactor <= maxPaddingZoomFactor</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- * @hide
- */
- @ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final Key<android.util.Range<Float>> EFV_PADDING_ZOOM_FACTOR_RANGE =
- new Key<android.util.Range<Float>>("android.efv.paddingZoomFactorRange", new TypeReference<android.util.Range<Float>>() {{ }});
-
/**
* Mapping from INFO_SESSION_CONFIGURATION_QUERY_VERSION to session characteristics key.
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 04a810a..9b87df9 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -141,12 +141,6 @@
public static final int EXTENSION_NIGHT = 4;
/**
- * An extension that aims to lock and stabilize a given region or object of interest.
- */
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final int EXTENSION_EYES_FREE_VIDEOGRAPHY = 5;
-
- /**
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@@ -154,8 +148,7 @@
EXTENSION_FACE_RETOUCH,
EXTENSION_BOKEH,
EXTENSION_HDR,
- EXTENSION_NIGHT,
- EXTENSION_EYES_FREE_VIDEOGRAPHY})
+ EXTENSION_NIGHT})
public @interface Extension {
}
@@ -634,9 +627,6 @@
public ExtensionConnectionManager() {
IntArray extensionList = new IntArray(EXTENSION_LIST.length);
extensionList.addAll(EXTENSION_LIST);
- if (Flags.concertModeApi()) {
- extensionList.add(EXTENSION_EYES_FREE_VIDEOGRAPHY);
- }
for (int extensionType : extensionList.toArray()) {
mConnections.put(extensionType, new ExtensionConnection());
@@ -837,9 +827,6 @@
IntArray extensionList = new IntArray(EXTENSION_LIST.length);
extensionList.addAll(EXTENSION_LIST);
- if (Flags.concertModeApi()) {
- extensionList.add(EXTENSION_EYES_FREE_VIDEOGRAPHY);
- }
for (int extensionType : extensionList.toArray()) {
try {
@@ -1598,28 +1585,4 @@
return Collections.unmodifiableSet(ret);
}
-
-
- /**
- * <p>Minimum and maximum padding zoom factors supported by this camera device for
- * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } used for
- * the {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension.</p>
- * <p>The minimum and maximum padding zoom factors supported by the device for
- * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } used as part of the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension feature. This extension specific camera characteristic can be queried using
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#get}.</p>
- * <p><b>Units</b>: A pair of padding zoom factors in floating-points:
- * (minPaddingZoomFactor, maxPaddingZoomFactor)</p>
- * <p><b>Range of valid values:</b><br></p>
- * <p>1.0 < minPaddingZoomFactor <= maxPaddingZoomFactor</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- */
- @PublicKey
- @NonNull
- @ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final Key<android.util.Range<Float>> EFV_PADDING_ZOOM_FACTOR_RANGE =
- CameraCharacteristics.EFV_PADDING_ZOOM_FACTOR_RANGE;
}
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 4819f67..a69a371 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -3897,36 +3897,6 @@
public static final int DISTORTION_CORRECTION_MODE_HIGH_QUALITY = 2;
//
- // Enumeration values for CaptureRequest#EFV_STABILIZATION_MODE
- //
-
- /**
- * <p>No stabilization.</p>
- * @see CaptureRequest#EFV_STABILIZATION_MODE
- * @hide
- */
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final int EFV_STABILIZATION_MODE_OFF = 0;
-
- /**
- * <p>Gimbal stabilization mode.</p>
- * @see CaptureRequest#EFV_STABILIZATION_MODE
- * @hide
- */
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final int EFV_STABILIZATION_MODE_GIMBAL = 1;
-
- /**
- * <p>Locked stabilization mode which uses the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * stabilization to directionally steady the target region.</p>
- * @see CaptureRequest#EFV_STABILIZATION_MODE
- * @hide
- */
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final int EFV_STABILIZATION_MODE_LOCKED = 2;
-
- //
// Enumeration values for CaptureResult#CONTROL_AE_STATE
//
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 3b69aa7..3f5ae91 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -2730,7 +2730,6 @@
*/
@PublicKey
@NonNull
- @FlaggedApi(Flags.FLAG_CAMERA_MANUAL_FLASH_STRENGTH_CONTROL)
public static final Key<Integer> FLASH_STRENGTH_LEVEL =
new Key<Integer>("android.flash.strengthLevel", int.class);
@@ -4325,146 +4324,6 @@
public static final Key<Integer> EXTENSION_STRENGTH =
new Key<Integer>("android.extension.strength", int.class);
- /**
- * <p>Used to apply an additional digital zoom factor for the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p>
- * <p>For the {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * feature, an additional zoom factor is applied on top of the existing {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}.
- * This additional zoom factor serves as a buffer to provide more flexibility for the
- * {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED }
- * mode. If android.efv.paddingZoomFactor is not set, the default will be used.
- * The effectiveness of the stabilization may be influenced by the amount of padding zoom
- * applied. A higher padding zoom factor can stabilize the target region more effectively
- * with greater flexibility but may potentially impact image quality. Conversely, a lower
- * padding zoom factor may be used to prioritize preserving image quality, albeit with less
- * leeway in stabilizing the target region. It is recommended to set the
- * android.efv.paddingZoomFactor to at least 1.5.</p>
- * <p>If android.efv.autoZoom is enabled, the requested android.efv.paddingZoomFactor will be overridden.
- * android.efv.maxPaddingZoomFactor can be checked for more details on controlling the
- * padding zoom factor during android.efv.autoZoom.</p>
- * <p><b>Range of valid values:</b><br>
- * android.efv.paddingZoomFactorRange</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- *
- * @see CaptureRequest#CONTROL_ZOOM_RATIO
- * @hide
- */
- @ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final Key<Float> EFV_PADDING_ZOOM_FACTOR =
- new Key<Float>("android.efv.paddingZoomFactor", float.class);
-
- /**
- * <p>Used to enable or disable auto zoom for the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p>
- * <p>Turn on auto zoom to let the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * feature decide at any given point a combination of
- * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} and android.efv.paddingZoomFactor
- * to keep the target region in view and stabilized. The combination chosen by the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * will equal the requested {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} multiplied with the requested
- * android.efv.paddingZoomFactor. A limit can be set on the padding zoom if wanting
- * to control image quality further using android.efv.maxPaddingZoomFactor.</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- *
- * @see CaptureRequest#CONTROL_ZOOM_RATIO
- * @hide
- */
- @ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final Key<Boolean> EFV_AUTO_ZOOM =
- new Key<Boolean>("android.efv.autoZoom", boolean.class);
-
- /**
- * <p>Used to limit the android.efv.paddingZoomFactor if
- * android.efv.autoZoom is enabled for the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p>
- * <p>If android.efv.autoZoom is enabled, this key can be used to set a limit
- * on the android.efv.paddingZoomFactor chosen by the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode
- * to control image quality.</p>
- * <p><b>Range of valid values:</b><br>
- * The range of android.efv.paddingZoomFactorRange. Use a value greater than or equal to
- * the android.efv.paddingZoomFactor to effectively utilize this key.</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- * @hide
- */
- @ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR =
- new Key<Float>("android.efv.maxPaddingZoomFactor", float.class);
-
- /**
- * <p>Set the stabilization mode for the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension</p>
- * <p>The desired stabilization mode. Gimbal stabilization mode provides simple, non-locked
- * video stabilization. Locked mode uses the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * stabilization feature to fixate on the current region, utilizing it as the target area for
- * stabilization.</p>
- * <p><b>Possible values:</b></p>
- * <ul>
- * <li>{@link #EFV_STABILIZATION_MODE_OFF OFF}</li>
- * <li>{@link #EFV_STABILIZATION_MODE_GIMBAL GIMBAL}</li>
- * <li>{@link #EFV_STABILIZATION_MODE_LOCKED LOCKED}</li>
- * </ul>
- *
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- * @see #EFV_STABILIZATION_MODE_OFF
- * @see #EFV_STABILIZATION_MODE_GIMBAL
- * @see #EFV_STABILIZATION_MODE_LOCKED
- * @hide
- */
- @ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final Key<Integer> EFV_STABILIZATION_MODE =
- new Key<Integer>("android.efv.stabilizationMode", int.class);
-
- /**
- * <p>Used to update the target region for the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p>
- * <p>A android.util.Pair<Integer,Integer> that represents the desired
- * <Horizontal,Vertical> shift of the current locked view (or target region) in
- * pixels. Negative values indicate left and upward shifts, while positive values indicate
- * right and downward shifts in the active array coordinate system.</p>
- * <p><b>Range of valid values:</b><br>
- * android.util.Pair<Integer,Integer> represents the
- * <Horizontal,Vertical> shift. The range for the horizontal shift is
- * [-max(android.efv.paddingRegion-left), max(android.efv.paddingRegion-right)].
- * The range for the vertical shift is
- * [-max(android.efv.paddingRegion-top), max(android.efv.paddingRegion-bottom)]</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- * @hide
- */
- @ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final Key<android.util.Pair<Integer,Integer>> EFV_TRANSLATE_VIEWPORT =
- new Key<android.util.Pair<Integer,Integer>>("android.efv.translateViewport", new TypeReference<android.util.Pair<Integer,Integer>>() {{ }});
-
- /**
- * <p>Representing the desired clockwise rotation
- * of the target region in degrees for the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p>
- * <p>Value representing the desired clockwise rotation of the target
- * region in degrees.</p>
- * <p><b>Range of valid values:</b><br>
- * 0 to 360</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- * @hide
- */
- @ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final Key<Float> EFV_ROTATE_VIEWPORT =
- new Key<Float>("android.efv.rotateViewport", float.class);
-
/*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
* End generated code
*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 34ce92c..75d617c 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -3022,7 +3022,6 @@
*/
@PublicKey
@NonNull
- @FlaggedApi(Flags.FLAG_CAMERA_MANUAL_FLASH_STRENGTH_CONTROL)
public static final Key<Integer> FLASH_STRENGTH_LEVEL =
new Key<Integer>("android.flash.strengthLevel", int.class);
@@ -5940,214 +5939,6 @@
public static final Key<Integer> EXTENSION_STRENGTH =
new Key<Integer>("android.extension.strength", int.class);
- /**
- * <p>The padding region for the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p>
- * <p>An array [left, top, right, bottom] of the padding in pixels remaining on all four sides
- * before the target region starts to go out of bounds.</p>
- * <p>The padding region denotes the area surrounding the stabilized target region within which
- * the camera can be moved while maintaining the target region in view. As the camera moves,
- * the padding region adjusts to represent the proximity of the target region to the
- * boundary, which is the point at which the target region will start to go out of bounds.</p>
- * <p><b>Range of valid values:</b><br>
- * The padding is the number of remaining pixels of padding in each direction.
- * The pixels reference the active array coordinate system. Negative values indicate the target
- * region is out of bounds. The value for this key may be null for when the stabilization mode is
- * in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_OFF }
- * or {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_GIMBAL } mode.</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- * @hide
- */
- @ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final Key<int[]> EFV_PADDING_REGION =
- new Key<int[]>("android.efv.paddingRegion", int[].class);
-
- /**
- * <p>The padding region when android.efv.autoZoom is enabled for the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p>
- * <p>An array [left, top, right, bottom] of the padding in pixels remaining on all four sides
- * before the target region starts to go out of bounds.</p>
- * <p>This may differ from android.efv.paddingRegion as the field of view can change
- * during android.efv.autoZoom, altering the boundary region and thus updating the padding between the
- * target region and the boundary.</p>
- * <p><b>Range of valid values:</b><br>
- * The padding is the number of remaining pixels of padding in each direction
- * when android.efv.autoZoom is enabled. Negative values indicate the target region is out of bounds.
- * The value for this key may be null for when the android.efv.autoZoom is not enabled.</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- * @hide
- */
- @ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final Key<int[]> EFV_AUTO_ZOOM_PADDING_REGION =
- new Key<int[]>("android.efv.autoZoomPaddingRegion", int[].class);
-
- /**
- * <p>List of coordinates representing the target region relative to the
- * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE }
- * for the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension in
- * {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p>
- * <p>A list of android.graphics.PointF that define the coordinates of the target region
- * relative to the
- * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE }.
- * The array represents the target region coordinates as: top-left, top-right, bottom-left,
- * bottom-right.</p>
- * <p><b>Range of valid values:</b><br>
- * The list of target coordinates will define a region within the bounds of the
- * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE }</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- * @hide
- */
- @ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final Key<android.graphics.PointF[]> EFV_TARGET_COORDINATES =
- new Key<android.graphics.PointF[]>("android.efv.targetCoordinates", android.graphics.PointF[].class);
-
- /**
- * <p>Used to apply an additional digital zoom factor for the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p>
- * <p>For the {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * feature, an additional zoom factor is applied on top of the existing {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}.
- * This additional zoom factor serves as a buffer to provide more flexibility for the
- * {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED }
- * mode. If android.efv.paddingZoomFactor is not set, the default will be used.
- * The effectiveness of the stabilization may be influenced by the amount of padding zoom
- * applied. A higher padding zoom factor can stabilize the target region more effectively
- * with greater flexibility but may potentially impact image quality. Conversely, a lower
- * padding zoom factor may be used to prioritize preserving image quality, albeit with less
- * leeway in stabilizing the target region. It is recommended to set the
- * android.efv.paddingZoomFactor to at least 1.5.</p>
- * <p>If android.efv.autoZoom is enabled, the requested android.efv.paddingZoomFactor will be overridden.
- * android.efv.maxPaddingZoomFactor can be checked for more details on controlling the
- * padding zoom factor during android.efv.autoZoom.</p>
- * <p><b>Range of valid values:</b><br>
- * android.efv.paddingZoomFactorRange</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- *
- * @see CaptureRequest#CONTROL_ZOOM_RATIO
- * @hide
- */
- @ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final Key<Float> EFV_PADDING_ZOOM_FACTOR =
- new Key<Float>("android.efv.paddingZoomFactor", float.class);
-
- /**
- * <p>Set the stabilization mode for the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension</p>
- * <p>The desired stabilization mode. Gimbal stabilization mode provides simple, non-locked
- * video stabilization. Locked mode uses the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * stabilization feature to fixate on the current region, utilizing it as the target area for
- * stabilization.</p>
- * <p><b>Possible values:</b></p>
- * <ul>
- * <li>{@link #EFV_STABILIZATION_MODE_OFF OFF}</li>
- * <li>{@link #EFV_STABILIZATION_MODE_GIMBAL GIMBAL}</li>
- * <li>{@link #EFV_STABILIZATION_MODE_LOCKED LOCKED}</li>
- * </ul>
- *
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- * @see #EFV_STABILIZATION_MODE_OFF
- * @see #EFV_STABILIZATION_MODE_GIMBAL
- * @see #EFV_STABILIZATION_MODE_LOCKED
- * @hide
- */
- @ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final Key<Integer> EFV_STABILIZATION_MODE =
- new Key<Integer>("android.efv.stabilizationMode", int.class);
-
- /**
- * <p>Used to enable or disable auto zoom for the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p>
- * <p>Turn on auto zoom to let the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * feature decide at any given point a combination of
- * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} and android.efv.paddingZoomFactor
- * to keep the target region in view and stabilized. The combination chosen by the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * will equal the requested {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} multiplied with the requested
- * android.efv.paddingZoomFactor. A limit can be set on the padding zoom if wanting
- * to control image quality further using android.efv.maxPaddingZoomFactor.</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- *
- * @see CaptureRequest#CONTROL_ZOOM_RATIO
- * @hide
- */
- @ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final Key<Boolean> EFV_AUTO_ZOOM =
- new Key<Boolean>("android.efv.autoZoom", boolean.class);
-
- /**
- * <p>Representing the desired clockwise rotation
- * of the target region in degrees for the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p>
- * <p>Value representing the desired clockwise rotation of the target
- * region in degrees.</p>
- * <p><b>Range of valid values:</b><br>
- * 0 to 360</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- * @hide
- */
- @ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final Key<Float> EFV_ROTATE_VIEWPORT =
- new Key<Float>("android.efv.rotateViewport", float.class);
-
- /**
- * <p>Used to update the target region for the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p>
- * <p>A android.util.Pair<Integer,Integer> that represents the desired
- * <Horizontal,Vertical> shift of the current locked view (or target region) in
- * pixels. Negative values indicate left and upward shifts, while positive values indicate
- * right and downward shifts in the active array coordinate system.</p>
- * <p><b>Range of valid values:</b><br>
- * android.util.Pair<Integer,Integer> represents the
- * <Horizontal,Vertical> shift. The range for the horizontal shift is
- * [-max(android.efv.paddingRegion-left), max(android.efv.paddingRegion-right)].
- * The range for the vertical shift is
- * [-max(android.efv.paddingRegion-top), max(android.efv.paddingRegion-bottom)]</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- * @hide
- */
- @ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final Key<android.util.Pair<Integer,Integer>> EFV_TRANSLATE_VIEWPORT =
- new Key<android.util.Pair<Integer,Integer>>("android.efv.translateViewport", new TypeReference<android.util.Pair<Integer,Integer>>() {{ }});
-
- /**
- * <p>Used to limit the android.efv.paddingZoomFactor if
- * android.efv.autoZoom is enabled for the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p>
- * <p>If android.efv.autoZoom is enabled, this key can be used to set a limit
- * on the android.efv.paddingZoomFactor chosen by the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode
- * to control image quality.</p>
- * <p><b>Range of valid values:</b><br>
- * The range of android.efv.paddingZoomFactorRange. Use a value greater than or equal to
- * the android.efv.paddingZoomFactor to effectively utilize this key.</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- * @hide
- */
- @ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR =
- new Key<Float>("android.efv.maxPaddingZoomFactor", float.class);
-
/*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
* End generated code
*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/
diff --git a/core/java/android/hardware/camera2/ExtensionCaptureRequest.java b/core/java/android/hardware/camera2/ExtensionCaptureRequest.java
deleted file mode 100644
index b681ce4..0000000
--- a/core/java/android/hardware/camera2/ExtensionCaptureRequest.java
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hardware.camera2;
-
-import android.annotation.FlaggedApi;
-import android.annotation.NonNull;
-import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.CaptureRequest.Key;
-import android.hardware.camera2.impl.ExtensionKey;
-import android.hardware.camera2.impl.PublicKey;
-
-import com.android.internal.camera.flags.Flags;
-
-/**
- * ExtensionCaptureRequest contains definitions for extension-specific CaptureRequest keys that
- * can be used to configure a {@link android.hardware.camera2.CaptureRequest} during a
- * {@link android.hardware.camera2.CameraExtensionSession}.
- *
- * Note that ExtensionCaptureRequest is not intended to be used as a replacement
- * for CaptureRequest in the extensions. It serves as a supplementary class providing
- * extension-specific CaptureRequest keys. Developers should use these keys in conjunction
- * with regular CaptureRequest objects during a
- * {@link android.hardware.camera2.CameraExtensionSession}.
- *
- * @see CaptureRequest
- * @see CameraExtensionSession
- */
-@FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
-public final class ExtensionCaptureRequest {
-
- /** To avoid exposing constructor */
- private ExtensionCaptureRequest() {}
-
- /**
- * <p>Used to apply an additional digital zoom factor for the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p>
- * <p>For the {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * feature, an additional zoom factor is applied on top of the existing {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}.
- * This additional zoom factor serves as a buffer to provide more flexibility for the
- * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED }
- * mode. If {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } is not set, the default will be used.
- * The effectiveness of the stabilization may be influenced by the amount of padding zoom
- * applied. A higher padding zoom factor can stabilize the target region more effectively
- * with greater flexibility but may potentially impact image quality. Conversely, a lower
- * padding zoom factor may be used to prioritize preserving image quality, albeit with less
- * leeway in stabilizing the target region. It is recommended to set the
- * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } to at least 1.5.</p>
- * <p>If {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is enabled, the requested {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } will be overridden.
- * {@link ExtensionCaptureRequest#EFV_MAX_PADDING_ZOOM_FACTOR } can be checked for more details on controlling the
- * padding zoom factor during {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM }.</p>
- * <p><b>Range of valid values:</b><br>
- * {@link CameraExtensionCharacteristics#EFV_PADDING_ZOOM_FACTOR_RANGE }</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- *
- * @see CaptureRequest#CONTROL_ZOOM_RATIO
- * @see ExtensionCaptureRequest#EFV_AUTO_ZOOM
- * @see ExtensionCaptureRequest#EFV_MAX_PADDING_ZOOM_FACTOR
- * @see ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR
- * @see CameraExtensionCharacteristics#EFV_PADDING_ZOOM_FACTOR_RANGE
- */
- @PublicKey
- @NonNull
- @ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final Key<Float> EFV_PADDING_ZOOM_FACTOR = CaptureRequest.EFV_PADDING_ZOOM_FACTOR;
-
- /**
- * <p>Used to enable or disable auto zoom for the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p>
- * <p>Turn on auto zoom to let the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * feature decide at any given point a combination of
- * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} and {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR }
- * to keep the target region in view and stabilized. The combination chosen by the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * will equal the requested {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} multiplied with the requested
- * {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR }. A limit can be set on the padding zoom if wanting
- * to control image quality further using {@link ExtensionCaptureRequest#EFV_MAX_PADDING_ZOOM_FACTOR }.</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- *
- * @see CaptureRequest#CONTROL_ZOOM_RATIO
- * @see ExtensionCaptureRequest#EFV_MAX_PADDING_ZOOM_FACTOR
- * @see ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR
- */
- @PublicKey
- @NonNull
- @ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final Key<Boolean> EFV_AUTO_ZOOM = CaptureRequest.EFV_AUTO_ZOOM;
-
- /**
- * <p>Used to limit the {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } if
- * {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is enabled for the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p>
- * <p>If {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is enabled, this key can be used to set a limit
- * on the {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } chosen by the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode
- * to control image quality.</p>
- * <p><b>Range of valid values:</b><br>
- * The range of {@link CameraExtensionCharacteristics#EFV_PADDING_ZOOM_FACTOR_RANGE Range}. Use a value greater than or equal to
- * the {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } to
- * effectively utilize this key.</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- *
- * @see ExtensionCaptureRequest#EFV_AUTO_ZOOM
- * @see ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR
- * @see CameraExtensionCharacteristics#EFV_PADDING_ZOOM_FACTOR_RANGE
- */
- @PublicKey
- @NonNull
- @ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR = CaptureRequest.EFV_MAX_PADDING_ZOOM_FACTOR;
-
- /**
- * <p>Set the stabilization mode for the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension</p>
- * <p>The desired stabilization mode. Gimbal stabilization mode provides simple, non-locked
- * video stabilization. Locked mode uses the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * stabilization feature to fixate on the current region, utilizing it as the target area for
- * stabilization.</p>
- * <p><b>Possible values:</b></p>
- * <ul>
- * <li>{@link #EFV_STABILIZATION_MODE_OFF OFF}</li>
- * <li>{@link #EFV_STABILIZATION_MODE_GIMBAL GIMBAL}</li>
- * <li>{@link #EFV_STABILIZATION_MODE_LOCKED LOCKED}</li>
- * </ul>
- *
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- * @see #EFV_STABILIZATION_MODE_OFF
- * @see #EFV_STABILIZATION_MODE_GIMBAL
- * @see #EFV_STABILIZATION_MODE_LOCKED
- */
- @PublicKey
- @NonNull
- @ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final Key<Integer> EFV_STABILIZATION_MODE = CaptureRequest.EFV_STABILIZATION_MODE;
-
- /**
- * <p>Used to update the target region for the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p>
- * <p>A android.util.Pair<Integer,Integer> that represents the desired
- * <Horizontal,Vertical> shift of the current locked view (or target region) in
- * pixels. Negative values indicate left and upward shifts, while positive values indicate
- * right and downward shifts in the active array coordinate system.</p>
- * <p><b>Range of valid values:</b><br>
- * android.util.Pair<Integer,Integer> represents the
- * <Horizontal,Vertical> shift. The range for the horizontal shift is
- * [-max({@link ExtensionCaptureResult#EFV_PADDING_REGION }-left), max({@link ExtensionCaptureResult#EFV_PADDING_REGION }-right)].
- * The range for the vertical shift is
- * [-max({@link ExtensionCaptureResult#EFV_PADDING_REGION }-top), max({@link ExtensionCaptureResult#EFV_PADDING_REGION }-bottom)]</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- *
- * @see ExtensionCaptureResult#EFV_PADDING_REGION
- */
- @PublicKey
- @NonNull
- @ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final Key<android.util.Pair<Integer,Integer>> EFV_TRANSLATE_VIEWPORT = CaptureRequest.EFV_TRANSLATE_VIEWPORT;
-
- /**
- * <p>Representing the desired clockwise rotation
- * of the target region in degrees for the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p>
- * <p>Value representing the desired clockwise rotation of the target
- * region in degrees.</p>
- * <p><b>Range of valid values:</b><br>
- * 0 to 360</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- */
- @PublicKey
- @NonNull
- @ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final Key<Float> EFV_ROTATE_VIEWPORT = CaptureRequest.EFV_ROTATE_VIEWPORT;
-
-
- //
- // Enumeration values for CaptureRequest#EFV_STABILIZATION_MODE
- //
-
- /**
- * <p>No stabilization.</p>
- * @see ExtensionCaptureRequest#EFV_STABILIZATION_MODE
- */
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final int EFV_STABILIZATION_MODE_OFF = CaptureRequest.EFV_STABILIZATION_MODE_OFF;
-
- /**
- * <p>Gimbal stabilization mode.</p>
- * @see ExtensionCaptureRequest#EFV_STABILIZATION_MODE
- */
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final int EFV_STABILIZATION_MODE_GIMBAL = CaptureRequest.EFV_STABILIZATION_MODE_GIMBAL;
-
- /**
- * <p>Locked stabilization mode which uses the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * stabilization to directionally steady the target region.</p>
- * @see ExtensionCaptureRequest#EFV_STABILIZATION_MODE
- */
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final int EFV_STABILIZATION_MODE_LOCKED = CaptureRequest.EFV_STABILIZATION_MODE_LOCKED;
-
-}
diff --git a/core/java/android/hardware/camera2/ExtensionCaptureResult.java b/core/java/android/hardware/camera2/ExtensionCaptureResult.java
deleted file mode 100644
index b7ba78c..0000000
--- a/core/java/android/hardware/camera2/ExtensionCaptureResult.java
+++ /dev/null
@@ -1,274 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hardware.camera2;
-
-import android.annotation.FlaggedApi;
-import android.annotation.NonNull;
-import android.hardware.camera2.CameraExtensionCharacteristics;
-import android.hardware.camera2.CaptureResult;
-import android.hardware.camera2.CaptureResult.Key;
-import android.hardware.camera2.impl.ExtensionKey;
-import android.hardware.camera2.impl.PublicKey;
-
-import com.android.internal.camera.flags.Flags;
-
-/**
- * ExtensionCaptureResult contains definitions for extension-specific CaptureResult keys that
- * are available during a {@link android.hardware.camera2.CameraExtensionSession} after a
- * {@link android.hardware.camera2.CaptureRequest} is processed.
- *
- * Note that ExtensionCaptureResult is not intended to be used as a replacement
- * for CaptureResult in the extensions. It serves as a supplementary class providing
- * extension-specific CaptureResult keys. Developers should use these keys in conjunction
- * with regular CaptureResult objects during a
- * {@link android.hardware.camera2.CameraExtensionSession}.
- *
- * @see CaptureResult
- * @see CaptureRequest
- * @see CameraExtensionSession
- */
-@FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
-public final class ExtensionCaptureResult {
-
- /** To avoid exposing constructor */
- private ExtensionCaptureResult() {}
-
- /**
- * <p>The padding region for the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p>
- * <p>An array [left, top, right, bottom] of the padding in pixels remaining on all four sides
- * before the target region starts to go out of bounds.</p>
- * <p>The padding region denotes the area surrounding the stabilized target region within which
- * the camera can be moved while maintaining the target region in view. As the camera moves,
- * the padding region adjusts to represent the proximity of the target region to the
- * boundary, which is the point at which the target region will start to go out of bounds.</p>
- * <p><b>Range of valid values:</b><br>
- * The padding is the number of remaining pixels of padding in each direction.
- * The pixels reference the active array coordinate system. Negative values indicate the target region
- * is out of bounds. The value for this key may be null for when the stabilization mode is
- * in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_OFF }
- * or {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_GIMBAL } mode.</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- */
- @PublicKey
- @NonNull
- @ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final Key<int[]> EFV_PADDING_REGION = CaptureResult.EFV_PADDING_REGION;
-
- /**
- * <p>The padding region when {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is enabled for the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p>
- * <p>An array [left, top, right, bottom] of the padding in pixels remaining on all four sides
- * before the target region starts to go out of bounds.</p>
- * <p>This may differ from {@link ExtensionCaptureResult#EFV_PADDING_REGION } as the field of view can change
- * during {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM }, altering the boundary region and thus updating the padding between the
- * target region and the boundary.</p>
- * <p><b>Range of valid values:</b><br>
- * The padding is the number of remaining pixels of padding in each direction
- * when {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is enabled. Negative values indicate the target region is out of bounds.
- * The value for this key may be null for when the {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is not enabled.</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- *
- * @see ExtensionCaptureRequest#EFV_AUTO_ZOOM
- * @see ExtensionCaptureResult#EFV_PADDING_REGION
- */
- @PublicKey
- @NonNull
- @ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final Key<int[]> EFV_AUTO_ZOOM_PADDING_REGION = CaptureResult.EFV_AUTO_ZOOM_PADDING_REGION;
-
- /**
- * <p>List of coordinates representing the target region relative to the
- * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE }
- * for the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension in
- * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p>
- * <p>A list of android.graphics.PointF that define the coordinates of the target region
- * relative to the
- * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE }.
- * The array represents the target region coordinates as: top-left, top-right, bottom-left,
- * bottom-right.</p>
- * <p><b>Range of valid values:</b><br>
- * The list of target coordinates will define a region within the bounds of the
- * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE }</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- */
- @PublicKey
- @NonNull
- @ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final Key<android.graphics.PointF[]> EFV_TARGET_COORDINATES = CaptureResult.EFV_TARGET_COORDINATES;
-
- /**
- * <p>Used to apply an additional digital zoom factor for the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p>
- * <p>For the {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * feature, an additional zoom factor is applied on top of the existing {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}.
- * This additional zoom factor serves as a buffer to provide more flexibility for the
- * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED }
- * mode. If {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } is not set, the default will be used.
- * The effectiveness of the stabilization may be influenced by the amount of padding zoom
- * applied. A higher padding zoom factor can stabilize the target region more effectively
- * with greater flexibility but may potentially impact image quality. Conversely, a lower
- * padding zoom factor may be used to prioritize preserving image quality, albeit with less
- * leeway in stabilizing the target region. It is recommended to set the
- * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } to at least 1.5.</p>
- * <p>If {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is enabled, the requested {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } will be overridden.
- * {@link ExtensionCaptureRequest#EFV_MAX_PADDING_ZOOM_FACTOR } can be checked for more details on controlling the
- * padding zoom factor during {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM }.</p>
- * <p><b>Range of valid values:</b><br>
- * {@link CameraExtensionCharacteristics#EFV_PADDING_ZOOM_FACTOR_RANGE }</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- *
- * @see CaptureRequest#CONTROL_ZOOM_RATIO
- * @see ExtensionCaptureRequest#EFV_AUTO_ZOOM
- * @see ExtensionCaptureRequest#EFV_MAX_PADDING_ZOOM_FACTOR
- * @see ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR
- * @see CameraExtensionCharacteristics#EFV_PADDING_ZOOM_FACTOR_RANGE
- */
- @PublicKey
- @NonNull
- @ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final Key<Float> EFV_PADDING_ZOOM_FACTOR = CaptureResult.EFV_PADDING_ZOOM_FACTOR;
-
- /**
- * <p>Set the stabilization mode for the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension</p>
- * <p>The desired stabilization mode. Gimbal stabilization mode provides simple, non-locked
- * video stabilization. Locked mode uses the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * stabilization feature to fixate on the current region, utilizing it as the target area for
- * stabilization.</p>
- * <p><b>Possible values:</b></p>
- * <ul>
- * <li>{@link #EFV_STABILIZATION_MODE_OFF OFF}</li>
- * <li>{@link #EFV_STABILIZATION_MODE_GIMBAL GIMBAL}</li>
- * <li>{@link #EFV_STABILIZATION_MODE_LOCKED LOCKED}</li>
- * </ul>
- *
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- * @see #EFV_STABILIZATION_MODE_OFF
- * @see #EFV_STABILIZATION_MODE_GIMBAL
- * @see #EFV_STABILIZATION_MODE_LOCKED
- */
- @PublicKey
- @NonNull
- @ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final Key<Integer> EFV_STABILIZATION_MODE = CaptureResult.EFV_STABILIZATION_MODE;
-
- /**
- * <p>Used to enable or disable auto zoom for the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p>
- * <p>Turn on auto zoom to let the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * feature decide at any given point a combination of
- * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} and {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR }
- * to keep the target region in view and stabilized. The combination chosen by the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * will equal the requested {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} multiplied with the requested
- * {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR }. A limit can be set on the padding zoom if wanting
- * to control image quality further using {@link ExtensionCaptureRequest#EFV_MAX_PADDING_ZOOM_FACTOR }.</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- *
- * @see CaptureRequest#CONTROL_ZOOM_RATIO
- * @see ExtensionCaptureRequest#EFV_MAX_PADDING_ZOOM_FACTOR
- * @see ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR
- */
- @PublicKey
- @NonNull
- @ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final Key<Boolean> EFV_AUTO_ZOOM = CaptureResult.EFV_AUTO_ZOOM;
-
- /**
- * <p>Representing the desired clockwise rotation
- * of the target region in degrees for the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p>
- * <p>Value representing the desired clockwise rotation of the target
- * region in degrees.</p>
- * <p><b>Range of valid values:</b><br>
- * 0 to 360</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- */
- @PublicKey
- @NonNull
- @ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final Key<Float> EFV_ROTATE_VIEWPORT = CaptureResult.EFV_ROTATE_VIEWPORT;
-
- /**
- * <p>Used to update the target region for the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p>
- * <p>A android.util.Pair<Integer,Integer> that represents the desired
- * <Horizontal,Vertical> shift of the current locked view (or target region) in
- * pixels. Negative values indicate left and upward shifts, while positive values indicate
- * right and downward shifts in the active array coordinate system.</p>
- * <p><b>Range of valid values:</b><br>
- * android.util.Pair<Integer,Integer> represents the
- * <Horizontal,Vertical> shift. The range for the horizontal shift is
- * [-max({@link ExtensionCaptureResult#EFV_PADDING_REGION }-left), max({@link ExtensionCaptureResult#EFV_PADDING_REGION }-right)].
- * The range for the vertical shift is
- * [-max({@link ExtensionCaptureResult#EFV_PADDING_REGION }-top), max({@link ExtensionCaptureResult#EFV_PADDING_REGION }-bottom)]</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- *
- * @see ExtensionCaptureResult#EFV_PADDING_REGION
- */
- @PublicKey
- @NonNull
- @ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final Key<android.util.Pair<Integer,Integer>> EFV_TRANSLATE_VIEWPORT = CaptureResult.EFV_TRANSLATE_VIEWPORT;
-
- /**
- * <p>Used to limit the {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } if
- * {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is enabled for the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p>
- * <p>If {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is enabled, this key can be used to set a limit
- * on the {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } chosen by the
- * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
- * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode
- * to control image quality.</p>
- * <p><b>Range of valid values:</b><br>
- * The range of {@link CameraExtensionCharacteristics#EFV_PADDING_ZOOM_FACTOR_RANGE }. Use a value greater than or equal to
- * the {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } to
- * effectively utilize this key.</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- *
- * @see ExtensionCaptureRequest#EFV_AUTO_ZOOM
- * @see ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR
- * @see CameraExtensionCharacteristics#EFV_PADDING_ZOOM_FACTOR_RANGE
- */
- @PublicKey
- @NonNull
- @ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
- public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR = CaptureResult.EFV_MAX_PADDING_ZOOM_FACTOR;
-
-}
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java
index 4ddf602..b5fb050 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java
@@ -205,10 +205,6 @@
*/
@SuppressWarnings("AndroidFrameworkCompatChange")
public static boolean isCameraDeviceSetupSupported(CameraCharacteristics chars) {
- if (!Flags.featureCombinationQuery()) {
- return false;
- }
-
Integer queryVersion = chars.get(
CameraCharacteristics.INFO_SESSION_CONFIGURATION_QUERY_VERSION);
return queryVersion != null && queryVersion > Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
diff --git a/core/java/android/hardware/camera2/params/SessionConfiguration.java b/core/java/android/hardware/camera2/params/SessionConfiguration.java
index 9bd4860..50c6b5b 100644
--- a/core/java/android/hardware/camera2/params/SessionConfiguration.java
+++ b/core/java/android/hardware/camera2/params/SessionConfiguration.java
@@ -165,12 +165,10 @@
source.readTypedList(outConfigs, OutputConfiguration.CREATOR);
// Ignore the values for hasSessionParameters and settings because we cannot reconstruct
// the CaptureRequest object.
- if (Flags.featureCombinationQuery()) {
- boolean hasSessionParameters = source.readBoolean();
- if (hasSessionParameters) {
- CameraMetadataNative settings = new CameraMetadataNative();
- settings.readFromParcel(source);
- }
+ boolean hasSessionParameters = source.readBoolean();
+ if (hasSessionParameters) {
+ CameraMetadataNative settings = new CameraMetadataNative();
+ settings.readFromParcel(source);
}
if ((inputWidth > 0) && (inputHeight > 0) && (inputFormat != -1)) {
@@ -212,14 +210,12 @@
dest.writeBoolean(/*isMultiResolution*/ false);
}
dest.writeTypedList(mOutputConfigurations);
- if (Flags.featureCombinationQuery()) {
- if (mSessionParameters != null) {
- dest.writeBoolean(/*hasSessionParameters*/true);
- CameraMetadataNative metadata = mSessionParameters.getNativeCopy();
- metadata.writeToParcel(dest, /*flags*/0);
- } else {
- dest.writeBoolean(/*hasSessionParameters*/false);
- }
+ if (mSessionParameters != null) {
+ dest.writeBoolean(/*hasSessionParameters*/true);
+ CameraMetadataNative metadata = mSessionParameters.getNativeCopy();
+ metadata.writeToParcel(dest, /*flags*/0);
+ } else {
+ dest.writeBoolean(/*hasSessionParameters*/false);
}
}
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 9612a53..7185719 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -1445,7 +1445,7 @@
* system's display configuration.
*/
public static final String CACHE_KEY_DISPLAY_INFO_PROPERTY =
- "cache_key.display_info";
+ PropertyInvalidatedCache.createSystemCacheKey("display_info");
/**
* Invalidates the contents of the display info cache for all applications. Can only
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index 8592ded..177ee6f 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -20,15 +20,15 @@
import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS;
import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG;
import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG;
-import static com.android.hardware.input.Flags.FLAG_KEYBOARD_REPEAT_KEYS;
import static com.android.hardware.input.Flags.keyboardA11yBounceKeysFlag;
import static com.android.hardware.input.Flags.keyboardA11ySlowKeysFlag;
import static com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag;
import static com.android.hardware.input.Flags.keyboardA11yMouseKeys;
-import static com.android.hardware.input.Flags.keyboardRepeatKeys;
import static com.android.hardware.input.Flags.touchpadTapDragging;
import static com.android.hardware.input.Flags.touchpadVisualizer;
import static com.android.input.flags.Flags.enableInputFilterRustImpl;
+import static com.android.input.flags.Flags.FLAG_KEYBOARD_REPEAT_KEYS;
+import static com.android.input.flags.Flags.keyboardRepeatKeys;
import android.Manifest;
import android.annotation.FlaggedApi;
@@ -800,7 +800,7 @@
*
* <p>
* ‘Repeat keys’ is a feature which allows users to generate key repeats when a particular
- * key on the physical keyboard is held down. This accessibility feature allows the user
+ * key on the physical keyboard is held down. This feature allows the user
* to configure the timeout before the key repeats begin as well as the delay
* between successive key repeats.
* </p>
@@ -812,7 +812,31 @@
}
/**
- * Get Accessibility repeat keys timeout duration in milliseconds.
+ * Whether "Repeat keys" feature is enabled.
+ * Repeat keys is ON by default.
+ * The repeat keys timeout and delay would have the default values in the default ON case.
+ *
+ * <p>
+ * 'Repeat keys’ is a feature which allows users to generate key repeats when a particular
+ * key on the physical keyboard is held down. This feature allows the user
+ * to configure the timeout before the key repeats begin as well as the delay
+ * between successive key repeats.
+ * </p>
+ *
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(FLAG_KEYBOARD_REPEAT_KEYS)
+ public static boolean isRepeatKeysEnabled(@NonNull Context context) {
+ if (!isRepeatKeysFeatureFlagEnabled()) {
+ return true;
+ }
+ return Settings.Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.KEY_REPEAT_ENABLED, 1, UserHandle.USER_CURRENT) != 0;
+ }
+
+ /**
+ * Get repeat keys timeout duration in milliseconds.
* The default key repeat timeout is {@link ViewConfiguration#DEFAULT_KEY_REPEAT_TIMEOUT_MS}.
*
* @param context The application context
@@ -823,7 +847,7 @@
*
* <p>
* ‘Repeat keys’ is a feature which allows users to generate key repeats when a particular
- * key on the physical keyboard is held down. This accessibility feature allows the user
+ * key on the physical keyboard is held down. This feature allows the user
* to configure the timeout before the key repeats begin as well as the delay
* between successive key repeats.
* </p>
@@ -832,14 +856,17 @@
*/
@TestApi
@FlaggedApi(FLAG_KEYBOARD_REPEAT_KEYS)
- public static int getAccessibilityRepeatKeysTimeout(@NonNull Context context) {
+ public static int getRepeatKeysTimeout(@NonNull Context context) {
+ if (!isRepeatKeysFeatureFlagEnabled()) {
+ return ViewConfiguration.getKeyRepeatTimeout();
+ }
return Settings.Secure.getIntForUser(context.getContentResolver(),
Settings.Secure.KEY_REPEAT_TIMEOUT_MS, ViewConfiguration.getKeyRepeatTimeout(),
UserHandle.USER_CURRENT);
}
/**
- * Get Accessibility repeat keys delay rate in milliseconds.
+ * Get repeat keys delay rate in milliseconds.
* The default key repeat delay is {@link ViewConfiguration#DEFAULT_KEY_REPEAT_DELAY_MS}.
*
* @param context The application context
@@ -850,7 +877,7 @@
*
* <p>
* ‘Repeat keys’ is a feature which allows users to generate key repeats when a particular
- * key on the physical keyboard is held down. This accessibility feature allows the user
+ * key on the physical keyboard is held down. This feature allows the user
* to configure the timeout before the key repeats begin as well as the delay
* between successive key repeats.
* </p>
@@ -859,14 +886,41 @@
*/
@TestApi
@FlaggedApi(FLAG_KEYBOARD_REPEAT_KEYS)
- public static int getAccessibilityRepeatKeysDelay(@NonNull Context context) {
+ public static int getRepeatKeysDelay(@NonNull Context context) {
+ if (!isRepeatKeysFeatureFlagEnabled()) {
+ return ViewConfiguration.getKeyRepeatDelay();
+ }
return Settings.Secure.getIntForUser(context.getContentResolver(),
Settings.Secure.KEY_REPEAT_DELAY_MS, ViewConfiguration.getKeyRepeatDelay(),
UserHandle.USER_CURRENT);
}
/**
- * Set Accessibility repeat keys timeout duration in milliseconds.
+ * Set repeat keys feature enabled/disabled.
+ *
+ * <p>
+ * 'Repeat keys’ is a feature which allows users to generate key repeats when a particular
+ * key on the physical keyboard is held down. This feature allows the user
+ * to configure the timeout before the key repeats begin as well as the delay
+ * between successive key repeats.
+ * </p>
+ *
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(FLAG_KEYBOARD_REPEAT_KEYS)
+ @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
+ public static void setRepeatKeysEnabled(@NonNull Context context,
+ boolean enabled) {
+ if (!isRepeatKeysFeatureFlagEnabled()) {
+ return;
+ }
+ Settings.Secure.putIntForUser(context.getContentResolver(),
+ Settings.Secure.KEY_REPEAT_ENABLED, enabled ? 1 : 0, UserHandle.USER_CURRENT);
+ }
+
+ /**
+ * Set repeat keys timeout duration in milliseconds.
*
* @param timeoutTimeMillis time duration for which a key should be pressed after which the
* pressed key will be repeated. The timeout must be between
@@ -875,7 +929,7 @@
*
* <p>
* ‘Repeat keys’ is a feature which allows users to generate key repeats when a particular
- * key on the physical keyboard is held down. This accessibility feature allows the user
+ * key on the physical keyboard is held down. This feature allows the user
* to configure the timeout before the key repeats begin as well as the delay
* between successive key repeats.
* </p>
@@ -885,8 +939,12 @@
@TestApi
@FlaggedApi(FLAG_KEYBOARD_REPEAT_KEYS)
@RequiresPermission(Manifest.permission.WRITE_SETTINGS)
- public static void setAccessibilityRepeatKeysTimeout(@NonNull Context context,
+ public static void setRepeatKeysTimeout(@NonNull Context context,
int timeoutTimeMillis) {
+ if (!isRepeatKeysFeatureFlagEnabled()
+ && !isRepeatKeysEnabled(context)) {
+ return;
+ }
if (timeoutTimeMillis < MIN_KEY_REPEAT_TIMEOUT_MILLIS
|| timeoutTimeMillis > MAX_KEY_REPEAT_TIMEOUT_MILLIS) {
throw new IllegalArgumentException(
@@ -900,7 +958,7 @@
}
/**
- * Set Accessibility repeat key delay duration in milliseconds.
+ * Set repeat key delay duration in milliseconds.
*
* @param delayTimeMillis Time duration between successive key repeats when a key is
* pressed down. The delay duration must be between
@@ -908,7 +966,7 @@
* {@link #MAX_KEY_REPEAT_DELAY_MILLIS}
* <p>
* ‘Repeat keys’ is a feature which allows users to generate key repeats when a particular
- * key on the physical keyboard is held down. This accessibility feature allows the user
+ * key on the physical keyboard is held down. This feature allows the user
* to configure the timeout before the key repeats begin as well as the delay
* between successive key repeats.
* </p>
@@ -918,8 +976,12 @@
@TestApi
@FlaggedApi(FLAG_KEYBOARD_REPEAT_KEYS)
@RequiresPermission(Manifest.permission.WRITE_SETTINGS)
- public static void setAccessibilityRepeatKeysDelay(@NonNull Context context,
+ public static void setRepeatKeysDelay(@NonNull Context context,
int delayTimeMillis) {
+ if (!isRepeatKeysFeatureFlagEnabled()
+ && !isRepeatKeysEnabled(context)) {
+ return;
+ }
if (delayTimeMillis < MIN_KEY_REPEAT_DELAY_MILLIS
|| delayTimeMillis > MAX_KEY_REPEAT_DELAY_MILLIS) {
throw new IllegalArgumentException(
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 1a309c6..983bbc3 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -119,6 +119,13 @@
}
flag {
+ namespace: "input_native"
+ name: "use_key_gesture_event_handler_multi_press_gestures"
+ description: "Use KeyGestureEvent handler APIs to control multi key press gestures"
+ bug: "358569822"
+}
+
+flag {
name: "keyboard_repeat_keys"
namespace: "input_native"
description: "Allow configurable timeout before key repeat and repeat delay rate for key repeats"
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index 6f11d3a..af93c96 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -35,7 +35,6 @@
import android.util.ArraySet;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
import com.android.server.vcn.util.PersistableBundleUtils;
@@ -434,7 +433,14 @@
@NonNull
public int[] getExposedCapabilities() {
// Sorted set guarantees ordering
- return ArrayUtils.convertToIntArray(new ArrayList<>(mExposedCapabilities));
+ final int[] caps = new int[mExposedCapabilities.size()];
+
+ int i = 0;
+ for (int c : mExposedCapabilities) {
+ caps[i++] = c;
+ }
+
+ return caps;
}
/**
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkSpecifier.java b/core/java/android/net/vcn/VcnUnderlyingNetworkSpecifier.java
index a975637..e1d1b3c6 100644
--- a/core/java/android/net/vcn/VcnUnderlyingNetworkSpecifier.java
+++ b/core/java/android/net/vcn/VcnUnderlyingNetworkSpecifier.java
@@ -24,7 +24,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.internal.util.ArrayUtils;
import java.util.Arrays;
import java.util.Objects;
@@ -114,8 +113,13 @@
@Override
public boolean canBeSatisfiedBy(NetworkSpecifier other) {
if (other instanceof TelephonyNetworkSpecifier) {
- return ArrayUtils.contains(
- mSubIds, ((TelephonyNetworkSpecifier) other).getSubscriptionId());
+ final int targetSubId = ((TelephonyNetworkSpecifier) other).getSubscriptionId();
+ for (int subId : mSubIds) {
+ if (targetSubId == subId) {
+ return true;
+ }
+ }
+ return false;
}
// TODO(b/180140053): Allow matching against WifiNetworkAgentSpecifier
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index f3efd89..8b267bf 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -141,6 +141,7 @@
/**
* Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
* integer containing the charge counter present in the battery.
+ * It shows the available battery power in µAh
* {@hide}
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -166,6 +167,76 @@
public static final String EXTRA_CHARGING_STATUS = "android.os.extra.CHARGING_STATUS";
/**
+ * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+ * Int value representing the battery's capacity level. These constants are key indicators of
+ * battery status and system capabilities, guiding power management decisions for both the
+ * system and apps:
+ * {@link #BATTERY_CAPACITY_LEVEL_UNSUPPORTED}: Feature not supported on this device.
+ * {@link #BATTERY_CAPACITY_LEVEL_UNKNOWN}: Battery status is unavailable or uninitialized.
+ * {@link #BATTERY_CAPACITY_LEVEL_CRITICAL}: Battery is critically low and the Android
+ * framework has been notified to schedule a shutdown by this value
+ * {@link #BATTERY_CAPACITY_LEVEL_LOW}: Android framework must limit background jobs to
+ * avoid impacting charging speed
+ * {@link #BATTERY_CAPACITY_LEVEL_NORMAL}: Battery level and charging rates are normal,
+ * battery temperature is within normal range and adapter power is enough to charge the
+ * battery at an acceptable rate. Android framework can run light background tasks without
+ * affecting charging performance severely.
+ * {@link #BATTERY_CAPACITY_LEVEL_HIGH}: Battery level is high, battery temperature is
+ * within normal range and adapter power is enough to charge the battery at an acceptable
+ * rate while running background loads. Android framework can run background tasks without
+ * affecting charging or battery performance.
+ * {@link #BATTERY_CAPACITY_LEVEL_FULL}: The battery is full, battery temperature is
+ * within normal range and adapter power is enough to sustain running background loads.
+ * Android framework can run background tasks without affecting the battery level or
+ * battery performance.
+ */
+
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public static final String EXTRA_CAPACITY_LEVEL = "android.os.extra.CAPACITY_LEVEL";
+
+ /**
+ * Battery capacity level is unsupported. @see EXTRA_CAPACITY_LEVEL
+ */
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public static final int BATTERY_CAPACITY_LEVEL_UNSUPPORTED = -1;
+
+ /**
+ * Battery capacity level is unknown. @see EXTRA_CAPACITY_LEVEL
+ */
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public static final int BATTERY_CAPACITY_LEVEL_UNKNOWN = 0;
+
+ /**
+ * Battery capacity level is critical. @see EXTRA_CAPACITY_LEVEL
+ */
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public static final int BATTERY_CAPACITY_LEVEL_CRITICAL = 1;
+
+ /**
+ * Battery capacity level is low. @see EXTRA_CAPACITY_LEVEL
+ */
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public static final int BATTERY_CAPACITY_LEVEL_LOW = 2;
+
+ /**
+ * Battery capacity level is normal. @see EXTRA_CAPACITY_LEVEL
+ */
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public static final int BATTERY_CAPACITY_LEVEL_NORMAL = 3;
+
+ /**
+ * Battery capacity level is high. @see EXTRA_CAPACITY_LEVEL
+ */
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public static final int BATTERY_CAPACITY_LEVEL_HIGH = 4;
+
+ /**
+ * Battery capacity level is full. @see EXTRA_CAPACITY_LEVEL
+ */
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public static final int BATTERY_CAPACITY_LEVEL_FULL = 5;
+
+ /**
* Extra for {@link android.content.Intent#ACTION_BATTERY_LEVEL_CHANGED}:
* Contains list of Bundles representing battery events
* @hide
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index c22f46c..80546cd 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -650,13 +650,13 @@
* weakly referenced by JNI so the strong references here are needed to keep the callbacks
* around until the proxy is GC'ed.
*/
- private List<IFrozenStateChangeCallback> mFrozenStateChangeCallbacks =
+ private List<FrozenStateChangeCallback> mFrozenStateChangeCallbacks =
Collections.synchronizedList(new ArrayList<>());
/**
- * See {@link IBinder#addFrozenStateChangeCallback(IFrozenStateChangeCallback)}
+ * See {@link IBinder#addFrozenStateChangeCallback(FrozenStateChangeCallback)}
*/
- public void addFrozenStateChangeCallback(IFrozenStateChangeCallback callback)
+ public void addFrozenStateChangeCallback(FrozenStateChangeCallback callback)
throws RemoteException {
addFrozenStateChangeCallbackNative(callback);
mFrozenStateChangeCallbacks.add(callback);
@@ -665,16 +665,16 @@
/**
* See {@link IBinder#removeFrozenStateChangeCallback}
*/
- public boolean removeFrozenStateChangeCallback(IFrozenStateChangeCallback callback) {
+ public boolean removeFrozenStateChangeCallback(FrozenStateChangeCallback callback) {
mFrozenStateChangeCallbacks.remove(callback);
return removeFrozenStateChangeCallbackNative(callback);
}
- private native void addFrozenStateChangeCallbackNative(IFrozenStateChangeCallback callback)
+ private native void addFrozenStateChangeCallbackNative(FrozenStateChangeCallback callback)
throws RemoteException;
private native boolean removeFrozenStateChangeCallbackNative(
- IFrozenStateChangeCallback callback);
+ FrozenStateChangeCallback callback);
/**
* Perform a dump on the remote object
@@ -762,10 +762,9 @@
}
private static void invokeFrozenStateChangeCallback(
- IFrozenStateChangeCallback callback, IBinder binderProxy, int stateIndex) {
+ FrozenStateChangeCallback callback, IBinder binderProxy, int stateIndex) {
try {
- callback.onFrozenStateChanged(binderProxy,
- IFrozenStateChangeCallback.State.values()[stateIndex]);
+ callback.onFrozenStateChanged(binderProxy, stateIndex);
} catch (RuntimeException exc) {
Log.w("BinderNative", "Uncaught exception from frozen state change callback",
exc);
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index 80f39bf..d0828c3 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -16,8 +16,10 @@
package android.os;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.util.Log;
import android.util.Printer;
@@ -819,16 +821,25 @@
}
/**
+ * WARNING: This API is dangerous because if the implementation
+ * of equals() is broken, it would delete unrelated events. For example,
+ * if object.equals() always returns true, it'd remove all messages.
+ *
+ * For this reason, never expose this API to non-platform code. i.e.
+ * this shouldn't be exposed to SystemApi.PRIVILEGED_APPS.
+ *
* Remove any pending posts of messages with code 'what' and whose obj is
* 'object' that are in the message queue. If <var>object</var> is null,
* all messages will be removed.
- * <p>
- * Similar to {@link #removeMessages(int, Object)} but uses object equality
+ *
+ * <p>Similar to {@link #removeMessages(int, Object)} but uses object equality
* ({@link Object#equals(Object)}) instead of reference equality (==) in
* determining whether object is the message's obj'.
*
*@hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @FlaggedApi(android.os.Flags.FLAG_MAINLINE_VCN_PLATFORM_API)
public final void removeEqualMessages(int what, @Nullable Object object) {
mQueue.removeEqualMessages(this, what, disallowNullArgumentIfShared(object));
}
@@ -843,12 +854,25 @@
}
/**
+ * WARNING: This API is dangerous because if the implementation
+ * of equals() is broken, it would delete unrelated events. For example,
+ * if object.equals() always returns true, it'd remove all messages.
+ *
+ * For this reason, never expose this API to non-platform code. i.e.
+ * this shouldn't be exposed to SystemApi.PRIVILEGED_APPS.
+ *
* Remove any pending posts of callbacks and sent messages whose
* <var>obj</var> is <var>token</var>. If <var>token</var> is null,
* all callbacks and messages will be removed.
*
+ * <p>Similar to {@link #removeCallbacksAndMessages(Object)} but uses object
+ * equality ({@link Object#equals(Object)}) instead of reference equality (==) in
+ * determining whether object is the message's obj'.
+ *
*@hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @FlaggedApi(android.os.Flags.FLAG_MAINLINE_VCN_PLATFORM_API)
public final void removeCallbacksAndEqualMessages(@Nullable Object token) {
mQueue.removeCallbacksAndEqualMessages(this, disallowNullArgumentIfShared(token));
}
@@ -864,6 +888,8 @@
* Return whether there are any messages or callbacks currently scheduled on this handler.
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @FlaggedApi(android.os.Flags.FLAG_MAINLINE_VCN_PLATFORM_API)
public final boolean hasMessagesOrCallbacks() {
return mQueue.hasMessages(this);
}
diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java
index 8185e8e..a997f4c 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -16,11 +16,15 @@
package android.os;
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import java.io.FileDescriptor;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* Base interface for a remotable object, the core part of a lightweight
@@ -377,9 +381,24 @@
*/
public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags);
- /** @hide */
- interface IFrozenStateChangeCallback {
- enum State {FROZEN, UNFROZEN};
+ /**
+ * A callback interface for receiving frozen state change events.
+ */
+ @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK)
+ interface FrozenStateChangeCallback {
+ /**
+ * @hide
+ */
+ @IntDef(prefix = {"STATE_"}, value = {
+ STATE_FROZEN,
+ STATE_UNFROZEN,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface State {
+ }
+
+ int STATE_FROZEN = 0;
+ int STATE_UNFROZEN = 1;
/**
* Interface for receiving a callback when the process hosting an IBinder
@@ -387,13 +406,13 @@
* @param who The IBinder whose hosting process has changed state.
* @param state The latest state.
*/
- void onFrozenStateChanged(@NonNull IBinder who, State state);
+ void onFrozenStateChanged(@NonNull IBinder who, @State int state);
}
/**
- * {@link addFrozenStateChangeCallback} provides a callback mechanism to notify about process
- * frozen/unfrozen events. Upon registration and any subsequent state changes, the callback is
- * invoked with the latest process frozen state.
+ * This method provides a callback mechanism to notify about process frozen/unfrozen events.
+ * Upon registration and any subsequent state changes, the callback is invoked with the latest
+ * process frozen state.
*
* <p>If the listener process (the one using this API) is itself frozen, state change events
* might be combined into a single one with the latest frozen state. This single event would
@@ -410,19 +429,19 @@
*
* <p>@throws {@link UnsupportedOperationException} if the kernel binder driver does not support
* this feature.
- * @hide
*/
- default void addFrozenStateChangeCallback(@NonNull IFrozenStateChangeCallback callback)
+ @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK)
+ default void addFrozenStateChangeCallback(@NonNull FrozenStateChangeCallback callback)
throws RemoteException {
throw new UnsupportedOperationException();
}
/**
- * Unregister a {@link IFrozenStateChangeCallback}. The callback will no longer be invoked when
+ * Unregister a {@link FrozenStateChangeCallback}. The callback will no longer be invoked when
* the hosting process changes its frozen state.
- * @hide
*/
- default boolean removeFrozenStateChangeCallback(@NonNull IFrozenStateChangeCallback callback) {
+ @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK)
+ default boolean removeFrozenStateChangeCallback(@NonNull FrozenStateChangeCallback callback) {
throw new UnsupportedOperationException();
}
}
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 026013c..e4c12b6 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -1144,9 +1144,10 @@
}
private static final String CACHE_KEY_IS_POWER_SAVE_MODE_PROPERTY =
- "cache_key.is_power_save_mode";
+ PropertyInvalidatedCache.createSystemCacheKey("is_power_save_mode");
- private static final String CACHE_KEY_IS_INTERACTIVE_PROPERTY = "cache_key.is_interactive";
+ private static final String CACHE_KEY_IS_INTERACTIVE_PROPERTY =
+ PropertyInvalidatedCache.createSystemCacheKey("is_interactive");
private static final int MAX_CACHE_ENTRIES = 1;
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index a4a7a98..1ca4574 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -3763,7 +3763,8 @@
}
private static final String CACHE_KEY_IS_USER_UNLOCKED_PROPERTY =
- "cache_key.is_user_unlocked";
+ PropertyInvalidatedCache.createPropertyName(
+ PropertyInvalidatedCache.MODULE_SYSTEM, "is_user_unlocked");
private final PropertyInvalidatedCache<Integer, Boolean> mIsUserUnlockedCache =
new PropertyInvalidatedCache<Integer, Boolean>(
@@ -6694,7 +6695,9 @@
}
/* Cache key for anything that assumes that userIds cannot be re-used without rebooting. */
- private static final String CACHE_KEY_STATIC_USER_PROPERTIES = "cache_key.static_user_props";
+ private static final String CACHE_KEY_STATIC_USER_PROPERTIES =
+ PropertyInvalidatedCache.createPropertyName(
+ PropertyInvalidatedCache.MODULE_SYSTEM, "static_user_props");
private final PropertyInvalidatedCache<Integer, String> mProfileTypeCache =
new PropertyInvalidatedCache<Integer, String>(32, CACHE_KEY_STATIC_USER_PROPERTIES) {
@@ -6721,7 +6724,9 @@
}
/* Cache key for UserProperties object. */
- private static final String CACHE_KEY_USER_PROPERTIES = "cache_key.user_properties";
+ private static final String CACHE_KEY_USER_PROPERTIES =
+ PropertyInvalidatedCache.createPropertyName(
+ PropertyInvalidatedCache.MODULE_SYSTEM, "user_properties");
// TODO: It would be better to somehow have this as static, so that it can work cross-context.
private final PropertyInvalidatedCache<Integer, UserProperties> mUserPropertiesCache =
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 39bd15c..f670601 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -153,6 +153,14 @@
}
flag {
+ name: "binder_frozen_state_change_callback"
+ is_exported: true
+ namespace: "system_performance"
+ description: "Guards the frozen state change callback API."
+ bug: "361157077"
+}
+
+flag {
name: "message_queue_tail_tracking"
namespace: "system_performance"
description: "track tail of message queue."
@@ -208,3 +216,11 @@
bug: "346294653"
is_exported: true
}
+
+flag {
+ name: "mainline_vcn_platform_api"
+ namespace: "vcn"
+ description: "Expose platform APIs to mainline VCN"
+ is_exported: true
+ bug: "366598445"
+}
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 7e51cb0..e98397d 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -1796,7 +1796,8 @@
}
/** @hide */
- public static final String CACHE_KEY_PACKAGE_INFO = "cache_key.package_info";
+ public static final String CACHE_KEY_PACKAGE_INFO =
+ PropertyInvalidatedCache.createSystemCacheKey("package_info");
/** @hide */
private static final PropertyInvalidatedCache<PermissionQuery, Integer> sPermissionCache =
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 01c230f..a622810 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -3018,169 +3018,180 @@
com.android.internal.R.string.config_rawContactsLocalAccountType));
}
+
+
/**
- * Represents the state of the default account, and the actual {@link Account} if it's
- * a cloud account.
- * If the default account is set to {@link #DEFAULT_ACCOUNT_STATE_LOCAL} or
- * {@link #DEFAULT_ACCOUNT_STATE_CLOUD}, new raw contacts requested for insertion
- * without a
- * specified {@link Account} will be saved in the default account.
- * The default account can have one of the following four states:
- * <ul>
- * <li> {@link #DEFAULT_ACCOUNT_STATE_NOT_SET}: The default account has not
- * been set by the user. </li>
- * <li> {@link #DEFAULT_ACCOUNT_STATE_LOCAL}: The default account is set to
- * the local device storage. New raw contacts requested for insertion without a
- * specified
- * {@link Account} will be saved in a null or custom local account. </li>
- * <li> {@link #DEFAULT_ACCOUNT_STATE_CLOUD}: The default account is set to a
- * cloud-synced account. New raw contacts requested for insertion without a specified
- * {@link Account} will be saved in the default cloud account. </li>
- * </ul>
+ * Class containing utility methods around the default account.
+ * New raw contacts requested to be inserted without a specified {@link Account} will be
+ * saved in the default account.
*/
@FlaggedApi(Flags.FLAG_NEW_DEFAULT_ACCOUNT_API_ENABLED)
- public static final class DefaultAccountAndState {
- /** A state indicating that default account is not set. */
- public static final int DEFAULT_ACCOUNT_STATE_NOT_SET = 1;
-
- /** A state indicating that default account is set to local device storage. */
- public static final int DEFAULT_ACCOUNT_STATE_LOCAL = 2;
+ public static final class DefaultAccount {
/**
- * A state indicating that the default account is set as an account that is synced
- * to the cloud.
+ * Represents the state of the default account, and the actual {@link Account} if it's
+ * a cloud account.
+ * If the default account is set to {@link #DEFAULT_ACCOUNT_STATE_LOCAL} or
+ * {@link #DEFAULT_ACCOUNT_STATE_CLOUD}, new raw contacts requested for insertion
+ * without a
+ * specified {@link Account} will be saved in the default account.
+ * The default account can have one of the following four states:
+ * <ul>
+ * <li> {@link #DEFAULT_ACCOUNT_STATE_NOT_SET}: The default account has not
+ * been set by the user. </li>
+ * <li> {@link #DEFAULT_ACCOUNT_STATE_LOCAL}: The default account is set to
+ * the local device storage. New raw contacts requested for insertion without a
+ * specified
+ * {@link Account} will be saved in a null or custom local account. </li>
+ * <li> {@link #DEFAULT_ACCOUNT_STATE_CLOUD}: The default account is set to a
+ * cloud-synced account. New raw contacts requested for insertion without a specified
+ * {@link Account} will be saved in the default cloud account. </li>
+ * </ul>
*/
- public static final int DEFAULT_ACCOUNT_STATE_CLOUD = 3;
+ @FlaggedApi(Flags.FLAG_NEW_DEFAULT_ACCOUNT_API_ENABLED)
+ public static final class DefaultAccountAndState {
+ /** A state indicating that default account is not set. */
+ public static final int DEFAULT_ACCOUNT_STATE_NOT_SET = 1;
- /**
- * The state of the default account. One of
- * {@link #DEFAULT_ACCOUNT_STATE_NOT_SET},
- * {@link #DEFAULT_ACCOUNT_STATE_LOCAL} or
- * {@link #DEFAULT_ACCOUNT_STATE_CLOUD}.
- */
- @DefaultAccountState
- private final int mState;
+ /** A state indicating that default account is set to local device storage. */
+ public static final int DEFAULT_ACCOUNT_STATE_LOCAL = 2;
- /**
- * The account of the default account, when {@link mState} is {
- *
- * @link #STATE_SET_TO_CLOUD}, or null otherwise.
- */
- private final Account mCloudAccount;
+ /**
+ * A state indicating that the default account is set as an account that is synced
+ * to the cloud.
+ */
+ public static final int DEFAULT_ACCOUNT_STATE_CLOUD = 3;
- /**
- * Constructs a new `DefaultAccountAndState` instance with the specified state and
- * cloud
- * account.
- *
- * @param state The state of the default account.
- * @param cloudAccount The cloud account associated with the default account,
- * or null if the state is not
- * {@link #DEFAULT_ACCOUNT_STATE_CLOUD}.
- */
- public DefaultAccountAndState(@DefaultAccountState int state,
- @Nullable Account cloudAccount) {
- if (!isValidDefaultAccountState(state)) {
- throw new IllegalArgumentException("Invalid default account state.");
- }
- if ((state == DEFAULT_ACCOUNT_STATE_CLOUD) != (cloudAccount != null)) {
- throw new IllegalArgumentException(
- "Default account can be set to cloud if and only if the cloud "
- + "account is provided.");
- }
- this.mState = state;
- this.mCloudAccount =
- (mState == DEFAULT_ACCOUNT_STATE_CLOUD) ? cloudAccount : null;
- }
+ /**
+ * The state of the default account. One of
+ * {@link #DEFAULT_ACCOUNT_STATE_NOT_SET},
+ * {@link #DEFAULT_ACCOUNT_STATE_LOCAL} or
+ * {@link #DEFAULT_ACCOUNT_STATE_CLOUD}.
+ */
+ @DefaultAccountState
+ private final int mState;
- /**
- * Creates a `DefaultAccountAndState` instance representing a default account
- * that is set to the cloud and associated with the specified cloud account.
- *
- * @param cloudAccount The non-null cloud account associated with the default
- * contacts
- * account.
- * @return A new `DefaultAccountAndState` instance with state
- * {@link #DEFAULT_ACCOUNT_STATE_CLOUD}.
- */
- public static @NonNull DefaultAccountAndState ofCloud(
- @NonNull Account cloudAccount) {
- return new DefaultAccountAndState(DEFAULT_ACCOUNT_STATE_CLOUD, cloudAccount);
- }
+ /**
+ * The account of the default account, when {@link mState} is {
+ *
+ * @link #STATE_SET_TO_CLOUD}, or null otherwise.
+ */
+ private final Account mCloudAccount;
- /**
- * Creates a `DefaultAccountAndState` instance representing a default account
- * that is set to the local device storage.
- *
- * @return A new `DefaultAccountAndState` instance with state
- * {@link #DEFAULT_ACCOUNT_STATE_LOCAL}.
- */
- public static @NonNull DefaultAccountAndState ofLocal() {
- return new DefaultAccountAndState(DEFAULT_ACCOUNT_STATE_LOCAL, null);
- }
-
- /**
- * Creates a `DefaultAccountAndState` instance representing a default account
- * that is not set.
- *
- * @return A new `DefaultAccountAndState` instance with state
- * {@link #DEFAULT_ACCOUNT_STATE_NOT_SET}.
- */
- public static @NonNull DefaultAccountAndState ofNotSet() {
- return new DefaultAccountAndState(DEFAULT_ACCOUNT_STATE_NOT_SET, null);
- }
-
- /**
- * @return the state of the default account.
- */
- @DefaultAccountState
- public int getState() {
- return mState;
- }
-
- /**
- * @return the cloud account associated with the default account, or null if the
- * state is not {@link #DEFAULT_ACCOUNT_STATE_CLOUD}.
- */
- public @Nullable Account getCloudAccount() {
- return mCloudAccount;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mState, mCloudAccount);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!(obj instanceof DefaultAccountAndState that)) {
- return false;
+ /**
+ * Constructs a new `DefaultAccountAndState` instance with the specified state and
+ * cloud
+ * account.
+ *
+ * @param state The state of the default account.
+ * @param cloudAccount The cloud account associated with the default account,
+ * or null if the state is not
+ * {@link #DEFAULT_ACCOUNT_STATE_CLOUD}.
+ */
+ public DefaultAccountAndState(@DefaultAccountState int state,
+ @Nullable Account cloudAccount) {
+ if (!isValidDefaultAccountState(state)) {
+ throw new IllegalArgumentException("Invalid default account state.");
+ }
+ if ((state == DEFAULT_ACCOUNT_STATE_CLOUD) != (cloudAccount != null)) {
+ throw new IllegalArgumentException(
+ "Default account can be set to cloud if and only if the cloud "
+ + "account is provided.");
+ }
+ this.mState = state;
+ this.mCloudAccount =
+ (mState == DEFAULT_ACCOUNT_STATE_CLOUD) ? cloudAccount : null;
}
- return mState == that.mState && Objects.equals(mCloudAccount,
- that.mCloudAccount);
- }
+ /**
+ * Creates a `DefaultAccountAndState` instance representing a default account
+ * that is set to the cloud and associated with the specified cloud account.
+ *
+ * @param cloudAccount The non-null cloud account associated with the default
+ * contacts
+ * account.
+ * @return A new `DefaultAccountAndState` instance with state
+ * {@link #DEFAULT_ACCOUNT_STATE_CLOUD}.
+ */
+ public static @NonNull DefaultAccountAndState ofCloud(
+ @NonNull Account cloudAccount) {
+ return new DefaultAccountAndState(DEFAULT_ACCOUNT_STATE_CLOUD, cloudAccount);
+ }
- private static boolean isValidDefaultAccountState(int state) {
- return state == DEFAULT_ACCOUNT_STATE_NOT_SET
- || state == DEFAULT_ACCOUNT_STATE_LOCAL
- || state == DEFAULT_ACCOUNT_STATE_CLOUD;
- }
+ /**
+ * Creates a `DefaultAccountAndState` instance representing a default account
+ * that is set to the local device storage.
+ *
+ * @return A new `DefaultAccountAndState` instance with state
+ * {@link #DEFAULT_ACCOUNT_STATE_LOCAL}.
+ */
+ public static @NonNull DefaultAccountAndState ofLocal() {
+ return new DefaultAccountAndState(DEFAULT_ACCOUNT_STATE_LOCAL, null);
+ }
- /**
- * Annotation for all default account states.
- *
- * @hide
- */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(
- prefix = {"DEFAULT_ACCOUNT_STATE_"},
- value = {DEFAULT_ACCOUNT_STATE_NOT_SET,
- DEFAULT_ACCOUNT_STATE_LOCAL, DEFAULT_ACCOUNT_STATE_CLOUD})
- public @interface DefaultAccountState {
+ /**
+ * Creates a `DefaultAccountAndState` instance representing a default account
+ * that is not set.
+ *
+ * @return A new `DefaultAccountAndState` instance with state
+ * {@link #DEFAULT_ACCOUNT_STATE_NOT_SET}.
+ */
+ public static @NonNull DefaultAccountAndState ofNotSet() {
+ return new DefaultAccountAndState(DEFAULT_ACCOUNT_STATE_NOT_SET, null);
+ }
+
+ /**
+ * @return the state of the default account.
+ */
+ @DefaultAccountState
+ public int getState() {
+ return mState;
+ }
+
+ /**
+ * @return the cloud account associated with the default account, or null if the
+ * state is not {@link #DEFAULT_ACCOUNT_STATE_CLOUD}.
+ */
+ public @Nullable Account getCloudAccount() {
+ return mCloudAccount;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mState, mCloudAccount);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof DefaultAccountAndState that)) {
+ return false;
+ }
+
+ return mState == that.mState && Objects.equals(mCloudAccount,
+ that.mCloudAccount);
+ }
+
+ private static boolean isValidDefaultAccountState(int state) {
+ return state == DEFAULT_ACCOUNT_STATE_NOT_SET
+ || state == DEFAULT_ACCOUNT_STATE_LOCAL
+ || state == DEFAULT_ACCOUNT_STATE_CLOUD;
+ }
+
+ /**
+ * Annotation for all default account states.
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = {"DEFAULT_ACCOUNT_STATE_"},
+ value = {DEFAULT_ACCOUNT_STATE_NOT_SET,
+ DEFAULT_ACCOUNT_STATE_LOCAL, DEFAULT_ACCOUNT_STATE_CLOUD})
+ public @interface DefaultAccountState {
+ }
}
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e32625e..b8a8be1 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -111,6 +111,7 @@
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.net.URISyntaxException;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -3022,6 +3023,9 @@
/** @hide - Private call() method to query the 'configuration' table */
public static final String CALL_METHOD_LIST_CONFIG = "LIST_config";
+ /** @hide - Private call() method to query the 'configuration' tables' namespaces */
+ public static final String CALL_METHOD_LIST_NAMESPACES_CONFIG = "LIST_namespaces_config";
+
/** @hide - Private call() method to disable / re-enable syncs to the 'configuration' table */
public static final String CALL_METHOD_SET_SYNC_DISABLED_MODE_CONFIG =
"SET_SYNC_DISABLED_MODE_config";
@@ -9154,15 +9158,27 @@
public static final String MULTI_PRESS_TIMEOUT = "multi_press_timeout";
/**
+ * Whether to enable key repeats for Physical Keyboard.
+ *
+ * If set to false, continuous key presses on
+ * physical keyboard will not cause the pressed key to repeated.
+ * @hide
+ */
+ @Readable
+ public static final String KEY_REPEAT_ENABLED = "key_repeat_enabled";
+
+ /**
* The duration before a key repeat begins in milliseconds.
* @hide
*/
+ @Readable
public static final String KEY_REPEAT_TIMEOUT_MS = "key_repeat_timeout";
/**
* The duration between successive key repeats in milliseconds.
* @hide
*/
+ @Readable
public static final String KEY_REPEAT_DELAY_MS = "key_repeat_delay";
/**
@@ -20458,6 +20474,10 @@
*
* The keys take the form {@code namespace/flag}, and the values are the flag values.
*
+ * Note: this API is _not_ performant, and may make a large number of
+ * Binder calls. It is intended for use in testing and debugging, and
+ * should not be used in performance-sensitive code.
+ *
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@@ -20469,13 +20489,33 @@
Bundle arg = new Bundle();
arg.putInt(Settings.CALL_METHOD_USER_KEY, resolver.getUserId());
IContentProvider cp = sProviderHolder.getProvider(resolver);
- Bundle b = cp.call(resolver.getAttributionSource(),
- sProviderHolder.mUri.getAuthority(), CALL_METHOD_LIST_CONFIG, null, arg);
- if (b != null) {
- Map<String, String> flagsToValues =
- (HashMap) b.getSerializable(Settings.NameValueTable.VALUE,
- java.util.HashMap.class);
- allFlags.putAll(flagsToValues);
+
+ if (Flags.reduceBinderTransactionSizeForGetAllProperties()) {
+ Bundle b = cp.call(resolver.getAttributionSource(),
+ sProviderHolder.mUri.getAuthority(),
+ CALL_METHOD_LIST_NAMESPACES_CONFIG, null, arg);
+ if (b != null) {
+ HashSet<String> namespaces =
+ (HashSet) b.getSerializable(Settings.NameValueTable.VALUE,
+ java.util.HashSet.class);
+ for (String namespace : namespaces) {
+ Map<String, String> keyValues =
+ getStrings(namespace, new ArrayList());
+ for (String key : keyValues.keySet()) {
+ allFlags.put(namespace + "/" + key, keyValues.get(key));
+ }
+ }
+ }
+ } else {
+ Bundle b = cp.call(resolver.getAttributionSource(),
+ sProviderHolder.mUri.getAuthority(),
+ CALL_METHOD_LIST_CONFIG, null, arg);
+ if (b != null) {
+ Map<String, String> flagsToValues =
+ (HashMap) b.getSerializable(Settings.NameValueTable.VALUE,
+ java.util.HashMap.class);
+ allFlags.putAll(flagsToValues);
+ }
}
} catch (RemoteException e) {
Log.w(TAG, "Can't query configuration table for " + CONTENT_URI, e);
diff --git a/core/java/android/provider/flags.aconfig b/core/java/android/provider/flags.aconfig
index 5c0f873..4c63673 100644
--- a/core/java/android/provider/flags.aconfig
+++ b/core/java/android/provider/flags.aconfig
@@ -52,3 +52,14 @@
description: "Enable the new ContactsContract Default Account APIs."
bug: "359957527"
}
+
+flag {
+ name: "reduce_binder_transaction_size_for_get_all_properties"
+ namespace: "core_experiments_team_internal"
+ description: "Reduce Binder transaction size in getAllProperties calls"
+ bug: "362652574"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 77dde5e..d45b24e 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -1046,6 +1046,8 @@
DEFAULT_SUPPRESSED_VISUAL_EFFECTS);
} else if (MANUAL_TAG.equals(tag)) {
rt.manualRule = readRuleXml(parser);
+ // manualRule.enabled can never be false, but it was broken in some builds.
+ rt.manualRule.enabled = true;
// Manual rule may be present prior to modes_ui if it were on, but in that
// case it would not have a set policy, so make note of the need to set
// it up later.
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index ad457ce..384add5 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -70,6 +70,7 @@
import android.hardware.HardwareBuffer;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
+import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -370,6 +371,7 @@
private float mDefaultDimAmount = 0.05f;
SurfaceControl mBbqSurfaceControl;
BLASTBufferQueue mBlastBufferQueue;
+ IBinder mBbqApplyToken = new Binder();
private SurfaceControl mScreenshotSurfaceControl;
private Point mScreenshotSize = new Point();
@@ -2390,6 +2392,7 @@
if (mBlastBufferQueue == null) {
mBlastBufferQueue = new BLASTBufferQueue("Wallpaper", mBbqSurfaceControl,
width, height, format);
+ mBlastBufferQueue.setApplyToken(mBbqApplyToken);
// We only return the Surface the first time, as otherwise
// it hasn't changed and there is no need to update.
ret = mBlastBufferQueue.createSurface();
diff --git a/core/java/android/text/ClientFlags.java b/core/java/android/text/ClientFlags.java
index c2ad508..ca88764 100644
--- a/core/java/android/text/ClientFlags.java
+++ b/core/java/android/text/ClientFlags.java
@@ -16,21 +16,14 @@
package android.text;
-import com.android.text.flags.Flags;
-
/**
* An aconfig feature flags that can be accessible from application process without
* ContentProvider IPCs.
*
* When you add new flags, you have to add flag string to {@link TextFlags#TEXT_ACONFIGS_FLAGS}.
*
+ * TODO(nona): Remove this class.
* @hide
*/
public class ClientFlags {
- /**
- * @see Flags#fixMisalignedContextMenu()
- */
- public static boolean fixMisalignedContextMenu() {
- return TextFlags.isFeatureEnabled(Flags.FLAG_FIX_MISALIGNED_CONTEXT_MENU);
- }
}
diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java
index 076721f..f69a333 100644
--- a/core/java/android/text/TextFlags.java
+++ b/core/java/android/text/TextFlags.java
@@ -19,11 +19,10 @@
import android.annotation.NonNull;
import android.app.AppGlobals;
-import com.android.text.flags.Flags;
-
/**
* Flags in the "text" namespace.
*
+ * TODO(nona): Remove this class.
* @hide
*/
public final class TextFlags {
@@ -55,7 +54,6 @@
* List of text flags to be transferred to the application process.
*/
public static final String[] TEXT_ACONFIGS_FLAGS = {
- Flags.FLAG_FIX_MISALIGNED_CONTEXT_MENU,
};
/**
@@ -64,7 +62,6 @@
* The order must be the same to the TEXT_ACONFIG_FLAGS.
*/
public static final boolean[] TEXT_ACONFIG_DEFAULT_VALUE = {
- Flags.fixMisalignedContextMenu(),
};
/**
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 3c61f4f..c83285a 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -84,27 +84,6 @@
}
flag {
- name: "fix_font_update_failure"
- namespace: "text"
- description: "There was a bug of updating system font from Android 13 to 14. This flag for fixing the migration failure."
- is_fixed_read_only: true
- bug: "331717791"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
- name: "fix_misaligned_context_menu"
- namespace: "text"
- description: "Fix the context menu misalignment and incosistent icon size."
- bug: "332542108"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "missing_getter_apis"
namespace: "text"
description: "Fix the lint warning about missing getters."
@@ -154,26 +133,6 @@
}
flag {
- name: "portuguese_hyphenator"
- namespace: "text"
- description: "Portuguese taiored hyphenator"
- bug: "344656282"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
- name: "dont_break_email_in_nobreak_tag"
- namespace: "text"
- description: "Prevent line break inside email."
- bug: "350691716"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "handwriting_gesture_with_transformation"
namespace: "text"
description: "Fix handwriting gesture is not working when view has transformation."
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index f8c97eb..53935e8 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1341,7 +1341,7 @@
public HdrCapabilities getHdrCapabilities() {
synchronized (mLock) {
updateDisplayInfoLocked();
- if (mDisplayInfo.hdrCapabilities == null) {
+ if (mDisplayInfo.hdrCapabilities == null || mDisplayInfo.isForceSdr) {
return null;
}
int[] supportedHdrTypes;
@@ -1363,6 +1363,7 @@
supportedHdrTypes[index++] = enabledType;
}
}
+
return new HdrCapabilities(supportedHdrTypes,
mDisplayInfo.hdrCapabilities.mMaxLuminance,
mDisplayInfo.hdrCapabilities.mMaxAverageLuminance,
@@ -2087,6 +2088,7 @@
/**
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static String stateToString(int state) {
switch (state) {
case STATE_UNKNOWN:
@@ -2109,6 +2111,7 @@
}
/** @hide */
+ @android.ravenwood.annotation.RavenwoodKeep
public static String stateReasonToString(@StateReason int reason) {
switch (reason) {
case STATE_REASON_UNKNOWN:
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 157cec8..cac3e3c 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -230,6 +230,9 @@
/** The formats disabled by user **/
public int[] userDisabledHdrTypes = {};
+ /** When true, all HDR capabilities are disabled **/
+ public boolean isForceSdr;
+
/**
* Indicates whether the display can be switched into a mode with minimal post
* processing.
@@ -440,6 +443,7 @@
&& colorMode == other.colorMode
&& Arrays.equals(supportedColorModes, other.supportedColorModes)
&& Objects.equals(hdrCapabilities, other.hdrCapabilities)
+ && isForceSdr == other.isForceSdr
&& Arrays.equals(userDisabledHdrTypes, other.userDisabledHdrTypes)
&& minimalPostProcessingSupported == other.minimalPostProcessingSupported
&& logicalDensityDpi == other.logicalDensityDpi
@@ -502,6 +506,7 @@
supportedColorModes = Arrays.copyOf(
other.supportedColorModes, other.supportedColorModes.length);
hdrCapabilities = other.hdrCapabilities;
+ isForceSdr = other.isForceSdr;
userDisabledHdrTypes = other.userDisabledHdrTypes;
minimalPostProcessingSupported = other.minimalPostProcessingSupported;
logicalDensityDpi = other.logicalDensityDpi;
@@ -567,6 +572,7 @@
supportedColorModes[i] = source.readInt();
}
hdrCapabilities = source.readParcelable(null, android.view.Display.HdrCapabilities.class);
+ isForceSdr = source.readBoolean();
minimalPostProcessingSupported = source.readBoolean();
logicalDensityDpi = source.readInt();
physicalXDpi = source.readFloat();
@@ -636,6 +642,7 @@
dest.writeInt(supportedColorModes[i]);
}
dest.writeParcelable(hdrCapabilities, flags);
+ dest.writeBoolean(isForceSdr);
dest.writeBoolean(minimalPostProcessingSupported);
dest.writeInt(logicalDensityDpi);
dest.writeFloat(physicalXDpi);
@@ -874,6 +881,8 @@
sb.append(Arrays.toString(appsSupportedModes));
sb.append(", hdrCapabilities ");
sb.append(hdrCapabilities);
+ sb.append(", isForceSdr ");
+ sb.append(isForceSdr);
sb.append(", userDisabledHdrTypes ");
sb.append(Arrays.toString(userDisabledHdrTypes));
sb.append(", minimalPostProcessingSupported ");
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 6b4340a..15a4715 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -38,6 +38,7 @@
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -55,7 +56,6 @@
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.window.flags.Flags;
import java.io.PrintWriter;
import java.util.Objects;
@@ -146,7 +146,7 @@
forceConsumingTypes |= type;
}
- if (Flags.enableCaptionCompatInsetForceConsumptionAlways()
+ if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS.isEnabled()
&& (flags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) != 0) {
forceConsumingOpaqueCaptionBar = true;
}
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index dd950e8..b21e85a 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -174,24 +174,26 @@
@IntDef(prefix = {"POINTER_ICON_VECTOR_STYLE_FILL_"}, value = {
POINTER_ICON_VECTOR_STYLE_FILL_BLACK,
POINTER_ICON_VECTOR_STYLE_FILL_GREEN,
- POINTER_ICON_VECTOR_STYLE_FILL_YELLOW,
+ POINTER_ICON_VECTOR_STYLE_FILL_RED,
POINTER_ICON_VECTOR_STYLE_FILL_PINK,
- POINTER_ICON_VECTOR_STYLE_FILL_BLUE
+ POINTER_ICON_VECTOR_STYLE_FILL_BLUE,
+ POINTER_ICON_VECTOR_STYLE_FILL_PURPLE
})
@Retention(RetentionPolicy.SOURCE)
public @interface PointerIconVectorStyleFill {}
/** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_BLACK = 0;
/** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_GREEN = 1;
- /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_YELLOW = 2;
+ /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_RED = 2;
/** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_PINK = 3;
/** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_BLUE = 4;
+ /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_PURPLE = 5;
// If adding a PointerIconVectorStyleFill, update END value for {@link SystemSettingsValidators}
/** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_BEGIN =
POINTER_ICON_VECTOR_STYLE_FILL_BLACK;
/** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_END =
- POINTER_ICON_VECTOR_STYLE_FILL_BLUE;
+ POINTER_ICON_VECTOR_STYLE_FILL_PURPLE;
/** @hide */
@IntDef(prefix = {"POINTER_ICON_VECTOR_STYLE_STROKE_"}, value = {
@@ -712,12 +714,14 @@
com.android.internal.R.style.PointerIconVectorStyleFillBlack;
case POINTER_ICON_VECTOR_STYLE_FILL_GREEN ->
com.android.internal.R.style.PointerIconVectorStyleFillGreen;
- case POINTER_ICON_VECTOR_STYLE_FILL_YELLOW ->
- com.android.internal.R.style.PointerIconVectorStyleFillYellow;
+ case POINTER_ICON_VECTOR_STYLE_FILL_RED ->
+ com.android.internal.R.style.PointerIconVectorStyleFillRed;
case POINTER_ICON_VECTOR_STYLE_FILL_PINK ->
com.android.internal.R.style.PointerIconVectorStyleFillPink;
case POINTER_ICON_VECTOR_STYLE_FILL_BLUE ->
com.android.internal.R.style.PointerIconVectorStyleFillBlue;
+ case POINTER_ICON_VECTOR_STYLE_FILL_PURPLE ->
+ com.android.internal.R.style.PointerIconVectorStyleFillPurple;
default -> com.android.internal.R.style.PointerIconVectorStyleFillBlack;
};
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e10cc28..33e7905 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -125,11 +125,11 @@
import static android.view.flags.Flags.toolkitSetFrameRateReadOnly;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
+import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.text.flags.Flags.disableHandwritingInitiatorForIme;
import static com.android.window.flags.Flags.enableBufferTransformHintFromDisplay;
-import static com.android.window.flags.Flags.enableCaptionCompatInsetForceConsumption;
import static com.android.window.flags.Flags.insetsControlChangedItem;
import static com.android.window.flags.Flags.insetsControlSeq;
import static com.android.window.flags.Flags.setScPropertiesInClient;
@@ -829,6 +829,7 @@
private final SurfaceControl mSurfaceControl = new SurfaceControl();
private BLASTBufferQueue mBlastBufferQueue;
+ private IBinder mBbqApplyToken = new Binder();
private final HdrRenderState mHdrRenderState = new HdrRenderState(this);
@@ -2743,6 +2744,10 @@
mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl,
mSurfaceSize.x, mSurfaceSize.y, mWindowAttributes.format);
mBlastBufferQueue.setTransactionHangCallback(sTransactionHangCallback);
+ // If we create and destroy BBQ without recreating the SurfaceControl, we can end up
+ // queuing buffers on multiple apply tokens causing out of order buffer submissions. We
+ // fix this by setting the same apply token on all BBQs created by this VRI.
+ mBlastBufferQueue.setApplyToken(mBbqApplyToken);
Surface blastSurface;
if (addSchandleToVriSurface()) {
blastSurface = mBlastBufferQueue.createSurfaceWithHandle();
@@ -3209,10 +3214,10 @@
typesToShow |= Type.navigationBars();
}
if (captionIsHiddenByFlags && !captionWasHiddenByFlags
- && enableCaptionCompatInsetForceConsumption()) {
+ && ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isEnabled()) {
typesToHide |= Type.captionBar();
} else if (!captionIsHiddenByFlags && captionWasHiddenByFlags
- && enableCaptionCompatInsetForceConsumption()) {
+ && ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isEnabled()) {
typesToShow |= Type.captionBar();
}
if (typesToHide != 0) {
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index b9751c8..d90455a 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -920,7 +920,8 @@
final Configuration.Builder builder = Configuration.Builder.withSurface(
cujType,
jankContext.getDisplayContext(),
- jankContext.getTargetSurfaceControl())
+ jankContext.getTargetSurfaceControl(),
+ jankContext.getDisplayContext().getMainThreadHandler())
.setTag(String.format(Locale.US, "%d@%d@%s", animType,
useSeparatedThread ? 0 : 1, jankContext.getHostPackageName()));
InteractionJankMonitor.getInstance().begin(builder);
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index e1154ca..06820cd 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1003,6 +1003,55 @@
public int getActionTag() {
return SET_EMPTY_VIEW_ACTION_TAG;
}
+
+ @Override
+ public boolean canWriteToProto() {
+ return true;
+ }
+
+ @Override
+ public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) {
+ final long token = out.start(RemoteViewsProto.Action.SET_EMPTY_VIEW_ACTION);
+ out.write(RemoteViewsProto.SetEmptyViewAction.VIEW_ID,
+ appResources.getResourceName(mViewId));
+ out.write(RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID,
+ appResources.getResourceName(mViewId));
+ out.end(token);
+ }
+
+ public static PendingResources<Action> createFromProto(ProtoInputStream in)
+ throws Exception {
+ final LongSparseArray<Object> values = new LongSparseArray<>();
+
+ final long token = in.start(RemoteViewsProto.Action.SET_EMPTY_VIEW_ACTION);
+ while (in.nextField() != NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.SetEmptyViewAction.VIEW_ID:
+ values.put(RemoteViewsProto.SetEmptyViewAction.VIEW_ID,
+ in.readString(RemoteViewsProto.SetEmptyViewAction.VIEW_ID));
+ break;
+ case (int) RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID:
+ values.put(RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID,
+ in.readString(RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID));
+ break;
+ default:
+ Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ in.end(token);
+
+ checkContainsKeys(values, new long[]{RemoteViewsProto.SetEmptyViewAction.VIEW_ID,
+ RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID});
+
+ return (context, resources, rootData, depth) -> {
+ int viewId = getAsIdentifier(resources, values,
+ RemoteViewsProto.SetEmptyViewAction.VIEW_ID);
+ int emptyViewId = getAsIdentifier(resources, values,
+ RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID);
+ return new SetEmptyView(viewId, emptyViewId);
+ };
+ }
}
private static class SetPendingIntentTemplate extends Action {
@@ -1243,6 +1292,68 @@
mItems.visitUris(visitor);
}
+
+ @Override
+ public boolean canWriteToProto() {
+ // Skip actions that do not contain items (intent only actions)
+ return mItems != null;
+ }
+
+ @Override
+ public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) {
+ if (mItems == null) return;
+ final long token = out.start(
+ RemoteViewsProto.Action.SET_REMOTE_COLLECTION_ITEM_LIST_ADAPTER_ACTION);
+ out.write(RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID,
+ appResources.getResourceName(mViewId));
+ final long itemsToken = out.start(
+ RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS);
+ mItems.writeToProto(context, out, /* attached= */ true);
+ out.end(itemsToken);
+ out.end(token);
+ }
+ }
+
+ private PendingResources<Action> createSetRemoteCollectionItemListAdapterActionFromProto(
+ ProtoInputStream in) throws Exception {
+ final LongSparseArray<Object> values = new LongSparseArray<>();
+
+ final long token = in.start(
+ RemoteViewsProto.Action.SET_REMOTE_COLLECTION_ITEM_LIST_ADAPTER_ACTION);
+ while (in.nextField() != NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID:
+ values.put(RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID,
+ in.readString(
+ RemoteViewsProto
+ .SetRemoteCollectionItemListAdapterAction.VIEW_ID));
+ break;
+ case (int) RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS:
+ final long itemsToken = in.start(
+ RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS);
+ values.put(RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS,
+ RemoteCollectionItems.createFromProto(in));
+ in.end(itemsToken);
+ break;
+ default:
+ Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ in.end(token);
+
+ checkContainsKeys(values,
+ new long[]{RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID,
+ RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS});
+
+ return (context, resources, rootData, depth) -> {
+ int viewId = getAsIdentifier(resources, values,
+ RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID);
+ return new SetRemoteCollectionItemListAdapterAction(viewId,
+ ((PendingResources<RemoteCollectionItems>) values.get(
+ RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS))
+ .create(context, resources, rootData, depth));
+ };
}
/**
@@ -2036,6 +2147,68 @@
public int getActionTag() {
return SET_DRAWABLE_TINT_TAG;
}
+
+ @Override
+ public boolean canWriteToProto() {
+ return true;
+ }
+
+ @Override
+ public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) {
+ final long token = out.start(RemoteViewsProto.Action.SET_DRAWABLE_TINT_ACTION);
+ out.write(RemoteViewsProto.SetDrawableTintAction.VIEW_ID,
+ appResources.getResourceName(mViewId));
+ out.write(RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER, mColorFilter);
+ out.write(RemoteViewsProto.SetDrawableTintAction.FILTER_MODE,
+ PorterDuff.modeToInt(mFilterMode));
+ out.write(RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND, mTargetBackground);
+ out.end(token);
+ }
+
+ public static PendingResources<Action> createFromProto(ProtoInputStream in)
+ throws Exception {
+ final LongSparseArray<Object> values = new LongSparseArray<>();
+
+ final long token = in.start(RemoteViewsProto.Action.SET_DRAWABLE_TINT_ACTION);
+ while (in.nextField() != NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.SetDrawableTintAction.VIEW_ID:
+ values.put(RemoteViewsProto.SetDrawableTintAction.VIEW_ID,
+ in.readString(RemoteViewsProto.SetDrawableTintAction.VIEW_ID));
+ break;
+ case (int) RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND:
+ values.put(RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND,
+ in.readBoolean(
+ RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND));
+ break;
+ case (int) RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER:
+ values.put(RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER,
+ in.readInt(RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER));
+ break;
+ case (int) RemoteViewsProto.SetDrawableTintAction.FILTER_MODE:
+ values.put(RemoteViewsProto.SetDrawableTintAction.FILTER_MODE,
+ PorterDuff.intToMode(in.readInt(
+ RemoteViewsProto.SetDrawableTintAction.FILTER_MODE)));
+ break;
+ default:
+ Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ in.end(token);
+
+ checkContainsKeys(values, new long[]{RemoteViewsProto.SetDrawableTintAction.VIEW_ID});
+
+ return (context, resources, rootData, depth) -> {
+ int viewId = getAsIdentifier(resources, values,
+ RemoteViewsProto.SetDrawableTintAction.VIEW_ID);
+ return new SetDrawableTint(viewId, (boolean) values.get(
+ RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND, false),
+ (int) values.get(RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER, 0),
+ (PorterDuff.Mode) values.get(
+ RemoteViewsProto.SetDrawableTintAction.FILTER_MODE));
+ };
+ }
}
/**
@@ -2047,7 +2220,7 @@
* target {@link View#getBackground()}.
* <p>
*/
- private class SetRippleDrawableColor extends Action {
+ private static class SetRippleDrawableColor extends Action {
ColorStateList mColorStateList;
SetRippleDrawableColor(@IdRes int id, ColorStateList colorStateList) {
@@ -2082,6 +2255,58 @@
public int getActionTag() {
return SET_RIPPLE_DRAWABLE_COLOR_TAG;
}
+
+ @Override
+ public boolean canWriteToProto() {
+ return true;
+ }
+
+ @Override
+ public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) {
+ final long token = out.start(RemoteViewsProto.Action.SET_RIPPLE_DRAWABLE_COLOR_ACTION);
+ out.write(RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID,
+ appResources.getResourceName(mViewId));
+ writeColorStateListToProto(out, mColorStateList,
+ RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST);
+ out.end(token);
+ }
+
+ public static PendingResources<Action> createFromProto(ProtoInputStream in)
+ throws Exception {
+ final LongSparseArray<Object> values = new LongSparseArray<>();
+
+ final long token = in.start(RemoteViewsProto.Action.SET_RIPPLE_DRAWABLE_COLOR_ACTION);
+ while (in.nextField() != NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID:
+ values.put(RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID,
+ in.readString(
+ RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID));
+ break;
+ case (int) RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST:
+ values.put(RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST,
+ createColorStateListFromProto(in,
+ RemoteViewsProto
+ .SetRippleDrawableColorAction.COLOR_STATE_LIST));
+ break;
+ default:
+ Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ in.end(token);
+
+ checkContainsKeys(values,
+ new long[]{RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID,
+ RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST});
+
+ return (context, resources, rootData, depth) -> {
+ int viewId = getAsIdentifier(resources, values,
+ RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID);
+ return new SetRippleDrawableColor(viewId, (ColorStateList) values.get(
+ RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST));
+ };
+ }
}
/**
@@ -2987,6 +3212,82 @@
public int getActionTag() {
return RESOURCE_REFLECTION_ACTION_TAG;
}
+
+ @Override
+ public boolean canWriteToProto() {
+ return true;
+ }
+
+ @Override
+ public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) {
+ final long token = out.start(RemoteViewsProto.Action.RESOURCE_REFLECTION_ACTION);
+ out.write(RemoteViewsProto.ResourceReflectionAction.VIEW_ID,
+ appResources.getResourceName(mViewId));
+ out.write(RemoteViewsProto.ResourceReflectionAction.METHOD_NAME, mMethodName);
+ out.write(RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE, mType);
+ out.write(RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE, mResourceType);
+ if (mResId != 0) {
+ out.write(RemoteViewsProto.ResourceReflectionAction.RES_ID,
+ appResources.getResourceName(mResId));
+ }
+ out.end(token);
+ }
+
+ public static PendingResources<Action> createFromProto(ProtoInputStream in)
+ throws Exception {
+ final LongSparseArray<Object> values = new LongSparseArray<>();
+
+ final long token = in.start(RemoteViewsProto.Action.RESOURCE_REFLECTION_ACTION);
+ while (in.nextField() != NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.ResourceReflectionAction.VIEW_ID:
+ values.put(RemoteViewsProto.ResourceReflectionAction.VIEW_ID,
+ in.readString(RemoteViewsProto.ResourceReflectionAction.VIEW_ID));
+ break;
+ case (int) RemoteViewsProto.ResourceReflectionAction.METHOD_NAME:
+ values.put(RemoteViewsProto.ResourceReflectionAction.METHOD_NAME,
+ in.readString(
+ RemoteViewsProto.ResourceReflectionAction.METHOD_NAME));
+ break;
+ case (int) RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE:
+ values.put(RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE,
+ in.readInt(
+ RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE));
+ break;
+ case (int) RemoteViewsProto.ResourceReflectionAction.RES_ID:
+ values.put(RemoteViewsProto.ResourceReflectionAction.RES_ID,
+ in.readString(RemoteViewsProto.ResourceReflectionAction.RES_ID));
+ break;
+ case (int) RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE:
+ values.put(RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE,
+ in.readInt(
+ RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE));
+ break;
+ default:
+ Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ in.end(token);
+
+ checkContainsKeys(values, new long[]{RemoteViewsProto.ResourceReflectionAction.VIEW_ID,
+ RemoteViewsProto.ResourceReflectionAction.METHOD_NAME,
+ RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE});
+
+ return (context, resources, rootData, depth) -> {
+ int viewId = getAsIdentifier(resources, values,
+ RemoteViewsProto.ResourceReflectionAction.VIEW_ID);
+
+ int resId = (values.indexOfKey(RemoteViewsProto.ResourceReflectionAction.RES_ID)
+ >= 0) ? getAsIdentifier(resources, values,
+ RemoteViewsProto.ResourceReflectionAction.RES_ID) : 0;
+ return new ResourceReflectionAction(viewId,
+ (String) values.get(RemoteViewsProto.ResourceReflectionAction.METHOD_NAME),
+ (int) values.get(RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE),
+ (int) values.get(RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE,
+ 0), resId);
+ };
+ }
}
private static final class AttributeReflectionAction extends BaseReflectionAction {
@@ -4593,6 +4894,61 @@
public int getActionTag() {
return SET_INT_TAG_TAG;
}
+
+ @Override
+ public boolean canWriteToProto() {
+ return true;
+ }
+
+ @Override
+ public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) {
+ final long token = out.start(RemoteViewsProto.Action.SET_INT_TAG_ACTION);
+ out.write(RemoteViewsProto.SetIntTagAction.VIEW_ID,
+ appResources.getResourceName(mViewId));
+ out.write(RemoteViewsProto.SetIntTagAction.KEY,
+ appResources.getResourceName(mKey)); // rebase
+ out.write(RemoteViewsProto.SetIntTagAction.TAG, mTag);
+ out.end(token);
+ }
+
+ public static PendingResources<Action> createFromProto(ProtoInputStream in)
+ throws Exception {
+ final LongSparseArray<Object> values = new LongSparseArray<>();
+
+ final long token = in.start(RemoteViewsProto.Action.SET_INT_TAG_ACTION);
+ while (in.nextField() != NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.SetIntTagAction.VIEW_ID:
+ values.put(RemoteViewsProto.SetIntTagAction.VIEW_ID,
+ in.readString(RemoteViewsProto.SetIntTagAction.VIEW_ID));
+ break;
+ case (int) RemoteViewsProto.SetIntTagAction.KEY:
+ values.put(RemoteViewsProto.SetIntTagAction.KEY,
+ in.readString(RemoteViewsProto.SetIntTagAction.KEY));
+ break;
+ case (int) RemoteViewsProto.SetIntTagAction.TAG:
+ values.put(RemoteViewsProto.SetIntTagAction.TAG,
+ in.readInt(RemoteViewsProto.SetIntTagAction.TAG));
+ break;
+ default:
+ Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ in.end(token);
+
+ checkContainsKeys(values, new long[]{RemoteViewsProto.SetIntTagAction.VIEW_ID,
+ RemoteViewsProto.SetIntTagAction.KEY});
+
+ return (context, resources, rootData, depth) -> {
+ int viewId = getAsIdentifier(resources, values,
+ RemoteViewsProto.SetIntTagAction.VIEW_ID);
+ int keyId = getAsIdentifier(resources, values,
+ RemoteViewsProto.SetIntTagAction.KEY);
+ return new SetIntTagAction(viewId, keyId,
+ (int) values.get(RemoteViewsProto.SetIntTagAction.TAG, 0));
+ };
+ }
}
private static class SetCompoundButtonCheckedAction extends Action {
@@ -4643,6 +4999,56 @@
public int getActionTag() {
return SET_COMPOUND_BUTTON_CHECKED_TAG;
}
+
+ @Override
+ public boolean canWriteToProto() {
+ return true;
+ }
+
+ @Override
+ public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) {
+ final long token = out.start(
+ RemoteViewsProto.Action.SET_COMPOUND_BUTTON_CHECKED_ACTION);
+ out.write(RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID,
+ appResources.getResourceName(mViewId));
+ out.write(RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED, mChecked);
+ out.end(token);
+ }
+
+ public static PendingResources<Action> createFromProto(ProtoInputStream in)
+ throws Exception {
+ final LongSparseArray<Object> values = new LongSparseArray<>();
+
+ final long token = in.start(RemoteViewsProto.Action.SET_COMPOUND_BUTTON_CHECKED_ACTION);
+ while (in.nextField() != NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID:
+ values.put(RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID,
+ in.readString(
+ RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID));
+ break;
+ case (int) RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED:
+ values.put(RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED,
+ in.readBoolean(
+ RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED));
+ break;
+ default:
+ Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ in.end(token);
+
+ checkContainsKeys(values,
+ new long[]{RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID});
+
+ return (context, resources, rootData, depth) -> {
+ int viewId = getAsIdentifier(resources, values,
+ RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID);
+ return new SetCompoundButtonCheckedAction(viewId, (boolean) values.get(
+ RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED, false));
+ };
+ }
}
private static class SetRadioGroupCheckedAction extends Action {
@@ -4707,6 +5113,61 @@
public int getActionTag() {
return SET_RADIO_GROUP_CHECKED;
}
+
+ @Override
+ public boolean canWriteToProto() {
+ return true;
+ }
+
+ @Override
+ public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) {
+ final long token = out.start(RemoteViewsProto.Action.SET_RADIO_GROUP_CHECKED_ACTION);
+ out.write(RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID,
+ appResources.getResourceName(mViewId));
+ if (mCheckedId != -1) {
+ out.write(RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID,
+ appResources.getResourceName(mCheckedId));
+ }
+ out.end(token);
+ }
+
+ public static PendingResources<Action> createFromProto(ProtoInputStream in)
+ throws Exception {
+ final LongSparseArray<Object> values = new LongSparseArray<>();
+
+ final long token = in.start(RemoteViewsProto.Action.SET_RADIO_GROUP_CHECKED_ACTION);
+ while (in.nextField() != NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID:
+ values.put(RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID,
+ in.readString(RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID));
+ break;
+ case (int) RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID:
+ values.put(RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID,
+ in.readString(
+ RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID));
+ break;
+ default:
+ Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ in.end(token);
+
+ checkContainsKeys(values,
+ new long[]{RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID});
+
+ return (context, resources, rootData, depth) -> {
+ int viewId = getAsIdentifier(resources, values,
+ RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID);
+
+ int checkedId = (values.indexOfKey(
+ RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID) >= 0)
+ ? getAsIdentifier(resources, values,
+ RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID) : -1;
+ return new SetRadioGroupCheckedAction(viewId, checkedId);
+ };
+ }
}
private static class SetViewOutlinePreferredRadiusAction extends Action {
@@ -8450,6 +8911,7 @@
public static PendingResources<RemoteCollectionItems> createFromProto(ProtoInputStream in)
throws Exception {
final LongSparseArray<Object> values = new LongSparseArray<>();
+
values.put(RemoteViewsProto.RemoteCollectionItems.IDS, new ArrayList<Long>());
values.put(RemoteViewsProto.RemoteCollectionItems.VIEWS,
new ArrayList<PendingResources<RemoteViews>>());
@@ -9207,6 +9669,22 @@
return ReflectionAction.createFromProto(in);
case (int) RemoteViewsProto.Action.REMOVE_FROM_PARENT_ACTION:
return RemoveFromParentAction.createFromProto(in);
+ case (int) RemoteViewsProto.Action.RESOURCE_REFLECTION_ACTION:
+ return ResourceReflectionAction.createFromProto(in);
+ case (int) RemoteViewsProto.Action.SET_COMPOUND_BUTTON_CHECKED_ACTION:
+ return SetCompoundButtonCheckedAction.createFromProto(in);
+ case (int) RemoteViewsProto.Action.SET_DRAWABLE_TINT_ACTION:
+ return SetDrawableTint.createFromProto(in);
+ case (int) RemoteViewsProto.Action.SET_EMPTY_VIEW_ACTION:
+ return SetEmptyView.createFromProto(in);
+ case (int) RemoteViewsProto.Action.SET_INT_TAG_ACTION:
+ return SetIntTagAction.createFromProto(in);
+ case (int) RemoteViewsProto.Action.SET_RADIO_GROUP_CHECKED_ACTION:
+ return SetRadioGroupCheckedAction.createFromProto(in);
+ case (int) RemoteViewsProto.Action.SET_REMOTE_COLLECTION_ITEM_LIST_ADAPTER_ACTION:
+ return rv.createSetRemoteCollectionItemListAdapterActionFromProto(in);
+ case (int) RemoteViewsProto.Action.SET_RIPPLE_DRAWABLE_COLOR_ACTION:
+ return SetRippleDrawableColor.createFromProto(in);
default:
throw new RuntimeException("Unhandled field while reading Action proto!\n"
+ ProtoUtils.currentFieldToString(in));
diff --git a/core/java/android/window/TransitionFilter.java b/core/java/android/window/TransitionFilter.java
index 3cfde87..8bb4c52 100644
--- a/core/java/android/window/TransitionFilter.java
+++ b/core/java/android/window/TransitionFilter.java
@@ -187,6 +187,7 @@
/** If non-null, requires the change to specifically have or not-have a custom animation. */
public Boolean mCustomAnimation = null;
+ public IBinder mTaskFragmentToken = null;
public Requirement() {
}
@@ -204,12 +205,19 @@
// 0: null, 1: false, 2: true
final int customAnimRaw = in.readInt();
mCustomAnimation = customAnimRaw == 0 ? null : Boolean.valueOf(customAnimRaw == 2);
+ mTaskFragmentToken = in.readStrongBinder();
}
/** Go through changes and find if at-least one change matches this filter */
boolean matches(@NonNull TransitionInfo info) {
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
+
+ if (mTaskFragmentToken != null
+ && !mTaskFragmentToken.equals(change.getTaskFragmentToken())) {
+ continue;
+ }
+
if (mMustBeIndependent && !TransitionInfo.isIndependent(change, info)) {
// Only look at independent animating windows.
continue;
@@ -313,6 +321,7 @@
dest.writeStrongBinder(mLaunchCookie);
int customAnimRaw = mCustomAnimation == null ? 0 : (mCustomAnimation ? 2 : 1);
dest.writeInt(customAnimRaw);
+ dest.writeStrongBinder(mTaskFragmentToken);
}
@NonNull
@@ -357,6 +366,9 @@
if (mCustomAnimation != null) {
out.append(" customAnim=").append(mCustomAnimation.booleanValue());
}
+ if (mTaskFragmentToken != null) {
+ out.append(" taskFragmentToken=").append(mTaskFragmentToken);
+ }
out.append("}");
return out.toString();
}
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index ec79f94..14505f5 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -49,6 +49,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
+import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.Surface;
@@ -681,6 +682,7 @@
private float mSnapshotLuma;
private ComponentName mActivityComponent = null;
private AnimationOptions mAnimationOptions = null;
+ private IBinder mTaskFragmentToken = null;
public Change(@Nullable WindowContainerToken container, @NonNull SurfaceControl leash) {
mContainer = container;
@@ -712,6 +714,7 @@
mSnapshotLuma = in.readFloat();
mActivityComponent = in.readTypedObject(ComponentName.CREATOR);
mAnimationOptions = in.readTypedObject(AnimationOptions.CREATOR);
+ mTaskFragmentToken = in.readStrongBinder();
}
private Change localRemoteCopy() {
@@ -737,6 +740,7 @@
out.mSnapshotLuma = mSnapshotLuma;
out.mActivityComponent = mActivityComponent;
out.mAnimationOptions = mAnimationOptions;
+ out.mTaskFragmentToken = mTaskFragmentToken;
return out;
}
@@ -854,6 +858,14 @@
mAnimationOptions = options;
}
+ /**
+ * Sets the client-defined TaskFragment token. Only set this if the window is a
+ * client-organized TaskFragment.
+ */
+ public void setTaskFragmentToken(@Nullable IBinder token) {
+ mTaskFragmentToken = token;
+ }
+
/** @return the container that is changing. May be null if non-remotable (eg. activity) */
@Nullable
public WindowContainerToken getContainer() {
@@ -1009,6 +1021,15 @@
return mAnimationOptions;
}
+ /**
+ * Returns the client-defined TaskFragment token. {@code null} if this window is not a
+ * client-organized TaskFragment.
+ */
+ @Nullable
+ public IBinder getTaskFragmentToken() {
+ return mTaskFragmentToken;
+ }
+
/** @hide */
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -1035,6 +1056,7 @@
dest.writeFloat(mSnapshotLuma);
dest.writeTypedObject(mActivityComponent, flags);
dest.writeTypedObject(mAnimationOptions, flags);
+ dest.writeStrongBinder(mTaskFragmentToken);
}
@NonNull
@@ -1110,6 +1132,9 @@
if (mAnimationOptions != null) {
sb.append(" opt=").append(mAnimationOptions);
}
+ if (mTaskFragmentToken != null) {
+ sb.append(" taskFragmentToken=").append(mTaskFragmentToken);
+ }
sb.append('}');
return sb.toString();
}
diff --git a/core/java/android/window/flags/DesktopModeFlags.java b/core/java/android/window/flags/DesktopModeFlags.java
index 5c53d66..701b6be 100644
--- a/core/java/android/window/flags/DesktopModeFlags.java
+++ b/core/java/android/window/flags/DesktopModeFlags.java
@@ -17,7 +17,9 @@
package android.window.flags;
import android.annotation.Nullable;
-import android.content.Context;
+import android.app.ActivityThread;
+import android.app.Application;
+import android.content.ContentResolver;
import android.provider.Settings;
import android.util.Log;
@@ -39,9 +41,13 @@
*/
public enum DesktopModeFlags {
// All desktop mode related flags to be overridden by developer option toggle will be added here
- DESKTOP_WINDOWING_MODE(
+ ENABLE_DESKTOP_WINDOWING_MODE(
Flags::enableDesktopWindowingMode, /* shouldOverrideByDevOption= */ true),
- DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, false);
+ ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, false),
+ ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION(
+ Flags::enableCaptionCompatInsetForceConsumption, true),
+ ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS(
+ Flags::enableCaptionCompatInsetForceConsumptionAlways, true);
private static final String TAG = "DesktopModeFlagsUtil";
// Function called to obtain aconfig flag value.
@@ -62,14 +68,15 @@
* Determines state of flag based on the actual flag and desktop mode developer option
* overrides.
*/
- public boolean isEnabled(Context context) {
+ public boolean isEnabled() {
+ Application application = ActivityThread.currentApplication();
if (!Flags.showDesktopWindowingDevOption()
|| !mShouldOverrideByDevOption
- || context.getContentResolver() == null) {
+ || application == null) {
return mFlagFunction.get();
} else {
boolean shouldToggleBeEnabledByDefault = Flags.enableDesktopWindowingMode();
- return switch (getToggleOverride(context)) {
+ return switch (getToggleOverride(application.getContentResolver())) {
case OVERRIDE_UNSET -> mFlagFunction.get();
// When toggle override matches its default state, don't override flags. This
// helps users reset their feature overrides.
@@ -79,14 +86,14 @@
}
}
- private ToggleOverride getToggleOverride(Context context) {
+ private ToggleOverride getToggleOverride(ContentResolver contentResolver) {
// If cached, return it
if (sCachedToggleOverride != null) {
return sCachedToggleOverride;
}
// Otherwise, fetch and cache it
- ToggleOverride override = getToggleOverrideFromSystem(context);
+ ToggleOverride override = getToggleOverrideFromSystem(contentResolver);
sCachedToggleOverride = override;
Log.d(TAG, "Toggle override initialized to: " + override);
return override;
@@ -95,9 +102,9 @@
/**
* Returns {@link ToggleOverride} from Settings.Global set by toggle.
*/
- private ToggleOverride getToggleOverrideFromSystem(Context context) {
+ private ToggleOverride getToggleOverrideFromSystem(ContentResolver contentResolver) {
int settingValue = Settings.Global.getInt(
- context.getContentResolver(),
+ contentResolver,
Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES,
ToggleOverride.OVERRIDE_UNSET.getSetting()
);
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 12f5e1d..cc5e583 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -117,6 +117,13 @@
}
flag {
+ name: "enable_tile_resizing"
+ namespace: "lse_desktop_experience"
+ description: "Enables drawing a divider bar upon tiling tasks left and right in desktop mode for simultaneous resizing"
+ bug: "351769839"
+}
+
+flag {
name: "respect_orientation_change_for_unresizeable"
namespace: "lse_desktop_experience"
description: "Whether to resize task to respect requested orientation change of unresizeable activity"
@@ -285,3 +292,10 @@
description: "Creates a shell transition when display focus switches."
bug: "356109871"
}
+
+flag {
+ name: "enter_desktop_by_default_on_freeform_displays"
+ namespace: "lse_desktop_experience"
+ description: "Allow entering desktop mode by default on freeform displays"
+ bug: "361419732"
+}
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 13648de..9ae3fc1 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -122,3 +122,10 @@
description: "Requires apps to opt-in to overlay pass through touches and provide APIs to opt-in"
bug: "358129114"
}
+
+flag {
+ namespace: "windowing_sdk"
+ name: "wlinfo_oncreate"
+ description: "Makes WindowLayoutInfo accessible without racing in the Activity#onCreate()"
+ bug: "337820752"
+}
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index c7e1fba..ef08e49 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -330,9 +330,10 @@
* @param cujType the specific {@link Cuj.CujType}.
* @return boolean true if the tracker is started successfully, false otherwise.
*/
- public boolean begin(SurfaceControl surface, Context context, @Cuj.CujType int cujType) {
+ public boolean begin(SurfaceControl surface, Context context, Handler handler,
+ @Cuj.CujType int cujType) {
try {
- return begin(Configuration.Builder.withSurface(cujType, context, surface));
+ return begin(Configuration.Builder.withSurface(cujType, context, surface, handler));
} catch (IllegalArgumentException ex) {
Log.d(TAG, "Build configuration failed!", ex);
return false;
@@ -348,11 +349,12 @@
* @param tag a tag containing extra information about the interaction.
* @return boolean true if the tracker is started successfully, false otherwise.
*/
- public boolean begin(SurfaceControl surface, Context context, @Cuj.CujType int cujType,
+ public boolean begin(SurfaceControl surface, Context context, Handler handler,
+ @Cuj.CujType int cujType,
String tag) {
try {
final Configuration.Builder builder =
- Configuration.Builder.withSurface(cujType, context, surface);
+ Configuration.Builder.withSurface(cujType, context, surface, handler);
if (!TextUtils.isEmpty(tag)) {
builder.setTag(tag);
}
@@ -689,20 +691,23 @@
private SurfaceControl mAttrSurfaceControl;
private final @Cuj.CujType int mAttrCujType;
private boolean mAttrDeferMonitor = true;
+ private Handler mHandler = null;
/**
* Creates a builder which instruments only surface.
* @param cuj The enum defined in {@link Cuj.CujType}.
* @param context context
* @param surfaceControl surface control
+ * @param uiThreadHandler UI thread for that surface
* @return builder
*/
public static Builder withSurface(@Cuj.CujType int cuj, @NonNull Context context,
- @NonNull SurfaceControl surfaceControl) {
+ @NonNull SurfaceControl surfaceControl, @NonNull Handler uiThreadHandler) {
return new Builder(cuj)
.setContext(context)
.setSurfaceControl(surfaceControl)
- .setSurfaceOnly(true);
+ .setSurfaceOnly(true)
+ .setHandler(uiThreadHandler);
}
/**
@@ -722,6 +727,18 @@
}
/**
+ * Specifies the UI thread handler. If not provided, the View's one will be used.
+ * If only a surface is provided without handler, the app main thread will be used.
+ *
+ * @param uiThreadHandler handler associated to the cuj UI thread
+ * @return builder
+ */
+ public Builder setHandler(Handler uiThreadHandler) {
+ mHandler = uiThreadHandler;
+ return this;
+ }
+
+ /**
* Specifies a view, must be set if {@link #setSurfaceOnly(boolean)} is set to false.
* @param view an attached view
* @return builder
@@ -798,13 +815,13 @@
return new Configuration(
mAttrCujType, mAttrView, mAttrTag, mAttrTimeout,
mAttrSurfaceOnly, mAttrContext, mAttrSurfaceControl,
- mAttrDeferMonitor);
+ mAttrDeferMonitor, mHandler);
}
}
private Configuration(@Cuj.CujType int cuj, View view, @NonNull String tag, long timeout,
boolean surfaceOnly, Context context, SurfaceControl surfaceControl,
- boolean deferMonitor) {
+ boolean deferMonitor, Handler handler) {
mCujType = cuj;
mTag = tag;
mSessionName = generateSessionName(Cuj.getNameOfCuj(cuj), tag);
@@ -816,8 +833,16 @@
: (view != null ? view.getContext().getApplicationContext() : null);
mSurfaceControl = surfaceControl;
mDeferMonitor = deferMonitor;
+ if (handler != null) {
+ mHandler = handler;
+ } else if (mSurfaceOnly) {
+ Log.w(TAG, "No UIThread provided for " + mSessionName
+ + " (surface only). Defaulting to app main thread.");
+ mHandler = mContext.getMainThreadHandler();
+ } else {
+ mHandler = mView.getHandler();
+ }
validate();
- mHandler = mSurfaceOnly ? mContext.getMainThreadHandler() : mView.getHandler();
}
@VisibleForTesting
@@ -858,6 +883,12 @@
shouldThrow = true;
msg.append("Must pass in a valid surface control if only instrument surface; ");
}
+ if (mHandler == null) {
+ shouldThrow = true;
+ msg.append(
+ "Must pass a UI thread handler when only a surface control is "
+ + "provided.");
+ }
} else {
if (!hasValidView()) {
shouldThrow = true;
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 4708be8..84dfc49 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -38,6 +38,8 @@
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.flags.Flags.customizableWindowHeaders;
+import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION;
+import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS;
import static com.android.internal.policy.PhoneWindow.FEATURE_OPTIONS_PANEL;
@@ -114,7 +116,6 @@
import com.android.internal.widget.ActionBarContextView;
import com.android.internal.widget.BackgroundFallback;
import com.android.internal.widget.floatingtoolbar.FloatingToolbar;
-import com.android.window.flags.Flags;
import java.util.List;
import java.util.concurrent.Executor;
@@ -1217,14 +1218,15 @@
final boolean hideCaptionBar = fullscreen
|| (requestedVisibleTypes & WindowInsets.Type.captionBar()) == 0;
- final boolean consumingCaptionBar = Flags.enableCaptionCompatInsetForceConsumption()
- && ((mLastForceConsumingTypes & WindowInsets.Type.captionBar()) != 0
+ final boolean consumingCaptionBar =
+ ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isEnabled()
+ && ((mLastForceConsumingTypes & WindowInsets.Type.captionBar()) != 0
&& hideCaptionBar);
final boolean isOpaqueCaptionBar = customizableWindowHeaders()
&& (appearance & APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND) == 0;
final boolean consumingOpaqueCaptionBar =
- Flags.enableCaptionCompatInsetForceConsumptionAlways()
+ ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS.isEnabled()
&& mLastForceConsumingOpaqueCaptionBar
&& isOpaqueCaptionBar;
diff --git a/core/java/com/android/internal/policy/ScreenDecorationsUtils.java b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java
index 067e5e88..b23515a 100644
--- a/core/java/com/android/internal/policy/ScreenDecorationsUtils.java
+++ b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java
@@ -37,6 +37,8 @@
*
* Note that if the context is not an UI context(not associated with Display), it will use
* default display.
+ *
+ * If the associated display is not internal, will return 0.
*/
public static float getWindowCornerRadius(Context context) {
final Resources resources = context.getResources();
@@ -44,7 +46,13 @@
return 0f;
}
// Use Context#getDisplayNoVerify() in case the context is not an UI context.
- final String displayUniqueId = context.getDisplayNoVerify().getUniqueId();
+ final Display display = context.getDisplayNoVerify();
+ // The radius is only valid for internal displays, since the corner radius of external or
+ // virtual displays is not known when window corners are configured or are not supported.
+ if (display.getType() != Display.TYPE_INTERNAL) {
+ return 0f;
+ }
+ final String displayUniqueId = display.getUniqueId();
// Radius that should be used in case top or bottom aren't defined.
float defaultRadius = RoundedCorners.getRoundedCornerRadius(resources, displayUniqueId)
- RoundedCorners.getRoundedCornerRadiusAdjustment(resources, displayUniqueId);
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index e440dc9..fbc058c 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -210,6 +210,8 @@
DataSourceParams
.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP)
.build();
+ // NOTE: Registering that datasource is an async operation, so there may be no data traced
+ // for some messages logged right after the construction of this class.
mDataSource.register(params);
this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider;
this.mViewerConfigReader = viewerConfigReader;
@@ -223,17 +225,17 @@
"ServiceManager returned a null ProtoLog Configuration Service");
try {
- var args = new ProtoLogConfigurationService.RegisterClientArgs();
+ var args = new ProtoLogConfigurationServiceImpl.RegisterClientArgs();
if (viewerConfigFilePath != null) {
args.setViewerConfigFile(viewerConfigFilePath);
}
final var groupArgs = Stream.of(groups)
- .map(group -> new ProtoLogConfigurationService.RegisterClientArgs
+ .map(group -> new ProtoLogConfigurationServiceImpl.RegisterClientArgs
.GroupConfig(group.name(), group.isLogToLogcat()))
- .toArray(
- ProtoLogConfigurationService.RegisterClientArgs.GroupConfig[]::new);
+ .toArray(ProtoLogConfigurationServiceImpl
+ .RegisterClientArgs.GroupConfig[]::new);
args.setGroups(groupArgs);
mProtoLogConfigurationService.registerClient(this, args);
diff --git a/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java b/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java
index 7031d69..d65aaae 100644
--- a/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java
+++ b/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java
@@ -16,434 +16,32 @@
package com.android.internal.protolog;
-import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.GROUPS;
-import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.ID;
-import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.NAME;
-import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.TAG;
-import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MESSAGES;
-import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.GROUP_ID;
-import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LEVEL;
-import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LOCATION;
-import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE;
-import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE_ID;
-
import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SystemService;
-import android.content.Context;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.os.ShellCallback;
-import android.tracing.perfetto.DataSourceParams;
-import android.tracing.perfetto.InitArguments;
-import android.tracing.perfetto.Producer;
-import android.util.Log;
-import android.util.proto.ProtoInputStream;
-import android.util.proto.ProtoOutputStream;
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-
-/**
- * The ProtoLog service is responsible for orchestrating centralized actions of the protolog tracing
- * system. Currently this service has the following roles:
- * - Handle shell commands to toggle logging ProtoLog messages for specified groups to logcat.
- * - Handle viewer config dumping (the mapping from message hash to message string) for all protolog
- * clients. This is for two reasons: firstly, because client processes might be frozen so might
- * not response to the request to dump their viewer config when the trace is stopped; secondly,
- * multiple processes might be running the same code with the same viewer config, this centralized
- * service ensures we don't dump the same viewer config multiple times across processes.
- * <p>
- * {@link com.android.internal.protolog.IProtoLogClient ProtoLog clients} register themselves to
- * this service on initialization.
- * <p>
- * This service is intended to run on the system server, such that it never gets frozen.
- */
-@SystemService(Context.PROTOLOG_CONFIGURATION_SERVICE)
-public final class ProtoLogConfigurationService extends IProtoLogConfigurationService.Stub {
- private static final String LOG_TAG = "ProtoLogConfigurationService";
-
- private final ProtoLogDataSource mDataSource;
-
- /**
- * Keeps track of how many of each viewer config file is currently registered.
- * Use to keep track of which viewer config files are actively being used in tracing and might
- * need to be dumped on flush.
- */
- private final Map<String, Integer> mConfigFileCounts = new HashMap<>();
- /**
- * Keeps track of the viewer config file of each client if available.
- */
- private final Map<IProtoLogClient, String> mClientConfigFiles = new HashMap<>();
-
- /**
- * Keeps track of all the protolog groups that have been registered by clients and are still
- * being actively traced.
- */
- private final Set<String> mRegisteredGroups = new HashSet<>();
- /**
- * Keeps track of all the clients that are actively tracing a given protolog group.
- */
- private final Map<String, Set<IProtoLogClient>> mGroupToClients = new HashMap<>();
-
- /**
- * Keeps track of whether or not a given group should be logged to logcat.
- * True when logging to logcat, false otherwise.
- */
- private final Map<String, Boolean> mLogGroupToLogcatStatus = new TreeMap<>();
-
- /**
- * Keeps track of all the tracing instance ids that are actively running for ProtoLog.
- */
- private final Set<Integer> mRunningInstances = new HashSet<>();
-
- private final ViewerConfigFileTracer mViewerConfigFileTracer;
-
- public ProtoLogConfigurationService() {
- this(ProtoLogDataSource::new, ProtoLogConfigurationService::dumpTransitionTraceConfig);
- }
-
- @VisibleForTesting
- public ProtoLogConfigurationService(@NonNull ProtoLogDataSourceBuilder dataSourceBuilder) {
- this(dataSourceBuilder, ProtoLogConfigurationService::dumpTransitionTraceConfig);
- }
-
- @VisibleForTesting
- public ProtoLogConfigurationService(@NonNull ViewerConfigFileTracer tracer) {
- this(ProtoLogDataSource::new, tracer);
- }
-
- @VisibleForTesting
- public ProtoLogConfigurationService(
- @NonNull ProtoLogDataSourceBuilder dataSourceBuilder,
- @NonNull ViewerConfigFileTracer tracer) {
- mDataSource = dataSourceBuilder.build(
- this::onTracingInstanceStart,
- this::onTracingInstanceFlush,
- this::onTracingInstanceStop
- );
-
- // Initialize the Perfetto producer and register the Perfetto ProtoLog datasource to be
- // receive the lifecycle callbacks of the datasource and write the viewer configs if and
- // when required to the datasource.
- Producer.init(InitArguments.DEFAULTS);
- final var params = new DataSourceParams.Builder()
- .setBufferExhaustedPolicy(DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP)
- .build();
- mDataSource.register(params);
-
- mViewerConfigFileTracer = tracer;
- }
-
- public static class RegisterClientArgs extends IRegisterClientArgs.Stub {
- /**
- * The viewer config file to be registered for this client ProtoLog process.
- */
- @Nullable
- private String mViewerConfigFile = null;
- /**
- * The list of all groups that this client protolog process supports and might trace.
- */
- @NonNull
- private String[] mGroups = new String[0];
- /**
- * The default logcat status of the ProtoLog client. True is logging to logcat, false
- * otherwise. The indices should match the indices in {@link mGroups}.
- */
- @NonNull
- private boolean[] mLogcatStatus = new boolean[0];
-
- public record GroupConfig(@NonNull String group, boolean logToLogcat) {}
-
- /**
- * Specify groups to register with this client that will be used for protologging in this
- * process.
- * @param groups to register with this client.
- * @return self
- */
- public RegisterClientArgs setGroups(GroupConfig... groups) {
- mGroups = new String[groups.length];
- mLogcatStatus = new boolean[groups.length];
-
- for (int i = 0; i < groups.length; i++) {
- mGroups[i] = groups[i].group;
- mLogcatStatus[i] = groups[i].logToLogcat;
- }
-
- return this;
- }
-
- /**
- * Set the viewer config file that the logs in this process are using.
- * @param viewerConfigFile The file path of the viewer config.
- * @return self
- */
- public RegisterClientArgs setViewerConfigFile(@NonNull String viewerConfigFile) {
- mViewerConfigFile = viewerConfigFile;
-
- return this;
- }
-
- @Override
- @NonNull
- public String[] getGroups() {
- return mGroups;
- }
-
- @Override
- @NonNull
- public boolean[] getGroupsDefaultLogcatStatus() {
- return mLogcatStatus;
- }
-
- @Nullable
- @Override
- public String getViewerConfigFile() {
- return mViewerConfigFile;
- }
- }
-
- @FunctionalInterface
- public interface ViewerConfigFileTracer {
- /**
- * Write the viewer config data to the trace buffer.
- *
- * @param dataSource The target datasource to write the viewer config to.
- * @param viewerConfigFilePath The path of the viewer config file which contains the data we
- * want to write to the trace buffer.
- * @throws FileNotFoundException if the viewerConfigFilePath is invalid.
- */
- void trace(@NonNull ProtoLogDataSource dataSource, @NonNull String viewerConfigFilePath);
- }
-
- @Override
- public void registerClient(@NonNull IProtoLogClient client, @NonNull IRegisterClientArgs args)
- throws RemoteException {
- client.asBinder().linkToDeath(() -> onClientBinderDeath(client), /* flags */ 0);
-
- final String viewerConfigFile = args.getViewerConfigFile();
- if (viewerConfigFile != null) {
- registerViewerConfigFile(client, viewerConfigFile);
- }
-
- registerGroups(client, args.getGroups(), args.getGroupsDefaultLogcatStatus());
- }
-
- @Override
- public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
- @Nullable FileDescriptor err, @NonNull String[] args, @Nullable ShellCallback callback,
- @NonNull ResultReceiver resultReceiver) throws RemoteException {
- new ProtoLogCommandHandler(this)
- .exec(this, in, out, err, args, callback, resultReceiver);
- }
-
+public interface ProtoLogConfigurationService extends IProtoLogConfigurationService {
/**
* Get the list of groups clients have registered to the protolog service.
* @return The list of ProtoLog groups registered with this service.
*/
@NonNull
- public String[] getGroups() {
- return mRegisteredGroups.toArray(new String[0]);
- }
-
- /**
- * Enable logging target groups to logcat.
- * @param groups we want to enable logging them to logcat for.
- */
- public void enableProtoLogToLogcat(String... groups) {
- toggleProtoLogToLogcat(true, groups);
- }
-
- /**
- * Disable logging target groups to logcat.
- * @param groups we want to disable from being logged to logcat.
- */
- public void disableProtoLogToLogcat(String... groups) {
- toggleProtoLogToLogcat(false, groups);
- }
+ String[] getGroups();
/**
* Check if a group is logging to logcat
* @param group The group we want to check for
* @return True iff we are logging this group to logcat.
*/
- public boolean isLoggingToLogcat(@NonNull String group) {
- final Boolean isLoggingToLogcat = mLogGroupToLogcatStatus.get(group);
+ boolean isLoggingToLogcat(@NonNull String group);
- if (isLoggingToLogcat == null) {
- throw new RuntimeException(
- "Trying to get logcat logging status of non-registered group " + group);
- }
+ /**
+ * Enable logging target groups to logcat.
+ * @param groups we want to enable logging them to logcat for.
+ */
+ void enableProtoLogToLogcat(@NonNull String... groups);
- return isLoggingToLogcat;
- }
-
- private void registerViewerConfigFile(
- @NonNull IProtoLogClient client, @NonNull String viewerConfigFile) {
- final var count = mConfigFileCounts.getOrDefault(viewerConfigFile, 0);
- mConfigFileCounts.put(viewerConfigFile, count + 1);
- mClientConfigFiles.put(client, viewerConfigFile);
- }
-
- private void registerGroups(@NonNull IProtoLogClient client, @NonNull String[] groups,
- @NonNull boolean[] logcatStatuses) throws RemoteException {
- if (groups.length != logcatStatuses.length) {
- throw new RuntimeException(
- "Expected groups and logcatStatuses to have the same length, "
- + "but groups has length " + groups.length
- + " and logcatStatuses has length " + logcatStatuses.length);
- }
-
- for (int i = 0; i < groups.length; i++) {
- String group = groups[i];
- boolean logcatStatus = logcatStatuses[i];
-
- mRegisteredGroups.add(group);
-
- mGroupToClients.putIfAbsent(group, new HashSet<>());
- mGroupToClients.get(group).add(client);
-
- if (!mLogGroupToLogcatStatus.containsKey(group)) {
- mLogGroupToLogcatStatus.put(group, logcatStatus);
- }
-
- boolean requestedLogToLogcat = mLogGroupToLogcatStatus.get(group);
- if (requestedLogToLogcat != logcatStatus) {
- client.toggleLogcat(requestedLogToLogcat, new String[] { group });
- }
- }
- }
-
- private void toggleProtoLogToLogcat(boolean enabled, @NonNull String[] groups) {
- final var clientToGroups = new HashMap<IProtoLogClient, Set<String>>();
-
- for (String group : groups) {
- final var clients = mGroupToClients.get(group);
-
- if (clients == null) {
- // No clients associated to this group
- Log.w(LOG_TAG, "Attempting to toggle log to logcat for group " + group
- + " with no registered clients.");
- continue;
- }
-
- for (IProtoLogClient client : clients) {
- clientToGroups.putIfAbsent(client, new HashSet<>());
- clientToGroups.get(client).add(group);
- }
- }
-
- for (IProtoLogClient client : clientToGroups.keySet()) {
- try {
- client.toggleLogcat(enabled, clientToGroups.get(client).toArray(new String[0]));
- } catch (RemoteException e) {
- throw new RuntimeException(
- "Failed to toggle logcat status for groups on client", e);
- }
- }
-
- for (String group : groups) {
- mLogGroupToLogcatStatus.put(group, enabled);
- }
- }
-
- private void onTracingInstanceStart(int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) {
- mRunningInstances.add(instanceIdx);
- }
-
- private void onTracingInstanceFlush() {
- for (String fileName : mConfigFileCounts.keySet()) {
- mViewerConfigFileTracer.trace(mDataSource, fileName);
- }
- }
-
- private void onTracingInstanceStop(int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) {
- mRunningInstances.remove(instanceIdx);
- }
-
- private static void dumpTransitionTraceConfig(@NonNull ProtoLogDataSource dataSource,
- @NonNull String viewerConfigFilePath) {
- Utils.dumpViewerConfig(dataSource, () -> {
- try {
- return new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
- } catch (FileNotFoundException e) {
- throw new RuntimeException(
- "Failed to load viewer config file " + viewerConfigFilePath, e);
- }
- });
- }
-
- private void onClientBinderDeath(@NonNull IProtoLogClient client) {
- // Dump the tracing config now if no other client is going to dump the same config file.
- String configFile = mClientConfigFiles.get(client);
- if (configFile != null) {
- final var newCount = mConfigFileCounts.get(configFile) - 1;
- mConfigFileCounts.put(configFile, newCount);
- boolean lastProcessWithViewerConfig = newCount == 0;
- if (lastProcessWithViewerConfig) {
- mViewerConfigFileTracer.trace(mDataSource, configFile);
- }
- }
- }
-
- private static void writeViewerConfigGroup(
- @NonNull ProtoInputStream pis, @NonNull ProtoOutputStream os) throws IOException {
- final long inGroupToken = pis.start(GROUPS);
- final long outGroupToken = os.start(GROUPS);
-
- while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
- switch (pis.getFieldNumber()) {
- case (int) ID -> {
- int id = pis.readInt(ID);
- os.write(ID, id);
- }
- case (int) NAME -> {
- String name = pis.readString(NAME);
- os.write(NAME, name);
- }
- case (int) TAG -> {
- String tag = pis.readString(TAG);
- os.write(TAG, tag);
- }
- default ->
- throw new RuntimeException(
- "Unexpected field id " + pis.getFieldNumber());
- }
- }
-
- pis.end(inGroupToken);
- os.end(outGroupToken);
- }
-
- private static void writeViewerConfigMessage(
- @NonNull ProtoInputStream pis, @NonNull ProtoOutputStream os) throws IOException {
- final long inMessageToken = pis.start(MESSAGES);
- final long outMessagesToken = os.start(MESSAGES);
-
- while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
- switch (pis.getFieldNumber()) {
- case (int) MESSAGE_ID -> os.write(MESSAGE_ID,
- pis.readLong(MESSAGE_ID));
- case (int) MESSAGE -> os.write(MESSAGE, pis.readString(MESSAGE));
- case (int) LEVEL -> os.write(LEVEL, pis.readInt(LEVEL));
- case (int) GROUP_ID -> os.write(GROUP_ID, pis.readInt(GROUP_ID));
- case (int) LOCATION -> os.write(LOCATION, pis.readString(LOCATION));
- default ->
- throw new RuntimeException(
- "Unexpected field id " + pis.getFieldNumber());
- }
- }
-
- pis.end(inMessageToken);
- os.end(outMessagesToken);
- }
+ /**
+ * Disable logging target groups to logcat.
+ * @param groups we want to disable from being logged to logcat.
+ */
+ void disableProtoLogToLogcat(@NonNull String... groups);
}
diff --git a/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java b/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java
new file mode 100644
index 0000000..e382ac1
--- /dev/null
+++ b/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java
@@ -0,0 +1,454 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.protolog;
+
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.GROUPS;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.ID;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.NAME;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.TAG;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MESSAGES;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.GROUP_ID;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LEVEL;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LOCATION;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE_ID;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.tracing.perfetto.DataSourceParams;
+import android.tracing.perfetto.InitArguments;
+import android.tracing.perfetto.Producer;
+import android.util.Log;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+/**
+ * The ProtoLog service is responsible for orchestrating centralized actions of the protolog tracing
+ * system. Currently this service has the following roles:
+ * - Handle shell commands to toggle logging ProtoLog messages for specified groups to logcat.
+ * - Handle viewer config dumping (the mapping from message hash to message string) for all protolog
+ * clients. This is for two reasons: firstly, because client processes might be frozen so might
+ * not response to the request to dump their viewer config when the trace is stopped; secondly,
+ * multiple processes might be running the same code with the same viewer config, this centralized
+ * service ensures we don't dump the same viewer config multiple times across processes.
+ * <p>
+ * {@link com.android.internal.protolog.IProtoLogClient ProtoLog clients} register themselves to
+ * this service on initialization.
+ * <p>
+ * This service is intended to run on the system server, such that it never gets frozen.
+ */
+@SystemService(Context.PROTOLOG_CONFIGURATION_SERVICE)
+public class ProtoLogConfigurationServiceImpl extends IProtoLogConfigurationService.Stub
+ implements ProtoLogConfigurationService {
+ private static final String LOG_TAG = "ProtoLogConfigurationService";
+
+ private final ProtoLogDataSource mDataSource;
+
+ /**
+ * Keeps track of how many of each viewer config file is currently registered.
+ * Use to keep track of which viewer config files are actively being used in tracing and might
+ * need to be dumped on flush.
+ */
+ private final Map<String, Integer> mConfigFileCounts = new HashMap<>();
+ /**
+ * Keeps track of the viewer config file of each client if available.
+ */
+ private final Map<IProtoLogClient, String> mClientConfigFiles = new HashMap<>();
+
+ /**
+ * Keeps track of all the protolog groups that have been registered by clients and are still
+ * being actively traced.
+ */
+ private final Set<String> mRegisteredGroups = new HashSet<>();
+ /**
+ * Keeps track of all the clients that are actively tracing a given protolog group.
+ */
+ private final Map<String, Set<IProtoLogClient>> mGroupToClients = new HashMap<>();
+
+ /**
+ * Keeps track of whether or not a given group should be logged to logcat.
+ * True when logging to logcat, false otherwise.
+ */
+ private final Map<String, Boolean> mLogGroupToLogcatStatus = new TreeMap<>();
+
+ /**
+ * Keeps track of all the tracing instance ids that are actively running for ProtoLog.
+ */
+ private final Set<Integer> mRunningInstances = new HashSet<>();
+
+ private final ViewerConfigFileTracer mViewerConfigFileTracer;
+
+ public ProtoLogConfigurationServiceImpl() {
+ this(ProtoLogDataSource::new, ProtoLogConfigurationServiceImpl::dumpTransitionTraceConfig);
+ }
+
+ @VisibleForTesting
+ public ProtoLogConfigurationServiceImpl(@NonNull ProtoLogDataSourceBuilder dataSourceBuilder) {
+ this(dataSourceBuilder, ProtoLogConfigurationServiceImpl::dumpTransitionTraceConfig);
+ }
+
+ @VisibleForTesting
+ public ProtoLogConfigurationServiceImpl(@NonNull ViewerConfigFileTracer tracer) {
+ this(ProtoLogDataSource::new, tracer);
+ }
+
+ @VisibleForTesting
+ public ProtoLogConfigurationServiceImpl(
+ @NonNull ProtoLogDataSourceBuilder dataSourceBuilder,
+ @NonNull ViewerConfigFileTracer tracer) {
+ mDataSource = dataSourceBuilder.build(
+ this::onTracingInstanceStart,
+ this::onTracingInstanceFlush,
+ this::onTracingInstanceStop
+ );
+
+ // Initialize the Perfetto producer and register the Perfetto ProtoLog datasource to be
+ // receive the lifecycle callbacks of the datasource and write the viewer configs if and
+ // when required to the datasource.
+ Producer.init(InitArguments.DEFAULTS);
+ final var params = new DataSourceParams.Builder()
+ .setBufferExhaustedPolicy(DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP)
+ .build();
+ mDataSource.register(params);
+
+ mViewerConfigFileTracer = tracer;
+ }
+
+ public static class RegisterClientArgs extends IRegisterClientArgs.Stub {
+ /**
+ * The viewer config file to be registered for this client ProtoLog process.
+ */
+ @Nullable
+ private String mViewerConfigFile = null;
+ /**
+ * The list of all groups that this client protolog process supports and might trace.
+ */
+ @NonNull
+ private String[] mGroups = new String[0];
+ /**
+ * The default logcat status of the ProtoLog client. True is logging to logcat, false
+ * otherwise. The indices should match the indices in {@link mGroups}.
+ */
+ @NonNull
+ private boolean[] mLogcatStatus = new boolean[0];
+
+ public record GroupConfig(@NonNull String group, boolean logToLogcat) {}
+
+ /**
+ * Specify groups to register with this client that will be used for protologging in this
+ * process.
+ * @param groups to register with this client.
+ * @return self
+ */
+ public RegisterClientArgs setGroups(GroupConfig... groups) {
+ mGroups = new String[groups.length];
+ mLogcatStatus = new boolean[groups.length];
+
+ for (int i = 0; i < groups.length; i++) {
+ mGroups[i] = groups[i].group;
+ mLogcatStatus[i] = groups[i].logToLogcat;
+ }
+
+ return this;
+ }
+
+ /**
+ * Set the viewer config file that the logs in this process are using.
+ * @param viewerConfigFile The file path of the viewer config.
+ * @return self
+ */
+ public RegisterClientArgs setViewerConfigFile(@NonNull String viewerConfigFile) {
+ mViewerConfigFile = viewerConfigFile;
+
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public String[] getGroups() {
+ return mGroups;
+ }
+
+ @Override
+ @NonNull
+ public boolean[] getGroupsDefaultLogcatStatus() {
+ return mLogcatStatus;
+ }
+
+ @Nullable
+ @Override
+ public String getViewerConfigFile() {
+ return mViewerConfigFile;
+ }
+ }
+
+ @FunctionalInterface
+ public interface ViewerConfigFileTracer {
+ /**
+ * Write the viewer config data to the trace buffer.
+ *
+ * @param dataSource The target datasource to write the viewer config to.
+ * @param viewerConfigFilePath The path of the viewer config file which contains the data we
+ * want to write to the trace buffer.
+ * @throws FileNotFoundException if the viewerConfigFilePath is invalid.
+ */
+ void trace(@NonNull ProtoLogDataSource dataSource, @NonNull String viewerConfigFilePath);
+ }
+
+ @Override
+ public void registerClient(@NonNull IProtoLogClient client, @NonNull IRegisterClientArgs args)
+ throws RemoteException {
+ client.asBinder().linkToDeath(() -> onClientBinderDeath(client), /* flags */ 0);
+
+ final String viewerConfigFile = args.getViewerConfigFile();
+ if (viewerConfigFile != null) {
+ registerViewerConfigFile(client, viewerConfigFile);
+ }
+
+ registerGroups(client, args.getGroups(), args.getGroupsDefaultLogcatStatus());
+ }
+
+ @Override
+ public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+ @Nullable FileDescriptor err, @NonNull String[] args, @Nullable ShellCallback callback,
+ @NonNull ResultReceiver resultReceiver) throws RemoteException {
+ new ProtoLogCommandHandler(this)
+ .exec(this, in, out, err, args, callback, resultReceiver);
+ }
+
+ /**
+ * Get the list of groups clients have registered to the protolog service.
+ * @return The list of ProtoLog groups registered with this service.
+ */
+ @Override
+ @NonNull
+ public String[] getGroups() {
+ return mRegisteredGroups.toArray(new String[0]);
+ }
+
+ /**
+ * Enable logging target groups to logcat.
+ * @param groups we want to enable logging them to logcat for.
+ */
+ @Override
+ public void enableProtoLogToLogcat(@NonNull String... groups) {
+ toggleProtoLogToLogcat(true, groups);
+ }
+
+ /**
+ * Disable logging target groups to logcat.
+ * @param groups we want to disable from being logged to logcat.
+ */
+ @Override
+ public void disableProtoLogToLogcat(@NonNull String... groups) {
+ toggleProtoLogToLogcat(false, groups);
+ }
+
+ /**
+ * Check if a group is logging to logcat
+ * @param group The group we want to check for
+ * @return True iff we are logging this group to logcat.
+ */
+ @Override
+ public boolean isLoggingToLogcat(@NonNull String group) {
+ final Boolean isLoggingToLogcat = mLogGroupToLogcatStatus.get(group);
+
+ if (isLoggingToLogcat == null) {
+ throw new RuntimeException(
+ "Trying to get logcat logging status of non-registered group " + group);
+ }
+
+ return isLoggingToLogcat;
+ }
+
+ private void registerViewerConfigFile(
+ @NonNull IProtoLogClient client, @NonNull String viewerConfigFile) {
+ final var count = mConfigFileCounts.getOrDefault(viewerConfigFile, 0);
+ mConfigFileCounts.put(viewerConfigFile, count + 1);
+ mClientConfigFiles.put(client, viewerConfigFile);
+ }
+
+ private void registerGroups(@NonNull IProtoLogClient client, @NonNull String[] groups,
+ @NonNull boolean[] logcatStatuses) throws RemoteException {
+ if (groups.length != logcatStatuses.length) {
+ throw new RuntimeException(
+ "Expected groups and logcatStatuses to have the same length, "
+ + "but groups has length " + groups.length
+ + " and logcatStatuses has length " + logcatStatuses.length);
+ }
+
+ for (int i = 0; i < groups.length; i++) {
+ String group = groups[i];
+ boolean logcatStatus = logcatStatuses[i];
+
+ mRegisteredGroups.add(group);
+
+ mGroupToClients.putIfAbsent(group, new HashSet<>());
+ mGroupToClients.get(group).add(client);
+
+ if (!mLogGroupToLogcatStatus.containsKey(group)) {
+ mLogGroupToLogcatStatus.put(group, logcatStatus);
+ }
+
+ boolean requestedLogToLogcat = mLogGroupToLogcatStatus.get(group);
+ if (requestedLogToLogcat != logcatStatus) {
+ client.toggleLogcat(requestedLogToLogcat, new String[] { group });
+ }
+ }
+ }
+
+ private void toggleProtoLogToLogcat(boolean enabled, @NonNull String[] groups) {
+ final var clientToGroups = new HashMap<IProtoLogClient, Set<String>>();
+
+ for (String group : groups) {
+ final var clients = mGroupToClients.get(group);
+
+ if (clients == null) {
+ // No clients associated to this group
+ Log.w(LOG_TAG, "Attempting to toggle log to logcat for group " + group
+ + " with no registered clients.");
+ continue;
+ }
+
+ for (IProtoLogClient client : clients) {
+ clientToGroups.putIfAbsent(client, new HashSet<>());
+ clientToGroups.get(client).add(group);
+ }
+ }
+
+ for (IProtoLogClient client : clientToGroups.keySet()) {
+ try {
+ client.toggleLogcat(enabled, clientToGroups.get(client).toArray(new String[0]));
+ } catch (RemoteException e) {
+ throw new RuntimeException(
+ "Failed to toggle logcat status for groups on client", e);
+ }
+ }
+
+ for (String group : groups) {
+ mLogGroupToLogcatStatus.put(group, enabled);
+ }
+ }
+
+ private void onTracingInstanceStart(int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) {
+ mRunningInstances.add(instanceIdx);
+ }
+
+ private void onTracingInstanceFlush() {
+ for (String fileName : mConfigFileCounts.keySet()) {
+ mViewerConfigFileTracer.trace(mDataSource, fileName);
+ }
+ }
+
+ private void onTracingInstanceStop(int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) {
+ mRunningInstances.remove(instanceIdx);
+ }
+
+ private static void dumpTransitionTraceConfig(@NonNull ProtoLogDataSource dataSource,
+ @NonNull String viewerConfigFilePath) {
+ Utils.dumpViewerConfig(dataSource, () -> {
+ try {
+ return new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException(
+ "Failed to load viewer config file " + viewerConfigFilePath, e);
+ }
+ });
+ }
+
+ private void onClientBinderDeath(@NonNull IProtoLogClient client) {
+ // Dump the tracing config now if no other client is going to dump the same config file.
+ String configFile = mClientConfigFiles.get(client);
+ if (configFile != null) {
+ final var newCount = mConfigFileCounts.get(configFile) - 1;
+ mConfigFileCounts.put(configFile, newCount);
+ boolean lastProcessWithViewerConfig = newCount == 0;
+ if (lastProcessWithViewerConfig) {
+ mViewerConfigFileTracer.trace(mDataSource, configFile);
+ }
+ }
+ }
+
+ private static void writeViewerConfigGroup(
+ @NonNull ProtoInputStream pis, @NonNull ProtoOutputStream os) throws IOException {
+ final long inGroupToken = pis.start(GROUPS);
+ final long outGroupToken = os.start(GROUPS);
+
+ while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (pis.getFieldNumber()) {
+ case (int) ID -> {
+ int id = pis.readInt(ID);
+ os.write(ID, id);
+ }
+ case (int) NAME -> {
+ String name = pis.readString(NAME);
+ os.write(NAME, name);
+ }
+ case (int) TAG -> {
+ String tag = pis.readString(TAG);
+ os.write(TAG, tag);
+ }
+ default ->
+ throw new RuntimeException(
+ "Unexpected field id " + pis.getFieldNumber());
+ }
+ }
+
+ pis.end(inGroupToken);
+ os.end(outGroupToken);
+ }
+
+ private static void writeViewerConfigMessage(
+ @NonNull ProtoInputStream pis, @NonNull ProtoOutputStream os) throws IOException {
+ final long inMessageToken = pis.start(MESSAGES);
+ final long outMessagesToken = os.start(MESSAGES);
+
+ while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (pis.getFieldNumber()) {
+ case (int) MESSAGE_ID -> os.write(MESSAGE_ID,
+ pis.readLong(MESSAGE_ID));
+ case (int) MESSAGE -> os.write(MESSAGE, pis.readString(MESSAGE));
+ case (int) LEVEL -> os.write(LEVEL, pis.readInt(LEVEL));
+ case (int) GROUP_ID -> os.write(GROUP_ID, pis.readInt(GROUP_ID));
+ case (int) LOCATION -> os.write(LOCATION, pis.readString(LOCATION));
+ default ->
+ throw new RuntimeException(
+ "Unexpected field id " + pis.getFieldNumber());
+ }
+ }
+
+ pis.end(inMessageToken);
+ os.end(outMessagesToken);
+ }
+}
diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java
index b73cacb..bdb33c4 100644
--- a/core/java/com/android/internal/view/menu/ListMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java
@@ -33,8 +33,6 @@
import android.widget.RadioButton;
import android.widget.TextView;
-import com.android.text.flags.Flags;
-
/**
* The item view for each item in the ListView-based MenuViews.
*/
@@ -283,10 +281,7 @@
private void insertIconView() {
LayoutInflater inflater = getInflater();
- mIconView = (ImageView) inflater.inflate(
- !Flags.fixMisalignedContextMenu()
- ? com.android.internal.R.layout.list_menu_item_fixed_size_icon :
- com.android.internal.R.layout.list_menu_item_icon,
+ mIconView = (ImageView) inflater.inflate(com.android.internal.R.layout.list_menu_item_icon,
this, false);
addContentView(mIconView, 0);
}
diff --git a/core/java/com/android/internal/view/menu/StandardMenuPopup.java b/core/java/com/android/internal/view/menu/StandardMenuPopup.java
index c43a8c6..8e2536a 100644
--- a/core/java/com/android/internal/view/menu/StandardMenuPopup.java
+++ b/core/java/com/android/internal/view/menu/StandardMenuPopup.java
@@ -35,8 +35,6 @@
import android.widget.PopupWindow.OnDismissListener;
import android.widget.TextView;
-import com.android.text.flags.Flags;
-
import java.util.Objects;
/**
@@ -122,8 +120,7 @@
mMenu = menu;
mOverflowOnly = overflowOnly;
final LayoutInflater inflater = LayoutInflater.from(context);
- mAdapter = new MenuAdapter(menu, inflater, mOverflowOnly,
- Flags.fixMisalignedContextMenu() ? ITEM_LAYOUT_MATERIAL : ITEM_LAYOUT);
+ mAdapter = new MenuAdapter(menu, inflater, mOverflowOnly, ITEM_LAYOUT_MATERIAL);
mPopupStyleAttr = popupStyleAttr;
mPopupStyleRes = popupStyleRes;
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 90cb10a..9a4ff8f 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -290,7 +290,6 @@
"libasync_safe",
"libbinderthreadstateutils",
"libdmabufinfo",
- "libgif",
"libgui_window_info_static",
"libkernelconfigs",
"libnativehelper_lazy",
diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp
index 70505a4..b9c3bf7 100644
--- a/core/jni/android_graphics_BLASTBufferQueue.cpp
+++ b/core/jni/android_graphics_BLASTBufferQueue.cpp
@@ -16,16 +16,16 @@
#define LOG_TAG "BLASTBufferQueue"
-#include <nativehelper/JNIHelp.h>
-
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/android_view_Surface.h>
-#include <utils/Log.h>
-#include <utils/RefBase.h>
-
+#include <android_util_Binder.h>
#include <gui/BLASTBufferQueue.h>
#include <gui/Surface.h>
#include <gui/SurfaceComposerClient.h>
+#include <nativehelper/JNIHelp.h>
+#include <utils/Log.h>
+#include <utils/RefBase.h>
+
#include "core_jni_helpers.h"
namespace android {
@@ -209,6 +209,12 @@
reinterpret_cast<jlong>(transaction));
}
+static void nativeSetApplyToken(JNIEnv* env, jclass clazz, jlong ptr, jobject applyTokenObject) {
+ sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr);
+ sp<IBinder> token(ibinderForJavaObject(env, applyTokenObject));
+ return queue->setApplyToken(std::move(token));
+}
+
static const JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
// clang-format off
@@ -227,6 +233,7 @@
{"nativeSetTransactionHangCallback",
"(JLandroid/graphics/BLASTBufferQueue$TransactionHangCallback;)V",
(void*)nativeSetTransactionHangCallback},
+ {"nativeSetApplyToken", "(JLandroid/os/IBinder;)V", (void*)nativeSetApplyToken},
// clang-format on
};
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index f2c70b5..8003bb7 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -1747,9 +1747,9 @@
{"linkToDeathNative", "(Landroid/os/IBinder$DeathRecipient;I)V", (void*)android_os_BinderProxy_linkToDeath},
{"unlinkToDeathNative", "(Landroid/os/IBinder$DeathRecipient;I)Z", (void*)android_os_BinderProxy_unlinkToDeath},
{"addFrozenStateChangeCallbackNative",
- "(Landroid/os/IBinder$IFrozenStateChangeCallback;)V", (void*)android_os_BinderProxy_addFrozenStateChangeCallback},
+ "(Landroid/os/IBinder$FrozenStateChangeCallback;)V", (void*)android_os_BinderProxy_addFrozenStateChangeCallback},
{"removeFrozenStateChangeCallbackNative",
- "(Landroid/os/IBinder$IFrozenStateChangeCallback;)Z", (void*)android_os_BinderProxy_removeFrozenStateChangeCallback},
+ "(Landroid/os/IBinder$FrozenStateChangeCallback;)Z", (void*)android_os_BinderProxy_removeFrozenStateChangeCallback},
{"getNativeFinalizer", "()J", (void*)android_os_BinderProxy_getNativeFinalizer},
{"getExtension", "()Landroid/os/IBinder;", (void*)android_os_BinderProxy_getExtension},
};
@@ -1774,7 +1774,7 @@
"(Landroid/os/IBinder$DeathRecipient;Landroid/os/IBinder;)V");
gBinderProxyOffsets.mInvokeFrozenStateChangeCallback =
GetStaticMethodIDOrDie(env, clazz, "invokeFrozenStateChangeCallback",
- "(Landroid/os/IBinder$IFrozenStateChangeCallback;Landroid/os/"
+ "(Landroid/os/IBinder$FrozenStateChangeCallback;Landroid/os/"
"IBinder;I)V");
gBinderProxyOffsets.mNativeData = GetFieldIDOrDie(env, clazz, "mNativeData", "J");
diff --git a/core/jni/jni_wrappers.h b/core/jni/jni_wrappers.h
index 3b29e30..21b5b13 100644
--- a/core/jni/jni_wrappers.h
+++ b/core/jni/jni_wrappers.h
@@ -69,9 +69,47 @@
return static_cast<T>(res);
}
+// Inline variable that specifies the method binding format.
+// The expected format is 'XX${method}XX', where ${method} represents the original method name.
+// This variable is shared across all translation units. This is treated as a global variable as
+// per C++ 17.
+inline std::string jniMethodFormat;
+
+inline static void setJniMethodFormat(std::string value) {
+ jniMethodFormat = value;
+}
+
+// Potentially translates the given JNINativeMethods if setJniMethodFormat has been set.
+// Has no effect otherwise
+inline const JNINativeMethod* maybeRenameJniMethods(const JNINativeMethod* gMethods,
+ int numMethods) {
+ if (jniMethodFormat.empty()) {
+ return gMethods;
+ }
+ // Make a copy of gMethods with reformatted method names.
+ JNINativeMethod* modifiedMethods = new JNINativeMethod[numMethods];
+ LOG_ALWAYS_FATAL_IF(!modifiedMethods, "Failed to allocate a copy of the JNI methods");
+
+ size_t methodNamePos = jniMethodFormat.find("${method}");
+ LOG_ALWAYS_FATAL_IF(methodNamePos == std::string::npos,
+ "Invalid jniMethodFormat: could not find '${method}' in pattern");
+
+ for (int i = 0; i < numMethods; i++) {
+ modifiedMethods[i] = gMethods[i];
+ std::string modifiedName = jniMethodFormat;
+ modifiedName.replace(methodNamePos, 9, gMethods[i].name);
+ char* modifiedNameChars = new char[modifiedName.length() + 1];
+ LOG_ALWAYS_FATAL_IF(!modifiedNameChars, "Failed to allocate the new method name");
+ std::strcpy(modifiedNameChars, modifiedName.c_str());
+ modifiedMethods[i].name = modifiedNameChars;
+ }
+ return modifiedMethods;
+}
+
static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods) {
- int res = jniRegisterNativeMethods(env, className, gMethods, numMethods);
+ const JNINativeMethod* modifiedMethods = maybeRenameJniMethods(gMethods, numMethods);
+ int res = jniRegisterNativeMethods(env, className, modifiedMethods, numMethods);
LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
return res;
}
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index cb7c226..606e829 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -401,6 +401,7 @@
optional SettingProto long_press_timeout = 35 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto key_press_timeout_ms = 96 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto key_press_delay_ms = 97 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto key_repeat_enabled = 102 [ (android.privacy).dest = DEST_AUTOMATIC ];
message ManagedProfile {
option (android.msg_privacy).dest = DEST_EXPLICIT;
@@ -735,5 +736,5 @@
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 102;
+ // Next tag = 103;
}
diff --git a/core/proto/android/widget/remoteviews.proto b/core/proto/android/widget/remoteviews.proto
index 47c97b0..f477d32 100644
--- a/core/proto/android/widget/remoteviews.proto
+++ b/core/proto/android/widget/remoteviews.proto
@@ -299,6 +299,14 @@
NightModeReflectionAction night_mode_reflection_action = 5;
ReflectionAction reflection_action = 6;
RemoveFromParentAction remove_from_parent_action = 7;
+ ResourceReflectionAction resource_reflection_action = 8;
+ SetCompoundButtonCheckedAction set_compound_button_checked_action = 9;
+ SetDrawableTintAction set_drawable_tint_action = 10;
+ SetEmptyViewAction set_empty_view_action = 11;
+ SetIntTagAction set_int_tag_action = 12;
+ SetRadioGroupCheckedAction set_radio_group_checked_action = 13;
+ SetRemoteCollectionItemListAdapterAction set_remote_collection_item_list_adapter_action = 14;
+ SetRippleDrawableColorAction set_ripple_drawable_color_action = 15;
}
}
@@ -374,6 +382,52 @@
message RemoveFromParentAction {
optional string view_id = 1;
}
+
+ message ResourceReflectionAction {
+ optional string view_id = 1;
+ optional string method_name = 2;
+ optional int32 resource_type = 3;
+ optional string res_id = 4;
+ optional int32 parameter_type = 5;
+ }
+
+ message SetCompoundButtonCheckedAction {
+ optional string view_id = 1;
+ optional bool checked = 2;
+ }
+
+ message SetDrawableTintAction {
+ optional string view_id = 1;
+ optional bool target_background = 2;
+ optional int32 color_filter = 3;
+ optional int32 filter_mode = 4;
+ }
+
+ message SetEmptyViewAction {
+ optional string view_id = 1;
+ optional string empty_view_id = 2;
+ }
+
+ message SetIntTagAction {
+ optional string view_id = 1;
+ optional string key = 2;
+ optional int32 tag = 3;
+ }
+
+ message SetRadioGroupCheckedAction {
+ optional string view_id = 1;
+ optional string checked_id = 2;
+ }
+
+ message SetRemoteCollectionItemListAdapterAction {
+ optional string view_id = 1;
+ optional RemoteCollectionItems items = 2;
+ }
+
+ message SetRippleDrawableColorAction {
+ optional string view_id = 1;
+ optional android.content.res.ColorStateListProto color_state_list = 2;
+ }
}
diff --git a/core/res/res/layout/miniresolver.xml b/core/res/res/layout/miniresolver.xml
index e60e0b0..db1c779 100644
--- a/core/res/res/layout/miniresolver.xml
+++ b/core/res/res/layout/miniresolver.xml
@@ -122,7 +122,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
- android:maxLines="2"
+ android:layout_toStartOf="@id/button_open"
+ android:layout_marginEnd="8dp"
android:background="@drawable/resolver_outlined_button_bg"
style="?android:attr/borderlessButtonStyle"
android:paddingHorizontal="16dp"
@@ -136,7 +137,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
- android:maxLines="2"
android:paddingHorizontal="16dp"
android:background="@drawable/resolver_button_bg"
style="?android:attr/borderlessButtonStyle"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index d95309c..421b7d2 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -7096,6 +7096,10 @@
<!-- Maximum number of active tasks on a given Desktop Windowing session. Set to 0 for unlimited. -->
<integer name="config_maxDesktopWindowingActiveTasks">0</integer>
+ <!-- Whether a display enters desktop mode by default when the windowing mode of the display's
+ root TaskDisplayArea is set to WINDOWING_MODE_FREEFORM. -->
+ <bool name="config_enterDesktopByDefaultOnFreeformDisplay">false</bool>
+
<!-- Frame rate compatibility value for Wallpaper
FRAME_RATE_COMPATIBILITY_MIN (102) is used by default for lower power consumption -->
<integer name="config_wallpaperFrameRateCompatibility">102</integer>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index dc99634..579dc91 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1509,7 +1509,7 @@
</style>
<!-- @hide -->
- <style name="PointerIconVectorStyleFillYellow">
+ <style name="PointerIconVectorStyleFillRed">
<item name="pointerIconVectorFill">#F55E57</item>
<item name="pointerIconVectorFillInverse">#F55E57</item>
</style>
@@ -1527,6 +1527,12 @@
</style>
<!-- @hide -->
+ <style name="PointerIconVectorStyleFillPurple">
+ <item name="pointerIconVectorFill">#AD72FF</item>
+ <item name="pointerIconVectorFillInverse">#AD72FF</item>
+ </style>
+
+ <!-- @hide -->
<style name="PointerIconVectorStyleStrokeWhite">
<item name="pointerIconVectorStroke">@color/white</item>
<item name="pointerIconVectorStrokeInverse">@color/black</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b80947d..0396659 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1705,9 +1705,10 @@
<java-symbol type="style" name="VectorPointer" />
<java-symbol type="style" name="PointerIconVectorStyleFillBlack" />
<java-symbol type="style" name="PointerIconVectorStyleFillGreen" />
- <java-symbol type="style" name="PointerIconVectorStyleFillYellow" />
+ <java-symbol type="style" name="PointerIconVectorStyleFillRed" />
<java-symbol type="style" name="PointerIconVectorStyleFillPink" />
<java-symbol type="style" name="PointerIconVectorStyleFillBlue" />
+ <java-symbol type="style" name="PointerIconVectorStyleFillPurple" />
<java-symbol type="attr" name="pointerIconVectorFill" />
<java-symbol type="style" name="PointerIconVectorStyleStrokeWhite" />
<java-symbol type="style" name="PointerIconVectorStyleStrokeBlack" />
@@ -5542,6 +5543,10 @@
<!-- Maximum number of active tasks on a given Desktop Windowing session. Set to 0 for unlimited. -->
<java-symbol type="integer" name="config_maxDesktopWindowingActiveTasks"/>
+ <!-- Whether a display enters desktop mode by default when the windowing mode of the display's
+ root TaskDisplayArea is set to WINDOWING_MODE_FREEFORM. -->
+ <java-symbol type="bool" name="config_enterDesktopByDefaultOnFreeformDisplay" />
+
<!-- Frame rate compatibility value for Wallpaper -->
<java-symbol type="integer" name="config_wallpaperFrameRateCompatibility" />
diff --git a/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/src/com/android/frameworks/coretests/bfscctestapp/BfsccTestAppCmdService.java b/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/src/com/android/frameworks/coretests/bfscctestapp/BfsccTestAppCmdService.java
index 77e8a40..fe54aa8 100644
--- a/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/src/com/android/frameworks/coretests/bfscctestapp/BfsccTestAppCmdService.java
+++ b/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/src/com/android/frameworks/coretests/bfscctestapp/BfsccTestAppCmdService.java
@@ -30,31 +30,30 @@
public class BfsccTestAppCmdService extends Service {
private IBfsccTestAppCmdService.Stub mBinder = new IBfsccTestAppCmdService.Stub() {
- private final LinkedBlockingQueue<IBinder.IFrozenStateChangeCallback.State> mNotifications =
+ private final LinkedBlockingQueue<Integer> mNotifications =
new LinkedBlockingQueue<>();
@Override
public void listenTo(IBinder binder) throws RemoteException {
binder.addFrozenStateChangeCallback(
- (IBinder who, IBinder.IFrozenStateChangeCallback.State state)
- -> mNotifications.offer(state));
+ (IBinder who, int state) -> mNotifications.offer(state));
}
@Override
public boolean[] waitAndConsumeNotifications() {
List<Boolean> results = new ArrayList<>();
try {
- IBinder.IFrozenStateChangeCallback.State state =
- mNotifications.poll(5, TimeUnit.SECONDS);
+ Integer state = mNotifications.poll(5, TimeUnit.SECONDS);
if (state != null) {
- results.add(state == IBinder.IFrozenStateChangeCallback.State.FROZEN);
+ results.add(
+ state.intValue() == IBinder.FrozenStateChangeCallback.STATE_FROZEN);
}
} catch (InterruptedException e) {
return null;
}
while (mNotifications.size() > 0) {
- results.add(mNotifications.poll()
- == IBinder.IFrozenStateChangeCallback.State.FROZEN);
+ results.add(mNotifications.poll().intValue()
+ == IBinder.FrozenStateChangeCallback.STATE_FROZEN);
}
boolean[] convertedResults = new boolean[results.size()];
for (int i = 0; i < results.size(); i++) {
diff --git a/core/tests/coretests/src/android/os/BinderFrozenStateChangeNotificationTest.java b/core/tests/coretests/src/android/os/BinderFrozenStateChangeNotificationTest.java
index ee2e7e0..195a18a 100644
--- a/core/tests/coretests/src/android/os/BinderFrozenStateChangeNotificationTest.java
+++ b/core/tests/coretests/src/android/os/BinderFrozenStateChangeNotificationTest.java
@@ -52,7 +52,7 @@
import java.util.concurrent.atomic.AtomicReference;
/**
- * Tests functionality of {@link android.os.IBinder.IFrozenStateChangeCallback}.
+ * Tests functionality of {@link android.os.IBinder.FrozenStateChangeCallback}.
*/
@RunWith(AndroidJUnit4.class)
@IgnoreUnderRavenwood(blockedBy = ActivityManager.class)
@@ -157,7 +157,7 @@
@Test
public void onStateChangeNotCalledAfterCallbackRemoved() throws Exception {
final LinkedBlockingQueue<Boolean> results = new LinkedBlockingQueue<>();
- IBinder.IFrozenStateChangeCallback callback;
+ IBinder.FrozenStateChangeCallback callback;
if ((callback = createCallback(mBfsccTestAppCmdService.asBinder(), results)) == null) {
return;
}
@@ -171,7 +171,7 @@
public void multipleCallbacks() throws Exception {
final LinkedBlockingQueue<Boolean> results1 = new LinkedBlockingQueue<>();
final LinkedBlockingQueue<Boolean> results2 = new LinkedBlockingQueue<>();
- IBinder.IFrozenStateChangeCallback callback1;
+ IBinder.FrozenStateChangeCallback callback1;
if ((callback1 = createCallback(mBfsccTestAppCmdService.asBinder(), results1)) == null) {
return;
}
@@ -197,8 +197,8 @@
public void onStateChangeCalledWithTheRightBinder() throws Exception {
final IBinder binder = mBfsccTestAppCmdService.asBinder();
final LinkedBlockingQueue<IBinder> results = new LinkedBlockingQueue<>();
- IBinder.IFrozenStateChangeCallback callback =
- (IBinder who, IBinder.IFrozenStateChangeCallback.State state) -> results.offer(who);
+ IBinder.FrozenStateChangeCallback callback =
+ (IBinder who, int state) -> results.offer(who);
try {
binder.addFrozenStateChangeCallback(callback);
} catch (UnsupportedOperationException e) {
@@ -221,12 +221,12 @@
}
}
- private IBinder.IFrozenStateChangeCallback createCallback(IBinder binder, Queue<Boolean> queue)
+ private IBinder.FrozenStateChangeCallback createCallback(IBinder binder, Queue<Boolean> queue)
throws RemoteException {
try {
- final IBinder.IFrozenStateChangeCallback callback =
- (IBinder who, IBinder.IFrozenStateChangeCallback.State state) ->
- queue.offer(state == IBinder.IFrozenStateChangeCallback.State.FROZEN);
+ final IBinder.FrozenStateChangeCallback callback =
+ (IBinder who, int state) ->
+ queue.offer(state == IBinder.FrozenStateChangeCallback.STATE_FROZEN);
binder.addFrozenStateChangeCallback(callback);
return callback;
} catch (UnsupportedOperationException e) {
diff --git a/core/tests/coretests/src/android/tracing/OWNERS b/core/tests/coretests/src/android/tracing/OWNERS
deleted file mode 100644
index 86a7e88..0000000
--- a/core/tests/coretests/src/android/tracing/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include platform/development:/tools/winscope/OWNERS
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/tracing/TEST_MAPPING b/core/tests/coretests/src/android/tracing/TEST_MAPPING
deleted file mode 100644
index 4b7adf9..0000000
--- a/core/tests/coretests/src/android/tracing/TEST_MAPPING
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "postsubmit": [
- {
- "name": "FrameworksCoreTests_android_tracing",
- "file_patterns": [".*\\.java"]
- }
- ]
-}
diff --git a/core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java b/core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java
index 32345e6..dd40695 100644
--- a/core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java
+++ b/core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java
@@ -16,7 +16,7 @@
package android.window.flags;
-import static android.window.flags.DesktopModeFlags.DESKTOP_WINDOWING_MODE;
+import static android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE;
import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE;
import static com.android.window.flags.Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS;
@@ -75,7 +75,7 @@
public void isEnabled_devOptionFlagDisabled_overrideOff_featureFlagOn_returnsTrue() {
setOverride(OVERRIDE_OFF_SETTING);
// In absence of dev options, follow flag
- assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
+ assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue();
}
@@ -84,7 +84,7 @@
public void isEnabled_devOptionFlagDisabled_overrideOn_featureFlagOff_returnsFalse() {
setOverride(OVERRIDE_ON_SETTING);
- assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
+ assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse();
}
@Test
@@ -93,7 +93,7 @@
setOverride(OVERRIDE_UNSET_SETTING);
// For overridableFlag, for unset overrides, follow flag
- assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
+ assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue();
}
@Test
@@ -103,7 +103,7 @@
setOverride(OVERRIDE_UNSET_SETTING);
// For overridableFlag, for unset overrides, follow flag
- assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
+ assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse();
}
@Test
@@ -112,7 +112,7 @@
setOverride(null);
// For overridableFlag, in absence of overrides, follow flag
- assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
+ assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue();
}
@Test
@@ -122,7 +122,7 @@
setOverride(null);
// For overridableFlag, in absence of overrides, follow flag
- assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
+ assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse();
}
@Test
@@ -131,7 +131,7 @@
setOverride(-2);
// For overridableFlag, for unrecognized overrides, follow flag
- assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
+ assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue();
}
@Test
@@ -141,7 +141,7 @@
setOverride(-2);
// For overridableFlag, for unrecognizable overrides, follow flag
- assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
+ assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse();
}
@Test
@@ -150,7 +150,7 @@
setOverride(OVERRIDE_OFF_SETTING);
// For overridableFlag, follow override if they exist
- assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
+ assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse();
}
@Test
@@ -160,7 +160,7 @@
setOverride(OVERRIDE_ON_SETTING);
// For overridableFlag, follow override if they exist
- assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
+ assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue();
}
@Test
@@ -169,12 +169,12 @@
setOverride(OVERRIDE_OFF_SETTING);
// For overridableFlag, follow override if they exist
- assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
+ assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse();
setOverride(OVERRIDE_ON_SETTING);
// Keep overrides constant through the process
- assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse();
+ assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse();
}
@Test
@@ -184,12 +184,12 @@
setOverride(OVERRIDE_ON_SETTING);
// For overridableFlag, follow override if they exist
- assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
+ assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue();
setOverride(OVERRIDE_OFF_SETTING);
// Keep overrides constant through the process
- assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue();
+ assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue();
}
@Test
@@ -199,7 +199,7 @@
setOverride(OVERRIDE_UNSET_SETTING);
// For unset overrides, follow flag
- assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue();
+ assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isTrue();
}
@Test
@@ -208,7 +208,7 @@
public void isEnabled_dwFlagOn_overrideUnset_featureFlagOff_returnsFalse() {
setOverride(OVERRIDE_UNSET_SETTING);
// For unset overrides, follow flag
- assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse();
+ assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isFalse();
}
@Test
@@ -221,7 +221,7 @@
setOverride(OVERRIDE_ON_SETTING);
// When toggle override matches its default state (dw flag), don't override flags
- assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue();
+ assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isTrue();
}
@Test
@@ -231,7 +231,7 @@
setOverride(OVERRIDE_ON_SETTING);
// When toggle override matches its default state (dw flag), don't override flags
- assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse();
+ assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isFalse();
}
@Test
@@ -244,7 +244,7 @@
setOverride(OVERRIDE_OFF_SETTING);
// Follow override if they exist, and is not equal to default toggle state (dw flag)
- assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue();
+ assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isTrue();
}
@Test
@@ -254,7 +254,7 @@
setOverride(OVERRIDE_OFF_SETTING);
// Follow override if they exist, and is not equal to default toggle state (dw flag)
- assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse();
+ assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isFalse();
}
@Test
@@ -267,7 +267,7 @@
setOverride(OVERRIDE_UNSET_SETTING);
// For unset overrides, follow flag
- assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue();
+ assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isTrue();
}
@Test
@@ -280,7 +280,7 @@
setOverride(OVERRIDE_UNSET_SETTING);
// For unset overrides, follow flag
- assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse();
+ assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isFalse();
}
@Test
@@ -293,7 +293,7 @@
setOverride(OVERRIDE_ON_SETTING);
// Follow override if they exist, and is not equal to default toggle state (dw flag)
- assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue();
+ assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isTrue();
}
@Test
@@ -306,7 +306,7 @@
setOverride(OVERRIDE_ON_SETTING);
// Follow override if they exist, and is not equal to default toggle state (dw flag)
- assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse();
+ assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isFalse();
}
@Test
@@ -319,7 +319,7 @@
setOverride(OVERRIDE_OFF_SETTING);
// When toggle override matches its default state (dw flag), don't override flags
- assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue();
+ assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isTrue();
}
@Test
@@ -332,7 +332,7 @@
setOverride(OVERRIDE_OFF_SETTING);
// When toggle override matches its default state (dw flag), don't override flags
- assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse();
+ assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isFalse();
}
private void setOverride(Integer setting) {
diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
index 5af272c..d6b2a78 100644
--- a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
@@ -117,6 +117,7 @@
// Simulate a trace session and see if begin / end are invoked.
assertThat(monitor.begin(mSurfaceControl, mActivity.getApplicationContext(),
+ mActivity.getMainThreadHandler(),
Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue();
verify(tracker).begin();
assertThat(monitor.end(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue();
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java b/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java
index 397cdcf..67de25e 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java
@@ -125,12 +125,12 @@
}
@Override
- public void addFrozenStateChangeCallback(IFrozenStateChangeCallback callback)
+ public void addFrozenStateChangeCallback(FrozenStateChangeCallback callback)
throws RemoteException {
}
@Override
- public boolean removeFrozenStateChangeCallback(IFrozenStateChangeCallback callback) {
+ public boolean removeFrozenStateChangeCallback(FrozenStateChangeCallback callback) {
return false;
}
diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java
index c52f700..90723b2 100644
--- a/graphics/java/android/graphics/BLASTBufferQueue.java
+++ b/graphics/java/android/graphics/BLASTBufferQueue.java
@@ -17,6 +17,7 @@
package android.graphics;
import android.annotation.NonNull;
+import android.os.IBinder;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -47,6 +48,7 @@
long frameNumber);
private static native void nativeSetTransactionHangCallback(long ptr,
TransactionHangCallback callback);
+ private static native void nativeSetApplyToken(long ptr, IBinder applyToken);
public interface TransactionHangCallback {
void onTransactionHang(String reason);
@@ -204,4 +206,8 @@
public void setTransactionHangCallback(TransactionHangCallback hangCallback) {
nativeSetTransactionHangCallback(mNativeObject, hangCallback);
}
+
+ public void setApplyToken(IBinder applyToken) {
+ nativeSetApplyToken(mNativeObject, applyToken);
+ }
}
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 6d31578..e07471c 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -128,6 +128,22 @@
private static final WeakHashMap<Bitmap, Void> sAllBitmaps = new WeakHashMap<>();
/**
+ * @hide
+ */
+ private static NativeAllocationRegistry getRegistry(boolean malloc, long size) {
+ final long free = nativeGetNativeFinalizer();
+ if (com.android.libcore.Flags.nativeMetrics()) {
+ Class cls = Bitmap.class;
+ return malloc ? NativeAllocationRegistry.createMalloced(cls, free, size)
+ : NativeAllocationRegistry.createNonmalloced(cls, free, size);
+ } else {
+ ClassLoader loader = Bitmap.class.getClassLoader();
+ return malloc ? NativeAllocationRegistry.createMalloced(loader, free, size)
+ : NativeAllocationRegistry.createNonmalloced(loader, free, size);
+ }
+ }
+
+ /**
* Private constructor that must receive an already allocated native bitmap
* int (pointer).
*/
@@ -151,7 +167,6 @@
mWidth = width;
mHeight = height;
mRequestPremultiplied = requestPremultiplied;
-
mNinePatchChunk = ninePatchChunk;
mNinePatchInsets = ninePatchInsets;
if (density >= 0) {
@@ -159,17 +174,9 @@
}
mNativePtr = nativeBitmap;
-
final int allocationByteCount = getAllocationByteCount();
- NativeAllocationRegistry registry;
- if (fromMalloc) {
- registry = NativeAllocationRegistry.createMalloced(
- Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), allocationByteCount);
- } else {
- registry = NativeAllocationRegistry.createNonmalloced(
- Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), allocationByteCount);
- }
- registry.registerNativeAllocation(this, nativeBitmap);
+ getRegistry(fromMalloc, allocationByteCount).registerNativeAllocation(this, mNativePtr);
+
synchronized (Bitmap.class) {
sAllBitmaps.put(this, null);
}
diff --git a/graphics/java/android/graphics/Gainmap.java b/graphics/java/android/graphics/Gainmap.java
index 0a6fb84..63ca3b8 100644
--- a/graphics/java/android/graphics/Gainmap.java
+++ b/graphics/java/android/graphics/Gainmap.java
@@ -18,7 +18,9 @@
import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
+import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
@@ -26,6 +28,9 @@
import libcore.util.NativeAllocationRegistry;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Gainmap represents a mechanism for augmenting an SDR image to produce an HDR one with variable
* display adjustment capability. It is a combination of a set of metadata describing how to apply
@@ -83,6 +88,27 @@
*/
public final class Gainmap implements Parcelable {
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"GAINMAP_DIRECTION_"},
+ value = {GAINMAP_DIRECTION_SDR_TO_HDR,
+ GAINMAP_DIRECTION_HDR_TO_SDR})
+ public @interface GainmapDirection {}
+
+ /**
+ * The gainmap will be applied as if the base image were SDR, and fully applying the gainmap
+ * results in an HDR image.
+ */
+ @FlaggedApi(Flags.FLAG_ISO_GAINMAP_APIS)
+ public static final int GAINMAP_DIRECTION_SDR_TO_HDR = 0;
+
+ /**
+ * The gainmap will be applied as if the base image were HDR, and fully applying the gainmap
+ * results in an SDR image.
+ */
+ @FlaggedApi(Flags.FLAG_ISO_GAINMAP_APIS)
+ public static final int GAINMAP_DIRECTION_HDR_TO_SDR = 1;
+
// Use a Holder to allow static initialization of Gainmap in the boot image.
private static class NoImagePreloadHolder {
public static final NativeAllocationRegistry sRegistry =
@@ -252,8 +278,9 @@
}
/**
- * Sets the hdr/sdr ratio at which point the gainmap is fully applied.
- * @param max The hdr/sdr ratio at which the gainmap is fully applied. Must be >= 1.0f
+ * Sets the hdr/sdr ratio at which point applying the gainmap results in an HDR rendition.
+ * @param max The hdr/sdr ratio at which point applying the gainmap results in an HDR rendition.
+ * Must be >= 1.0f
*/
public void setDisplayRatioForFullHdr(@FloatRange(from = 1.0f) float max) {
if (!Float.isFinite(max) || max < 1f) {
@@ -264,7 +291,7 @@
}
/**
- * Gets the hdr/sdr ratio at which point the gainmap is fully applied.
+ * Gets the hdr/sdr ratio at which point applying the gainmap results in an HDR rendition
*/
@NonNull
public float getDisplayRatioForFullHdr() {
@@ -272,8 +299,9 @@
}
/**
- * Sets the hdr/sdr ratio below which only the SDR image is displayed.
- * @param min The minimum hdr/sdr ratio at which to begin applying the gainmap. Must be >= 1.0f
+ * Sets the hdr/sdr ratio below which applying the gainmap results in an SDR rendition.
+ * @param min The minimum hdr/sdr ratio at which point applying the gainmap results in an SDR
+ * rendition. Must be >= 1.0f
*/
public void setMinDisplayRatioForHdrTransition(@FloatRange(from = 1.0f) float min) {
if (!Float.isFinite(min) || min < 1f) {
@@ -284,7 +312,7 @@
}
/**
- * Gets the hdr/sdr ratio below which only the SDR image is displayed.
+ * Gets the hdr/sdr ratio below which applying the gainmap results in an SDR rendition.
*/
@NonNull
public float getMinDisplayRatioForHdrTransition() {
@@ -292,6 +320,55 @@
}
/**
+ * Sets the colorspace that the gainmap math should be applied in.
+ * Only the primaries are what is relevant for applying the gainmap. The transfer and range
+ * characteritics are ignored.
+ *
+ * If the supplied ColorSpace is null, then applying the gainmap will be done using the color
+ * gamut of the base image.
+ */
+ @FlaggedApi(Flags.FLAG_ISO_GAINMAP_APIS)
+ public void setAlternativeImagePrimaries(@Nullable ColorSpace colorSpace) {
+ long colorSpaceInstance = colorSpace == null ? 0 : colorSpace.getNativeInstance();
+ nSetAlternativeColorSpace(mNativePtr, colorSpaceInstance);
+ }
+
+ /**
+ * Gets the colorspace that the gainmap math should be applied in.
+ * Only the primaries are what is relevant for applying the gainmap. The transfer and range
+ * characteritics are ignored.
+ *
+ * If the returned ColorSpace is null, then applying the gainmap will be done using the color
+ * gamut of the base image.
+ */
+ @FlaggedApi(Flags.FLAG_ISO_GAINMAP_APIS)
+ @Nullable
+ public ColorSpace getAlternativeImagePrimaries() {
+ return nGetAlternativeColorSpace(mNativePtr);
+ }
+
+ /**
+ * Sets the direction that the gainmap math should be applied in.
+ */
+ @FlaggedApi(Flags.FLAG_ISO_GAINMAP_APIS)
+ public void setGainmapDirection(@GainmapDirection int direction) {
+ if (direction != GAINMAP_DIRECTION_SDR_TO_HDR
+ && direction != GAINMAP_DIRECTION_HDR_TO_SDR) {
+ throw new IllegalArgumentException("Invalid gainmap direction: " + direction);
+ }
+ nSetDirection(mNativePtr, direction);
+ }
+
+ /**
+ * Gets the direction that the gainmap math should be applied in.
+ */
+ @FlaggedApi(Flags.FLAG_ISO_GAINMAP_APIS)
+ public @GainmapDirection int getGainmapDirection() {
+ return nGetDirection(mNativePtr);
+ }
+
+
+ /**
* No special parcel contents.
*/
@Override
@@ -361,6 +438,10 @@
private static native void nSetDisplayRatioSdr(long ptr, float min);
private static native float nGetDisplayRatioSdr(long ptr);
+ private static native void nSetAlternativeColorSpace(long ptr, long colorSpacePtr);
+ private static native ColorSpace nGetAlternativeColorSpace(long ptr);
+ private static native void nSetDirection(long ptr, int direction);
+ private static native int nGetDirection(long ptr);
private static native void nWriteGainmapToParcel(long ptr, Parcel dest);
private static native void nReadGainmapFromParcel(long ptr, Parcel src);
}
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_dialog_background.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_dialog_background.xml
index e7c89d1..f37fb8d 100644
--- a/libs/WindowManager/Shell/res/drawable/letterbox_education_dialog_background.xml
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_dialog_background.xml
@@ -17,6 +17,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="rectangle">
- <solid android:color="?androidprv:attr/colorSurface"/>
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainerHigh"/>
<corners android:radius="@dimen/letterbox_education_dialog_corner_radius"/>
</shape>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background_ripple.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background_ripple.xml
index 72ebef6..3fdd059 100644
--- a/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background_ripple.xml
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background_ripple.xml
@@ -32,7 +32,7 @@
</item>
<item>
<shape android:shape="rectangle">
- <solid android:color="?androidprv:attr/colorAccentPrimaryVariant"/>
+ <solid android:color="?androidprv:attr/materialColorPrimary"/>
<corners android:radius="@dimen/letterbox_education_dialog_button_radius"/>
<padding android:left="@dimen/letterbox_education_dialog_horizontal_padding"
android:top="@dimen/letterbox_education_dialog_vertical_padding"
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_light_bulb.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_light_bulb.xml
index 4a1e748..67929df 100644
--- a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_light_bulb.xml
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_light_bulb.xml
@@ -18,10 +18,13 @@
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:width="@dimen/letterbox_education_dialog_title_icon_width"
android:height="@dimen/letterbox_education_dialog_title_icon_height"
- android:viewportWidth="45"
- android:viewportHeight="44">
-
- <path
- android:fillColor="?androidprv:attr/colorAccentPrimaryVariant"
- android:pathData="M11 40H19C19 42.2 17.2 44 15 44C12.8 44 11 42.2 11 40ZM7 38L23 38V34L7 34L7 38ZM30 19C30 26.64 24.68 30.72 22.46 32L7.54 32C5.32 30.72 0 26.64 0 19C0 10.72 6.72 4 15 4C23.28 4 30 10.72 30 19ZM26 19C26 12.94 21.06 8 15 8C8.94 8 4 12.94 4 19C4 23.94 6.98 26.78 8.7 28L21.3 28C23.02 26.78 26 23.94 26 19ZM39.74 14.74L37 16L39.74 17.26L41 20L42.26 17.26L45 16L42.26 14.74L41 12L39.74 14.74ZM35 12L36.88 7.88L41 6L36.88 4.12L35 0L33.12 4.12L29 6L33.12 7.88L35 12Z" />
+ android:viewportWidth="32"
+ android:viewportHeight="32">
+ <group>
+ <clip-path
+ android:pathData="M0,0h32v32h-32z"/>
+ <path
+ android:pathData="M5.867,22.667C4.489,21.844 3.389,20.733 2.567,19.333C1.744,17.933 1.333,16.378 1.333,14.667C1.333,12.067 2.233,9.867 4.033,8.067C5.856,6.244 8.067,5.333 10.667,5.333C13.267,5.333 15.467,6.244 17.267,8.067C19.089,9.867 20,12.067 20,14.667C20,16.378 19.589,17.933 18.767,19.333C17.944,20.733 16.844,21.844 15.467,22.667H5.867ZM6.667,20H14.667C15.511,19.356 16.167,18.578 16.633,17.667C17.1,16.733 17.333,15.733 17.333,14.667C17.333,12.822 16.678,11.256 15.367,9.967C14.078,8.656 12.511,8 10.667,8C8.822,8 7.244,8.656 5.933,9.967C4.644,11.256 4,12.822 4,14.667C4,15.733 4.233,16.733 4.7,17.667C5.167,18.578 5.822,19.356 6.667,20ZM7.2,26.667C6.822,26.667 6.5,26.544 6.233,26.3C5.989,26.033 5.867,25.711 5.867,25.333C5.867,24.956 5.989,24.644 6.233,24.4C6.5,24.133 6.822,24 7.2,24H14.133C14.511,24 14.822,24.133 15.067,24.4C15.333,24.644 15.467,24.956 15.467,25.333C15.467,25.711 15.333,26.033 15.067,26.3C14.822,26.544 14.511,26.667 14.133,26.667H7.2ZM10.667,30.667C9.933,30.667 9.3,30.411 8.767,29.9C8.256,29.367 8,28.733 8,28H13.333C13.333,28.733 13.067,29.367 12.533,29.9C12.022,30.411 11.4,30.667 10.667,30.667ZM24.667,13.367C24.667,11.7 24.078,10.278 22.9,9.1C21.722,7.922 20.3,7.333 18.633,7.333C20.3,7.333 21.722,6.756 22.9,5.6C24.078,4.422 24.667,3 24.667,1.333C24.667,3 25.244,4.422 26.4,5.6C27.578,6.756 29,7.333 30.667,7.333C29,7.333 27.578,7.922 26.4,9.1C25.244,10.278 24.667,11.7 24.667,13.367Z"
+ android:fillColor="?androidprv:attr/materialColorPrimary"/>
+ </group>
</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_reposition.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_reposition.xml
index 22a8f39..29e58a1 100644
--- a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_reposition.xml
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_reposition.xml
@@ -15,16 +15,16 @@
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="@dimen/letterbox_education_dialog_icon_width"
- android:height="@dimen/letterbox_education_dialog_icon_height"
- android:viewportWidth="40"
+ android:width="32dp"
+ android:height="32dp"
+ android:viewportWidth="32"
android:viewportHeight="32">
<path
+ android:pathData="M4,4C2.527,4 1.333,5.194 1.333,6.667V25.333C1.333,26.806 2.527,28 4,28H28C29.472,28 30.666,26.806 30.666,25.333V6.667C30.666,5.194 29.472,4 28,4H4ZM28,6.667H4V25.333H28V6.667Z"
android:fillColor="@color/letterbox_education_text_secondary"
- android:fillType="evenOdd"
- android:pathData="M4 0C1.79086 0 0 1.79086 0 4V28C0 30.2091 1.79086 32 4 32H36C38.2091 32 40 30.2091 40 28V4C40 1.79086 38.2091 0 36 0H4ZM36 4H4V28H36V4Z" />
+ android:fillType="evenOdd" />
<path
- android:fillColor="@color/letterbox_education_text_secondary"
- android:pathData="M19.98 8L17.16 10.82L20.32 14L12 14V18H20.32L17.14 21.18L19.98 24L28 16.02L19.98 8Z" />
+ android:pathData="M17.32,10.667L15.44,12.547L17.546,14.667L9.333,14.667L9.333,17.333H17.546L15.426,19.453L17.32,21.333L22.666,16.013L17.32,10.667Z"
+ android:fillColor="@color/letterbox_education_text_secondary" />
</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_split_screen.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_split_screen.xml
index 15e65f7..6a766d3 100644
--- a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_split_screen.xml
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_split_screen.xml
@@ -17,10 +17,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="@dimen/letterbox_education_dialog_icon_width"
android:height="@dimen/letterbox_education_dialog_icon_height"
- android:viewportWidth="40"
- android:viewportHeight="32">
+ android:viewportWidth="28"
+ android:viewportHeight="22">
<path
android:fillColor="@color/letterbox_education_text_secondary"
- android:pathData="M40 28L40 4C40 1.8 38.2 -7.86805e-08 36 -1.74846e-07L26 -6.11959e-07C23.8 -7.08124e-07 22 1.8 22 4L22 28C22 30.2 23.8 32 26 32L36 32C38.2 32 40 30.2 40 28ZM14 28L4 28L4 4L14 4L14 28ZM18 28L18 4C18 1.8 16.2 -1.04033e-06 14 -1.1365e-06L4 -1.57361e-06C1.8 -1.66978e-06 -7.86805e-08 1.8 -1.74846e-07 4L-1.22392e-06 28C-1.32008e-06 30.2 1.8 32 4 32L14 32C16.2 32 18 30.2 18 28Z" />
+ android:pathData="M27.333,19L27.333,3C27.333,1.533 26.133,0.333 24.666,0.333L18,0.333C16.533,0.333 15.333,1.533 15.333,3L15.333,19C15.333,20.467 16.533,21.667 18,21.667L24.666,21.667C26.133,21.667 27.333,20.467 27.333,19ZM10,19L3.333,19L3.333,3L10,3L10,19ZM12.666,19L12.666,3C12.666,1.533 11.466,0.333 10,0.333L3.333,0.333C1.866,0.333 0.666,1.533 0.666,3L0.666,19C0.666,20.467 1.866,21.667 3.333,21.667L10,21.667C11.466,21.667 12.666,20.467 12.666,19Z" />
</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background_ripple.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background_ripple.xml
index 1f12514..4207482 100644
--- a/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background_ripple.xml
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background_ripple.xml
@@ -32,7 +32,7 @@
</item>
<item>
<shape android:shape="rectangle">
- <solid android:color="?androidprv:attr/colorAccentPrimaryVariant"/>
+ <solid android:color="?androidprv:attr/materialColorPrimary"/>
<corners android:radius="@dimen/letterbox_restart_dialog_button_radius"/>
<padding android:left="@dimen/letterbox_restart_dialog_horizontal_padding"
android:top="@dimen/letterbox_restart_dialog_vertical_padding"
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_dialog_background.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_dialog_background.xml
index e3c18a2..72cfeef 100644
--- a/libs/WindowManager/Shell/res/drawable/letterbox_restart_dialog_background.xml
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_dialog_background.xml
@@ -17,6 +17,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="rectangle">
- <solid android:color="?androidprv:attr/colorSurface"/>
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainerHigh"/>
<corners android:radius="@dimen/letterbox_restart_dialog_corner_radius"/>
</shape>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background_ripple.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background_ripple.xml
index 3aa0981..816b350 100644
--- a/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background_ripple.xml
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background_ripple.xml
@@ -32,9 +32,9 @@
</item>
<item>
<shape android:shape="rectangle">
- <stroke android:color="?androidprv:attr/colorAccentPrimaryVariant"
+ <stroke android:color="?androidprv:attr/materialColorOutlineVariant"
android:width="1dp"/>
- <solid android:color="?androidprv:attr/colorSurface"/>
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainerHigh"/>
<corners android:radius="@dimen/letterbox_restart_dialog_button_radius"/>
<padding android:left="@dimen/letterbox_restart_dialog_horizontal_padding"
android:top="@dimen/letterbox_restart_dialog_vertical_padding"
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_header_ic_arrows.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_header_ic_arrows.xml
index 5053971..f13d26c 100644
--- a/libs/WindowManager/Shell/res/drawable/letterbox_restart_header_ic_arrows.xml
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_header_ic_arrows.xml
@@ -18,15 +18,13 @@
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:width="@dimen/letterbox_restart_dialog_title_icon_width"
android:height="@dimen/letterbox_restart_dialog_title_icon_height"
- android:viewportWidth="45"
- android:viewportHeight="44">
- <group
- android:scaleX="0.8"
- android:scaleY="0.8"
- android:translateX="8"
- android:translateY="8">
+ android:viewportWidth="32"
+ android:viewportHeight="32">
+ <group>
+ <clip-path
+ android:pathData="M0,0h32v32h-32z"/>
<path
- android:pathData="M0,36V24.5H3V30.85L10.4,23.45L12.55,25.6L5.15,33H11.5V36H0ZM24.5,36V33H30.85L23.5,25.65L25.65,23.5L33,30.85V24.5H36V36H24.5ZM10.35,12.5L3,5.15V11.5H0V0H11.5V3H5.15L12.5,10.35L10.35,12.5ZM25.65,12.5L23.5,10.35L30.85,3H24.5V0H36V11.5H33V5.15L25.65,12.5Z"
- android:fillColor="?androidprv:attr/colorAccentPrimaryVariant"/>
+ android:pathData="M8.533,25.333H10.667C11.044,25.333 11.356,25.467 11.6,25.733C11.867,25.978 12,26.289 12,26.667C12,27.044 11.867,27.367 11.6,27.633C11.356,27.878 11.044,28 10.667,28H5.333C4.956,28 4.633,27.878 4.367,27.633C4.122,27.367 4,27.044 4,26.667V21.333C4,20.956 4.122,20.644 4.367,20.4C4.633,20.133 4.956,20 5.333,20C5.711,20 6.022,20.133 6.267,20.4C6.533,20.644 6.667,20.956 6.667,21.333V23.467L9.867,20.267C10.111,20.022 10.422,19.9 10.8,19.9C11.178,19.9 11.489,20.022 11.733,20.267C11.978,20.511 12.1,20.822 12.1,21.2C12.1,21.578 11.978,21.889 11.733,22.133L8.533,25.333ZM23.467,25.333L20.267,22.133C20.022,21.889 19.9,21.578 19.9,21.2C19.9,20.822 20.022,20.511 20.267,20.267C20.511,20.022 20.822,19.9 21.2,19.9C21.578,19.9 21.889,20.022 22.133,20.267L25.333,23.467V21.333C25.333,20.956 25.456,20.644 25.7,20.4C25.967,20.133 26.289,20 26.667,20C27.044,20 27.356,20.133 27.6,20.4C27.867,20.644 28,20.956 28,21.333V26.667C28,27.044 27.867,27.367 27.6,27.633C27.356,27.878 27.044,28 26.667,28H21.333C20.956,28 20.633,27.878 20.367,27.633C20.122,27.367 20,27.044 20,26.667C20,26.289 20.122,25.978 20.367,25.733C20.633,25.467 20.956,25.333 21.333,25.333H23.467ZM6.667,8.533V10.667C6.667,11.044 6.533,11.367 6.267,11.633C6.022,11.878 5.711,12 5.333,12C4.956,12 4.633,11.878 4.367,11.633C4.122,11.367 4,11.044 4,10.667V5.333C4,4.956 4.122,4.644 4.367,4.4C4.633,4.133 4.956,4 5.333,4H10.667C11.044,4 11.356,4.133 11.6,4.4C11.867,4.644 12,4.956 12,5.333C12,5.711 11.867,6.033 11.6,6.3C11.356,6.544 11.044,6.667 10.667,6.667H8.533L11.733,9.867C11.978,10.111 12.1,10.422 12.1,10.8C12.1,11.178 11.978,11.489 11.733,11.733C11.489,11.978 11.178,12.1 10.8,12.1C10.422,12.1 10.111,11.978 9.867,11.733L6.667,8.533ZM25.333,8.533L22.133,11.733C21.889,11.978 21.578,12.1 21.2,12.1C20.822,12.1 20.511,11.978 20.267,11.733C20.022,11.489 19.9,11.178 19.9,10.8C19.9,10.422 20.022,10.111 20.267,9.867L23.467,6.667H21.333C20.956,6.667 20.633,6.544 20.367,6.3C20.122,6.033 20,5.711 20,5.333C20,4.956 20.122,4.644 20.367,4.4C20.633,4.133 20.956,4 21.333,4H26.667C27.044,4 27.356,4.133 27.6,4.4C27.867,4.644 28,4.956 28,5.333V10.667C28,11.044 27.867,11.367 27.6,11.633C27.356,11.878 27.044,12 26.667,12C26.289,12 25.967,11.878 25.7,11.633C25.456,11.367 25.333,11.044 25.333,10.667V8.533Z"
+ android:fillColor="?androidprv:attr/materialColorPrimary"/>
</group>
</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_action_layout.xml b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_action_layout.xml
index c77a4fd..bda087b 100644
--- a/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_action_layout.xml
+++ b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_action_layout.xml
@@ -24,10 +24,10 @@
<ImageView
android:id="@+id/letterbox_education_dialog_action_icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
android:layout_gravity="center"
- android:layout_marginBottom="20dp"/>
+ android:layout_marginBottom="12dp"/>
<TextView
android:fontFamily="@*android:string/config_bodyFontFamily"
@@ -37,7 +37,7 @@
android:layout_height="wrap_content"
android:lineSpacingExtra="4sp"
android:textAlignment="center"
- android:textColor="?android:attr/textColorSecondary"
+ android:textColor="?androidprv:attr/materialColorOnSurface"
android:textSize="14sp"/>
</LinearLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml
index 4d52567..488123a 100644
--- a/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml
+++ b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml
@@ -45,19 +45,16 @@
android:layout_height="wrap_content">
<LinearLayout
+ android:padding="24dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
- android:orientation="vertical"
- android:paddingTop="32dp"
- android:paddingBottom="32dp"
- android:paddingLeft="56dp"
- android:paddingRight="56dp">
+ android:orientation="vertical">
<ImageView
android:layout_width="@dimen/letterbox_education_dialog_title_icon_width"
android:layout_height="@dimen/letterbox_education_dialog_title_icon_height"
- android:layout_marginBottom="17dp"
+ android:layout_marginBottom="16dp"
android:src="@drawable/letterbox_education_ic_light_bulb"/>
<TextView
@@ -67,9 +64,8 @@
android:lineSpacingExtra="4sp"
android:text="@string/letterbox_education_dialog_title"
android:textAlignment="center"
- android:textColor="?android:attr/textColorPrimary"
- android:fontFamily="@*android:string/config_bodyFontFamilyMedium"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Headline"
+ android:textColor="?androidprv:attr/materialColorOnSurface"
+ android:fontFamily="@*android:string/config_headlineFontFamily"
android:textSize="24sp"/>
<LinearLayout
@@ -77,7 +73,8 @@
android:layout_height="wrap_content"
android:gravity="top"
android:orientation="horizontal"
- android:paddingTop="48dp">
+ android:layout_marginHorizontal="18dp"
+ android:layout_marginVertical="@dimen/letterbox_education_dialog_margin">
<com.android.wm.shell.compatui.LetterboxEduDialogActionLayout
android:layout_width="wrap_content"
@@ -101,15 +98,13 @@
android:lineHeight="20dp"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Small"
android:id="@+id/letterbox_education_dialog_dismiss_button"
- android:textStyle="bold"
android:layout_width="match_parent"
android:layout_height="56dp"
- android:layout_marginTop="40dp"
android:textSize="14sp"
android:background=
"@drawable/letterbox_education_dismiss_button_background_ripple"
android:text="@string/letterbox_education_got_it"
- android:textColor="?android:attr/textColorPrimaryInverse"
+ android:textColor="?androidprv:attr/materialColorOnPrimary"
android:textAlignment="center"
android:contentDescription="@string/letterbox_education_got_it"/>
diff --git a/libs/WindowManager/Shell/res/layout/letterbox_restart_dialog_layout.xml b/libs/WindowManager/Shell/res/layout/letterbox_restart_dialog_layout.xml
index 7f1aac3..045b975 100644
--- a/libs/WindowManager/Shell/res/layout/letterbox_restart_dialog_layout.xml
+++ b/libs/WindowManager/Shell/res/layout/letterbox_restart_dialog_layout.xml
@@ -70,26 +70,27 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/letterbox_restart_dialog_description"
- android:textAlignment="center"/>
+ android:gravity="start"/>
<LinearLayout
android:id="@+id/letterbox_restart_dialog_checkbox_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingVertical="14dp"
+ android:paddingVertical="16dp"
android:orientation="horizontal"
android:layout_gravity="center_vertical"
- android:layout_marginVertical="18dp">
+ android:layout_marginVertical="16dp">
<CheckBox
android:id="@+id/letterbox_restart_dialog_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginStart="12dp"
android:button="@drawable/letterbox_restart_checkbox_button"/>
<TextView
android:textAppearance="@style/RestartDialogCheckboxText"
- android:layout_marginStart="12dp"
+ android:layout_marginStart="20dp"
android:id="@+id/letterbox_restart_dialog_checkbox_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -101,16 +102,19 @@
<FrameLayout
android:minHeight="@dimen/letterbox_restart_dialog_button_height"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
+ android:layout_height="wrap_content"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_gravity="end">
<Button
android:textAppearance="@style/RestartDialogDismissButton"
android:id="@+id/letterbox_restart_dialog_dismiss_button"
+ style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginRight="8dp"
android:minWidth="@dimen/letterbox_restart_dialog_button_width"
android:minHeight="@dimen/letterbox_restart_dialog_button_height"
- android:layout_gravity="start"
android:background=
"@drawable/letterbox_restart_dismiss_button_background_ripple"
android:text="@string/letterbox_restart_cancel"
@@ -119,11 +123,11 @@
<Button
android:textAppearance="@style/RestartDialogConfirmButton"
android:id="@+id/letterbox_restart_dialog_restart_button"
+ style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="@dimen/letterbox_restart_dialog_button_width"
android:minHeight="@dimen/letterbox_restart_dialog_button_height"
- android:layout_gravity="end"
android:background=
"@drawable/letterbox_restart_button_background_ripple"
android:text="@string/letterbox_restart_restart"
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index cf18da6..b7aa158 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -16,7 +16,8 @@
* limitations under the License.
*/
-->
-<resources>
+<resources
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<color name="docked_divider_handle">#ffffff</color>
<color name="split_divider_background">@color/taskbar_background_dark</color>
<drawable name="forced_resizable_background">#59000000</drawable>
@@ -42,7 +43,7 @@
<color name="compat_controls_text">@android:color/system_neutral1_50</color>
<!-- Letterbox Education -->
- <color name="letterbox_education_text_secondary">@android:color/system_neutral2_200</color>
+ <color name="letterbox_education_text_secondary">?androidprv:attr/materialColorSecondary</color>
<!-- Letterbox Dialog -->
<color name="letterbox_dialog_background">@android:color/system_neutral1_900</color>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index c76c470..3d87183 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -304,31 +304,31 @@
<dimen name="letterbox_education_dialog_corner_radius">28dp</dimen>
<!-- The width of the top icon in the letterbox education dialog. -->
- <dimen name="letterbox_education_dialog_title_icon_width">45dp</dimen>
+ <dimen name="letterbox_education_dialog_title_icon_width">32dp</dimen>
<!-- The height of the top icon in the letterbox education dialog. -->
- <dimen name="letterbox_education_dialog_title_icon_height">44dp</dimen>
+ <dimen name="letterbox_education_dialog_title_icon_height">32dp</dimen>
<!-- The width of an icon in the letterbox education dialog. -->
- <dimen name="letterbox_education_dialog_icon_width">40dp</dimen>
+ <dimen name="letterbox_education_dialog_icon_width">28dp</dimen>
<!-- The height of an icon in the letterbox education dialog. -->
- <dimen name="letterbox_education_dialog_icon_height">32dp</dimen>
+ <dimen name="letterbox_education_dialog_icon_height">22dp</dimen>
<!-- The fixed width of the dialog if there is enough space in the parent. -->
- <dimen name="letterbox_education_dialog_width">472dp</dimen>
+ <dimen name="letterbox_education_dialog_width">364dp</dimen>
<!-- The margin between the dialog container and its parent. -->
- <dimen name="letterbox_education_dialog_margin">16dp</dimen>
+ <dimen name="letterbox_education_dialog_margin">24dp</dimen>
<!-- The width of each action container in the letterbox education dialog -->
- <dimen name="letterbox_education_dialog_action_width">140dp</dimen>
+ <dimen name="letterbox_education_dialog_action_width">124dp</dimen>
<!-- The space between two actions in the letterbox education dialog -->
- <dimen name="letterbox_education_dialog_space_between_actions">24dp</dimen>
+ <dimen name="letterbox_education_dialog_space_between_actions">32dp</dimen>
<!-- The corner radius of the buttons in the letterbox education dialog -->
- <dimen name="letterbox_education_dialog_button_radius">12dp</dimen>
+ <dimen name="letterbox_education_dialog_button_radius">28dp</dimen>
<!-- The horizontal padding for the buttons in the letterbox education dialog -->
<dimen name="letterbox_education_dialog_horizontal_padding">16dp</dimen>
@@ -367,7 +367,7 @@
<dimen name="letterbox_restart_dialog_button_width">82dp</dimen>
<!-- The width of the buttons in the restart dialog -->
- <dimen name="letterbox_restart_dialog_button_height">36dp</dimen>
+ <dimen name="letterbox_restart_dialog_button_height">40dp</dimen>
<!-- The corner radius of the buttons in the restart dialog -->
<dimen name="letterbox_restart_dialog_button_radius">18dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index 13c0e66..d061ae1 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -82,7 +82,7 @@
<item name="android:textColor">@color/tv_pip_edu_text</item>
</style>
- <style name="LetterboxDialog" parent="@android:style/Theme.Holo">
+ <style name="LetterboxDialog" parent="@android:style/Theme.DeviceDefault.Dialog.Alert">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:background">@color/letterbox_dialog_background</item>
@@ -90,7 +90,7 @@
<style name="RestartDialogTitleText">
<item name="android:textSize">24sp</item>
- <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
<item name="android:lineSpacingExtra">8sp</item>
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
</style>
@@ -102,23 +102,23 @@
<style name="RestartDialogBodyText" parent="RestartDialogBodyStyle">
<item name="android:letterSpacing">0.02</item>
- <item name="android:textColor">?android:attr/textColorSecondary</item>
+ <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item>
<item name="android:lineSpacingExtra">6sp</item>
</style>
<style name="RestartDialogCheckboxText" parent="RestartDialogBodyStyle">
- <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
<item name="android:lineSpacingExtra">6sp</item>
</style>
<style name="RestartDialogDismissButton" parent="RestartDialogBodyStyle">
<item name="android:lineSpacingExtra">2sp</item>
- <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:textColor">?androidprv:attr/materialColorPrimary</item>
</style>
<style name="RestartDialogConfirmButton" parent="RestartDialogBodyStyle">
<item name="android:lineSpacingExtra">2sp</item>
- <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
+ <item name="android:textColor">?androidprv:attr/materialColorOnPrimary</item>
</style>
<style name="ReachabilityEduHandLayout" parent="Theme.AppCompat.Light">
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
index 9027bf3..88878c6 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
@@ -40,6 +40,7 @@
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
+import android.app.TaskInfo;
import android.app.WindowConfiguration;
import android.graphics.Rect;
import android.util.ArrayMap;
@@ -339,6 +340,52 @@
return target;
}
+ /**
+ * Creates a new RemoteAnimationTarget from the provided change and leash
+ */
+ public static RemoteAnimationTarget newSyntheticTarget(ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl leash, @TransitionInfo.TransitionMode int mode, int order,
+ boolean isTranslucent) {
+ int taskId;
+ boolean isNotInRecents;
+ WindowConfiguration windowConfiguration;
+
+ if (taskInfo != null) {
+ taskId = taskInfo.taskId;
+ isNotInRecents = !taskInfo.isRunning;
+ windowConfiguration = taskInfo.configuration.windowConfiguration;
+ } else {
+ taskId = INVALID_TASK_ID;
+ isNotInRecents = true;
+ windowConfiguration = new WindowConfiguration();
+ }
+
+ Rect localBounds = new Rect();
+ RemoteAnimationTarget target = new RemoteAnimationTarget(
+ taskId,
+ newModeToLegacyMode(mode),
+ // TODO: once we can properly sync transactions across process,
+ // then get rid of this leash.
+ leash,
+ isTranslucent,
+ null,
+ // TODO(shell-transitions): we need to send content insets? evaluate how its used.
+ new Rect(0, 0, 0, 0),
+ order,
+ null,
+ localBounds,
+ new Rect(),
+ windowConfiguration,
+ isNotInRecents,
+ null,
+ new Rect(),
+ taskInfo,
+ false,
+ INVALID_WINDOW_TYPE
+ );
+ return target;
+ }
+
private static RemoteAnimationTarget getDividerTarget(TransitionInfo.Change change,
SurfaceControl leash) {
return new RemoteAnimationTarget(-1 /* taskId */, newModeToLegacyMode(change.getMode()),
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
index 3f97117..83619ef 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
@@ -90,8 +90,15 @@
/** The maximum override density allowed for tasks inside the desktop. */
private static final int DESKTOP_DENSITY_MAX = 1000;
- /** The number of [WindowDecorViewHost] instances to warm up on system start. */
- private static final int WINDOW_DECOR_PRE_WARM_SIZE = 2;
+ /**
+ * Sysprop declaring whether to enters desktop mode by default when the windowing mode of the
+ * display's root TaskDisplayArea is set to WINDOWING_MODE_FREEFORM.
+ *
+ * <p>If it is not defined, then {@code R.integer.config_enterDesktopByDefaultOnFreeformDisplay}
+ * is used.
+ */
+ public static final String ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAY_SYS_PROP =
+ "persist.wm.debug.enter_desktop_by_default_on_freeform_display";
/**
* Sysprop declaring the maximum number of Tasks to show in Desktop Mode at any one time.
@@ -105,14 +112,6 @@
private static final String MAX_TASK_LIMIT_SYS_PROP = "persist.wm.debug.desktop_max_task_limit";
/**
- * Sysprop declaring the number of [WindowDecorViewHost] instances to warm up on system start.
- *
- * <p>If it is not defined, then [WINDOW_DECOR_PRE_WARM_SIZE] is used.
- */
- private static final String WINDOW_DECOR_PRE_WARM_SIZE_SYS_PROP =
- "persist.wm.debug.desktop_window_decor_pre_warm_size";
-
- /**
* Return {@code true} if veiled resizing is active. If false, fluid resizing is used.
*/
public static boolean isVeiledResizeEnabled() {
@@ -152,12 +151,6 @@
context.getResources().getInteger(R.integer.config_maxDesktopWindowingActiveTasks));
}
- /** The number of [WindowDecorViewHost] instances to warm up on system start. */
- public static int getWindowDecorPreWarmSize() {
- return SystemProperties.getInt(WINDOW_DECOR_PRE_WARM_SIZE_SYS_PROP,
- WINDOW_DECOR_PRE_WARM_SIZE);
- }
-
/**
* Return {@code true} if the current device supports desktop mode.
*/
@@ -223,6 +216,19 @@
return !enforceDeviceRestrictions() || isDesktopModeSupported(context);
}
+ /**
+ * Return {@code true} if a display should enter desktop mode by default when the windowing mode
+ * of the display's root [TaskDisplayArea] is set to WINDOWING_MODE_FREEFORM.
+ */
+ public static boolean enterDesktopByDefaultOnFreeformDisplay(@NonNull Context context) {
+ if (!Flags.enterDesktopByDefaultOnFreeformDisplays()) {
+ return false;
+ }
+ return SystemProperties.getBoolean(ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAY_SYS_PROP,
+ context.getResources().getBoolean(
+ R.bool.config_enterDesktopByDefaultOnFreeformDisplay));
+ }
+
/** Dumps DesktopModeStatus flags and configs. */
public static void dump(PrintWriter pw, String prefix, Context context) {
String innerPrefix = prefix + " ";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 452d12a..7e6f434 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -46,7 +46,6 @@
import android.util.SparseArray;
import android.view.SurfaceControl;
import android.window.ITaskOrganizerController;
-import android.window.ScreenCapture;
import android.window.StartingWindowInfo;
import android.window.StartingWindowRemovalInfo;
import android.window.TaskAppearedInfo;
@@ -55,7 +54,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.FrameworkStatsLog;
-import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.compatui.api.CompatUIHandler;
@@ -74,7 +72,6 @@
import java.util.List;
import java.util.Objects;
import java.util.Optional;
-import java.util.function.Consumer;
/**
* Unified task organizer for all components in the shell.
@@ -561,19 +558,6 @@
mRecentTasks.ifPresent(recentTasks -> recentTasks.onTaskAdded(info.getTaskInfo()));
}
- /**
- * Take a screenshot of a task.
- */
- public void screenshotTask(RunningTaskInfo taskInfo, Rect crop,
- Consumer<ScreenCapture.ScreenshotHardwareBuffer> consumer) {
- final TaskAppearedInfo info = mTasks.get(taskInfo.taskId);
- if (info == null) {
- return;
- }
- ScreenshotUtils.captureLayer(info.getLeash(), crop, consumer);
- }
-
-
@Override
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
synchronized (mLock) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index f478b44..3e5adf3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -117,6 +117,7 @@
*/
private static final long MAX_ANIMATION_DURATION = 2000;
private final LatencyTracker mLatencyTracker;
+ @ShellMainThread private final Handler mHandler;
/** True when a back gesture is ongoing */
private boolean mBackGestureStarted = false;
@@ -218,7 +219,8 @@
@NonNull BackAnimationBackground backAnimationBackground,
ShellBackAnimationRegistry shellBackAnimationRegistry,
ShellCommandHandler shellCommandHandler,
- Transitions transitions) {
+ Transitions transitions,
+ @ShellMainThread Handler handler) {
this(
shellInit,
shellController,
@@ -230,7 +232,8 @@
backAnimationBackground,
shellBackAnimationRegistry,
shellCommandHandler,
- transitions);
+ transitions,
+ handler);
}
@VisibleForTesting
@@ -245,7 +248,8 @@
@NonNull BackAnimationBackground backAnimationBackground,
ShellBackAnimationRegistry shellBackAnimationRegistry,
ShellCommandHandler shellCommandHandler,
- Transitions transitions) {
+ Transitions transitions,
+ @NonNull @ShellMainThread Handler handler) {
mShellController = shellController;
mShellExecutor = shellExecutor;
mActivityTaskManager = activityTaskManager;
@@ -263,6 +267,7 @@
mTransitions = transitions;
mBackTransitionHandler = new BackTransitionHandler();
mTransitions.addHandler(mBackTransitionHandler);
+ mHandler = handler;
updateTouchableArea();
}
@@ -399,7 +404,7 @@
}
}
- private static class IBackAnimationImpl extends IBackAnimation.Stub
+ private class IBackAnimationImpl extends IBackAnimation.Stub
implements ExternalInterfaceBinder {
private BackAnimationController mController;
@@ -417,7 +422,8 @@
callback,
runner,
controller.mContext,
- CUJ_PREDICTIVE_BACK_HOME)));
+ CUJ_PREDICTIVE_BACK_HOME,
+ mHandler)));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index e24df0b..9ca9b73 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.content.Context;
+import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
import android.view.IRemoteAnimationFinishedCallback;
@@ -31,6 +32,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.Cuj.CujType;
import com.android.internal.jank.InteractionJankMonitor;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
/**
* Used to register the animation callback and runner, it will trigger result if gesture was finish
@@ -45,6 +47,8 @@
private final IRemoteAnimationRunner mRunner;
private final @CujType int mCujType;
private final Context mContext;
+ @ShellMainThread
+ private final Handler mHandler;
// Whether we are waiting to receive onAnimationStart
private boolean mWaitingAnimation;
@@ -56,18 +60,35 @@
@NonNull IOnBackInvokedCallback callback,
@NonNull IRemoteAnimationRunner runner,
@NonNull Context context,
- @CujType int cujType) {
+ @CujType int cujType,
+ @ShellMainThread Handler handler) {
mCallback = callback;
mRunner = runner;
mCujType = cujType;
mContext = context;
+ mHandler = handler;
}
public BackAnimationRunner(
@NonNull IOnBackInvokedCallback callback,
@NonNull IRemoteAnimationRunner runner,
- @NonNull Context context) {
- this(callback, runner, context, NO_CUJ);
+ @NonNull Context context,
+ @ShellMainThread Handler handler
+ ) {
+ this(callback, runner, context, NO_CUJ, handler);
+ }
+
+ /**
+ * @deprecated Use {@link BackAnimationRunner} constructor providing an handler for the ui
+ * thread of the animation.
+ */
+ @Deprecated
+ public BackAnimationRunner(
+ @NonNull IOnBackInvokedCallback callback,
+ @NonNull IRemoteAnimationRunner runner,
+ @NonNull Context context
+ ) {
+ this(callback, runner, context, NO_CUJ, context.getMainThreadHandler());
}
/** Returns the registered animation runner */
@@ -100,7 +121,7 @@
mWaitingAnimation = false;
if (shouldMonitorCUJ(apps)) {
interactionJankMonitor.begin(
- apps[0].leash, mContext, mCujType);
+ apps[0].leash, mContext, mHandler, mCujType);
}
try {
getRunner().onAnimationStart(TRANSIT_OLD_UNSET, apps, wallpapers,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
index 32e809a..3733930 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
@@ -26,6 +26,7 @@
import android.graphics.PointF
import android.graphics.Rect
import android.graphics.RectF
+import android.os.Handler
import android.os.RemoteException
import android.util.TimeUtils
import android.view.Choreographer
@@ -53,6 +54,7 @@
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup
import com.android.wm.shell.shared.animation.Interpolators
+import com.android.wm.shell.shared.annotations.ShellMainThread
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
@@ -61,7 +63,8 @@
private val context: Context,
private val background: BackAnimationBackground,
private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
- protected val transaction: SurfaceControl.Transaction
+ protected val transaction: SurfaceControl.Transaction,
+ @ShellMainThread handler: Handler,
) : ShellBackAnimation() {
protected val startClosingRect = RectF()
@@ -80,7 +83,13 @@
private var statusbarHeight = SystemBarUtils.getStatusBarHeight(context)
private val backAnimationRunner =
- BackAnimationRunner(Callback(), Runner(), context, Cuj.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY)
+ BackAnimationRunner(
+ Callback(),
+ Runner(),
+ context,
+ Cuj.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY,
+ handler,
+ )
private val initialTouchPos = PointF()
private val transformMatrix = Matrix()
private val tmpFloat9 = FloatArray(9)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index 3fcceca..7a56979 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -34,6 +34,7 @@
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.os.Handler;
import android.os.RemoteException;
import android.view.Choreographer;
import android.view.IRemoteAnimationFinishedCallback;
@@ -52,6 +53,7 @@
import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.shared.animation.Interpolators;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import javax.inject.Inject;
@@ -113,9 +115,10 @@
private float mVerticalMargin;
@Inject
- public CrossTaskBackAnimation(Context context, BackAnimationBackground background) {
+ public CrossTaskBackAnimation(Context context, BackAnimationBackground background,
+ @ShellMainThread Handler handler) {
mBackAnimationRunner = new BackAnimationRunner(
- new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_TASK);
+ new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_TASK, handler);
mBackground = background;
mContext = context;
loadResources();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
index b02f97b..2f7666b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.graphics.Rect
import android.graphics.RectF
+import android.os.Handler
import android.util.MathUtils
import android.view.SurfaceControl
import android.view.animation.Animation
@@ -30,6 +31,7 @@
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup
+import com.android.wm.shell.shared.annotations.ShellMainThread
import javax.inject.Inject
import kotlin.math.max
import kotlin.math.min
@@ -40,13 +42,15 @@
background: BackAnimationBackground,
rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
transaction: SurfaceControl.Transaction,
- private val customAnimationLoader: CustomAnimationLoader
+ private val customAnimationLoader: CustomAnimationLoader,
+ @ShellMainThread handler: Handler,
) :
CrossActivityBackAnimation(
context,
background,
rootTaskDisplayAreaOrganizer,
- transaction
+ transaction,
+ handler
) {
private var enterAnimation: Animation? = null
@@ -59,7 +63,8 @@
constructor(
context: Context,
background: BackAnimationBackground,
- rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+ rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ @ShellMainThread handler: Handler,
) : this(
context,
background,
@@ -67,7 +72,8 @@
SurfaceControl.Transaction(),
CustomAnimationLoader(
TransitionAnimation(context, false /* debug */, "CustomCrossActivityBackAnimation")
- )
+ ),
+ handler,
)
override fun preparePreCommitClosingRectMovement(swipeEdge: Int) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
index 66d8a5f..eecd769 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
@@ -16,11 +16,13 @@
package com.android.wm.shell.back
import android.content.Context
+import android.os.Handler
import android.view.SurfaceControl
import android.window.BackEvent
import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.shared.animation.Interpolators
+import com.android.wm.shell.shared.annotations.ShellMainThread
import javax.inject.Inject
import kotlin.math.max
@@ -30,13 +32,15 @@
constructor(
context: Context,
background: BackAnimationBackground,
- rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+ rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ @ShellMainThread handler: Handler,
) :
CrossActivityBackAnimation(
context,
background,
rootTaskDisplayAreaOrganizer,
- SurfaceControl.Transaction()
+ SurfaceControl.Transaction(),
+ handler
) {
private val postCommitInterpolator = Interpolators.EMPHASIZED
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 c545d73..af4a0c5 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
@@ -1241,8 +1241,9 @@
mBubbleData.dismissBubbleWithKey(
bubbleKey, Bubbles.DISMISS_USER_GESTURE_FROM_LAUNCHER, timestamp);
}
- if (selectedBubbleKey != null && !selectedBubbleKey.equals(bubbleKey)) {
- // We did not remove the selected bubble. Expand it again
+ if (mBubbleData.hasBubbles()) {
+ // We still have bubbles, if we dragged an individual bubble to dismiss we were expanded
+ // so re-expand to whatever is selected.
showExpandedViewForBubbleBar();
}
}
@@ -2007,7 +2008,7 @@
@Override
public void selectionChanged(BubbleViewProvider selectedBubble) {
// Only need to update the layer view if we're currently expanded for selection changes.
- if (mLayerView != null && isStackExpanded()) {
+ if (mLayerView != null && mLayerView.isExpanded()) {
mLayerView.showExpandedView(selectedBubble);
}
}
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 1c9c195..1367b7e 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
@@ -186,6 +186,10 @@
if (expandedView == null) {
return;
}
+ if (mExpandedBubble != null && mIsExpanded && b.getKey().equals(mExpandedBubble.getKey())) {
+ // Already showing this bubble, skip animating
+ return;
+ }
if (mExpandedBubble != null && !b.getKey().equals(mExpandedBubble.getKey())) {
removeView(mExpandedView);
mExpandedView = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index b8aa1b1..4b55fd0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -48,6 +48,7 @@
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
+import android.os.Handler;
import android.view.Display;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
@@ -75,6 +76,7 @@
import com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.animation.Interpolators;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;
import com.android.wm.shell.shared.split.SplitScreenConstants.SnapPosition;
import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
@@ -116,6 +118,8 @@
new PathInterpolator(0.2f, 0f, 0f, 1f);
private static final Interpolator GROW_INTERPOLATOR =
new PathInterpolator(0.45f, 0f, 0.5f, 1f);
+ @ShellMainThread
+ private final Handler mHandler;
private int mDividerWindowWidth;
private int mDividerInsets;
@@ -166,7 +170,8 @@
SplitLayoutHandler splitLayoutHandler,
SplitWindowManager.ParentContainerCallbacks parentContainerCallbacks,
DisplayController displayController, DisplayImeController displayImeController,
- ShellTaskOrganizer taskOrganizer, int parallaxType) {
+ ShellTaskOrganizer taskOrganizer, int parallaxType, @ShellMainThread Handler handler) {
+ mHandler = handler;
mContext = context.createConfigurationContext(configuration);
mOrientation = configuration.orientation;
mRotation = configuration.windowConfiguration.getRotation();
@@ -598,7 +603,8 @@
}
void onStartDragging() {
- mInteractionJankMonitor.begin(getDividerLeash(), mContext, CUJ_SPLIT_SCREEN_RESIZE);
+ mInteractionJankMonitor.begin(getDividerLeash(), mContext, mHandler,
+ CUJ_SPLIT_SCREEN_RESIZE);
}
void onDraggingCancelled() {
@@ -756,7 +762,7 @@
@Override
public void onAnimationStart(Animator animation) {
mInteractionJankMonitor.begin(getDividerLeash(),
- mContext, CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER);
+ mContext, mHandler, CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 4adea23..722fe1f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -444,7 +444,9 @@
BackAnimationBackground backAnimationBackground,
Optional<ShellBackAnimationRegistry> shellBackAnimationRegistry,
ShellCommandHandler shellCommandHandler,
- Transitions transitions) {
+ Transitions transitions,
+ @ShellMainThread Handler handler
+ ) {
if (BackAnimationController.IS_ENABLED) {
return shellBackAnimationRegistry.map(
(animations) ->
@@ -457,7 +459,8 @@
backAnimationBackground,
animations,
shellCommandHandler,
- transitions));
+ transitions,
+ handler));
}
return Optional.empty();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index b151c8b..584f272 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -75,6 +75,7 @@
import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator;
import com.android.wm.shell.desktopmode.SpringDragToDesktopTransitionHandler;
import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler;
+import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
import com.android.wm.shell.desktopmode.education.AppHandleEducationController;
import com.android.wm.shell.desktopmode.education.AppHandleEducationFilter;
import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository;
@@ -116,8 +117,6 @@
import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
import com.android.wm.shell.windowdecor.viewhost.DefaultWindowDecorViewHostSupplier;
-import com.android.wm.shell.windowdecor.viewhost.PooledWindowDecorViewHostSupplier;
-import com.android.wm.shell.windowdecor.viewhost.ReusableWindowDecorViewHost;
import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier;
import dagger.Binds;
@@ -143,7 +142,7 @@
includes = {
WMShellBaseModule.class,
PipModule.class,
- ShellBackAnimationModule.class,
+ ShellBackAnimationModule.class
})
public abstract class WMShellModule {
@@ -249,6 +248,7 @@
AssistContentRequester assistContentRequester,
MultiInstanceHelper multiInstanceHelper,
Optional<DesktopTasksLimiter> desktopTasksLimiter,
+ WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
Optional<DesktopActivityOrientationChangeHandler> desktopActivityOrientationHandler,
WindowDecorViewHostSupplier windowDecorViewHostSupplier) {
if (DesktopModeStatus.canEnterDesktopMode(context)) {
@@ -274,6 +274,7 @@
assistContentRequester,
multiInstanceHelper,
desktopTasksLimiter,
+ windowDecorCaptionHandleRepository,
desktopActivityOrientationHandler,
windowDecorViewHostSupplier);
}
@@ -355,7 +356,8 @@
@ShellMainThread ShellExecutor mainExecutor,
@ShellAnimationThread ShellExecutor animExecutor,
@DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
- InteractionJankMonitor interactionJankMonitor) {
+ InteractionJankMonitor interactionJankMonitor,
+ @ShellMainThread Handler handler) {
return new FreeformTaskTransitionHandler(
shellInit,
transitions,
@@ -365,7 +367,8 @@
mainExecutor,
animExecutor,
desktopModeTaskRepository,
- interactionJankMonitor);
+ interactionJankMonitor,
+ handler);
}
@WMSingleton
@@ -382,19 +385,8 @@
@WMSingleton
@Provides
static WindowDecorViewHostSupplier provideWindowDecorViewHostSupplier(
- @NonNull Context context,
- @ShellMainThread @NonNull CoroutineScope mainScope,
- @NonNull ShellInit shellInit) {
- if (DesktopModeStatus.canEnterDesktopMode(context)
- && Flags.enableDesktopWindowingScvhCache()) {
- final int maxPoolSize = DesktopModeStatus.getMaxTaskLimit(context);
- final int preWarmSize = DesktopModeStatus.getWindowDecorPreWarmSize();
- return new PooledWindowDecorViewHostSupplier(
- context, mainScope, shellInit,
- ReusableWindowDecorViewHost.DefaultFactory.INSTANCE, maxPoolSize, preWarmSize);
- } else {
- return new DefaultWindowDecorViewHostSupplier(mainScope);
- }
+ @ShellMainThread @NonNull CoroutineScope mainScope) {
+ return new DefaultWindowDecorViewHostSupplier(mainScope);
}
//
@@ -487,10 +479,11 @@
@Provides
static RecentsTransitionHandler provideRecentsTransitionHandler(
ShellInit shellInit,
+ ShellTaskOrganizer shellTaskOrganizer,
Transitions transitions,
Optional<RecentTasksController> recentTasksController,
HomeTransitionObserver homeTransitionObserver) {
- return new RecentsTransitionHandler(shellInit, transitions,
+ return new RecentsTransitionHandler(shellInit, shellTaskOrganizer, transitions,
recentTasksController.orElse(null), homeTransitionObserver);
}
@@ -616,6 +609,7 @@
RecentsTransitionHandler recentsTransitionHandler,
MultiInstanceHelper multiInstanceHelper,
@ShellMainThread ShellExecutor mainExecutor,
+ @ShellMainThread Handler mainHandler,
Optional<DesktopTasksLimiter> desktopTasksLimiter,
Optional<RecentTasksController> recentTasksController,
InteractionJankMonitor interactionJankMonitor) {
@@ -628,7 +622,7 @@
dragToDesktopTransitionHandler, desktopModeTaskRepository,
desktopModeLoggerTransitionObserver, launchAdjacentController,
recentsTransitionHandler, multiInstanceHelper, mainExecutor, desktopTasksLimiter,
- recentTasksController.orElse(null), interactionJankMonitor);
+ recentTasksController.orElse(null), interactionJankMonitor, mainHandler);
}
@WMSingleton
@@ -638,7 +632,8 @@
Transitions transitions,
@DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
ShellTaskOrganizer shellTaskOrganizer,
- InteractionJankMonitor interactionJankMonitor) {
+ InteractionJankMonitor interactionJankMonitor,
+ @ShellMainThread Handler handler) {
int maxTaskLimit = DesktopModeStatus.getMaxTaskLimit(context);
if (!DesktopModeStatus.canEnterDesktopMode(context)
|| !ENABLE_DESKTOP_WINDOWING_TASK_LIMIT.isEnabled(context)
@@ -652,7 +647,8 @@
shellTaskOrganizer,
maxTaskLimit,
interactionJankMonitor,
- context)
+ context,
+ handler)
);
}
@@ -699,9 +695,10 @@
static ExitDesktopTaskTransitionHandler provideExitDesktopTaskTransitionHandler(
Transitions transitions,
Context context,
- InteractionJankMonitor interactionJankMonitor) {
+ InteractionJankMonitor interactionJankMonitor,
+ @ShellMainThread Handler handler) {
return new ExitDesktopTaskTransitionHandler(
- transitions, context, interactionJankMonitor);
+ transitions, context, interactionJankMonitor, handler);
}
@WMSingleton
@@ -786,6 +783,12 @@
@WMSingleton
@Provides
+ static WindowDecorCaptionHandleRepository provideAppHandleRepository() {
+ return new WindowDecorCaptionHandleRepository();
+ }
+
+ @WMSingleton
+ @Provides
static AppHandleEducationController provideAppHandleEducationController(
AppHandleEducationFilter appHandleEducationFilter,
ShellTaskOrganizer shellTaskOrganizer,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index 037fbb2..3a4764d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -101,7 +101,8 @@
DisplayInsetsController displayInsetsController,
TabletopModeController pipTabletopController,
Optional<OneHandedController> oneHandedController,
- @ShellMainThread ShellExecutor mainExecutor) {
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ShellMainThread Handler handler) {
return Optional.ofNullable(PipController.create(
context, shellInit, shellCommandHandler, shellController,
displayController, pipAnimationController, pipAppOpsListener,
@@ -111,7 +112,7 @@
pipTransitionState, pipTouchHandler, pipTransitionController,
windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
displayInsetsController, pipTabletopController, oneHandedController,
- mainExecutor));
+ mainExecutor, handler));
}
// Handler is used by Icon.loadDrawableAsync
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
index c8ffe28..bd61722 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -162,7 +162,7 @@
fun calculateAspectRatio(taskInfo: RunningTaskInfo): Float {
val appLetterboxWidth = taskInfo.appCompatTaskInfo.topActivityLetterboxAppWidth
val appLetterboxHeight = taskInfo.appCompatTaskInfo.topActivityLetterboxAppHeight
- if (taskInfo.appCompatTaskInfo.isTopActivityLetterboxed) {
+ if (taskInfo.appCompatTaskInfo.isTopActivityLetterboxed || !taskInfo.canChangeAspectRatio) {
return maxOf(appLetterboxWidth, appLetterboxHeight) /
minOf(appLetterboxWidth, appLetterboxHeight).toFloat()
}
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 7e96253..b8ebbcd 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
@@ -35,6 +35,7 @@
import android.graphics.Rect
import android.graphics.Region
import android.os.Binder
+import android.os.Handler
import android.os.IBinder
import android.os.SystemProperties
import android.util.Size
@@ -136,7 +137,8 @@
@ShellMainThread private val mainExecutor: ShellExecutor,
private val desktopTasksLimiter: Optional<DesktopTasksLimiter>,
private val recentTasksController: RecentTasksController?,
- private val interactionJankMonitor: InteractionJankMonitor
+ private val interactionJankMonitor: InteractionJankMonitor,
+ @ShellMainThread private val handler: Handler,
) :
RemoteCallable<DesktopTasksController>,
Transitions.TransitionHandler,
@@ -303,6 +305,20 @@
private fun getSplitFocusedTask(task1: RunningTaskInfo, task2: RunningTaskInfo) =
if (task1.taskId == task2.parentTaskId) task2 else task1
+ private fun forceEnterDesktop(displayId: Int): Boolean {
+ if (!DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)) {
+ return false
+ }
+
+ val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)
+ requireNotNull(tdaInfo) {
+ "This method can only be called with the ID of a display having non-null DisplayArea."
+ }
+ val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
+ val isFreeformDisplay = tdaWindowingMode == WINDOWING_MODE_FREEFORM
+ return isFreeformDisplay
+ }
+
/** Moves task to desktop mode if task is running, else launches it in desktop mode. */
fun moveTaskToDesktop(
taskId: Int,
@@ -378,7 +394,7 @@
taskSurface: SurfaceControl,
) {
logV("startDragToDesktop taskId=%d", taskInfo.taskId)
- interactionJankMonitor.begin(taskSurface, context,
+ interactionJankMonitor.begin(taskSurface, context, handler,
CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
dragToDesktopTransitionHandler.startDragToDesktopTransition(
taskInfo.taskId,
@@ -782,7 +798,7 @@
releaseVisualIndicator()
if (!taskInfo.isResizeable && DesktopModeFlags.DISABLE_SNAP_RESIZE.isEnabled(context)) {
interactionJankMonitor.begin(
- taskSurface, context, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_non_resizable"
+ taskSurface, context, handler, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_non_resizable"
)
// reposition non-resizable app back to its original position before being dragged
@@ -795,7 +811,7 @@
)
} else {
interactionJankMonitor.begin(
- taskSurface, context, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_resizable"
+ taskSurface, context, handler, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_resizable"
)
snapToHalfScreen(taskInfo, taskSurface, currentDragBounds, position)
}
@@ -1180,10 +1196,11 @@
val wct = WindowContainerTransaction()
if (!isDesktopModeShowing(task.displayId)) {
logD("Bring desktop tasks to front on transition=taskId=%d", task.taskId)
- // We are outside of desktop mode and already existing desktop task is being launched.
- // We should make this task go to fullscreen instead of freeform. Note that this means
- // any re-launch of a freeform window outside of desktop will be in fullscreen.
- if (taskRepository.isActiveTask(task.taskId)) {
+ if (taskRepository.isActiveTask(task.taskId) && !forceEnterDesktop(task.displayId)) {
+ // We are outside of desktop mode and already existing desktop task is being
+ // launched. We should make this task go to fullscreen instead of freeform. Note
+ // that this means any re-launch of a freeform window outside of desktop will be in
+ // fullscreen as long as default-desktop flag is disabled.
addMoveToFullscreenChanges(wct, task)
return wct
}
@@ -1220,7 +1237,7 @@
transition: IBinder
): WindowContainerTransaction? {
logV("handleFullscreenTaskLaunch")
- if (isDesktopModeShowing(task.displayId)) {
+ if (isDesktopModeShowing(task.displayId) || forceEnterDesktop(task.displayId)) {
logD("Switch fullscreen task to freeform on transition: taskId=%d", task.taskId)
return WindowContainerTransaction().also { wct ->
addMoveToDesktopChanges(wct, task)
@@ -1593,7 +1610,7 @@
when (indicatorType) {
IndicatorType.TO_DESKTOP_INDICATOR -> {
// Start a new jank interaction for the drag release to desktop window animation.
- interactionJankMonitor.begin(taskSurface, context,
+ interactionJankMonitor.begin(taskSurface, context, handler,
CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE, "to_desktop")
finalizeDragToDesktop(taskInfo)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index 597637d..dae37f4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -18,6 +18,7 @@
import android.app.ActivityManager.RunningTaskInfo
import android.content.Context
+import android.os.Handler
import android.os.IBinder
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_TO_BACK
@@ -29,6 +30,7 @@
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup
+import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TransitionObserver
@@ -45,7 +47,8 @@
private val shellTaskOrganizer: ShellTaskOrganizer,
private val maxTasksLimit: Int,
private val interactionJankMonitor: InteractionJankMonitor,
- private val context: Context
+ private val context: Context,
+ @ShellMainThread private val handler: Handler,
) {
private val minimizeTransitionObserver = MinimizeTransitionObserver()
@VisibleForTesting
@@ -125,7 +128,7 @@
if (mActiveTaskDetails != null && mActiveTaskDetails.transitionInfo != null) {
// Begin minimize window CUJ instrumentation.
interactionJankMonitor.begin(
- mActiveTaskDetails.transitionInfo?.rootLeash, context,
+ mActiveTaskDetails.transitionInfo?.rootLeash, context, handler,
CUJ_DESKTOP_MODE_MINIMIZE_WINDOW
)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
index e87be52..dedd44f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
@@ -29,6 +29,7 @@
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
+import android.os.Handler;
import android.os.IBinder;
import android.util.DisplayMetrics;
import android.view.SurfaceControl;
@@ -44,6 +45,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.Cuj;
import com.android.internal.jank.InteractionJankMonitor;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.transition.Transitions;
@@ -63,6 +65,8 @@
private final Context mContext;
private final Transitions mTransitions;
private final InteractionJankMonitor mInteractionJankMonitor;
+ @ShellMainThread
+ private final Handler mHandler;
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback;
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
@@ -71,20 +75,24 @@
public ExitDesktopTaskTransitionHandler(
Transitions transitions,
Context context,
- InteractionJankMonitor interactionJankMonitor
- ) {
- this(transitions, SurfaceControl.Transaction::new, context, interactionJankMonitor);
+ InteractionJankMonitor interactionJankMonitor,
+ @ShellMainThread Handler handler
+ ) {
+ this(transitions, SurfaceControl.Transaction::new, context, interactionJankMonitor,
+ handler);
}
private ExitDesktopTaskTransitionHandler(
Transitions transitions,
Supplier<SurfaceControl.Transaction> supplier,
Context context,
- InteractionJankMonitor interactionJankMonitor) {
+ InteractionJankMonitor interactionJankMonitor,
+ @ShellMainThread Handler handler) {
mTransitions = transitions;
mTransactionSupplier = supplier;
mContext = context;
mInteractionJankMonitor = interactionJankMonitor;
+ mHandler = handler;
}
/**
@@ -154,7 +162,7 @@
final SurfaceControl sc = change.getLeash();
final Rect endBounds = change.getEndAbsBounds();
mInteractionJankMonitor
- .begin(sc, mContext, Cuj.CUJ_DESKTOP_MODE_EXIT_MODE);
+ .begin(sc, mContext, mHandler, Cuj.CUJ_DESKTOP_MODE_EXIT_MODE);
// Hide the first (fullscreen) frame because the animation will start from the freeform
// size.
startT.hide(sc)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepository.kt
new file mode 100644
index 0000000..7ae5370
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepository.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.graphics.Rect
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+/** Repository to observe caption state. */
+class WindowDecorCaptionHandleRepository {
+ private val _captionStateFlow = MutableStateFlow<CaptionState>(CaptionState.NoCaption)
+ /** Observer for app handle state changes. */
+ val captionStateFlow: StateFlow<CaptionState> = _captionStateFlow
+
+ /** Notifies [captionStateFlow] if there is a change to caption state. */
+ fun notifyCaptionChanged(captionState: CaptionState) {
+ _captionStateFlow.value = captionState
+ }
+}
+
+/**
+ * Represents the current status of the caption.
+ *
+ * It can be one of three options:
+ * * [AppHandle]: Indicating that there is at least one visible app handle on the screen.
+ * * [AppHeader]: Indicating that there is at least one visible app chip on the screen.
+ * * [NoCaption]: Signifying that no caption handle is currently visible on the device.
+ */
+sealed class CaptionState {
+ data class AppHandle(
+ val runningTaskInfo: RunningTaskInfo,
+ val isHandleMenuExpanded: Boolean,
+ val globalAppHandleBounds: Rect
+ ) : CaptionState()
+
+ data class AppHeader(
+ val runningTaskInfo: RunningTaskInfo,
+ val isHeaderMenuExpanded: Boolean,
+ val globalAppChipBounds: Rect
+ ) : CaptionState()
+
+ data object NoCaption : CaptionState()
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
index 832e2d2..517e209 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
@@ -28,6 +28,7 @@
import android.app.WindowConfiguration;
import android.content.Context;
import android.graphics.Rect;
+import android.os.Handler;
import android.os.IBinder;
import android.util.ArrayMap;
import android.view.SurfaceControl;
@@ -43,6 +44,7 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
@@ -65,6 +67,8 @@
private final InteractionJankMonitor mInteractionJankMonitor;
private final ShellExecutor mMainExecutor;
private final ShellExecutor mAnimExecutor;
+ @ShellMainThread
+ private final Handler mHandler;
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
@@ -79,7 +83,8 @@
ShellExecutor mainExecutor,
ShellExecutor animExecutor,
DesktopModeTaskRepository desktopModeTaskRepository,
- InteractionJankMonitor interactionJankMonitor) {
+ InteractionJankMonitor interactionJankMonitor,
+ @ShellMainThread Handler handler) {
mTransitions = transitions;
mContext = context;
mWindowDecorViewModel = windowDecorViewModel;
@@ -88,6 +93,7 @@
mInteractionJankMonitor = interactionJankMonitor;
mMainExecutor = mainExecutor;
mAnimExecutor = animExecutor;
+ mHandler = handler;
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
shellInit.addInitCallback(this::onInit, this);
}
@@ -267,7 +273,7 @@
change.getTaskInfo().displayId) == 1) {
// Starting the jank trace if closing the last window in desktop mode.
mInteractionJankMonitor.begin(
- sc, mContext, CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE);
+ sc, mContext, mHandler, CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE);
}
animator.addListener(
new AnimatorListenerAdapter() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 1827923..15472eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -55,6 +55,7 @@
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.shared.annotations.ExternalThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -203,7 +204,7 @@
DisplayController displayController, DisplayLayout displayLayout,
TaskStackListenerImpl taskStackListener,
InteractionJankMonitor jankMonitor, UiEventLogger uiEventLogger,
- ShellExecutor mainExecutor, Handler mainHandler) {
+ ShellExecutor mainExecutor, @ShellMainThread Handler mainHandler) {
OneHandedSettingsUtil settingsUtil = new OneHandedSettingsUtil();
OneHandedAccessibilityUtil accessibilityUtil = new OneHandedAccessibilityUtil(context);
OneHandedTimeoutHandler timeoutHandler = new OneHandedTimeoutHandler(mainExecutor);
@@ -217,7 +218,7 @@
mainExecutor);
OneHandedDisplayAreaOrganizer organizer = new OneHandedDisplayAreaOrganizer(
context, displayLayout, settingsUtil, animationController, tutorialHandler,
- jankMonitor, mainExecutor);
+ jankMonitor, mainExecutor, mainHandler);
OneHandedUiEventLogger oneHandedUiEventsLogger = new OneHandedUiEventLogger(uiEventLogger);
return new OneHandedController(context, shellInit, shellCommandHandler, shellController,
displayController, organizer, touchHandler, tutorialHandler, settingsUtil,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
index d157ca8..95e633d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.graphics.Rect;
+import android.os.Handler;
import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -42,6 +43,7 @@
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -70,6 +72,8 @@
private final OneHandedSettingsUtil mOneHandedSettingsUtil;
private final InteractionJankMonitor mJankMonitor;
private final Context mContext;
+ @ShellMainThread
+ private final Handler mHandler;
private boolean mIsReady;
private float mLastVisualOffset = 0;
@@ -136,9 +140,11 @@
OneHandedAnimationController animationController,
OneHandedTutorialHandler tutorialHandler,
InteractionJankMonitor jankMonitor,
- ShellExecutor mainExecutor) {
+ ShellExecutor mainExecutor,
+ @ShellMainThread Handler handler) {
super(mainExecutor);
mContext = context;
+ mHandler = handler;
setDisplayLayout(displayLayout);
mOneHandedSettingsUtil = oneHandedSettingsUtil;
mAnimationController = animationController;
@@ -333,7 +339,7 @@
getDisplayAreaTokenMap().entrySet().iterator().next();
final InteractionJankMonitor.Configuration.Builder builder =
InteractionJankMonitor.Configuration.Builder.withSurface(
- cujType, mContext, firstEntry.getValue());
+ cujType, mContext, firstEntry.getValue(), mHandler);
if (!TextUtils.isEmpty(tag)) {
builder.setTag(tag);
}
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 deb7691..af68442 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
@@ -44,6 +44,7 @@
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
+import android.os.Handler;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.util.Pair;
@@ -93,6 +94,7 @@
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.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -146,6 +148,8 @@
private Optional<OneHandedController> mOneHandedController;
private final ShellCommandHandler mShellCommandHandler;
private final ShellController mShellController;
+ @ShellMainThread
+ private final Handler mHandler;
protected final PipImpl mImpl;
private final Rect mTmpInsetBounds = new Rect();
@@ -405,7 +409,8 @@
DisplayInsetsController displayInsetsController,
TabletopModeController pipTabletopController,
Optional<OneHandedController> oneHandedController,
- ShellExecutor mainExecutor) {
+ ShellExecutor mainExecutor,
+ Handler handler) {
if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: Device doesn't support Pip feature", TAG);
@@ -418,7 +423,8 @@
pipDisplayLayoutState, pipMotionHelper, pipMediaController, phonePipMenuController,
pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController,
windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
- displayInsetsController, pipTabletopController, oneHandedController, mainExecutor)
+ displayInsetsController, pipTabletopController, oneHandedController, mainExecutor,
+ handler)
.mImpl;
}
@@ -446,11 +452,13 @@
DisplayInsetsController displayInsetsController,
TabletopModeController tabletopModeController,
Optional<OneHandedController> oneHandedController,
- ShellExecutor mainExecutor
+ ShellExecutor mainExecutor,
+ @ShellMainThread Handler handler
) {
mContext = context;
mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
+ mHandler = handler;
mImpl = new PipImpl();
mWindowManagerShellWrapper = windowManagerShellWrapper;
mDisplayController = displayController;
@@ -1047,7 +1055,8 @@
// Begin InteractionJankMonitor with PIP transition CUJs
final InteractionJankMonitor.Configuration.Builder builder =
InteractionJankMonitor.Configuration.Builder.withSurface(
- CUJ_PIP_TRANSITION, mContext, mPipTaskOrganizer.getSurfaceControl())
+ CUJ_PIP_TRANSITION, mContext, mPipTaskOrganizer.getSurfaceControl(),
+ mHandler)
.setTag(getTransitionTag(direction))
.setTimeout(2000);
InteractionJankMonitor.getInstance().begin(builder);
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 c660000..8077aee 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
@@ -20,9 +20,12 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
+import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_SLEEP;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -41,6 +44,7 @@
import android.content.Intent;
import android.graphics.Color;
import android.graphics.Rect;
+import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
@@ -64,6 +68,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IResultReceiver;
import com.android.internal.protolog.ProtoLog;
+import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -79,10 +84,15 @@
* Handles the Recents (overview) animation. Only one of these can run at a time. A recents
* transition must be created via {@link #startRecentsTransition}. Anything else will be ignored.
*/
-public class RecentsTransitionHandler implements Transitions.TransitionHandler {
+public class RecentsTransitionHandler implements Transitions.TransitionHandler,
+ Transitions.TransitionObserver {
private static final String TAG = "RecentsTransitionHandler";
+ // A placeholder for a synthetic transition that isn't backed by a true system transition
+ public static final IBinder SYNTHETIC_TRANSITION = new Binder();
+
private final Transitions mTransitions;
+ private final ShellTaskOrganizer mShellTaskOrganizer;
private final ShellExecutor mExecutor;
@Nullable
private final RecentTasksController mRecentTasksController;
@@ -99,19 +109,26 @@
private final HomeTransitionObserver mHomeTransitionObserver;
private @Nullable Color mBackgroundColor;
- public RecentsTransitionHandler(ShellInit shellInit, Transitions transitions,
+ public RecentsTransitionHandler(
+ @NonNull ShellInit shellInit,
+ @NonNull ShellTaskOrganizer shellTaskOrganizer,
+ @NonNull Transitions transitions,
@Nullable RecentTasksController recentTasksController,
- HomeTransitionObserver homeTransitionObserver) {
+ @NonNull HomeTransitionObserver homeTransitionObserver) {
+ mShellTaskOrganizer = shellTaskOrganizer;
mTransitions = transitions;
mExecutor = transitions.getMainExecutor();
mRecentTasksController = recentTasksController;
mHomeTransitionObserver = homeTransitionObserver;
if (!Transitions.ENABLE_SHELL_TRANSITIONS) return;
if (recentTasksController == null) return;
- shellInit.addInitCallback(() -> {
- recentTasksController.setTransitionHandler(this);
- transitions.addHandler(this);
- }, this);
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ mRecentTasksController.setTransitionHandler(this);
+ mTransitions.addHandler(this);
+ mTransitions.registerObserver(this);
}
/** Register a mixer handler. {@see RecentsMixedHandler}*/
@@ -138,17 +155,59 @@
mBackgroundColor = color;
}
+ /**
+ * Starts a new real/synthetic recents transition.
+ */
@VisibleForTesting
public IBinder startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
IApplicationThread appThread, IRecentsAnimationRunner listener) {
+ // only care about latest one.
+ mAnimApp = appThread;
+
+ // TODO(b/366021931): Formalize this later
+ final boolean isSyntheticRequest = options.containsKey("is_synthetic_recents_transition");
+ if (isSyntheticRequest) {
+ return startSyntheticRecentsTransition(listener);
+ } else {
+ return startRealRecentsTransition(intent, fillIn, options, listener);
+ }
+ }
+
+ /**
+ * Starts a synthetic recents transition that is not backed by a real WM transition.
+ */
+ private IBinder startSyntheticRecentsTransition(@NonNull IRecentsAnimationRunner listener) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "RecentsTransitionHandler.startRecentsTransition(synthetic)");
+ final RecentsController lastController = getLastController();
+ if (lastController != null) {
+ lastController.cancel(lastController.isSyntheticTransition()
+ ? "existing_running_synthetic_transition"
+ : "existing_running_transition");
+ return null;
+ }
+
+ // Create a new synthetic transition and start it immediately
+ final RecentsController controller = new RecentsController(listener);
+ controller.startSyntheticTransition();
+ mControllers.add(controller);
+ return SYNTHETIC_TRANSITION;
+ }
+
+ /**
+ * Starts a real WM-backed recents transition.
+ */
+ private IBinder startRealRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
+ IRecentsAnimationRunner listener) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"RecentsTransitionHandler.startRecentsTransition");
- // only care about latest one.
- mAnimApp = appThread;
- WindowContainerTransaction wct = new WindowContainerTransaction();
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.sendPendingIntent(intent, fillIn, options);
- final RecentsController controller = new RecentsController(listener);
+
+ // Find the mixed handler which should handle this request (if we are in a state where a
+ // mixed handler is needed). This is slightly convoluted because starting the transition
+ // requires the handler, but the mixed handler also needs a reference to the transition.
RecentsMixedHandler mixer = null;
Consumer<IBinder> setTransitionForMixer = null;
for (int i = 0; i < mMixers.size(); ++i) {
@@ -160,12 +219,11 @@
}
final IBinder transition = mTransitions.startTransition(TRANSIT_TO_FRONT, wct,
mixer == null ? this : mixer);
- for (int i = 0; i < mStateListeners.size(); i++) {
- mStateListeners.get(i).onTransitionStarted(transition);
- }
if (mixer != null) {
setTransitionForMixer.accept(transition);
}
+
+ final RecentsController controller = new RecentsController(listener);
if (transition != null) {
controller.setTransition(transition);
mControllers.add(controller);
@@ -187,11 +245,28 @@
return null;
}
- private int findController(IBinder transition) {
+ /**
+ * Returns if there is currently a pending or active recents transition.
+ */
+ @Nullable
+ private RecentsController getLastController() {
+ return !mControllers.isEmpty() ? mControllers.getLast() : null;
+ }
+
+ /**
+ * Finds an existing controller for the provided {@param transition}, or {@code null} if none
+ * exists.
+ */
+ @Nullable
+ @VisibleForTesting
+ RecentsController findController(@NonNull IBinder transition) {
for (int i = mControllers.size() - 1; i >= 0; --i) {
- if (mControllers.get(i).mTransition == transition) return i;
+ final RecentsController controller = mControllers.get(i);
+ if (controller.mTransition == transition) {
+ return controller;
+ }
}
- return -1;
+ return null;
}
@Override
@@ -199,13 +274,12 @@
SurfaceControl.Transaction startTransaction,
SurfaceControl.Transaction finishTransaction,
Transitions.TransitionFinishCallback finishCallback) {
- final int controllerIdx = findController(transition);
- if (controllerIdx < 0) {
+ final RecentsController controller = findController(transition);
+ if (controller == null) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"RecentsTransitionHandler.startAnimation: no controller found");
return false;
}
- final RecentsController controller = mControllers.get(controllerIdx);
final IApplicationThread animApp = mAnimApp;
mAnimApp = null;
if (!controller.start(info, startTransaction, finishTransaction, finishCallback)) {
@@ -221,13 +295,12 @@
public void mergeAnimation(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction t, IBinder mergeTarget,
Transitions.TransitionFinishCallback finishCallback) {
- final int targetIdx = findController(mergeTarget);
- if (targetIdx < 0) {
+ final RecentsController controller = findController(mergeTarget);
+ if (controller == null) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"RecentsTransitionHandler.mergeAnimation: no controller found");
return;
}
- final RecentsController controller = mControllers.get(targetIdx);
controller.merge(info, t, finishCallback);
}
@@ -244,8 +317,21 @@
}
}
+ @Override
+ public void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction) {
+ RecentsController controller = findController(SYNTHETIC_TRANSITION);
+ if (controller != null) {
+ // Cancel the existing synthetic transition if there is one
+ controller.cancel("incoming_transition");
+ }
+ }
+
/** There is only one of these and it gets reset on finish. */
- private class RecentsController extends IRecentsAnimationController.Stub {
+ @VisibleForTesting
+ class RecentsController extends IRecentsAnimationController.Stub {
+
private final int mInstanceId;
private IRecentsAnimationRunner mListener;
@@ -307,7 +393,8 @@
mDeathHandler = () -> {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.DeathRecipient: binder died", mInstanceId);
- finish(mWillFinishToHome, false /* leaveHint */, null /* finishCb */);
+ finishInner(mWillFinishToHome, false /* leaveHint */, null /* finishCb */,
+ "deathRecipient");
};
try {
mListener.asBinder().linkToDeath(mDeathHandler, 0 /* flags */);
@@ -317,6 +404,9 @@
}
}
+ /**
+ * Sets the started transition for this instance of the recents transition.
+ */
void setTransition(IBinder transition) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.setTransition: id=%s", mInstanceId, transition);
@@ -330,6 +420,10 @@
}
void cancel(boolean toHome, boolean withScreenshots, String reason) {
+ if (cancelSyntheticTransition(reason)) {
+ return;
+ }
+
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.cancel: toHome=%b reason=%s",
mInstanceId, toHome, reason);
@@ -341,7 +435,7 @@
}
}
if (mFinishCB != null) {
- finishInner(toHome, false /* userLeave */, null /* finishCb */);
+ finishInner(toHome, false /* userLeave */, null /* finishCb */, "cancel");
} else {
cleanUp();
}
@@ -436,6 +530,91 @@
}
}
+ /**
+ * Starts a new transition that is not backed by a system transition.
+ */
+ void startSyntheticTransition() {
+ mTransition = SYNTHETIC_TRANSITION;
+
+ // TODO(b/366021931): Update mechanism for pulling the home task, for now add home as
+ // both opening and closing since there's some pre-existing
+ // dependencies on having a closing task
+ final ActivityManager.RunningTaskInfo homeTask =
+ mShellTaskOrganizer.getRunningTasks(DEFAULT_DISPLAY).stream()
+ .filter(task -> task.getActivityType() == ACTIVITY_TYPE_HOME)
+ .findFirst()
+ .get();
+ final RemoteAnimationTarget openingTarget = TransitionUtil.newSyntheticTarget(
+ homeTask, mShellTaskOrganizer.getHomeTaskOverlayContainer(), TRANSIT_OPEN,
+ 0, true /* isTranslucent */);
+ final RemoteAnimationTarget closingTarget = TransitionUtil.newSyntheticTarget(
+ homeTask, mShellTaskOrganizer.getHomeTaskOverlayContainer(), TRANSIT_CLOSE,
+ 0, true /* isTranslucent */);
+ final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>();
+ apps.add(openingTarget);
+ apps.add(closingTarget);
+ try {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.start: calling onAnimationStart with %d apps",
+ mInstanceId, apps.size());
+ mListener.onAnimationStart(this,
+ apps.toArray(new RemoteAnimationTarget[apps.size()]),
+ new RemoteAnimationTarget[0],
+ new Rect(0, 0, 0, 0), new Rect(), new Bundle());
+ for (int i = 0; i < mStateListeners.size(); i++) {
+ mStateListeners.get(i).onAnimationStateChanged(true);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting recents animation", e);
+ cancel("startSynthetricTransition() failed");
+ }
+ }
+
+ /**
+ * Returns whether this transition is backed by a real system transition or not.
+ */
+ boolean isSyntheticTransition() {
+ return mTransition == SYNTHETIC_TRANSITION;
+ }
+
+ /**
+ * Called when a synthetic transition is canceled.
+ */
+ boolean cancelSyntheticTransition(String reason) {
+ if (!isSyntheticTransition()) {
+ return false;
+ }
+
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.cancelSyntheticTransition reason=%s",
+ mInstanceId, reason);
+ try {
+ // TODO(b/366021931): Notify the correct tasks once we build actual targets, and
+ // clean up leashes accordingly
+ mListener.onAnimationCanceled(new int[0], new TaskSnapshot[0]);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error canceling previous recents animation", e);
+ }
+ cleanUp();
+ return true;
+ }
+
+ /**
+ * Called when a synthetic transition is finished.
+ * @return
+ */
+ boolean finishSyntheticTransition() {
+ if (!isSyntheticTransition()) {
+ return false;
+ }
+
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.finishSyntheticTransition", mInstanceId);
+ // TODO(b/366021931): Clean up leashes accordingly
+ cleanUp();
+ return true;
+ }
+
boolean start(TransitionInfo info, SurfaceControl.Transaction t,
SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCB) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
@@ -662,7 +841,7 @@
// Set the callback once again so we can finish correctly.
mFinishCB = finishCB;
finishInner(true /* toHome */, false /* userLeave */,
- null /* finishCb */);
+ null /* finishCb */, "takeOverAnimation");
}, updatedStates);
});
}
@@ -810,7 +989,7 @@
sendCancelWithSnapshots();
mExecutor.executeDelayed(
() -> finishInner(true /* toHome */, false /* userLeaveHint */,
- null /* finishCb */), 0);
+ null /* finishCb */, "merge"), 0);
return;
}
if (recentsOpening != null) {
@@ -1005,7 +1184,7 @@
return;
}
final int displayId = mInfo.getRootCount() > 0 ? mInfo.getRoot(0).getDisplayId()
- : Display.DEFAULT_DISPLAY;
+ : DEFAULT_DISPLAY;
// transient launches don't receive focus automatically. Since we are taking over
// the gesture now, take focus explicitly.
// This also moves recents back to top if the user gestured before a switch
@@ -1038,11 +1217,16 @@
@Override
@SuppressLint("NewApi")
public void finish(boolean toHome, boolean sendUserLeaveHint, IResultReceiver finishCb) {
- mExecutor.execute(() -> finishInner(toHome, sendUserLeaveHint, finishCb));
+ mExecutor.execute(() -> finishInner(toHome, sendUserLeaveHint, finishCb,
+ "requested"));
}
private void finishInner(boolean toHome, boolean sendUserLeaveHint,
- IResultReceiver runnerFinishCb) {
+ IResultReceiver runnerFinishCb, String reason) {
+ if (finishSyntheticTransition()) {
+ return;
+ }
+
if (mFinishCB == null) {
Slog.e(TAG, "Duplicate call to finish");
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java
index e8733eb..95874c8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java
@@ -24,7 +24,4 @@
/** Notifies whether the recents animation is running. */
default void onAnimationStateChanged(boolean running) {
}
-
- /** Notifies that a recents shell transition has started. */
- default void onTransitionStarted(IBinder transition) {}
}
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 4ba84ee..e8eb10c 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
@@ -1654,7 +1654,7 @@
mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext,
mRootTaskInfo.configuration, this, mParentContainerCallbacks,
mDisplayController, mDisplayImeController, mTaskOrganizer,
- PARALLAX_ALIGN_CENTER /* parallaxType */);
+ PARALLAX_ALIGN_CENTER /* parallaxType */, mMainHandler);
mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);
}
@@ -2456,6 +2456,7 @@
final StageChangeRecord record = new StageChangeRecord();
final int transitType = info.getType();
TransitionInfo.Change pipChange = null;
+ int closingSplitTaskId = -1;
for (int iC = 0; iC < info.getChanges().size(); ++iC) {
final TransitionInfo.Change change = info.getChanges().get(iC);
if (change.getMode() == TRANSIT_CHANGE
@@ -2516,21 +2517,31 @@
+ " with " + taskInfo.taskId + " before startAnimation().");
}
}
+ if (isClosingType(change.getMode()) &&
+ getStageOfTask(change.getTaskInfo().taskId) != STAGE_TYPE_UNDEFINED) {
+ // If either one of the 2 stages is closing we're assuming we'll break split
+ closingSplitTaskId = change.getTaskInfo().taskId;
+ }
}
if (pipChange != null) {
TransitionInfo.Change pipReplacingChange = getPipReplacingChange(info, pipChange,
mMainStage.mRootTaskInfo.taskId, mSideStage.mRootTaskInfo.taskId,
getSplitItemStage(pipChange.getLastParent()));
- if (pipReplacingChange != null) {
+ boolean keepSplitWithPip = pipReplacingChange != null && closingSplitTaskId == -1;
+ if (keepSplitWithPip) {
// Set an enter transition for when startAnimation gets called again
mSplitTransitions.setEnterTransition(transition, /*remoteTransition*/ null,
TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, /*resizeAnim*/ false);
+ } else {
+ int finalClosingTaskId = closingSplitTaskId;
+ mRecentTasks.ifPresent(recentTasks ->
+ recentTasks.removeSplitPair(finalClosingTaskId));
+ logExit(EXIT_REASON_FULLSCREEN_REQUEST);
}
mMixedHandler.animatePendingEnterPipFromSplit(transition, info,
- startTransaction, finishTransaction, finishCallback,
- pipReplacingChange != null);
+ startTransaction, finishTransaction, finishCallback, keepSplitWithPip);
notifySplitAnimationFinished();
return true;
}
@@ -2821,8 +2832,12 @@
}
callbackWct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, false);
mWindowDecorViewModel.ifPresent(viewModel -> {
- viewModel.onTaskInfoChanged(finalMainChild.getTaskInfo());
- viewModel.onTaskInfoChanged(finalSideChild.getTaskInfo());
+ if (finalMainChild != null) {
+ viewModel.onTaskInfoChanged(finalMainChild.getTaskInfo());
+ }
+ if (finalSideChild != null) {
+ viewModel.onTaskInfoChanged(finalSideChild.getTaskInfo());
+ }
});
mPausingTasks.clear();
});
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 6013a1e..dec28fe 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
@@ -16,7 +16,7 @@
package com.android.wm.shell.transition;
-import static com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary;
+import static com.android.systemui.shared.Flags.returnAnimationFrameworkLongLived;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -257,7 +257,7 @@
@Override
public Transitions.TransitionHandler getHandlerForTakeover(
@NonNull IBinder transition, @NonNull TransitionInfo info) {
- if (!returnAnimationFrameworkLibrary()) {
+ if (!returnAnimationFrameworkLongLived()) {
return null;
}
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 aba8b61..d03832d 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
@@ -38,7 +38,7 @@
import static android.window.TransitionInfo.FLAG_NO_ANIMATION;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
-import static com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary;
+import static com.android.systemui.shared.Flags.returnAnimationFrameworkLongLived;
import static com.android.window.flags.Flags.ensureWallpaperInTransitions;
import static com.android.window.flags.Flags.migratePredictiveBackTransition;
import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
@@ -1252,7 +1252,7 @@
@Nullable
public TransitionHandler getHandlerForTakeover(
@NonNull IBinder transition, @NonNull TransitionInfo info) {
- if (!returnAnimationFrameworkLibrary()) {
+ if (!returnAnimationFrameworkLongLived()) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"Trying to get a handler for takeover but the flag is disabled");
return null;
@@ -1501,16 +1501,16 @@
* transition animation. The Transition system will apply it when
* finishCallback is called by the transition handler.
*/
- void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ default void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction);
+ @NonNull SurfaceControl.Transaction finishTransaction) {}
/**
* Called when the transition is starting to play. It isn't called for merged transitions.
*
* @param transition the unique token of this transition
*/
- void onTransitionStarting(@NonNull IBinder transition);
+ default void onTransitionStarting(@NonNull IBinder transition) {}
/**
* Called when a transition is merged into another transition. There won't be any following
@@ -1519,7 +1519,7 @@
* @param merged the unique token of the transition that's merged to another one
* @param playing the unique token of the transition that accepts the merge
*/
- void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing);
+ default void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {}
/**
* Called when the transition is finished. This isn't called for merged transitions.
@@ -1527,7 +1527,7 @@
* @param transition the unique token of this transition
* @param aborted {@code true} if this transition is aborted; {@code false} otherwise.
*/
- void onTransitionFinished(@NonNull IBinder transition, boolean aborted);
+ default void onTransitionFinished(@NonNull IBinder transition, boolean aborted) {}
}
@BinderThread
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 b14283f..7ea0bd6 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
@@ -109,8 +109,10 @@
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
import com.android.wm.shell.desktopmode.DesktopTasksLimiter;
import com.android.wm.shell.desktopmode.DesktopWallpaperActivity;
+import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
@@ -154,7 +156,7 @@
private final ShellTaskOrganizer mTaskOrganizer;
private final ShellController mShellController;
private final Context mContext;
- private final Handler mMainHandler;
+ private final @ShellMainThread Handler mMainHandler;
private final @ShellBackgroundThread ShellExecutor mBgExecutor;
private final Choreographer mMainChoreographer;
private final DisplayController mDisplayController;
@@ -163,7 +165,9 @@
private final InputManager mInputManager;
private final InteractionJankMonitor mInteractionJankMonitor;
private final MultiInstanceHelper mMultiInstanceHelper;
+ private final WindowDecorCaptionHandleRepository mWindowDecorCaptionHandleRepository;
private final Optional<DesktopTasksLimiter> mDesktopTasksLimiter;
+ private final AppHeaderViewHolder.Factory mAppHeaderViewHolderFactory;
private final WindowDecorViewHostSupplier mWindowDecorViewHostSupplier;
private boolean mTransitionDragActive;
@@ -214,7 +218,7 @@
public DesktopModeWindowDecorViewModel(
Context context,
ShellExecutor shellExecutor,
- Handler mainHandler,
+ @ShellMainThread Handler mainHandler,
Choreographer mainChoreographer,
@ShellBackgroundThread ShellExecutor bgExecutor,
ShellInit shellInit,
@@ -233,6 +237,7 @@
AssistContentRequester assistContentRequester,
MultiInstanceHelper multiInstanceHelper,
Optional<DesktopTasksLimiter> desktopTasksLimiter,
+ WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler,
WindowDecorViewHostSupplier windowDecorViewHostSupplier) {
this(
@@ -258,10 +263,12 @@
new DesktopModeWindowDecoration.Factory(),
new InputMonitorFactory(),
SurfaceControl.Transaction::new,
+ new AppHeaderViewHolder.Factory(),
rootTaskDisplayAreaOrganizer,
new SparseArray<>(),
interactionJankMonitor,
desktopTasksLimiter,
+ windowDecorCaptionHandleRepository,
activityOrientationChangeHandler,
new TaskPositionerFactory());
}
@@ -270,7 +277,7 @@
DesktopModeWindowDecorViewModel(
Context context,
ShellExecutor shellExecutor,
- Handler mainHandler,
+ @ShellMainThread Handler mainHandler,
Choreographer mainChoreographer,
@ShellBackgroundThread ShellExecutor bgExecutor,
ShellInit shellInit,
@@ -290,10 +297,12 @@
DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
InputMonitorFactory inputMonitorFactory,
Supplier<SurfaceControl.Transaction> transactionFactory,
+ AppHeaderViewHolder.Factory appHeaderViewHolderFactory,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
SparseArray<DesktopModeWindowDecoration> windowDecorByTaskId,
InteractionJankMonitor interactionJankMonitor,
Optional<DesktopTasksLimiter> desktopTasksLimiter,
+ WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler,
TaskPositionerFactory taskPositionerFactory) {
mContext = context;
@@ -316,6 +325,7 @@
mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory;
mInputMonitorFactory = inputMonitorFactory;
mTransactionFactory = transactionFactory;
+ mAppHeaderViewHolderFactory = appHeaderViewHolderFactory;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
mGenericLinksParser = genericLinksParser;
mInputManager = mContext.getSystemService(InputManager.class);
@@ -324,6 +334,7 @@
com.android.internal.R.string.config_systemUi);
mInteractionJankMonitor = interactionJankMonitor;
mDesktopTasksLimiter = desktopTasksLimiter;
+ mWindowDecorCaptionHandleRepository = windowDecorCaptionHandleRepository;
mActivityOrientationChangeHandler = activityOrientationChangeHandler;
mAssistContentRequester = assistContentRequester;
mOnDisplayChangingListener = (displayId, fromRotation, toRotation, displayAreaInfo, t) -> {
@@ -495,7 +506,8 @@
return;
}
mInteractionJankMonitor.begin(
- decoration.mTaskSurface, mContext, Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, source);
+ decoration.mTaskSurface, mContext, mMainHandler,
+ Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, source);
mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo);
decoration.closeHandleMenu();
decoration.closeMaximizeMenu();
@@ -512,7 +524,7 @@
Toast.makeText(mContext,
R.string.desktop_mode_non_resizable_snap_text, Toast.LENGTH_SHORT).show();
} else {
- mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext,
+ mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext, mMainHandler,
Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE, "maximize_menu_resizable");
mDesktopTasksController.snapToHalfScreen(
decoration.mTaskInfo,
@@ -548,7 +560,7 @@
return;
}
final WindowContainerTransaction wct = new WindowContainerTransaction();
- mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext,
+ mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext, mMainHandler,
CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU);
// App sometimes draws before the insets from WindowDecoration#relayout have
// been added, so they must be added here
@@ -860,6 +872,15 @@
handleCaptionThroughStatusBar(e, decoration);
final boolean wasDragging = mIsDragging;
updateDragStatus(e.getActionMasked());
+ final boolean upOrCancel = e.getActionMasked() == ACTION_UP
+ || e.getActionMasked() == ACTION_CANCEL;
+ if (wasDragging && upOrCancel) {
+ // When finishing a drag the event will be consumed, which means the pressed
+ // state of the App Handle must be manually reset to scale its drawable back to
+ // its original shape. This is necessary for drag gestures of the Handle that
+ // result in a cancellation (dragging back to the top).
+ v.setPressed(false);
+ }
// Only prevent onClick from receiving this event if it's a drag.
return wasDragging;
}
@@ -1180,8 +1201,10 @@
: SPLIT_POSITION_TOP_OR_LEFT;
final RunningTaskInfo oppositeTaskInfo =
mSplitScreenController.getTaskInfo(oppositePosition);
- mWindowDecorByTaskId.get(oppositeTaskInfo.taskId)
- .disposeStatusBarInputLayer();
+ if (oppositeTaskInfo != null) {
+ mWindowDecorByTaskId.get(oppositeTaskInfo.taskId)
+ .disposeStatusBarInputLayer();
+ }
}
}
mMoveToDesktopAnimator = null;
@@ -1364,10 +1387,12 @@
mBgExecutor,
mMainChoreographer,
mSyncQueue,
+ mAppHeaderViewHolderFactory,
mRootTaskDisplayAreaOrganizer,
mGenericLinksParser,
mAssistContentRequester,
mMultiInstanceHelper,
+ mWindowDecorCaptionHandleRepository,
mWindowDecorViewHostSupplier);
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
@@ -1378,7 +1403,8 @@
mDragStartListener,
mTransitions,
mInteractionJankMonitor,
- mTransactionFactory);
+ mTransactionFactory,
+ mMainHandler);
windowDecoration.setTaskDragResizer(taskPositioner);
final DesktopModeTouchEventListener touchEventListener =
@@ -1602,7 +1628,8 @@
DragPositioningCallbackUtility.DragStartListener dragStartListener,
Transitions transitions,
InteractionJankMonitor interactionJankMonitor,
- Supplier<SurfaceControl.Transaction> transactionFactory) {
+ Supplier<SurfaceControl.Transaction> transactionFactory,
+ Handler handler) {
final TaskPositioner taskPositioner = DesktopModeStatus.isVeiledResizeEnabled()
? new VeiledResizeTaskPositioner(
taskOrganizer,
@@ -1610,7 +1637,8 @@
displayController,
dragStartListener,
transitions,
- interactionJankMonitor)
+ interactionJankMonitor,
+ handler)
: new FluidResizeTaskPositioner(
taskOrganizer,
transitions,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 55da78e..10e4a39 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -24,8 +24,11 @@
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
+import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION;
+import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS;
import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
+import static com.android.wm.shell.shared.desktopmode.DesktopModeStatus.canEnterDesktopMode;
import static com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize;
@@ -83,6 +86,8 @@
import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.desktopmode.CaptionState;
+import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -163,6 +168,7 @@
private ExclusionRegionListener mExclusionRegionListener;
+ private final AppHeaderViewHolder.Factory mAppHeaderViewHolderFactory;
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
private final MaximizeMenuFactory mMaximizeMenuFactory;
private final HandleMenuFactory mHandleMenuFactory;
@@ -179,6 +185,7 @@
private final Runnable mCloseMaximizeWindowRunnable = this::closeMaximizeMenu;
private final Runnable mCapturedLinkExpiredRunnable = this::onCapturedLinkExpired;
private final MultiInstanceHelper mMultiInstanceHelper;
+ private final WindowDecorCaptionHandleRepository mWindowDecorCaptionHandleRepository;
DesktopModeWindowDecoration(
Context context,
@@ -192,20 +199,24 @@
@ShellBackgroundThread ShellExecutor bgExecutor,
Choreographer choreographer,
SyncTransactionQueue syncQueue,
+ AppHeaderViewHolder.Factory appHeaderViewHolderFactory,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
AppToWebGenericLinksParser genericLinksParser,
AssistContentRequester assistContentRequester,
MultiInstanceHelper multiInstanceHelper,
+ WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
WindowDecorViewHostSupplier windowDecorViewHostSupplier) {
this (context, userContext, displayController, splitScreenController, taskOrganizer,
taskInfo, taskSurface, handler, bgExecutor, choreographer, syncQueue,
- rootTaskDisplayAreaOrganizer, genericLinksParser, assistContentRequester,
+ appHeaderViewHolderFactory, rootTaskDisplayAreaOrganizer, genericLinksParser,
+ assistContentRequester,
SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
WindowContainerTransaction::new, SurfaceControl::new, new WindowManagerWrapper(
context.getSystemService(WindowManager.class)),
new SurfaceControlViewHostFactory() {}, windowDecorViewHostSupplier,
DefaultMaximizeMenuFactory.INSTANCE,
- DefaultHandleMenuFactory.INSTANCE, multiInstanceHelper);
+ DefaultHandleMenuFactory.INSTANCE, multiInstanceHelper,
+ windowDecorCaptionHandleRepository);
}
DesktopModeWindowDecoration(
@@ -220,6 +231,7 @@
@ShellBackgroundThread ShellExecutor bgExecutor,
Choreographer choreographer,
SyncTransactionQueue syncQueue,
+ AppHeaderViewHolder.Factory appHeaderViewHolderFactory,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
AppToWebGenericLinksParser genericLinksParser,
AssistContentRequester assistContentRequester,
@@ -232,7 +244,8 @@
WindowDecorViewHostSupplier windowDecorViewHostSupplier,
MaximizeMenuFactory maximizeMenuFactory,
HandleMenuFactory handleMenuFactory,
- MultiInstanceHelper multiInstanceHelper) {
+ MultiInstanceHelper multiInstanceHelper,
+ WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository) {
super(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface,
surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
windowContainerTransactionSupplier, surfaceControlSupplier,
@@ -242,6 +255,7 @@
mBgExecutor = bgExecutor;
mChoreographer = choreographer;
mSyncQueue = syncQueue;
+ mAppHeaderViewHolderFactory = appHeaderViewHolderFactory;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
mGenericLinksParser = genericLinksParser;
mAssistContentRequester = assistContentRequester;
@@ -249,6 +263,7 @@
mHandleMenuFactory = handleMenuFactory;
mMultiInstanceHelper = multiInstanceHelper;
mWindowManagerWrapper = windowManagerWrapper;
+ mWindowDecorCaptionHandleRepository = windowDecorCaptionHandleRepository;
}
/**
@@ -381,6 +396,9 @@
if (mResult.mRootView == null) {
// This means something blocks the window decor from showing, e.g. the task is hidden.
// Nothing is set up in this case including the decoration surface.
+ if (canEnterDesktopMode(mContext) && Flags.enableDesktopWindowingAppHandleEducation()) {
+ notifyNoCaptionHandle();
+ }
disposeStatusBarInputLayer();
Trace.endSection(); // DesktopModeWindowDecoration#relayout
return;
@@ -396,6 +414,9 @@
position.set(determineHandlePosition());
}
Trace.beginSection("DesktopModeWindowDecoration#relayout-bindData");
+ if (canEnterDesktopMode(mContext) && Flags.enableDesktopWindowingAppHandleEducation()) {
+ notifyCaptionStateChanged();
+ }
mWindowDecorViewHolder.bindData(mTaskInfo,
position,
mResult.mCaptionWidth,
@@ -405,6 +426,7 @@
if (!mTaskInfo.isFocused) {
closeHandleMenu();
+ closeManageWindowsMenu();
closeMaximizeMenu();
}
updateDragResizeListener(oldDecorationSurface);
@@ -505,6 +527,67 @@
return taskInfo.isFreeform() && taskInfo.isResizeable;
}
+ private void notifyCaptionStateChanged() {
+ // TODO: b/366159408 - Ensure bounds sent with notification account for RTL mode.
+ if (!canEnterDesktopMode(mContext) || !Flags.enableDesktopWindowingAppHandleEducation()) {
+ return;
+ }
+ if (!isCaptionVisible()) {
+ notifyNoCaptionHandle();
+ } else if (isAppHandle(mWindowDecorViewHolder)) {
+ // App handle is visible since `mWindowDecorViewHolder` is of type
+ // [AppHandleViewHolder].
+ final CaptionState captionState = new CaptionState.AppHandle(mTaskInfo,
+ isHandleMenuActive(), getCurrentAppHandleBounds());
+ mWindowDecorCaptionHandleRepository.notifyCaptionChanged(captionState);
+ } else {
+ // App header is visible since `mWindowDecorViewHolder` is of type
+ // [AppHeaderViewHolder].
+ ((AppHeaderViewHolder) mWindowDecorViewHolder).runOnAppChipGlobalLayout(
+ () -> {
+ notifyAppChipStateChanged();
+ return Unit.INSTANCE;
+ });
+ }
+ }
+
+ private void notifyNoCaptionHandle() {
+ if (!canEnterDesktopMode(mContext) || !Flags.enableDesktopWindowingAppHandleEducation()) {
+ return;
+ }
+ mWindowDecorCaptionHandleRepository.notifyCaptionChanged(
+ CaptionState.NoCaption.INSTANCE);
+ }
+
+ private Rect getCurrentAppHandleBounds() {
+ return new Rect(
+ mResult.mCaptionX,
+ /* top= */0,
+ mResult.mCaptionX + mResult.mCaptionWidth,
+ mResult.mCaptionHeight);
+ }
+
+ private void notifyAppChipStateChanged() {
+ final Rect appChipPositionInWindow =
+ ((AppHeaderViewHolder) mWindowDecorViewHolder).getAppChipLocationInWindow();
+ final Rect taskBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
+ final Rect appChipGlobalPosition = new Rect(
+ taskBounds.left + appChipPositionInWindow.left,
+ taskBounds.top + appChipPositionInWindow.top,
+ taskBounds.left + appChipPositionInWindow.right,
+ taskBounds.top + appChipPositionInWindow.bottom);
+ final CaptionState captionState = new CaptionState.AppHeader(
+ mTaskInfo,
+ isHandleMenuActive(),
+ appChipGlobalPosition);
+
+ mWindowDecorCaptionHandleRepository.notifyCaptionChanged(captionState);
+ }
+
+ private static boolean isDragResizable(ActivityManager.RunningTaskInfo taskInfo) {
+ return taskInfo.isFreeform() && taskInfo.isResizeable;
+ }
+
private void updateMaximizeMenu(SurfaceControl.Transaction startT) {
if (!isDragResizable(mTaskInfo, mContext) || !isMaximizeMenuActive()) {
return;
@@ -548,12 +631,13 @@
mResult.mRootView,
mOnCaptionTouchListener,
mOnCaptionButtonClickListener,
- mWindowManagerWrapper
+ mWindowManagerWrapper,
+ mHandler
);
} else if (mRelayoutParams.mLayoutResId
== R.layout.desktop_mode_app_header) {
loadAppInfoIfNeeded();
- return new AppHeaderViewHolder(
+ return mAppHeaderViewHolderFactory.create(
mResult.mRootView,
mOnCaptionTouchListener,
mOnCaptionButtonClickListener,
@@ -603,13 +687,13 @@
// their custom content.
relayoutParams.mInputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_SPY;
} else {
- if (Flags.enableCaptionCompatInsetForceConsumption()) {
+ if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isEnabled()) {
// Force-consume the caption bar insets when the app tries to hide the caption.
// This improves app compatibility of immersive apps.
relayoutParams.mInsetSourceFlags |= FLAG_FORCE_CONSUMING;
}
}
- if (Flags.enableCaptionCompatInsetForceConsumptionAlways()) {
+ if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS.isEnabled()) {
// Always force-consume the caption bar insets for maximum app compatibility,
// including non-immersive apps that just don't handle caption insets properly.
relayoutParams.mInsetSourceFlags |= FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
@@ -752,9 +836,12 @@
final ActivityInfo activityInfo = pm.getActivityInfo(baseActivity, 0 /* flags */);
final IconProvider provider = new IconProvider(mContext);
final Drawable appIconDrawable = provider.getIcon(activityInfo);
+ final Drawable badgedAppIconDrawable = pm.getUserBadgedIcon(appIconDrawable,
+ UserHandle.of(mTaskInfo.userId));
final BaseIconFactory headerIconFactory = createIconFactory(mContext,
R.dimen.desktop_mode_caption_icon_radius);
- mAppIconBitmap = headerIconFactory.createScaledBitmap(appIconDrawable, MODE_DEFAULT);
+ mAppIconBitmap = headerIconFactory.createIconBitmap(badgedAppIconDrawable,
+ 1f /* scale */);
final BaseIconFactory resizeVeilIconFactory = createIconFactory(mContext,
R.dimen.desktop_mode_resize_veil_icon_size);
@@ -988,7 +1075,7 @@
mAppIconBitmap,
mAppName,
mSplitScreenController,
- DesktopModeStatus.canEnterDesktopMode(mContext),
+ canEnterDesktopMode(mContext),
supportsMultiInstance,
shouldShowManageWindowsButton,
getBrowserLink(),
@@ -1021,6 +1108,9 @@
return Unit.INSTANCE;
}
);
+ if (canEnterDesktopMode(mContext) && Flags.enableDesktopWindowingAppHandleEducation()) {
+ notifyCaptionStateChanged();
+ }
mMinimumInstancesFound = false;
}
@@ -1061,7 +1151,10 @@
}
void closeManageWindowsMenu() {
- mManageWindowsMenu.close();
+ if (mManageWindowsMenu != null) {
+ mManageWindowsMenu.close();
+ }
+ mManageWindowsMenu = null;
}
private void updateGenericLink() {
@@ -1083,11 +1176,15 @@
mWindowDecorViewHolder.onHandleMenuClosed();
mHandleMenu.close();
mHandleMenu = null;
+ if (canEnterDesktopMode(mContext) && Flags.enableDesktopWindowingAppHandleEducation()) {
+ notifyCaptionStateChanged();
+ }
}
@Override
void releaseViews(WindowContainerTransaction wct) {
closeHandleMenu();
+ closeManageWindowsMenu();
closeMaximizeMenu();
super.releaseViews(wct);
}
@@ -1251,9 +1348,14 @@
public void close() {
closeDragResizeListener();
closeHandleMenu();
+ closeManageWindowsMenu();
mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId);
disposeResizeVeil();
disposeStatusBarInputLayer();
+ if (canEnterDesktopMode(mContext) && Flags.enableDesktopWindowingAppHandleEducation()) {
+ notifyNoCaptionHandle();
+ }
+
super.close();
}
@@ -1361,10 +1463,12 @@
@ShellBackgroundThread ShellExecutor bgExecutor,
Choreographer choreographer,
SyncTransactionQueue syncQueue,
+ AppHeaderViewHolder.Factory appHeaderViewHolderFactory,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
AppToWebGenericLinksParser genericLinksParser,
AssistContentRequester assistContentRequester,
MultiInstanceHelper multiInstanceHelper,
+ WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
WindowDecorViewHostSupplier windowDecorViewHostSupplier) {
return new DesktopModeWindowDecoration(
context,
@@ -1378,10 +1482,12 @@
bgExecutor,
choreographer,
syncQueue,
+ appHeaderViewHolderFactory,
rootTaskDisplayAreaOrganizer,
genericLinksParser,
assistContentRequester,
multiInstanceHelper,
+ windowDecorCaptionHandleRepository,
windowDecorViewHostSupplier);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 5998155..6eb5cca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -24,7 +24,10 @@
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
+import android.view.Choreographer;
import android.view.Surface;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
@@ -37,6 +40,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.transition.Transitions;
import java.util.function.Supplier;
@@ -63,14 +67,17 @@
private int mCtrlType;
private boolean mIsResizingOrAnimatingResize;
@Surface.Rotation private int mRotation;
+ @ShellMainThread
+ private final Handler mHandler;
public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
DesktopModeWindowDecoration windowDecoration,
DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
- Transitions transitions, InteractionJankMonitor interactionJankMonitor) {
+ Transitions transitions, InteractionJankMonitor interactionJankMonitor,
+ @ShellMainThread Handler handler) {
this(taskOrganizer, windowDecoration, displayController, dragStartListener,
- SurfaceControl.Transaction::new, transitions, interactionJankMonitor);
+ SurfaceControl.Transaction::new, transitions, interactionJankMonitor, handler);
}
public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
@@ -78,7 +85,7 @@
DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
Supplier<SurfaceControl.Transaction> supplier, Transitions transitions,
- InteractionJankMonitor interactionJankMonitor) {
+ InteractionJankMonitor interactionJankMonitor, @ShellMainThread Handler handler) {
mDesktopWindowDecoration = windowDecoration;
mTaskOrganizer = taskOrganizer;
mDisplayController = displayController;
@@ -86,6 +93,7 @@
mTransactionSupplier = supplier;
mTransitions = transitions;
mInteractionJankMonitor = interactionJankMonitor;
+ mHandler = handler;
}
@Override
@@ -97,7 +105,7 @@
if (isResizing()) {
// Capture CUJ for re-sizing window in DW mode.
mInteractionJankMonitor.begin(mDesktopWindowDecoration.mTaskSurface,
- mDesktopWindowDecoration.mContext, CUJ_DESKTOP_MODE_RESIZE_WINDOW);
+ mDesktopWindowDecoration.mContext, mHandler, CUJ_DESKTOP_MODE_RESIZE_WINDOW);
if (!mDesktopWindowDecoration.mTaskInfo.isFocused) {
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reorder(mDesktopWindowDecoration.mTaskInfo.token, true);
@@ -118,6 +126,11 @@
@Override
public Rect onDragPositioningMove(float x, float y) {
+ if (Looper.myLooper() != mHandler.getLooper()) {
+ // This method must run on the shell main thread to use the correct Choreographer
+ // instance below.
+ throw new IllegalStateException("This method must run on the shell main thread.");
+ }
PointF delta = DragPositioningCallbackUtility.calculateDelta(x, y, mRepositionStartPoint);
if (isResizing() && DragPositioningCallbackUtility.changeBounds(mCtrlType,
mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, delta,
@@ -131,10 +144,11 @@
} else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
// Begin window drag CUJ instrumentation only when drag position moves.
mInteractionJankMonitor.begin(mDesktopWindowDecoration.mTaskSurface,
- mDesktopWindowDecoration.mContext, CUJ_DESKTOP_MODE_DRAG_WINDOW);
+ mDesktopWindowDecoration.mContext, mHandler, CUJ_DESKTOP_MODE_DRAG_WINDOW);
final SurfaceControl.Transaction t = mTransactionSupplier.get();
DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration,
mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t, x, y);
+ t.setFrameTimeline(Choreographer.getInstance().getVsyncId());
t.apply();
}
return new Rect(mRepositionTaskBounds);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
index 1c11a8d..9c7d644 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
@@ -22,6 +22,7 @@
import android.graphics.Color
import android.graphics.Point
import android.hardware.input.InputManager
+import android.os.Handler
import android.view.MotionEvent.ACTION_DOWN
import android.view.SurfaceControl
import android.view.View
@@ -44,7 +45,8 @@
rootView: View,
onCaptionTouchListener: View.OnTouchListener,
onCaptionButtonClickListener: OnClickListener,
- private val windowManagerWrapper: WindowManagerWrapper
+ private val windowManagerWrapper: WindowManagerWrapper,
+ private val handler: Handler
) : WindowDecorationViewHolder(rootView) {
companion object {
@@ -54,6 +56,7 @@
private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption)
private val captionHandle: ImageButton = rootView.requireViewById(R.id.caption_handle)
private val inputManager = context.getSystemService(InputManager::class.java)
+ private var statusBarInputLayerExists = false
// An invisible View that takes up the same coordinates as captionHandle but is layered
// above the status bar. The purpose of this View is to receive input intended for
@@ -78,14 +81,18 @@
// If handle is not in status bar region(i.e., bottom stage in vertical split),
// do not create an input layer
if (position.y >= SystemBarUtils.getStatusBarHeight(context)) return
- if (!isCaptionVisible && hasStatusBarInputLayer() ) {
+ if (!isCaptionVisible && statusBarInputLayerExists) {
disposeStatusBarInputLayer()
return
}
- if (hasStatusBarInputLayer()) {
- updateStatusBarInputLayer(position)
+ // Input layer view creation / modification takes a significant amount of time;
+ // post them so we don't hold up DesktopModeWindowDecoration#relayout.
+ if (statusBarInputLayerExists) {
+ handler.post { updateStatusBarInputLayer(position) }
} else {
- createStatusBarInputLayer(position, width, height)
+ // Input layer is created on a delay; prevent multiple from being created.
+ statusBarInputLayerExists = true
+ handler.post { createStatusBarInputLayer(position, width, height) }
}
}
@@ -103,9 +110,10 @@
if (!Flags.enableHandleInputFix()) return
statusBarInputLayer = AdditionalSystemViewContainer(context, windowManagerWrapper,
taskInfo.taskId, handlePosition.x, handlePosition.y, handleWidth, handleHeight,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ )
val view = statusBarInputLayer?.view ?: error("Unable to find statusBarInputLayer View")
- val lp = statusBarInputLayer?.lp ?: error("Unable to find statusBarInputLayer" +
+ val lp = statusBarInputLayer?.lp ?: error("Unable to find statusBarInputLayer " +
"LayoutParams")
lp.title = "Handle Input Layer of task " + taskInfo.taskId
lp.setTrustedOverlay()
@@ -130,12 +138,11 @@
}
private fun updateStatusBarInputLayer(globalPosition: Point) {
- statusBarInputLayer?.setPosition(SurfaceControl.Transaction(), globalPosition.x.toFloat(),
- globalPosition.y.toFloat()) ?: return
- }
-
- private fun hasStatusBarInputLayer(): Boolean {
- return statusBarInputLayer != null
+ statusBarInputLayer?.setPosition(
+ SurfaceControl.Transaction(),
+ globalPosition.x.toFloat(),
+ globalPosition.y.toFloat()
+ ) ?: return
}
/**
@@ -143,8 +150,11 @@
* is not visible.
*/
fun disposeStatusBarInputLayer() {
- statusBarInputLayer?.releaseView()
- statusBarInputLayer = null
+ statusBarInputLayerExists = false
+ handler.post {
+ statusBarInputLayer?.releaseView()
+ statusBarInputLayer = null
+ }
}
private fun getCaptionHandleBarColor(taskInfo: RunningTaskInfo): Int {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
index 033d695..4a8cabc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
@@ -22,12 +22,14 @@
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.Point
+import android.graphics.Rect
import android.graphics.drawable.LayerDrawable
import android.graphics.drawable.RippleDrawable
import android.graphics.drawable.ShapeDrawable
import android.graphics.drawable.shapes.RoundRectShape
import android.view.View
import android.view.View.OnLongClickListener
+import android.view.ViewTreeObserver.OnGlobalLayoutListener
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
@@ -62,7 +64,7 @@
* finer controls such as a close window button and an "app info" section to pull up additional
* controls.
*/
-internal class AppHeaderViewHolder(
+class AppHeaderViewHolder(
rootView: View,
onCaptionTouchListener: View.OnTouchListener,
onCaptionButtonClickListener: View.OnClickListener,
@@ -279,6 +281,34 @@
maximizeButtonView.startHoverAnimation()
}
+ fun runOnAppChipGlobalLayout(runnable: () -> Unit) {
+ if (openMenuButton.isAttachedToWindow) {
+ // App chip is already inflated.
+ runnable()
+ return
+ }
+ // Wait for app chip to be inflated before notifying repository.
+ openMenuButton.viewTreeObserver.addOnGlobalLayoutListener(object :
+ OnGlobalLayoutListener {
+ override fun onGlobalLayout() {
+ runnable()
+ openMenuButton.viewTreeObserver.removeOnGlobalLayoutListener(this)
+ }
+ })
+ }
+
+ fun getAppChipLocationInWindow(): Rect {
+ val appChipBoundsInWindow = IntArray(2)
+ openMenuButton.getLocationInWindow(appChipBoundsInWindow)
+
+ return Rect(
+ /* left = */ appChipBoundsInWindow[0],
+ /* top = */ appChipBoundsInWindow[1],
+ /* right = */ appChipBoundsInWindow[0] + openMenuButton.width,
+ /* bottom = */ appChipBoundsInWindow[1] + openMenuButton.height
+ )
+ }
+
private fun getHeaderStyle(header: Header): HeaderStyle {
return HeaderStyle(
background = getHeaderBackground(header),
@@ -529,4 +559,26 @@
private const val LIGHT_THEME_UNFOCUSED_OPACITY = 166 // 65%
private const val FOCUSED_OPACITY = 255
}
+
+ class Factory {
+ fun create(
+ rootView: View,
+ onCaptionTouchListener: View.OnTouchListener,
+ onCaptionButtonClickListener: View.OnClickListener,
+ onLongClickListener: OnLongClickListener,
+ onCaptionGenericMotionListener: View.OnGenericMotionListener,
+ appName: CharSequence,
+ appIconBitmap: Bitmap,
+ onMaximizeHoverAnimationFinishedListener: () -> Unit,
+ ): AppHeaderViewHolder = AppHeaderViewHolder(
+ rootView,
+ onCaptionTouchListener,
+ onCaptionButtonClickListener,
+ onLongClickListener,
+ onCaptionGenericMotionListener,
+ appName,
+ appIconBitmap,
+ onMaximizeHoverAnimationFinishedListener,
+ )
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt
index 2341b09..5ea55b3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt
@@ -24,7 +24,7 @@
* Encapsulates the root [View] of a window decoration and its children to facilitate looking up
* children (via findViewById) and updating to the latest data from [RunningTaskInfo].
*/
-internal abstract class WindowDecorationViewHolder(rootView: View) {
+abstract class WindowDecorationViewHolder(rootView: View) {
val context: Context = rootView.context
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHost.kt
index 5156e47..139e679 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHost.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHost.kt
@@ -19,33 +19,51 @@
import android.content.res.Configuration
import android.view.Display
import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
import android.view.View
import android.view.WindowManager
+import android.view.WindowlessWindowManager
import androidx.tracing.Trace
import com.android.internal.annotations.VisibleForTesting
import com.android.wm.shell.shared.annotations.ShellMainThread
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
+typealias SurfaceControlViewHostFactory =
+ (Context, Display, WindowlessWindowManager, String) -> SurfaceControlViewHost
/**
- * A default implementation of [WindowDecorViewHost] backed by a [SurfaceControlViewHostAdapter].
+ * A default implementation of [WindowDecorViewHost] backed by a [SurfaceControlViewHost].
*
- * It supports asynchronously updating the view hierarchy using [updateViewAsync], in which
+ * It does not support swapping the root view added to the VRI of the [SurfaceControlViewHost], and
+ * any attempts to do will throw, which means that once a [View] is added using [updateView] or
+ * [updateViewAsync], only its properties and binding may be changed, its children views may be
+ * added, removed or changed and its [WindowManager.LayoutParams] may be changed.
+ * It also supports asynchronously updating the view hierarchy using [updateViewAsync], in which
* case the update work will be posted on the [ShellMainThread] with no delay.
*/
class DefaultWindowDecorViewHost(
- context: Context,
+ private val context: Context,
@ShellMainThread private val mainScope: CoroutineScope,
- display: Display,
- @VisibleForTesting val viewHostAdapter: SurfaceControlViewHostAdapter =
- SurfaceControlViewHostAdapter(context, display)
+ private val display: Display,
+ private val surfaceControlViewHostFactory: SurfaceControlViewHostFactory = { c, d, wwm, s ->
+ SurfaceControlViewHost(c, d, wwm, s)
+ }
) : WindowDecorViewHost {
+ private val rootSurface: SurfaceControl = SurfaceControl.Builder()
+ .setName("DefaultWindowDecorViewHost surface")
+ .setContainerLayer()
+ .setCallsite("DefaultWindowDecorViewHost#init")
+ .build()
+
+ private var wwm: WindowlessWindowManager? = null
+ @VisibleForTesting
+ var viewHost: SurfaceControlViewHost? = null
private var currentUpdateJob: Job? = null
override val surfaceControl: SurfaceControl
- get() = viewHostAdapter.rootSurface
+ get() = rootSurface
override fun updateView(
view: View,
@@ -74,7 +92,8 @@
override fun release(t: SurfaceControl.Transaction) {
clearCurrentUpdateJob()
- viewHostAdapter.release(t)
+ viewHost?.release()
+ t.remove(rootSurface)
}
private fun updateViewHost(
@@ -83,15 +102,45 @@
configuration: Configuration,
onDrawTransaction: SurfaceControl.Transaction?
) {
- viewHostAdapter.prepareViewHost(configuration)
- onDrawTransaction?.let {
- viewHostAdapter.applyTransactionOnDraw(it)
+ Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost")
+ if (wwm == null) {
+ wwm = WindowlessWindowManager(configuration, rootSurface, null)
}
- viewHostAdapter.updateView(view, attrs)
+ requireWindowlessWindowManager().setConfiguration(configuration)
+ if (viewHost == null) {
+ viewHost = surfaceControlViewHostFactory.invoke(
+ context,
+ display,
+ requireWindowlessWindowManager(),
+ "DefaultWindowDecorViewHost#updateViewHost"
+ )
+ }
+ onDrawTransaction?.let {
+ requireViewHost().rootSurfaceControl.applyTransactionOnDraw(it)
+ }
+ if (requireViewHost().view == null) {
+ Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost-setView")
+ requireViewHost().setView(view, attrs)
+ Trace.endSection()
+ } else {
+ check(requireViewHost().view == view) { "Changing view is not allowed" }
+ Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost-relayout")
+ requireViewHost().relayout(attrs)
+ Trace.endSection()
+ }
+ Trace.endSection()
}
private fun clearCurrentUpdateJob() {
currentUpdateJob?.cancel()
currentUpdateJob = null
}
+
+ private fun requireWindowlessWindowManager(): WindowlessWindowManager {
+ return wwm ?: error("Expected non-null windowless window manager")
+ }
+
+ private fun requireViewHost(): SurfaceControlViewHost {
+ return viewHost ?: error("Expected non-null view host")
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/PooledWindowDecorViewHostSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/PooledWindowDecorViewHostSupplier.kt
deleted file mode 100644
index b04188f..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/PooledWindowDecorViewHostSupplier.kt
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.wm.shell.windowdecor.viewhost
-
-import android.content.Context
-import android.os.Trace
-import android.util.Pools
-import android.view.Display
-import android.view.SurfaceControl
-import com.android.wm.shell.shared.annotations.ShellMainThread
-import com.android.wm.shell.sysui.ShellInit
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
-
-/**
- * A [WindowDecorViewHostSupplier] backed by a pool to allow recycling view hosts which may be
- * expensive to recreate for each new/updated window decoration.
- *
- * Callers can obtain [ReusableWindowDecorViewHost] using [acquire], which will return a pooled
- * object if available, or create a new instance and return it if needed. When done using a
- * [ReusableWindowDecorViewHost], it must be released using [release] to allow it to be sent back
- * into the pool and reused later on.
- *
- * This class also supports pre-warming [ReusableWindowDecorViewHost] instances, which will be put
- * into the pool immediately after creation.
- */
-class PooledWindowDecorViewHostSupplier(
- private val context: Context,
- @ShellMainThread private val mainScope: CoroutineScope,
- shellInit: ShellInit,
- private val viewHostFactory: ReusableWindowDecorViewHost.Factory =
- ReusableWindowDecorViewHost.DefaultFactory,
- maxPoolSize: Int,
- private val preWarmSize: Int,
-) : WindowDecorViewHostSupplier<ReusableWindowDecorViewHost> {
-
- private val pool: Pools.Pool<ReusableWindowDecorViewHost> = Pools.SynchronizedPool(maxPoolSize)
- private var nextDecorViewHostId = 0
-
- init {
- require(preWarmSize <= maxPoolSize) { "Pre-warm size should not exceed pool size" }
- shellInit.addInitCallback(this::onShellInit, this)
- }
-
- private fun onShellInit() {
- if (preWarmSize <= 0) {
- return
- }
- preWarmViewHosts(preWarmSize)
- }
-
- private fun preWarmViewHosts(preWarmSize: Int) {
- mainScope.launch {
- // Applying isn't needed, as the surface was never actually shown.
- val t = SurfaceControl.Transaction()
- repeat(preWarmSize) {
- val warmedViewHost = create(context, context.display).apply {
- warmUp()
- }
- // Put the warmed view host in the pool by releasing it.
- release(warmedViewHost, t)
- }
- }
- }
-
- override fun acquire(context: Context, display: Display): ReusableWindowDecorViewHost {
- val reusedDecorViewHost = pool.acquire()
- if (reusedDecorViewHost != null) {
- return reusedDecorViewHost
- }
- Trace.beginSection("WindowDecorViewHostPool#acquire-new")
- val newDecorViewHost = create(context, display)
- Trace.endSection()
- return newDecorViewHost
- }
-
- override fun release(viewHost: ReusableWindowDecorViewHost, t: SurfaceControl.Transaction) {
- val cached = pool.release(viewHost)
- if (!cached) {
- viewHost.release(t)
- }
- }
-
- private fun create(context: Context, display: Display): ReusableWindowDecorViewHost {
- return viewHostFactory.create(
- context,
- mainScope,
- display,
- nextDecorViewHostId++
- )
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/ReusableWindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/ReusableWindowDecorViewHost.kt
deleted file mode 100644
index 64536d1..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/ReusableWindowDecorViewHost.kt
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.wm.shell.windowdecor.viewhost
-
-import android.content.Context
-import android.content.res.Configuration
-import android.graphics.PixelFormat
-import android.os.Trace
-import android.view.Display
-import android.view.SurfaceControl
-import android.view.SurfaceControlViewHost
-import android.view.View
-import android.view.WindowManager
-import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-import android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
-import android.view.WindowManager.LayoutParams.TYPE_APPLICATION
-import android.widget.FrameLayout
-import com.android.internal.annotations.VisibleForTesting
-import com.android.wm.shell.shared.annotations.ShellMainThread
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
-
-/**
- * An implementation of [WindowDecorViewHost] that supports:
- * 1) Replacing the root [View], meaning [WindowDecorViewHost.updateView] maybe be
- * called with different [View] instances. This is useful when reusing [WindowDecorViewHost]s
- * instances for vastly different view hierarchies, such as Desktop Windowing's App Handles and
- * App Headers.
- * 2) Pre-warming of the underlying [SurfaceControlViewHost]s. Useful because their creation and
- * first root view assignment are expensive, which is undesirable in latency-sensitive code
- * paths like during a shell transition.
- */
-class ReusableWindowDecorViewHost(
- private val context: Context,
- @ShellMainThread private val mainScope: CoroutineScope,
- display: Display,
- val id: Int,
- @VisibleForTesting val viewHostAdapter: SurfaceControlViewHostAdapter =
- SurfaceControlViewHostAdapter(context, display)
-) : WindowDecorViewHost, Warmable {
-
- @VisibleForTesting
- val rootView = FrameLayout(context)
-
- private var currentUpdateJob: Job? = null
-
- override val surfaceControl: SurfaceControl
- get() = viewHostAdapter.rootSurface
-
- override fun warmUp() {
- if (viewHostAdapter.isInitialized()) {
- // Already warmed up.
- return
- }
- Trace.beginSection("$TAG#warmUp")
- viewHostAdapter.prepareViewHost(context.resources.configuration)
- viewHostAdapter.updateView(
- rootView,
- WindowManager.LayoutParams(
- 0 /* width*/,
- 0 /* height */,
- TYPE_APPLICATION,
- FLAG_NOT_FOCUSABLE or FLAG_SPLIT_TOUCH,
- PixelFormat.TRANSPARENT
- ).apply {
- setTitle("View root of $TAG#$id")
- setTrustedOverlay()
- }
- )
- Trace.endSection()
- }
-
- override fun updateView(
- view: View,
- attrs: WindowManager.LayoutParams,
- configuration: Configuration,
- onDrawTransaction: SurfaceControl.Transaction?
- ) {
- clearCurrentUpdateJob()
- updateViewHost(view, attrs, configuration, onDrawTransaction)
- }
-
- override fun updateViewAsync(
- view: View,
- attrs: WindowManager.LayoutParams,
- configuration: Configuration
- ) {
- clearCurrentUpdateJob()
- currentUpdateJob = mainScope.launch {
- updateViewHost(view, attrs, configuration, onDrawTransaction = null)
- }
- }
-
- override fun release(t: SurfaceControl.Transaction) {
- clearCurrentUpdateJob()
- viewHostAdapter.release(t)
- }
-
- private fun updateViewHost(
- view: View,
- attrs: WindowManager.LayoutParams,
- configuration: Configuration,
- onDrawTransaction: SurfaceControl.Transaction?
- ) {
- viewHostAdapter.prepareViewHost(configuration)
- onDrawTransaction?.let {
- viewHostAdapter.applyTransactionOnDraw(it)
- }
- rootView.removeAllViews()
- rootView.addView(view)
- viewHostAdapter.updateView(rootView, attrs)
- }
-
- private fun clearCurrentUpdateJob() {
- currentUpdateJob?.cancel()
- currentUpdateJob = null
- }
-
- interface Factory {
- fun create(
- context: Context,
- @ShellMainThread mainScope: CoroutineScope,
- display: Display,
- id: Int
- ): ReusableWindowDecorViewHost
- }
-
- object DefaultFactory : Factory {
- override fun create(
- context: Context,
- @ShellMainThread mainScope: CoroutineScope,
- display: Display,
- id: Int
- ): ReusableWindowDecorViewHost {
- return ReusableWindowDecorViewHost(
- context,
- mainScope,
- display,
- id
- )
- }
- }
-
- companion object {
- private const val TAG = "ReusableWindowDecorViewHost"
- }
-}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/SurfaceControlViewHostAdapter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/SurfaceControlViewHostAdapter.kt
deleted file mode 100644
index a54c9ba..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/SurfaceControlViewHostAdapter.kt
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.wm.shell.windowdecor.viewhost
-
-import android.content.Context
-import android.content.res.Configuration
-import android.view.AttachedSurfaceControl
-import android.view.Display
-import android.view.SurfaceControl
-import android.view.SurfaceControlViewHost
-import android.view.View
-import android.view.WindowManager
-import android.view.WindowlessWindowManager
-import androidx.tracing.Trace
-import com.android.internal.annotations.VisibleForTesting
-typealias SurfaceControlViewHostFactory =
- (Context, Display, WindowlessWindowManager, String) -> SurfaceControlViewHost
-
-/**
- * Adapter for a [SurfaceControlViewHost] and its backing [SurfaceControl].
- *
- * It does not support swapping the root view added to the VRI of the [SurfaceControlViewHost], and
- * any attempts to do will throw, which means that once a [View] is added using [updateView], only
- * its properties and binding may be changed, its children views may be added, removed or changed
- * and its [WindowManager.LayoutParams] may be changed.
- */
-class SurfaceControlViewHostAdapter(
- private val context: Context,
- private val display: Display,
- private val surfaceControlViewHostFactory: SurfaceControlViewHostFactory = { c, d, wwm, s ->
- SurfaceControlViewHost(c, d, wwm, s)
- }
-) {
- val rootSurface: SurfaceControl = SurfaceControl.Builder()
- .setName("SurfaceControlViewHostAdapter surface")
- .setContainerLayer()
- .setCallsite("SurfaceControlViewHostAdapter#init")
- .build()
-
- private var wwm: WindowlessWindowManager? = null
- @VisibleForTesting
- var viewHost: SurfaceControlViewHost? = null
-
- /** Initialize the [SurfaceControlViewHost] if needed. */
- fun prepareViewHost(configuration: Configuration) {
- if (wwm == null) {
- wwm = WindowlessWindowManager(configuration, rootSurface, null)
- }
- requireWindowlessWindowManager().setConfiguration(configuration)
- if (viewHost == null) {
- viewHost = surfaceControlViewHostFactory.invoke(
- context,
- display,
- requireWindowlessWindowManager(),
- "SurfaceControlViewHostAdapter#prepareViewHost"
- )
- }
- }
-
- /**
- * Request to apply the transaction atomically with the next draw of the view hierarchy.
- * See [AttachedSurfaceControl.applyTransactionOnDraw].
- */
- fun applyTransactionOnDraw(t: SurfaceControl.Transaction) {
- requireViewHost().rootSurfaceControl.applyTransactionOnDraw(t)
- }
-
- /** Update the view hierarchy of the view host. */
- fun updateView(view: View, attrs: WindowManager.LayoutParams) {
- if (requireViewHost().view == null) {
- Trace.beginSection("SurfaceControlViewHostAdapter#updateView-setView")
- requireViewHost().setView(view, attrs)
- Trace.endSection()
- } else {
- check(requireViewHost().view == view) { "Changing view is not allowed" }
- Trace.beginSection("SurfaceControlViewHostAdapter#updateView-relayout")
- requireViewHost().relayout(attrs)
- Trace.endSection()
- }
- }
-
- /** Release the view host and remove the backing surface. */
- fun release(t: SurfaceControl.Transaction) {
- viewHost?.release()
- t.remove(rootSurface)
- }
-
- /** Whether the view host has had a view hierarchy set. */
- fun isInitialized(): Boolean = viewHost?.view != null
-
- private fun requireWindowlessWindowManager(): WindowlessWindowManager {
- return wwm ?: error("Expected non-null windowless window manager")
- }
-
- private fun requireViewHost(): SurfaceControlViewHost {
- return viewHost ?: error("Expected non-null view host")
- }
-}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionResizeAndDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionResizeAndDrag.kt
new file mode 100644
index 0000000..f08e50e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionResizeAndDrag.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.scenarios
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.device.apphelpers.CalculatorAppHelper
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper.Corners.LEFT_TOP
+import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class StartAppMediaProjectionResizeAndDrag {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+
+ private val targetApp = CalculatorAppHelper(instrumentation)
+ private val mediaProjectionAppHelper = StartMediaProjectionAppHelper(instrumentation)
+ private val testApp = DesktopModeAppHelper(mediaProjectionAppHelper)
+
+ @Rule
+ @JvmField
+ val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, Rotation.ROTATION_0)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(0)
+ testApp.enterDesktopWithDrag(wmHelper, device)
+ }
+
+ @Test
+ open fun startMediaProjectionAndResize() {
+ mediaProjectionAppHelper.startSingleAppMediaProjection(wmHelper, targetApp)
+
+ with(DesktopModeAppHelper(targetApp)) {
+ val windowRect = wmHelper.getWindowRegion(this).bounds
+ // Set start x-coordinate as center of app header.
+ val startX = windowRect.centerX()
+ val startY = windowRect.top
+
+ dragWindow(startX, startY, endX = startX + 150, endY = startY + 150, wmHelper, device)
+ cornerResize(wmHelper, device, LEFT_TOP, -200, -200)
+ }
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ targetApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt
new file mode 100644
index 0000000..717ea30
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.scenarios
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.device.apphelpers.CalculatorAppHelper
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.MailAppHelper
+import com.android.server.wm.flicker.helpers.NewTasksAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class StartAppMediaProjectionWithMaxDesktopWindows {
+
+ val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ val tapl = LauncherInstrumentation()
+ val wmHelper = WindowManagerStateHelper(instrumentation)
+ val device = UiDevice.getInstance(instrumentation)
+
+ private val targetApp = CalculatorAppHelper(instrumentation)
+ private val mailApp = MailAppHelper(instrumentation)
+ private val newTasksApp = DesktopModeAppHelper(NewTasksAppHelper(instrumentation))
+ private val imeApp = DesktopModeAppHelper(ImeAppHelper(instrumentation))
+ private val simpleApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+ private val mediaProjectionAppHelper = StartMediaProjectionAppHelper(instrumentation)
+ private val testApp = DesktopModeAppHelper(mediaProjectionAppHelper)
+
+ @Rule
+ @JvmField
+ val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, Rotation.ROTATION_0)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(0)
+ testApp.enterDesktopWithDrag(wmHelper, device)
+ }
+
+ @Test
+ open fun startMediaProjection() {
+ // TODO(b/366455106) - handle max task Limit
+ mediaProjectionAppHelper.startSingleAppMediaProjection(wmHelper, targetApp)
+ mailApp.launchViaIntent(wmHelper)
+ simpleApp.launchViaIntent(wmHelper)
+ newTasksApp.launchViaIntent(wmHelper)
+ imeApp.launchViaIntent(wmHelper)
+ }
+
+ @After
+ fun teardown() {
+ mailApp.exit(wmHelper)
+ simpleApp.exit(wmHelper)
+ newTasksApp.exit(wmHelper)
+ imeApp.exit(wmHelper)
+ targetApp.exit(wmHelper)
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithMaxDesktopWindows.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithMaxDesktopWindows.kt
new file mode 100644
index 0000000..0051952
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithMaxDesktopWindows.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.scenarios
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.MailAppHelper
+import com.android.server.wm.flicker.helpers.NewTasksAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class StartScreenMediaProjectionWithMaxDesktopWindows {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+
+ @Rule
+ @JvmField
+ val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, Rotation.ROTATION_0)
+
+ private val mailApp = DesktopModeAppHelper(MailAppHelper(instrumentation))
+ private val newTasksApp = DesktopModeAppHelper(NewTasksAppHelper(instrumentation))
+ private val imeApp = DesktopModeAppHelper(ImeAppHelper(instrumentation))
+ private val simpleApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+ private val mediaProjectionAppHelper = StartMediaProjectionAppHelper(instrumentation)
+ private val testApp = DesktopModeAppHelper(mediaProjectionAppHelper)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ testApp.enterDesktopWithDrag(wmHelper, device)
+ }
+
+ @Test
+ open fun startMediaProjection() {
+ mediaProjectionAppHelper.startEntireScreenMediaProjection(wmHelper)
+ simpleApp.launchViaIntent(wmHelper)
+ mailApp.launchViaIntent(wmHelper)
+ newTasksApp.launchViaIntent(wmHelper)
+ imeApp.launchViaIntent(wmHelper)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ simpleApp.exit(wmHelper)
+ mailApp.exit(wmHelper)
+ newTasksApp.exit(wmHelper)
+ imeApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/Android.bp b/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/Android.bp
new file mode 100644
index 0000000..85e6a8d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/Android.bp
@@ -0,0 +1,38 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "WMShellFlickerTestsMediaProjection",
+ defaults: ["WMShellFlickerTestsDefault"],
+ manifest: "AndroidManifest.xml",
+ test_config_template: "AndroidTestTemplate.xml",
+ srcs: ["src/**/*.kt"],
+ static_libs: [
+ "WMShellFlickerTestsBase",
+ "WMShellScenariosMediaProjection",
+ "WMShellTestUtils",
+ ],
+ data: ["trace_config/*"],
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/AndroidManifest.xml b/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/AndroidManifest.xml
new file mode 100644
index 0000000..74b0daf
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/AndroidManifest.xml
@@ -0,0 +1,85 @@
+<!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.wm.shell.flicker">
+
+ <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
+ <!-- Read and write traces from external storage -->
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <!-- Allow the test to write directly to /sdcard/ -->
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+ <!-- Write secure settings -->
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <!-- Capture screen contents -->
+ <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
+ <!-- Enable / Disable tracing !-->
+ <uses-permission android:name="android.permission.DUMP" />
+ <!-- Run layers trace -->
+ <uses-permission android:name="android.permission.HARDWARE_TEST"/>
+ <!-- Capture screen recording -->
+ <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"/>
+ <!-- Workaround grant runtime permission exception from b/152733071 -->
+ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
+ <uses-permission android:name="android.permission.READ_LOGS"/>
+ <!-- Force-stop test apps -->
+ <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/>
+ <!-- Control test app's media session -->
+ <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/>
+ <!-- ATM.removeRootTasksWithActivityTypes() -->
+ <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
+ <!-- Enable bubble notification-->
+ <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
+ <!-- Allow the test to connect to perfetto trace processor -->
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
+ <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" />
+
+ <!-- Allow the test to write directly to /sdcard/ and connect to trace processor -->
+ <application android:requestLegacyExternalStorage="true"
+ android:networkSecurityConfig="@xml/network_security_config"
+ android:largeHeap="true">
+ <uses-library android:name="android.test.runner"/>
+
+ <service android:name=".NotificationListener"
+ android:exported="true"
+ android:label="WMShellTestsNotificationListenerService"
+ android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+ <intent-filter>
+ <action android:name="android.service.notification.NotificationListenerService" />
+ </intent-filter>
+ </service>
+
+ <service android:name="com.android.wm.shell.flicker.utils.MediaProjectionService"
+ android:foregroundServiceType="mediaProjection"
+ android:label="WMShellTestsMediaProjectionService"
+ android:enabled="true">
+ </service>
+
+ <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+ <provider
+ android:name="androidx.startup.InitializationProvider"
+ android:authorities="${applicationId}.androidx-startup"
+ tools:node="remove" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.wm.shell.flicker"
+ android:label="WindowManager Shell Flicker Tests">
+ </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/AndroidTestTemplate.xml
new file mode 100644
index 0000000..40dbbac
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/AndroidTestTemplate.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<configuration description="Runs WindowManager Shell Flicker Tests {MODULE}">
+ <option name="test-tag" value="FlickerTests"/>
+ <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+ <option name="isolated-storage" value="false"/>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- disable DeprecatedTargetSdk warning -->
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
+ <!-- keeps the screen on during tests -->
+ <option name="screen-always-on" value="on"/>
+ <!-- prevents the phone from restarting -->
+ <option name="force-skip-system-props" value="true"/>
+ <!-- set WM tracing verbose level to all -->
+ <option name="run-command" value="cmd window tracing level all"/>
+ <!-- set WM tracing to frame (avoid incomplete states) -->
+ <option name="run-command" value="cmd window tracing frame"/>
+ <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests -->
+ <option name="run-command" value="pm disable com.google.android.internal.betterbug"/>
+ <!-- ensure lock screen mode is swipe -->
+ <option name="run-command" value="locksettings set-disabled false"/>
+ <!-- restart launcher to activate TAPL -->
+ <option name="run-command"
+ value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher"/>
+ <!-- Increase trace size: 20mb for WM and 80mb for SF -->
+ <option name="run-command" value="cmd window tracing size 20480"/>
+ <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920"/>
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="test-user-token" value="%TEST_USER%"/>
+ <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+ <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
+ <option name="run-command" value="settings put system show_touches 1"/>
+ <option name="run-command" value="settings put system pointer_location 1"/>
+ <option name="teardown-command"
+ value="settings delete secure show_ime_with_hard_keyboard"/>
+ <option name="teardown-command" value="settings delete system show_touches"/>
+ <option name="teardown-command" value="settings delete system pointer_location"/>
+ <option name="teardown-command"
+ value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/>
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="{MODULE}.apk"/>
+ <option name="test-file-name" value="FlickerTestApp.apk"/>
+ </target_preparer>
+
+ <!-- Needed for pushing the trace config file -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="push-file"
+ key="trace_config.textproto"
+ value="/data/misc/perfetto-traces/trace_config.textproto"
+ />
+ <!--Install the content provider automatically when we push some file in sdcard folder.-->
+ <!--Needed to avoid the installation during the test suite.-->
+ <option name="push-file" key="trace_config.textproto" value="/sdcard/sample.textproto"/>
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="{PACKAGE}"/>
+ <option name="shell-timeout" value="6600s"/>
+ <option name="test-timeout" value="6000s"/>
+ <option name="hidden-api-checks" value="false"/>
+ <option name="device-listeners" value="android.device.collectors.PerfettoListener"/>
+ <!-- PerfettoListener related arguments -->
+ <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/>
+ <option name="instrumentation-arg"
+ key="perfetto_config_file"
+ value="trace_config.textproto"
+ />
+ <option name="instrumentation-arg" key="per_run" value="true"/>
+ <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/>
+ </test>
+ <!-- Needed for pulling the collected trace config on to the host -->
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="pull-pattern-keys" value="perfetto_file_path"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker/files"/>
+ <option name="collect-on-run-ended-only" value="true"/>
+ <option name="clean-up" value="true"/>
+ </metrics_collector>
+</configuration>
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/res/xml/network_security_config.xml b/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/res/xml/network_security_config.xml
new file mode 100644
index 0000000..4bd9ca0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/res/xml/network_security_config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<network-security-config>
+ <domain-config cleartextTrafficPermitted="true">
+ <domain includeSubdomains="true">localhost</domain>
+ </domain-config>
+</network-security-config>
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/trace_config/trace_config.textproto
new file mode 100644
index 0000000..9f2e497
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/trace_config/trace_config.textproto
@@ -0,0 +1,71 @@
+# 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.
+
+# proto-message: TraceConfig
+
+# Enable periodic flushing of the trace buffer into the output file.
+write_into_file: true
+
+# Writes the userspace buffer into the file every 1s.
+file_write_period_ms: 2500
+
+# See b/126487238 - we need to guarantee ordering of events.
+flush_period_ms: 30000
+
+# The trace buffers needs to be big enough to hold |file_write_period_ms| of
+# trace data. The trace buffer sizing depends on the number of trace categories
+# enabled and the device activity.
+
+# RSS events
+buffers: {
+ size_kb: 63488
+ fill_policy: RING_BUFFER
+}
+
+data_sources {
+ config {
+ name: "linux.process_stats"
+ target_buffer: 0
+ # polled per-process memory counters and process/thread names.
+ # If you don't want the polled counters, remove the "process_stats_config"
+ # section, but keep the data source itself as it still provides on-demand
+ # thread/process naming for ftrace data below.
+ process_stats_config {
+ scan_all_processes_on_start: true
+ }
+ }
+}
+
+data_sources: {
+ config {
+ name: "linux.ftrace"
+ ftrace_config {
+ ftrace_events: "ftrace/print"
+ ftrace_events: "task/task_newtask"
+ ftrace_events: "task/task_rename"
+ atrace_categories: "ss"
+ atrace_categories: "wm"
+ atrace_categories: "am"
+ atrace_categories: "aidl"
+ atrace_categories: "input"
+ atrace_categories: "binder_driver"
+ atrace_categories: "sched_process_exit"
+ atrace_apps: "com.android.server.wm.flicker.testapp"
+ atrace_apps: "com.android.systemui"
+ atrace_apps: "com.android.wm.shell.flicker.service"
+ atrace_apps: "com.google.android.apps.nexuslauncher"
+ }
+ }
+}
+
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/Android.bp b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/Android.bp
new file mode 100644
index 0000000..997a0af
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/Android.bp
@@ -0,0 +1,46 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_library {
+ name: "WMShellScenariosMediaProjection",
+ platform_apis: true,
+ optimize: {
+ enabled: false,
+ },
+ srcs: ["src/**/*.kt"],
+ static_libs: [
+ "WMShellFlickerTestsBase",
+ "WMShellTestUtils",
+ "wm-shell-flicker-utils",
+ "androidx.test.ext.junit",
+ "flickertestapplib",
+ "flickerlib-helpers",
+ "flickerlib-trace_processor_shell",
+ "platform-test-annotations",
+ "wm-flicker-common-app-helpers",
+ "launcher-helper-lib",
+ "launcher-aosp-tapl",
+ ],
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithDisplayRotations.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithDisplayRotations.kt
new file mode 100644
index 0000000..1573b58
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithDisplayRotations.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.scenarios
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.device.apphelpers.CalculatorAppHelper
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class StartAppMediaProjectionWithDisplayRotations {
+
+ val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ val tapl = LauncherInstrumentation()
+ val wmHelper = WindowManagerStateHelper(instrumentation)
+ val device = UiDevice.getInstance(instrumentation)
+
+ private val initialRotation = Rotation.ROTATION_0
+ private val targetApp = CalculatorAppHelper(instrumentation)
+ private val mediaProjectionAppHelper = StartMediaProjectionAppHelper(instrumentation)
+ private val testApp = DesktopModeAppHelper(mediaProjectionAppHelper)
+
+ @Rule
+ @JvmField
+ val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, initialRotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(initialRotation.value)
+ testApp.launchViaIntent(wmHelper)
+ }
+
+ @Test
+ open fun startMediaProjectionAndRotate() {
+ mediaProjectionAppHelper.startSingleAppMediaProjection(wmHelper, targetApp)
+ wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+
+ ChangeDisplayOrientationRule.setRotation(Rotation.ROTATION_90)
+ ChangeDisplayOrientationRule.setRotation(Rotation.ROTATION_270)
+ ChangeDisplayOrientationRule.setRotation(Rotation.ROTATION_0)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithDisplayRotations.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithDisplayRotations.kt
new file mode 100644
index 0000000..e80a895
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithDisplayRotations.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.scenarios
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class StartScreenMediaProjectionWithDisplayRotations {
+
+ val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ val tapl = LauncherInstrumentation()
+ val wmHelper = WindowManagerStateHelper(instrumentation)
+ val device = UiDevice.getInstance(instrumentation)
+
+ private val initialRotation = Rotation.ROTATION_0
+ private val mediaProjectionAppHelper = StartMediaProjectionAppHelper(instrumentation)
+ private val testApp = DesktopModeAppHelper(mediaProjectionAppHelper)
+
+ @Rule
+ @JvmField
+ val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, initialRotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ testApp.launchViaIntent(wmHelper)
+ }
+
+ @Test
+ open fun startMediaProjectionAndRotate() {
+ mediaProjectionAppHelper.startEntireScreenMediaProjection(wmHelper)
+ wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+
+ ChangeDisplayOrientationRule.setRotation(Rotation.ROTATION_90)
+ ChangeDisplayOrientationRule.setRotation(Rotation.ROTATION_270)
+ ChangeDisplayOrientationRule.setRotation(initialRotation)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MediaProjectionService.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MediaProjectionService.kt
new file mode 100644
index 0000000..aa4e216
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MediaProjectionService.kt
@@ -0,0 +1,110 @@
+/*
+ * 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.flicker.utils
+
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.Service
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.drawable.Icon
+import android.os.IBinder
+import android.os.Message
+import android.os.Messenger
+import android.os.RemoteException
+import android.util.Log
+
+class MediaProjectionService : Service() {
+
+ private var mTestBitmap: Bitmap? = null
+
+ private val notificationId: Int = 1
+ private val notificationChannelId: String = "MediaProjectionFlickerTest"
+ private val notificationChannelName = "FlickerMediaProjectionService"
+
+ var mMessenger: Messenger? = null
+
+ override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
+ mMessenger = intent.extras?.getParcelable(
+ MediaProjectionUtils.EXTRA_MESSENGER, Messenger::class.java)
+ startForeground()
+ return super.onStartCommand(intent, flags, startId)
+ }
+
+ override fun onBind(intent: Intent?): IBinder? = null
+
+ override fun onDestroy() {
+ mTestBitmap?.recycle()
+ mTestBitmap = null
+ sendMessage(MediaProjectionUtils.MSG_SERVICE_DESTROYED)
+ super.onDestroy()
+ }
+
+ private fun createNotificationIcon(): Icon {
+ Log.d(TAG, "createNotification")
+
+ mTestBitmap = Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888)
+ val canvas = Canvas(mTestBitmap!!)
+ canvas.drawColor(Color.BLUE)
+ return Icon.createWithBitmap(mTestBitmap)
+ }
+
+ private fun startForeground() {
+ Log.d(TAG, "startForeground")
+ val channel = NotificationChannel(
+ notificationChannelId,
+ notificationChannelName, NotificationManager.IMPORTANCE_NONE
+ )
+ channel.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
+
+ val notificationManager: NotificationManager =
+ getSystemService(NotificationManager::class.java)
+ notificationManager.createNotificationChannel(channel)
+
+ val notificationBuilder: Notification.Builder =
+ Notification.Builder(this, notificationChannelId)
+
+ val notification = notificationBuilder.setOngoing(true)
+ .setContentTitle("App is running")
+ .setSmallIcon(createNotificationIcon())
+ .setCategory(Notification.CATEGORY_SERVICE)
+ .setContentText("Context")
+ .build()
+
+ startForeground(notificationId, notification)
+ sendMessage(MediaProjectionUtils.MSG_START_FOREGROUND_DONE)
+ }
+
+ fun sendMessage(what: Int) {
+ Log.d(TAG, "sendMessage")
+ with(Message.obtain()) {
+ this.what = what
+ try {
+ mMessenger!!.send(this)
+ } catch (e: RemoteException) {
+ Log.d(TAG, "Unable to send message", e)
+ }
+ }
+ }
+
+ companion object {
+ private const val TAG: String = "FlickerMediaProjectionService"
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/Warmable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MediaProjectionUtils.kt
similarity index 70%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/Warmable.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MediaProjectionUtils.kt
index 0df9bfa..f9706969 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/Warmable.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MediaProjectionUtils.kt
@@ -13,11 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.wm.shell.windowdecor.viewhost
-/**
- * An interface for an object that can be warmed up before it's needed.
- */
-interface Warmable {
- fun warmUp()
-}
+package com.android.wm.shell.flicker.utils
+
+object MediaProjectionUtils {
+ const val REQUEST_CODE: Int = 99
+ const val MSG_START_FOREGROUND_DONE: Int = 1
+ const val MSG_SERVICE_DESTROYED: Int = 2
+ const val EXTRA_MESSENGER: String = "messenger"
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index b53ea38..227060d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -137,6 +137,8 @@
private Transitions mTransitions;
@Mock
private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+ @Mock
+ private Handler mHandler;
private BackAnimationController mController;
private TestableContentResolver mContentResolver;
@@ -161,13 +163,14 @@
mTestableLooper = TestableLooper.get(this);
mShellInit = spy(new ShellInit(mShellExecutor));
mDefaultCrossActivityBackAnimation = new DefaultCrossActivityBackAnimation(mContext,
- mAnimationBackground, mRootTaskDisplayAreaOrganizer);
- mCrossTaskBackAnimation = new CrossTaskBackAnimation(mContext, mAnimationBackground);
+ mAnimationBackground, mRootTaskDisplayAreaOrganizer, mHandler);
+ mCrossTaskBackAnimation = new CrossTaskBackAnimation(mContext, mAnimationBackground,
+ mHandler);
mShellBackAnimationRegistry =
new ShellBackAnimationRegistry(mDefaultCrossActivityBackAnimation,
mCrossTaskBackAnimation, /* dialogCloseAnimation= */ null,
new CustomCrossActivityBackAnimation(mContext, mAnimationBackground,
- mRootTaskDisplayAreaOrganizer),
+ mRootTaskDisplayAreaOrganizer, mHandler),
/* defaultBackToHomeAnimation= */ null);
mController =
new BackAnimationController(
@@ -181,7 +184,8 @@
mAnimationBackground,
mShellBackAnimationRegistry,
mShellCommandHandler,
- mTransitions);
+ mTransitions,
+ mHandler);
mShellInit.init();
mShellExecutor.flushAll();
mTouchableRegion = new Rect(0, 0, 100, 100);
@@ -344,7 +348,8 @@
mAnimationBackground,
mShellBackAnimationRegistry,
mShellCommandHandler,
- mTransitions);
+ mTransitions,
+ mHandler);
shellInit.init();
registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
@@ -898,7 +903,8 @@
new BackAnimationRunner(
mAnimatorCallback,
mBackAnimationRunner,
- mContext));
+ mContext,
+ mHandler));
}
private void unregisterAnimation(int type) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
index 080ad90..5b5ef6f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
@@ -22,6 +22,7 @@
import android.graphics.Color
import android.graphics.Point
import android.graphics.Rect
+import android.os.Handler
import android.os.RemoteException
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -66,6 +67,7 @@
@Mock private lateinit var transitionAnimation: TransitionAnimation
@Mock private lateinit var appCompatTaskInfo: AppCompatTaskInfo
@Mock private lateinit var transaction: Transaction
+ @Mock private lateinit var handler: Handler
private lateinit var customCrossActivityBackAnimation: CustomCrossActivityBackAnimation
private lateinit var customAnimationLoader: CustomAnimationLoader
@@ -80,7 +82,8 @@
backAnimationBackground,
rootTaskDisplayAreaOrganizer,
transaction,
- customAnimationLoader
+ customAnimationLoader,
+ handler,
)
whenever(transitionAnimation.loadAppTransitionAnimation(eq(PACKAGE_NAME), eq(OPEN_RES_ID)))
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
index f5847cc..cf69704 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
@@ -25,6 +25,7 @@
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.os.Handler;
import android.os.SystemClock;
import android.provider.DeviceConfig;
import android.view.InputDevice;
@@ -55,6 +56,7 @@
private @Mock DisplayController mDisplayController;
private @Mock DisplayImeController mDisplayImeController;
private @Mock ShellTaskOrganizer mTaskOrganizer;
+ private @Mock Handler mHandler;
private SplitLayout mSplitLayout;
private DividerView mDividerView;
@@ -65,7 +67,7 @@
Configuration configuration = getConfiguration();
mSplitLayout = new SplitLayout("TestSplitLayout", mContext, configuration,
mSplitLayoutHandler, mCallbacks, mDisplayController, mDisplayImeController,
- mTaskOrganizer, SplitLayout.PARALLAX_NONE);
+ mTaskOrganizer, SplitLayout.PARALLAX_NONE, mHandler);
SplitWindowManager splitWindowManager = new SplitWindowManager("TestSplitWindowManager",
mContext,
configuration, mCallbacks);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 82b3a7d..177e47a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -35,6 +35,7 @@
import android.app.ActivityManager;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.os.Handler;
import android.window.WindowContainerTransaction;
import androidx.test.annotation.UiThreadTest;
@@ -65,6 +66,7 @@
@Mock DisplayImeController mDisplayImeController;
@Mock ShellTaskOrganizer mTaskOrganizer;
@Mock WindowContainerTransaction mWct;
+ @Mock Handler mHandler;
@Captor ArgumentCaptor<Runnable> mRunnableCaptor;
private SplitLayout mSplitLayout;
@@ -80,7 +82,8 @@
mDisplayController,
mDisplayImeController,
mTaskOrganizer,
- SplitLayout.PARALLAX_NONE));
+ SplitLayout.PARALLAX_NONE,
+ mHandler));
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 10557dd..8f20841 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -18,7 +18,9 @@
import android.app.ActivityManager.RecentTaskInfo
import android.app.ActivityManager.RunningTaskInfo
+import android.app.ActivityOptions
import android.app.KeyguardManager
+import android.app.PendingIntent
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
@@ -38,11 +40,13 @@
import android.graphics.PointF
import android.graphics.Rect
import android.os.Binder
+import android.os.Handler
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
+import android.view.DragEvent
import android.view.Gravity
import android.view.SurfaceControl
import android.view.WindowManager
@@ -107,6 +111,7 @@
import com.android.wm.shell.transition.Transitions.TransitionHandler
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
+import java.util.function.Consumer
import java.util.Optional
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
@@ -131,8 +136,8 @@
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.atLeastOnce
-import org.mockito.kotlin.eq
import org.mockito.kotlin.capture
+import org.mockito.kotlin.eq
import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
@@ -177,6 +182,7 @@
private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
@Mock private lateinit var mockSurface: SurfaceControl
@Mock private lateinit var taskbarDesktopTaskListener: TaskbarDesktopTaskListener
+ @Mock private lateinit var mockHandler: Handler
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var controller: DesktopTasksController
@@ -217,7 +223,8 @@
shellTaskOrganizer,
MAX_TASK_LIMIT,
mockInteractionJankMonitor,
- mContext)
+ mContext,
+ mockHandler)
whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
@@ -270,7 +277,9 @@
shellExecutor,
Optional.of(desktopTasksLimiter),
recentTasksController,
- mockInteractionJankMonitor)
+ mockInteractionJankMonitor,
+ mockHandler,
+ )
}
@After
@@ -1666,6 +1675,36 @@
}
@Test
+ fun handleRequest_fullscreenTask_noTasks_enforceDesktop_freeformDisplay_returnFreeformWCT() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+ whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+
+ val fullscreenTask = createFullscreenTask()
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertNotNull(wct, "should handle request")
+ assertThat(wct.changes[fullscreenTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
+ assertThat(wct.hierarchyOps).hasSize(1)
+ wct.assertReorderAt(0, fullscreenTask, toTop = true)
+ }
+
+ @Test
+ fun handleRequest_fullscreenTask_noTasks_enforceDesktop_fullscreenDisplay_returnNull() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+ whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+
+ val fullscreenTask = createFullscreenTask()
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertThat(wct).isNull()
+ }
+
+ @Test
fun handleRequest_fullscreenTask_freeformNotVisible_returnNull() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
@@ -1725,6 +1764,37 @@
}
@Test
+ fun handleRequest_freeformTask_relaunchTask_enforceDesktop_freeformDisplay_noWinModeChange() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+ whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+
+ val freeformTask = setUpFreeformTask()
+ markTaskHidden(freeformTask)
+ val wct = controller.handleRequest(Binder(), createTransition(freeformTask))
+
+ assertNotNull(wct, "should handle request")
+ assertFalse(wct.anyWindowingModeChange(freeformTask.token))
+ }
+
+ @Test
+ fun handleRequest_freeformTask_relaunchTask_enforceDesktop_fullscreenDisplay_becomesUndefined() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+ whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+
+ val freeformTask = setUpFreeformTask()
+ markTaskHidden(freeformTask)
+ val wct = controller.handleRequest(Binder(), createTransition(freeformTask))
+
+ assertNotNull(wct, "should handle request")
+ assertThat(wct.changes[freeformTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
+ }
+
+ @Test
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_freeformTask_desktopWallpaperDisabled_freeformNotVisible_reorderedToTop() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
@@ -2956,6 +3026,8 @@
screenOrientation = SCREEN_ORIENTATION_LANDSCAPE
configuration.windowConfiguration.appBounds = bounds
}
+ appCompatTaskInfo.topActivityLetterboxAppWidth = bounds.width()
+ appCompatTaskInfo.topActivityLetterboxAppHeight = bounds.height()
isResizeable = false
}
@@ -3050,6 +3122,95 @@
assertThat(taskRepository.removeBoundsBeforeMaximize(task.taskId)).isNull()
}
+
+ @Test
+ fun onUnhandledDrag_newFreeformIntent() {
+ testOnUnhandledDrag(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR,
+ PointF(1200f, 700f),
+ Rect(240, 700, 2160, 1900))
+ }
+
+ @Test
+ fun onUnhandledDrag_newFreeformIntentSplitLeft() {
+ testOnUnhandledDrag(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR,
+ PointF(50f, 700f),
+ Rect(0, 0, 500, 1000))
+ }
+
+ @Test
+ fun onUnhandledDrag_newFreeformIntentSplitRight() {
+ testOnUnhandledDrag(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR,
+ PointF(2500f, 700f),
+ Rect(500, 0, 1000, 1000))
+ }
+
+ @Test
+ fun onUnhandledDrag_newFullscreenIntent() {
+ testOnUnhandledDrag(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
+ PointF(1200f, 50f),
+ Rect())
+ }
+
+ /**
+ * Assert that an unhandled drag event launches a PendingIntent with the
+ * windowing mode and bounds we are expecting.
+ */
+ private fun testOnUnhandledDrag(
+ indicatorType: DesktopModeVisualIndicator.IndicatorType,
+ inputCoordinate: PointF,
+ expectedBounds: Rect
+ ) {
+ setUpLandscapeDisplay()
+ val task = setUpFreeformTask()
+ markTaskVisible(task)
+ task.isFocused = true
+ val runningTasks = ArrayList<RunningTaskInfo>()
+ runningTasks.add(task)
+ val spyController = spy(controller)
+ val mockPendingIntent = mock(PendingIntent::class.java)
+ val mockDragEvent = mock(DragEvent::class.java)
+ val mockCallback = mock(Consumer::class.java)
+ val b = SurfaceControl.Builder()
+ b.setName("test surface")
+ val dragSurface = b.build()
+ whenever(shellTaskOrganizer.runningTasks).thenReturn(runningTasks)
+ whenever(mockDragEvent.dragSurface).thenReturn(dragSurface)
+ whenever(mockDragEvent.x).thenReturn(inputCoordinate.x)
+ whenever(mockDragEvent.y).thenReturn(inputCoordinate.y)
+ whenever(multiInstanceHelper.supportsMultiInstanceSplit(anyOrNull())).thenReturn(true)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ doReturn(indicatorType)
+ .whenever(spyController).updateVisualIndicator(
+ eq(task),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ eq(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT)
+ )
+
+ spyController.onUnhandledDrag(
+ mockPendingIntent,
+ mockDragEvent,
+ mockCallback as Consumer<Boolean>
+ )
+ val arg: ArgumentCaptor<WindowContainerTransaction> =
+ ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ var expectedWindowingMode: Int
+ if (indicatorType == DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR) {
+ expectedWindowingMode = WINDOWING_MODE_FULLSCREEN
+ // Fullscreen launches currently use default transitions
+ verify(transitions).startTransition(any(), capture(arg), anyOrNull())
+ } else {
+ expectedWindowingMode = WINDOWING_MODE_FREEFORM
+ // All other launches use a special handler.
+ verify(dragAndDropTransitionHandler).handleDropEvent(capture(arg))
+ }
+ assertThat(ActivityOptions.fromBundle(arg.value.hierarchyOps[0].launchOptions)
+ .launchWindowingMode).isEqualTo(expectedWindowingMode)
+ assertThat(ActivityOptions.fromBundle(arg.value.hierarchyOps[0].launchOptions)
+ .launchBounds).isEqualTo(expectedBounds)
+ }
+
private val desktopWallpaperIntent: Intent
get() = Intent(context, DesktopWallpaperActivity::class.java)
@@ -3114,6 +3275,18 @@
appCompatTaskInfo.isUserFullscreenOverrideEnabled = enableUserFullscreenOverride
appCompatTaskInfo.isSystemFullscreenOverrideEnabled = enableSystemFullscreenOverride
+ if (deviceOrientation == ORIENTATION_LANDSCAPE) {
+ configuration.windowConfiguration.appBounds =
+ Rect(0, 0, DISPLAY_DIMENSION_LONG, DISPLAY_DIMENSION_SHORT)
+ appCompatTaskInfo.topActivityLetterboxAppWidth = DISPLAY_DIMENSION_LONG
+ appCompatTaskInfo.topActivityLetterboxAppHeight = DISPLAY_DIMENSION_SHORT
+ } else {
+ configuration.windowConfiguration.appBounds =
+ Rect(0, 0, DISPLAY_DIMENSION_SHORT, DISPLAY_DIMENSION_LONG)
+ appCompatTaskInfo.topActivityLetterboxAppWidth = DISPLAY_DIMENSION_SHORT
+ appCompatTaskInfo.topActivityLetterboxAppHeight = DISPLAY_DIMENSION_LONG
+ }
+
if (shouldLetterbox) {
appCompatTaskInfo.setHasMinAspectRatioOverride(aspectRatioOverrideApplied)
if (deviceOrientation == ORIENTATION_LANDSCAPE &&
@@ -3130,14 +3303,6 @@
appCompatTaskInfo.topActivityLetterboxAppHeight = 1200
}
}
-
- if (deviceOrientation == ORIENTATION_LANDSCAPE) {
- configuration.windowConfiguration.appBounds =
- Rect(0, 0, DISPLAY_DIMENSION_LONG, DISPLAY_DIMENSION_SHORT)
- } else {
- configuration.windowConfiguration.appBounds =
- Rect(0, 0, DISPLAY_DIMENSION_SHORT, DISPLAY_DIMENSION_LONG)
- }
}
whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
runningTasks.add(task)
@@ -3359,6 +3524,14 @@
} ?: false
}
+private fun WindowContainerTransaction?.anyWindowingModeChange(
+ token: WindowContainerToken
+): Boolean {
+return this?.changes?.any { change ->
+ change.key == token.asBinder() && change.value.windowingMode >= 0
+} ?: false
+}
+
private fun createTaskInfo(id: Int) =
RecentTaskInfo().apply {
taskId = id
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index 2d0e428..61d03ca 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -18,6 +18,7 @@
import android.app.ActivityManager.RunningTaskInfo
import android.os.Binder
+import android.os.Handler
import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
@@ -70,6 +71,7 @@
@Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer
@Mock lateinit var transitions: Transitions
@Mock lateinit var interactionJankMonitor: InteractionJankMonitor
+ @Mock lateinit var handler: Handler
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var desktopTasksLimiter: DesktopTasksLimiter
@@ -85,7 +87,7 @@
desktopTasksLimiter =
DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, MAX_TASK_LIMIT,
- interactionJankMonitor, mContext)
+ interactionJankMonitor, mContext, handler)
}
@After
@@ -97,7 +99,7 @@
fun createDesktopTasksLimiter_withZeroLimit_shouldThrow() {
assertFailsWith<IllegalArgumentException> {
DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, 0,
- interactionJankMonitor, mContext)
+ interactionJankMonitor, mContext, handler)
}
}
@@ -105,7 +107,7 @@
fun createDesktopTasksLimiter_withNegativeLimit_shouldThrow() {
assertFailsWith<IllegalArgumentException> {
DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, -5,
- interactionJankMonitor, mContext)
+ interactionJankMonitor, mContext, handler)
}
}
@@ -334,7 +336,7 @@
fun getTaskToMinimizeIfNeeded_tasksAboveLimit_otherLimit_returnsBackTask() {
desktopTasksLimiter =
DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, MAX_TASK_LIMIT2,
- interactionJankMonitor, mContext)
+ interactionJankMonitor, mContext, handler)
val tasks = (1..MAX_TASK_LIMIT2 + 1).map { setUpFreeformTask() }
val minimizedTask = desktopTasksLimiter.getTaskToMinimizeIfNeeded(
@@ -375,6 +377,7 @@
verify(interactionJankMonitor).begin(
any(),
eq(mContext),
+ eq(handler),
eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW))
desktopTasksLimiter.getTransitionObserver().onTransitionFinished(
@@ -403,7 +406,9 @@
verify(interactionJankMonitor).begin(
any(),
eq(mContext),
- eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW))
+ eq(handler),
+ eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW),
+ )
desktopTasksLimiter.getTransitionObserver().onTransitionFinished(
transition,
@@ -432,6 +437,7 @@
verify(interactionJankMonitor).begin(
any(),
eq(mContext),
+ eq(handler),
eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW))
desktopTasksLimiter.getTransitionObserver().onTransitionMerged(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
index e0463b4..fefa933 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
@@ -34,6 +34,7 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
+import android.os.Handler;
import android.os.IBinder;
import android.util.DisplayMetrics;
import android.view.SurfaceControl;
@@ -81,6 +82,8 @@
Transitions.TransitionFinishCallback mTransitionFinishCallback;
@Mock
ShellExecutor mExecutor;
+ @Mock
+ Handler mHandler;
private Point mPoint;
private ExitDesktopTaskTransitionHandler mExitDesktopTaskTransitionHandler;
@@ -97,7 +100,7 @@
.thenReturn(getContext().getResources().getDisplayMetrics());
mExitDesktopTaskTransitionHandler = new ExitDesktopTaskTransitionHandler(mTransitions,
- mContext, mInteractionJankMonitor);
+ mContext, mInteractionJankMonitor, mHandler);
mPoint = new Point(0, 0);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepositoryTest.kt
new file mode 100644
index 0000000..e3caf2e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepositoryTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class WindowDecorCaptionHandleRepositoryTest {
+ private lateinit var captionHandleRepository: WindowDecorCaptionHandleRepository
+
+ @Before
+ fun setUp() {
+ captionHandleRepository = WindowDecorCaptionHandleRepository()
+ }
+
+ @Test
+ fun initialState_noAction_returnsNoCaption() {
+ // Check the initial value of `captionStateFlow`.
+ assertThat(captionHandleRepository.captionStateFlow.value).isEqualTo(CaptionState.NoCaption)
+ }
+
+ @Test
+ fun notifyCaptionChange_toAppHandleVisible_updatesStateWithCorrectData() {
+ val taskInfo = createTaskInfo(WINDOWING_MODE_FULLSCREEN, GMAIL_PACKAGE_NAME)
+ val appHandleCaptionState =
+ CaptionState.AppHandle(
+ taskInfo, false, Rect(/* left= */ 0, /* top= */ 1, /* right= */ 2, /* bottom= */ 3))
+
+ captionHandleRepository.notifyCaptionChanged(appHandleCaptionState)
+
+ assertThat(captionHandleRepository.captionStateFlow.value).isEqualTo(appHandleCaptionState)
+ }
+
+ @Test
+ fun notifyCaptionChange_toAppChipVisible_updatesStateWithCorrectData() {
+ val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM, GMAIL_PACKAGE_NAME)
+ val appHeaderCaptionState =
+ CaptionState.AppHeader(
+ taskInfo, true, Rect(/* left= */ 0, /* top= */ 1, /* right= */ 2, /* bottom= */ 3))
+
+ captionHandleRepository.notifyCaptionChanged(appHeaderCaptionState)
+
+ assertThat(captionHandleRepository.captionStateFlow.value).isEqualTo(appHeaderCaptionState)
+ }
+
+ @Test
+ fun notifyCaptionChange_toNoCaption_updatesState() {
+ captionHandleRepository.notifyCaptionChanged(CaptionState.NoCaption)
+
+ assertThat(captionHandleRepository.captionStateFlow.value).isEqualTo(CaptionState.NoCaption)
+ }
+
+ private fun createTaskInfo(
+ deviceWindowingMode: Int = WINDOWING_MODE_UNDEFINED,
+ runningTaskPackageName: String = LAUNCHER_PACKAGE_NAME
+ ): RunningTaskInfo =
+ RunningTaskInfo().apply {
+ configuration.windowConfiguration.apply { windowingMode = deviceWindowingMode }
+ topActivityInfo?.apply { packageName = runningTaskPackageName }
+ }
+
+ private companion object {
+ const val GMAIL_PACKAGE_NAME = "com.google.android.gm"
+ const val LAUNCHER_PACKAGE_NAME = "com.google.android.apps.nexuslauncher"
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
index 9c7f723..9146906 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
@@ -37,6 +37,7 @@
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Binder;
+import android.os.Handler;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.Display;
@@ -100,6 +101,8 @@
OneHandedSettingsUtil mMockSettingsUitl;
@Mock
InteractionJankMonitor mJankMonitor;
+ @Mock
+ Handler mMockHandler;
List<DisplayAreaAppearedInfo> mDisplayAreaAppearedInfoList = new ArrayList<>();
@@ -142,7 +145,8 @@
mMockAnimationController,
mTutorialHandler,
mJankMonitor,
- mMockShellMainExecutor));
+ mMockShellMainExecutor,
+ mMockHandler));
for (int i = 0; i < DISPLAYAREA_INFO_COUNT; i++) {
mDisplayAreaAppearedInfoList.add(getDummyDisplayAreaInfo());
@@ -429,7 +433,8 @@
mMockAnimationController,
mTutorialHandler,
mJankMonitor,
- mMockShellMainExecutor));
+ mMockShellMainExecutor,
+ mMockHandler));
assertThat(testSpiedDisplayAreaOrganizer.isReady()).isFalse();
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index f3944d5..9600351 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -40,6 +40,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.Handler;
import android.os.RemoteException;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -115,6 +116,7 @@
@Mock private PipParamsChangedForwarder mMockPipParamsChangedForwarder;
@Mock private DisplayInsetsController mMockDisplayInsetsController;
@Mock private TabletopModeController mMockTabletopModeController;
+ @Mock private Handler mMockHandler;
@Mock private DisplayLayout mMockDisplayLayout1;
@Mock private DisplayLayout mMockDisplayLayout2;
@@ -138,7 +140,7 @@
mMockPipTransitionController, mMockWindowManagerShellWrapper,
mMockTaskStackListener, mMockPipParamsChangedForwarder,
mMockDisplayInsetsController, mMockTabletopModeController,
- mMockOneHandedController, mMockExecutor);
+ mMockOneHandedController, mMockExecutor, mMockHandler);
mShellInit.init();
when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm);
when(mMockPipTouchHandler.getMotionHelper()).thenReturn(mMockPipMotionHelper);
@@ -230,7 +232,7 @@
mMockPipTransitionController, mMockWindowManagerShellWrapper,
mMockTaskStackListener, mMockPipParamsChangedForwarder,
mMockDisplayInsetsController, mMockTabletopModeController,
- mMockOneHandedController, mMockExecutor));
+ mMockOneHandedController, mMockExecutor, mMockHandler));
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
new file mode 100644
index 0000000..769acf7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.recents;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityTaskManager;
+import android.app.IApplicationThread;
+import android.app.KeyguardManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+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.HomeTransitionObserver;
+import com.android.wm.shell.transition.Transitions;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.quality.Strictness;
+
+import java.util.Optional;
+
+/**
+ * Tests for {@link RecentTasksController}
+ *
+ * Usage: atest WMShellUnitTests:RecentsTransitionHandlerTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RecentsTransitionHandlerTest extends ShellTestCase {
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private TaskStackListenerImpl mTaskStackListener;
+ @Mock
+ private ShellCommandHandler mShellCommandHandler;
+ @Mock
+ private DesktopModeTaskRepository mDesktopModeTaskRepository;
+ @Mock
+ private ActivityTaskManager mActivityTaskManager;
+ @Mock
+ private DisplayInsetsController mDisplayInsetsController;
+ @Mock
+ private IRecentTasksListener mRecentTasksListener;
+ @Mock
+ private TaskStackTransitionObserver mTaskStackTransitionObserver;
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ private ShellTaskOrganizer mShellTaskOrganizer;
+ private RecentTasksController mRecentTasksController;
+ private RecentTasksController mRecentTasksControllerReal;
+ private RecentsTransitionHandler mRecentsTransitionHandler;
+ private ShellInit mShellInit;
+ private ShellController mShellController;
+ private TestShellExecutor mMainExecutor;
+ private static StaticMockitoSession sMockitoSession;
+
+ @Before
+ public void setUp() {
+ sMockitoSession = mockitoSession().initMocks(this).strictness(Strictness.LENIENT)
+ .mockStatic(DesktopModeStatus.class).startMocking();
+ ExtendedMockito.doReturn(true)
+ .when(() -> DesktopModeStatus.canEnterDesktopMode(any()));
+
+ mMainExecutor = new TestShellExecutor();
+ when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
+ when(mContext.getSystemService(KeyguardManager.class))
+ .thenReturn(mock(KeyguardManager.class));
+ mShellInit = spy(new ShellInit(mMainExecutor));
+ mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler,
+ mDisplayInsetsController, mMainExecutor));
+ mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit,
+ mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
+ Optional.of(mDesktopModeTaskRepository), mTaskStackTransitionObserver,
+ mMainExecutor);
+ mRecentTasksController = spy(mRecentTasksControllerReal);
+ mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
+ null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController),
+ mMainExecutor);
+
+ final Transitions transitions = mock(Transitions.class);
+ doReturn(mMainExecutor).when(transitions).getMainExecutor();
+ mRecentsTransitionHandler = new RecentsTransitionHandler(mShellInit, mShellTaskOrganizer,
+ transitions, mRecentTasksController, mock(HomeTransitionObserver.class));
+
+ mShellInit.init();
+ }
+
+ @After
+ public void tearDown() {
+ sMockitoSession.finishMocking();
+ }
+
+ @Test
+ public void testStartSyntheticRecentsTransition_callsOnAnimationStart() throws Exception {
+ final IRecentsAnimationRunner runner = mock(IRecentsAnimationRunner.class);
+ doReturn(new Binder()).when(runner).asBinder();
+ Bundle options = new Bundle();
+ options.putBoolean("is_synthetic_recents_transition", true);
+ IBinder transition = mRecentsTransitionHandler.startRecentsTransition(
+ mock(PendingIntent.class), new Intent(), options, mock(IApplicationThread.class),
+ runner);
+ verify(runner).onAnimationStart(any(), any(), any(), any(), any(), any());
+
+ // Finish and verify no transition remains
+ mRecentsTransitionHandler.findController(transition).finish(true /* toHome */,
+ false /* sendUserLeaveHint */, null /* finishCb */);
+ mMainExecutor.flushAll();
+ assertNull(mRecentsTransitionHandler.findController(transition));
+ }
+
+ @Test
+ public void testStartSyntheticRecentsTransition_callsOnAnimationCancel() throws Exception {
+ final IRecentsAnimationRunner runner = mock(IRecentsAnimationRunner.class);
+ doReturn(new Binder()).when(runner).asBinder();
+ Bundle options = new Bundle();
+ options.putBoolean("is_synthetic_recents_transition", true);
+ IBinder transition = mRecentsTransitionHandler.startRecentsTransition(
+ mock(PendingIntent.class), new Intent(), options, mock(IApplicationThread.class),
+ runner);
+ verify(runner).onAnimationStart(any(), any(), any(), any(), any(), any());
+
+ mRecentsTransitionHandler.findController(transition).cancel("test");
+ mMainExecutor.flushAll();
+ verify(runner).onAnimationCanceled(any(), any());
+ assertNull(mRecentsTransitionHandler.findController(transition));
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 7937a84..aea14b9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -332,6 +332,35 @@
}
@Test
+ public void testTransitionFilterTaskFragmentToken() {
+ final IBinder taskFragmentToken = new Binder();
+
+ TransitionFilter filter = new TransitionFilter();
+ filter.mRequirements =
+ new TransitionFilter.Requirement[]{new TransitionFilter.Requirement()};
+ filter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
+ filter.mRequirements[0].mTaskFragmentToken = taskFragmentToken;
+
+ // Transition with the same token should match.
+ final TransitionInfo infoHasTaskFragmentToken = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN, taskFragmentToken).build();
+ assertTrue(filter.matches(infoHasTaskFragmentToken));
+
+ // Transition with a different token should not match.
+ final IBinder differentTaskFragmentToken = new Binder();
+ final TransitionInfo infoDifferentTaskFragmentToken =
+ new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN, differentTaskFragmentToken).build();
+ assertFalse(filter.matches(infoDifferentTaskFragmentToken));
+
+ // Transition without a token should not match.
+ final TransitionInfo infoNoTaskFragmentToken = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN, createTaskInfo(
+ 1, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD)).build();
+ assertFalse(filter.matches(infoNoTaskFragmentToken));
+ }
+
+ @Test
public void testTransitionFilterMultiRequirement() {
// filter that requires at-least one opening and one closing app
TransitionFilter filter = new TransitionFilter();
@@ -557,7 +586,7 @@
mMainExecutor.flushAll();
// Takeover shouldn't happen when the flag is disabled.
- setFlagsRule.disableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY);
+ setFlagsRule.disableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED);
IBinder transitToken = new Binder();
transitions.requestStartTransition(transitToken,
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
@@ -572,7 +601,7 @@
verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any());
// Takeover should happen when the flag is enabled.
- setFlagsRule.enableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY);
+ setFlagsRule.enableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED);
transitions.requestStartTransition(transitToken,
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
info = new TransitionInfoBuilder(TRANSIT_OPEN)
@@ -1211,7 +1240,7 @@
mTransactionPool, createTestDisplayController(), mMainExecutor,
mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class));
final RecentsTransitionHandler recentsHandler =
- new RecentsTransitionHandler(shellInit, transitions,
+ new RecentsTransitionHandler(shellInit, mock(ShellTaskOrganizer.class), transitions,
mock(RecentTasksController.class), mock(HomeTransitionObserver.class));
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
shellInit.init();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionInfoBuilder.java
index b8939e6f..49ae182 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionInfoBuilder.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionInfoBuilder.java
@@ -20,8 +20,10 @@
import static org.mockito.Mockito.mock;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.ComponentName;
+import android.os.IBinder;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;
@@ -51,21 +53,24 @@
}
public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
- @TransitionInfo.ChangeFlags int flags, ActivityManager.RunningTaskInfo taskInfo,
- ComponentName activityComponent) {
+ @TransitionInfo.ChangeFlags int flags,
+ @Nullable ActivityManager.RunningTaskInfo taskInfo,
+ @Nullable ComponentName activityComponent, @Nullable IBinder taskFragmentToken) {
final TransitionInfo.Change change = new TransitionInfo.Change(
taskInfo != null ? taskInfo.token : null, createMockSurface(true /* valid */));
change.setMode(mode);
change.setFlags(flags);
change.setTaskInfo(taskInfo);
change.setActivityComponent(activityComponent);
+ change.setTaskFragmentToken(taskFragmentToken);
return addChange(change);
}
/** Add a change to the TransitionInfo */
public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
@TransitionInfo.ChangeFlags int flags, ActivityManager.RunningTaskInfo taskInfo) {
- return addChange(mode, flags, taskInfo, null /* activityComponent */);
+ return addChange(mode, flags, taskInfo, null /* activityComponent */,
+ null /* taskFragmentToken */);
}
public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
@@ -76,13 +81,21 @@
/** Add a change to the TransitionInfo */
public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
ComponentName activityComponent) {
- return addChange(mode, TransitionInfo.FLAG_NONE, null /* taskinfo */, activityComponent);
+ return addChange(mode, TransitionInfo.FLAG_NONE, null /* taskinfo */, activityComponent,
+ null /* taskFragmentToken */);
}
public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode) {
return addChange(mode, TransitionInfo.FLAG_NONE, null /* taskInfo */);
}
+ /** Add a change with a TaskFragment token to the TransitionInfo */
+ public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
+ @Nullable IBinder taskFragmentToken) {
+ return addChange(mode, TransitionInfo.FLAG_NONE, null /* taskInfo */,
+ null /* activityComponent */, taskFragmentToken);
+ }
+
public TransitionInfoBuilder addChange(TransitionInfo.Change change) {
change.setDisplayId(DISPLAY_ID, DISPLAY_ID);
mInfo.addChange(change);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index a18fbf0..ee2a41c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -88,6 +88,7 @@
import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
import com.android.wm.shell.desktopmode.DesktopTasksLimiter
+import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
@@ -98,6 +99,7 @@
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeKeyguardChangeListener
import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener
+import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder
import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier
import java.util.Optional
import java.util.function.Consumer
@@ -164,6 +166,7 @@
DesktopModeWindowDecorViewModel.InputMonitorFactory
@Mock private lateinit var mockShellController: ShellController
@Mock private lateinit var mockShellExecutor: ShellExecutor
+ @Mock private lateinit var mockAppHeaderViewHolderFactory: AppHeaderViewHolder.Factory
@Mock private lateinit var mockRootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
@Mock private lateinit var mockShellCommandHandler: ShellCommandHandler
@Mock private lateinit var mockWindowManager: IWindowManager
@@ -182,6 +185,7 @@
@Mock private lateinit var mockTaskPositionerFactory:
DesktopModeWindowDecorViewModel.TaskPositionerFactory
@Mock private lateinit var mockTaskPositioner: TaskPositioner
+ @Mock private lateinit var mockCaptionHandleRepository: WindowDecorCaptionHandleRepository
@Mock private lateinit var mockWindowDecorViewHostSupplier: WindowDecorViewHostSupplier<*>
private lateinit var spyContext: TestableContext
@@ -236,10 +240,12 @@
mockDesktopModeWindowDecorFactory,
mockInputMonitorFactory,
transactionFactory,
+ mockAppHeaderViewHolderFactory,
mockRootTaskDisplayAreaOrganizer,
windowDecorByTaskIdSpy,
mockInteractionJankMonitor,
Optional.of(mockTasksLimiter),
+ mockCaptionHandleRepository,
Optional.of(mockActivityOrientationChangeHandler),
mockTaskPositionerFactory
)
@@ -247,7 +253,18 @@
whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
whenever(mockDisplayLayout.stableInsets()).thenReturn(STABLE_INSETS)
whenever(mockInputMonitorFactory.create(any(), any())).thenReturn(mockInputMonitor)
- whenever(mockTaskPositionerFactory.create(any(), any(), any(), any(), any(), any(), any()))
+ whenever(
+ mockTaskPositionerFactory.create(
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ any()
+ )
+ )
.thenReturn(mockTaskPositioner)
doReturn(mockToast).`when` { Toast.makeText(any(), anyInt(), anyInt()) }
@@ -1200,7 +1217,7 @@
whenever(
mockDesktopModeWindowDecorFactory.create(
any(), any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(),
- any(), any(), any(), any(), any())
+ any(), any(), any(), any(), any(), any(), any())
).thenReturn(decoration)
decoration.mTaskInfo = task
whenever(decoration.isFocused).thenReturn(task.isFocused)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 92199a1..a1867f3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -19,9 +19,11 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
+import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
@@ -38,6 +40,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -55,6 +58,7 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.PointF;
+import android.graphics.Rect;
import android.net.Uri;
import android.os.Handler;
import android.os.SystemProperties;
@@ -68,11 +72,13 @@
import android.view.Choreographer;
import android.view.Display;
import android.view.GestureDetector;
+import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.View;
+import android.view.WindowInsets;
import android.view.WindowManager;
import android.window.WindowContainerTransaction;
@@ -94,9 +100,12 @@
import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.desktopmode.CaptionState;
+import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams;
+import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHost;
import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier;
@@ -153,6 +162,10 @@
@Mock
private SyncTransactionQueue mMockSyncQueue;
@Mock
+ private AppHeaderViewHolder.Factory mMockAppHeaderViewHolderFactory;
+ @Mock
+ private AppHeaderViewHolder mMockAppHeaderViewHolder;
+ @Mock
private RootTaskDisplayAreaOrganizer mMockRootTaskDisplayAreaOrganizer;
@Mock
private Supplier<SurfaceControl.Transaction> mMockTransactionSupplier;
@@ -192,6 +205,8 @@
private HandleMenuFactory mMockHandleMenuFactory;
@Mock
private MultiInstanceHelper mMockMultiInstanceHelper;
+ @Mock
+ private WindowDecorCaptionHandleRepository mMockCaptionHandleRepository;
@Captor
private ArgumentCaptor<Function1<Boolean, Unit>> mOnMaxMenuHoverChangeListener;
@Captor
@@ -245,6 +260,8 @@
when(mMockWindowDecorViewHostSupplier.acquire(any(), eq(defaultDisplay)))
.thenReturn(mMockWindowDecorViewHost);
when(mMockWindowDecorViewHost.getSurfaceControl()).thenReturn(mock(SurfaceControl.class));
+ when(mMockAppHeaderViewHolderFactory.create(any(), any(), any(), any(), any(), any(), any(),
+ any())).thenReturn(mMockAppHeaderViewHolder);
}
@After
@@ -838,8 +855,144 @@
assertFalse(decoration.isHandleMenuActive());
}
- private void verifyHandleMenuCreated(@Nullable Uri uri) {
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ public void notifyCaptionStateChanged_flagDisabled_doNoNotify() {
+ when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true);
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ when(mMockDisplayController.getInsetsState(taskInfo.displayId))
+ .thenReturn(createInsetsState(statusBars(), /* visible= */true));
+ final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ spyWindowDecor.relayout(taskInfo);
+
+ verify(mMockCaptionHandleRepository, never()).notifyCaptionChanged(any());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ public void notifyCaptionStateChanged_inFullscreenMode_notifiesAppHandleVisible() {
+ when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true);
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ when(mMockDisplayController.getInsetsState(taskInfo.displayId))
+ .thenReturn(createInsetsState(statusBars(), /* visible= */true));
+ final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass(
+ CaptionState.class);
+
+ spyWindowDecor.relayout(taskInfo);
+
+ verify(mMockCaptionHandleRepository, atLeastOnce()).notifyCaptionChanged(
+ captionStateArgumentCaptor.capture());
+ assertThat(captionStateArgumentCaptor.getValue()).isInstanceOf(
+ CaptionState.AppHandle.class);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("TODO(b/367235906): Due to MONITOR_INPUT permission error")
+ public void notifyCaptionStateChanged_inWindowingMode_notifiesAppHeaderVisible() {
+ when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true);
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ when(mMockDisplayController.getInsetsState(taskInfo.displayId))
+ .thenReturn(createInsetsState(statusBars(), /* visible= */true));
+ when(mMockAppHeaderViewHolder.getAppChipLocationInWindow()).thenReturn(
+ new Rect(/* left= */ 0, /* top= */ 1, /* right= */ 2, /* bottom= */ 3));
+ final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ // Make non-resizable to avoid dealing with input-permissions (MONITOR_INPUT)
+ taskInfo.isResizeable = false;
+ ArgumentCaptor<Function0<Unit>> runnableArgumentCaptor = ArgumentCaptor.forClass(
+ Function0.class);
+ ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass(
+ CaptionState.class);
+
+ spyWindowDecor.relayout(taskInfo);
+ verify(mMockAppHeaderViewHolder, atLeastOnce()).runOnAppChipGlobalLayout(
+ runnableArgumentCaptor.capture());
+ runnableArgumentCaptor.getValue().invoke();
+
+ verify(mMockCaptionHandleRepository, atLeastOnce()).notifyCaptionChanged(
+ captionStateArgumentCaptor.capture());
+ assertThat(captionStateArgumentCaptor.getValue()).isInstanceOf(
+ CaptionState.AppHeader.class);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ public void notifyCaptionStateChanged_taskNotVisible_notifiesNoCaptionVisible() {
+ when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true);
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ false);
+ when(mMockDisplayController.getInsetsState(taskInfo.displayId))
+ .thenReturn(createInsetsState(statusBars(), /* visible= */true));
+ final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED);
+ ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass(
+ CaptionState.class);
+
+ spyWindowDecor.relayout(taskInfo);
+
+ verify(mMockCaptionHandleRepository, atLeastOnce()).notifyCaptionChanged(
+ captionStateArgumentCaptor.capture());
+ assertThat(captionStateArgumentCaptor.getValue()).isInstanceOf(
+ CaptionState.NoCaption.class);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ public void notifyCaptionStateChanged_captionHandleExpanded_notifiesHandleMenuExpanded() {
+ when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true);
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ when(mMockDisplayController.getInsetsState(taskInfo.displayId))
+ .thenReturn(createInsetsState(statusBars(), /* visible= */true));
+ final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass(
+ CaptionState.class);
+
+ spyWindowDecor.relayout(taskInfo);
+ createHandleMenu(spyWindowDecor);
+
+ verify(mMockCaptionHandleRepository, atLeastOnce()).notifyCaptionChanged(
+ captionStateArgumentCaptor.capture());
+ assertThat(captionStateArgumentCaptor.getValue()).isInstanceOf(
+ CaptionState.AppHandle.class);
+ assertThat(
+ ((CaptionState.AppHandle) captionStateArgumentCaptor.getValue())
+ .isHandleMenuExpanded()).isEqualTo(
+ true);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ public void notifyCaptionStateChanged_captionHandleClosed_notifiesHandleMenuClosed() {
+ when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true);
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ when(mMockDisplayController.getInsetsState(taskInfo.displayId))
+ .thenReturn(createInsetsState(statusBars(), /* visible= */true));
+ final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass(
+ CaptionState.class);
+
+ spyWindowDecor.relayout(taskInfo);
+ createHandleMenu(spyWindowDecor);
+ spyWindowDecor.closeHandleMenu();
+
+ verify(mMockCaptionHandleRepository, atLeastOnce()).notifyCaptionChanged(
+ captionStateArgumentCaptor.capture());
+ assertThat(captionStateArgumentCaptor.getValue()).isInstanceOf(
+ CaptionState.AppHandle.class);
+ assertThat(
+ ((CaptionState.AppHandle) captionStateArgumentCaptor.getValue())
+ .isHandleMenuExpanded()).isEqualTo(
+ false);
+
+ }
+
+ private void verifyHandleMenuCreated(@Nullable Uri uri) {
verify(mMockHandleMenuFactory).create(any(), any(), anyInt(), any(), any(),
any(), anyBoolean(), anyBoolean(), anyBoolean(), eq(uri), anyInt(),
anyInt(), anyInt());
@@ -907,12 +1060,13 @@
final DesktopModeWindowDecoration windowDecor = new DesktopModeWindowDecoration(mContext,
mContext, mMockDisplayController, mMockSplitScreenController,
mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl, mMockHandler, mBgExecutor,
- mMockChoreographer, mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer,
+ mMockChoreographer, mMockSyncQueue, mMockAppHeaderViewHolderFactory,
+ mMockRootTaskDisplayAreaOrganizer,
mMockGenericLinksParser, mMockAssistContentRequester, SurfaceControl.Builder::new,
mMockTransactionSupplier, WindowContainerTransaction::new, SurfaceControl::new,
new WindowManagerWrapper(mMockWindowManager), mMockSurfaceControlViewHostFactory,
mMockWindowDecorViewHostSupplier, maximizeMenuFactory, mMockHandleMenuFactory,
- mMockMultiInstanceHelper);
+ mMockMultiInstanceHelper, mMockCaptionHandleRepository);
windowDecor.setCaptionListeners(mMockTouchEventListener, mMockTouchEventListener,
mMockTouchEventListener, mMockTouchEventListener);
windowDecor.setExclusionRegionListener(mMockExclusionRegionListener);
@@ -952,6 +1106,14 @@
!= 0;
}
+ private InsetsState createInsetsState(@WindowInsets.Type.InsetsType int type, boolean visible) {
+ final InsetsState state = new InsetsState();
+ final InsetsSource source = new InsetsSource(/* id= */0, type);
+ source.setVisible(visible);
+ state.addSource(source);
+ return state;
+ }
+
private static class TestTouchEventListener extends GestureDetector.SimpleOnGestureListener
implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener,
View.OnGenericMotionListener, DragDetector.MotionEventHandler {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index 7784af6..1273ee8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -21,7 +21,9 @@
import android.content.res.Resources
import android.graphics.Point
import android.graphics.Rect
+import android.os.Handler
import android.os.IBinder
+import android.os.Looper
import android.testing.AndroidTestingRunner
import android.view.Display
import android.view.Surface.ROTATION_0
@@ -33,6 +35,7 @@
import android.window.TransitionInfo
import android.window.WindowContainerToken
import androidx.test.filters.SmallTest
+import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
import com.android.internal.jank.InteractionJankMonitor
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
@@ -107,6 +110,7 @@
private lateinit var mockResources: Resources
@Mock
private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
+ private val mainHandler = Handler(Looper.getMainLooper())
private lateinit var taskPositioner: VeiledResizeTaskPositioner
@@ -155,12 +159,13 @@
mockDragStartListener,
mockTransactionFactory,
mockTransitions,
- mockInteractionJankMonitor
+ mockInteractionJankMonitor,
+ mainHandler,
)
}
@Test
- fun testDragResize_noMove_doesNotShowResizeVeil() {
+ fun testDragResize_noMove_doesNotShowResizeVeil() = runOnUiThread {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
STARTING_BOUNDS.left.toFloat(),
@@ -172,6 +177,7 @@
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
+
verify(mockTransitions, never()).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
token == taskBinder &&
@@ -182,7 +188,7 @@
}
@Test
- fun testDragResize_movesTask_doesNotShowResizeVeil() {
+ fun testDragResize_movesTask_doesNotShowResizeVeil() = runOnUiThread {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_UNDEFINED,
STARTING_BOUNDS.left.toFloat(),
@@ -217,7 +223,7 @@
}
@Test
- fun testDragResize_resize_boundsUpdateOnEnd() {
+ fun testDragResize_resize_boundsUpdateOnEnd() = runOnUiThread {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
STARTING_BOUNDS.right.toFloat(),
@@ -258,7 +264,7 @@
}
@Test
- fun testDragResize_noEffectiveMove_skipsTransactionOnEnd() {
+ fun testDragResize_noEffectiveMove_skipsTransactionOnEnd() = runOnUiThread {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
STARTING_BOUNDS.left.toFloat(),
@@ -290,9 +296,8 @@
})
}
-
@Test
- fun testDragResize_drag_setBoundsNotRunIfDragEndsInDisallowedEndArea() {
+ fun testDragResize_drag_setBoundsNotRunIfDragEndsInDisallowedEndArea() = runOnUiThread {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_UNDEFINED, // drag
STARTING_BOUNDS.left.toFloat(),
@@ -317,7 +322,7 @@
}
@Test
- fun testDragResize_resize_resizingTaskReorderedToTopWhenNotFocused() {
+ fun testDragResize_resize_resizingTaskReorderedToTopWhenNotFocused() = runOnUiThread {
mockDesktopWindowDecoration.mTaskInfo.isFocused = false
taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT, // Resize right
@@ -333,7 +338,7 @@
}
@Test
- fun testDragResize_resize_resizingTaskNotReorderedToTopWhenFocused() {
+ fun testDragResize_resize_resizingTaskNotReorderedToTopWhenFocused() = runOnUiThread {
mockDesktopWindowDecoration.mTaskInfo.isFocused = true
taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT, // Resize right
@@ -349,7 +354,7 @@
}
@Test
- fun testDragResize_drag_draggedTaskNotReorderedToTop() {
+ fun testDragResize_drag_draggedTaskNotReorderedToTop() = runOnUiThread {
mockDesktopWindowDecoration.mTaskInfo.isFocused = false
taskPositioner.onDragPositioningStart(
CTRL_TYPE_UNDEFINED, // drag
@@ -366,7 +371,7 @@
}
@Test
- fun testDragResize_drag_updatesStableBoundsOnRotate() {
+ fun testDragResize_drag_updatesStableBoundsOnRotate() = runOnUiThread {
// Test landscape stable bounds
performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat() + 2000,
@@ -412,7 +417,7 @@
}
@Test
- fun testIsResizingOrAnimatingResizeSet() {
+ fun testIsResizingOrAnimatingResizeSet() = runOnUiThread {
Assert.assertFalse(taskPositioner.isResizingOrAnimating)
taskPositioner.onDragPositioningStart(
@@ -439,7 +444,7 @@
}
@Test
- fun testIsResizingOrAnimatingResizeResetAfterStartAnimation() {
+ fun testIsResizingOrAnimatingResizeResetAfterStartAnimation() = runOnUiThread {
performDrag(
STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat(),
STARTING_BOUNDS.left.toFloat() - 20, STARTING_BOUNDS.top.toFloat() - 20,
@@ -453,7 +458,7 @@
}
@Test
- fun testStartAnimation_useEndRelOffset() {
+ fun testStartAnimation_useEndRelOffset() = runOnUiThread {
val changeMock = mock(TransitionInfo.Change::class.java)
val startTransaction = mock(Transaction::class.java)
val finishTransaction = mock(Transaction::class.java)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostTest.kt
index 1b0b7d9..1b2ce9e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostTest.kt
@@ -18,6 +18,7 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
import android.view.View
import android.view.WindowManager
import androidx.test.filters.SmallTest
@@ -27,6 +28,7 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertThrows
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
@@ -57,8 +59,54 @@
onDrawTransaction = null
)
- assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isTrue()
- assertThat(windowDecorViewHost.view()).isEqualTo(view)
+ assertThat(windowDecorViewHost.viewHost).isNotNull()
+ assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(view)
+ }
+
+ @Test
+ fun updateView_alreadyLaidOut_relayouts() = runTest {
+ val windowDecorViewHost = createDefaultViewHost()
+ val view = View(context)
+ windowDecorViewHost.updateView(
+ view = view,
+ attrs = WindowManager.LayoutParams(100, 100),
+ configuration = context.resources.configuration,
+ onDrawTransaction = null
+ )
+
+ val otherParams = WindowManager.LayoutParams(200, 200)
+ windowDecorViewHost.updateView(
+ view = view,
+ attrs = otherParams,
+ configuration = context.resources.configuration,
+ onDrawTransaction = null
+ )
+
+ assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(view)
+ assertThat(windowDecorViewHost.viewHost!!.view!!.layoutParams.width)
+ .isEqualTo(otherParams.width)
+ }
+
+ @Test
+ fun updateView_replacingView_throws() = runTest {
+ val windowDecorViewHost = createDefaultViewHost()
+ val view = View(context)
+ windowDecorViewHost.updateView(
+ view = view,
+ attrs = WindowManager.LayoutParams(100, 100),
+ configuration = context.resources.configuration,
+ onDrawTransaction = null
+ )
+
+ val otherView = View(context)
+ assertThrows(Exception::class.java) {
+ windowDecorViewHost.updateView(
+ view = otherView,
+ attrs = WindowManager.LayoutParams(100, 100),
+ configuration = context.resources.configuration,
+ onDrawTransaction = null
+ )
+ }
}
@OptIn(ExperimentalCoroutinesApi::class)
@@ -77,7 +125,7 @@
)
// No view host yet, since the coroutine hasn't run.
- assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isFalse()
+ assertThat(windowDecorViewHost.viewHost).isNull()
windowDecorViewHost.updateView(
view = syncView,
@@ -89,13 +137,14 @@
// Would run coroutine if it hadn't been cancelled.
advanceUntilIdle()
- assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isTrue()
- assertThat(windowDecorViewHost.view()).isNotNull()
+ assertThat(windowDecorViewHost.viewHost).isNotNull()
+ assertThat(windowDecorViewHost.viewHost!!.view).isNotNull()
// View host view/attrs should match the ones from the sync call, plus, since the
// sync/async were made with different views, if the job hadn't been cancelled there
// would've been an exception thrown as replacing views isn't allowed.
- assertThat(windowDecorViewHost.view()).isEqualTo(syncView)
- assertThat(windowDecorViewHost.view()!!.layoutParams.width).isEqualTo(syncAttrs.width)
+ assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(syncView)
+ assertThat(windowDecorViewHost.viewHost!!.view!!.layoutParams.width)
+ .isEqualTo(syncAttrs.width)
}
@OptIn(ExperimentalCoroutinesApi::class)
@@ -111,11 +160,11 @@
configuration = context.resources.configuration,
)
- assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isFalse()
+ assertThat(windowDecorViewHost.viewHost).isNull()
advanceUntilIdle()
- assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isTrue()
+ assertThat(windowDecorViewHost.viewHost).isNotNull()
}
@OptIn(ExperimentalCoroutinesApi::class)
@@ -138,8 +187,9 @@
advanceUntilIdle()
- assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isTrue()
- assertThat(windowDecorViewHost.view()).isEqualTo(otherView)
+ assertThat(windowDecorViewHost.viewHost).isNotNull()
+ assertThat(windowDecorViewHost.viewHost!!.view).isNotNull()
+ assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(otherView)
}
@Test
@@ -157,15 +207,16 @@
val t = mock(SurfaceControl.Transaction::class.java)
windowDecorViewHost.release(t)
- verify(windowDecorViewHost.viewHostAdapter).release(t)
+ verify(windowDecorViewHost.viewHost!!).release()
+ verify(t).remove(windowDecorViewHost.surfaceControl)
}
private fun CoroutineScope.createDefaultViewHost() = DefaultWindowDecorViewHost(
context = context,
mainScope = this,
display = context.display,
- viewHostAdapter = spy(SurfaceControlViewHostAdapter(context, context.display)),
+ surfaceControlViewHostFactory = { c, d, wwm, s ->
+ spy(SurfaceControlViewHost(c, d, wwm, s))
+ }
)
-
- private fun DefaultWindowDecorViewHost.view(): View? = viewHostAdapter.viewHost?.view
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/PooledWindowDecorViewHostSupplierTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/PooledWindowDecorViewHostSupplierTest.kt
deleted file mode 100644
index a7e4213..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/PooledWindowDecorViewHostSupplierTest.kt
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.wm.shell.windowdecor.viewhost
-
-import android.testing.AndroidTestingRunner
-import android.view.SurfaceControl
-import androidx.test.filters.SmallTest
-import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.TestShellExecutor
-import com.android.wm.shell.sysui.ShellInit
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.advanceUntilIdle
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-import org.mockito.kotlin.any
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.never
-import org.mockito.kotlin.verify
-import org.mockito.kotlin.whenever
-
-/**
- * Tests for [PooledWindowDecorViewHostSupplier].
- *
- * Build/Install/Run:
- * atest WMShellUnitTests:PooledWindowDecorViewHostSupplierTest
- */
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class PooledWindowDecorViewHostSupplierTest : ShellTestCase() {
-
- private val testExecutor = TestShellExecutor()
- private val testShellInit = ShellInit(testExecutor)
- @Mock
- private lateinit var mockViewHostFactory: ReusableWindowDecorViewHost.Factory
-
- private lateinit var supplier: PooledWindowDecorViewHostSupplier
-
- @Test
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- }
-
- @Test
- fun onInit_warmsAndPoolsViewHosts() = runTest {
- supplier = createSupplier(maxPoolSize = 5, preWarmSize = 2)
- val mockViewHost1 = mock<ReusableWindowDecorViewHost>()
- val mockViewHost2 = mock<ReusableWindowDecorViewHost>()
- whenever(mockViewHostFactory
- .create(context, this, context.display, id = 0))
- .thenReturn(mockViewHost1)
- whenever(mockViewHostFactory
- .create(context, this, context.display, id = 1))
- .thenReturn(mockViewHost2)
-
- testExecutor.flushAll()
- advanceUntilIdle()
-
- // Both were warmed up.
- verify(mockViewHost1).warmUp()
- verify(mockViewHost2).warmUp()
- // Both were released, so re-acquiring them provides the same instance.
- assertThat(mockViewHost2)
- .isEqualTo(supplier.acquire(context, context.display))
- assertThat(mockViewHost1)
- .isEqualTo(supplier.acquire(context, context.display))
- }
-
- @Test(expected = Throwable::class)
- fun onInit_warmUpSizeExceedsPoolSize_throws() = runTest {
- createSupplier(maxPoolSize = 3, preWarmSize = 4)
- }
-
- @Test
- fun acquire_poolHasInstances_reuses() = runTest {
- supplier = createSupplier(maxPoolSize = 5, preWarmSize = 0)
-
- // Prepare the pool with one instance.
- val mockViewHost = mock<ReusableWindowDecorViewHost>()
- supplier.release(mockViewHost, SurfaceControl.Transaction())
-
- assertThat(mockViewHost)
- .isEqualTo(supplier.acquire(context, context.display))
- verify(mockViewHostFactory, never()).create(any(), any(), any(), any())
- }
-
- @Test
- fun acquire_pooledHasZeroInstances_creates() = runTest {
- supplier = createSupplier(maxPoolSize = 5, preWarmSize = 0)
-
- supplier.acquire(context, context.display)
-
- verify(mockViewHostFactory).create(context, this, context.display, id = 0)
- }
-
- @Test
- fun release_poolBelowLimit_caches() = runTest {
- supplier = createSupplier(maxPoolSize = 5, preWarmSize = 0)
-
- val mockViewHost = mock<ReusableWindowDecorViewHost>()
- val mockT = mock<SurfaceControl.Transaction>()
- supplier.release(mockViewHost, mockT)
-
- assertThat(mockViewHost)
- .isEqualTo(supplier.acquire(context, context.display))
- }
-
- @Test
- fun release_poolBelowLimit_doesNotReleaseViewHost() = runTest {
- supplier = createSupplier(maxPoolSize = 5, preWarmSize = 0)
-
- val mockViewHost = mock<ReusableWindowDecorViewHost>()
- val mockT = mock<SurfaceControl.Transaction>()
- supplier.release(mockViewHost, mockT)
-
- verify(mockViewHost, never()).release(mockT)
- }
-
- @Test
- fun release_poolAtLimit_doesNotCache() = runTest {
- supplier = createSupplier(maxPoolSize = 1, preWarmSize = 0)
- val mockT = mock<SurfaceControl.Transaction>()
- val mockViewHost = mock<ReusableWindowDecorViewHost>()
- supplier.release(mockViewHost, mockT) // Maxes pool.
-
- val mockViewHost2 = mock<ReusableWindowDecorViewHost>()
- supplier.release(mockViewHost2, mockT) // Beyond limit.
-
- assertThat(mockViewHost)
- .isEqualTo(supplier.acquire(context, context.display))
- // Second one wasn't cached, so the acquired one should've been a new instance.
- assertThat(mockViewHost2)
- .isNotEqualTo(supplier.acquire(context, context.display))
- }
-
- @Test
- fun release_poolAtLimit_releasesViewHost() = runTest {
- supplier = createSupplier(maxPoolSize = 1, preWarmSize = 0)
- val mockT = mock<SurfaceControl.Transaction>()
- val mockViewHost = mock<ReusableWindowDecorViewHost>()
- supplier.release(mockViewHost, mockT) // Maxes pool.
-
- val mockViewHost2 = mock<ReusableWindowDecorViewHost>()
- supplier.release(mockViewHost2, mockT) // Beyond limit.
-
- // Second one doesn't fit, so it needs to be released.
- verify(mockViewHost2).release(mockT)
- }
-
- private fun CoroutineScope.createSupplier(
- maxPoolSize: Int,
- preWarmSize: Int
- ) = PooledWindowDecorViewHostSupplier(
- context,
- this,
- testShellInit,
- mockViewHostFactory,
- maxPoolSize,
- preWarmSize
- ).also {
- testShellInit.init()
- }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/ReusableWindowDecorViewHostTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/ReusableWindowDecorViewHostTest.kt
deleted file mode 100644
index de2444e..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/ReusableWindowDecorViewHostTest.kt
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.wm.shell.windowdecor.viewhost
-
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.SurfaceControl
-import android.view.View
-import android.view.WindowManager
-import androidx.test.filters.SmallTest
-import com.android.wm.shell.ShellTestCase
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.advanceUntilIdle
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
-import org.mockito.kotlin.spy
-import org.mockito.kotlin.verify
-
-/**
- * Tests for [ReusableWindowDecorViewHost].
- *
- * Build/Install/Run:
- * atest WMShellUnitTests:ReusableWindowDecorViewHostTest
- */
-@SmallTest
-@TestableLooper.RunWithLooper
-@RunWith(AndroidTestingRunner::class)
-class ReusableWindowDecorViewHostTest : ShellTestCase() {
-
- @Test
- fun warmUp_addsRootView() = runTest {
- val reusableVH = createReusableViewHost().apply {
- warmUp()
- }
-
- assertThat(reusableVH.viewHostAdapter.isInitialized()).isTrue()
- assertThat(reusableVH.view()).isEqualTo(reusableVH.rootView)
- }
-
- @Test
- fun update_differentView_replacesView() = runTest {
- val view = View(context)
- val lp = WindowManager.LayoutParams()
- val reusableVH = createReusableViewHost()
- reusableVH.updateView(view, lp, context.resources.configuration, null)
-
- assertThat(reusableVH.rootView.childCount).isEqualTo(1)
- assertThat(reusableVH.rootView.getChildAt(0)).isEqualTo(view)
-
- val newView = View(context)
- val newLp = WindowManager.LayoutParams()
- reusableVH.updateView(newView, newLp, context.resources.configuration, null)
-
- assertThat(reusableVH.rootView.childCount).isEqualTo(1)
- assertThat(reusableVH.rootView.getChildAt(0)).isEqualTo(newView)
- }
-
- @OptIn(ExperimentalCoroutinesApi::class)
- @Test
- fun updateView_clearsPendingAsyncJob() = runTest {
- val reusableVH = createReusableViewHost()
- val asyncView = View(context)
- val syncView = View(context)
- val asyncAttrs = WindowManager.LayoutParams(100, 100)
- val syncAttrs = WindowManager.LayoutParams(200, 200)
-
- reusableVH.updateViewAsync(
- view = asyncView,
- attrs = asyncAttrs,
- configuration = context.resources.configuration,
- )
-
- // No view host yet, since the coroutine hasn't run.
- assertThat(reusableVH.viewHostAdapter.isInitialized()).isFalse()
-
- reusableVH.updateView(
- view = syncView,
- attrs = syncAttrs,
- configuration = context.resources.configuration,
- onDrawTransaction = null
- )
-
- // Would run coroutine if it hadn't been cancelled.
- advanceUntilIdle()
-
- assertThat(reusableVH.viewHostAdapter.isInitialized()).isTrue()
- // View host view/attrs should match the ones from the sync call, plus, since the
- // sync/async were made with different views, if the job hadn't been cancelled there
- // would've been an exception thrown as replacing views isn't allowed.
- assertThat(reusableVH.rootView.getChildAt(0)).isEqualTo(syncView)
- assertThat(reusableVH.view()!!.layoutParams.width).isEqualTo(syncAttrs.width)
- }
-
- @OptIn(ExperimentalCoroutinesApi::class)
- @Test
- fun updateViewAsync() = runTest {
- val reusableVH = createReusableViewHost()
- val view = View(context)
- val attrs = WindowManager.LayoutParams(100, 100)
-
- reusableVH.updateViewAsync(
- view = view,
- attrs = attrs,
- configuration = context.resources.configuration,
- )
-
- assertThat(reusableVH.viewHostAdapter.isInitialized()).isFalse()
-
- advanceUntilIdle()
-
- assertThat(reusableVH.viewHostAdapter.isInitialized()).isTrue()
- }
-
- @OptIn(ExperimentalCoroutinesApi::class)
- @Test
- fun updateViewAsync_clearsPendingAsyncJob() = runTest {
- val reusableVH = createReusableViewHost()
-
- val view = View(context)
- reusableVH.updateViewAsync(
- view = view,
- attrs = WindowManager.LayoutParams(100, 100),
- configuration = context.resources.configuration,
- )
- val otherView = View(context)
- reusableVH.updateViewAsync(
- view = otherView,
- attrs = WindowManager.LayoutParams(100, 100),
- configuration = context.resources.configuration,
- )
-
- advanceUntilIdle()
-
- assertThat(reusableVH.viewHostAdapter.isInitialized()).isTrue()
- assertThat(reusableVH.rootView.getChildAt(0)).isEqualTo(otherView)
- }
-
- @Test
- fun release() = runTest {
- val reusableVH = createReusableViewHost()
-
- val view = View(context)
- reusableVH.updateView(
- view = view,
- attrs = WindowManager.LayoutParams(100, 100),
- configuration = context.resources.configuration,
- onDrawTransaction = null
- )
-
- val t = mock(SurfaceControl.Transaction::class.java)
- reusableVH.release(t)
-
- verify(reusableVH.viewHostAdapter).release(t)
- }
-
- private fun CoroutineScope.createReusableViewHost() = ReusableWindowDecorViewHost(
- context = context,
- mainScope = this,
- display = context.display,
- id = 1,
- viewHostAdapter = spy(SurfaceControlViewHostAdapter(context, context.display)),
- )
-
- private fun ReusableWindowDecorViewHost.view(): View? = viewHostAdapter.viewHost?.view
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/SurfaceControlViewHostAdapterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/SurfaceControlViewHostAdapterTest.kt
deleted file mode 100644
index d6c80a7..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/SurfaceControlViewHostAdapterTest.kt
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.wm.shell.windowdecor.viewhost
-
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.SurfaceControl
-import android.view.SurfaceControlViewHost
-import android.view.View
-import android.view.WindowManager
-import androidx.test.filters.SmallTest
-import com.android.wm.shell.ShellTestCase
-import com.google.common.truth.Truth.assertThat
-import org.junit.Assert.assertThrows
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
-import org.mockito.kotlin.spy
-import org.mockito.kotlin.verify
-
-/**
- * Tests for [SurfaceControlViewHostAdapter].
- *
- * Build/Install/Run:
- * atest WMShellUnitTests:SurfaceControlViewHostAdapterTest
- */
-@SmallTest
-@TestableLooper.RunWithLooper
-@RunWith(AndroidTestingRunner::class)
-class SurfaceControlViewHostAdapterTest : ShellTestCase() {
-
- private lateinit var adapter: SurfaceControlViewHostAdapter
-
- @Before
- fun setUp() {
- adapter = SurfaceControlViewHostAdapter(
- context,
- context.display,
- surfaceControlViewHostFactory = { c, d, wwm, s ->
- spy(SurfaceControlViewHost(c, d, wwm, s))
- }
- )
- }
-
- @Test
- fun prepareViewHost() {
- adapter.prepareViewHost(context.resources.configuration)
-
- assertThat(adapter.viewHost).isNotNull()
- }
-
- @Test
- fun prepareViewHost_alreadyCreated_skips() {
- adapter.prepareViewHost(context.resources.configuration)
-
- val viewHost = adapter.viewHost!!
-
- adapter.prepareViewHost(context.resources.configuration)
-
- assertThat(adapter.viewHost).isEqualTo(viewHost)
- }
-
- @Test
- fun updateView_layoutInViewHost() {
- val view = View(context)
- adapter.prepareViewHost(context.resources.configuration)
-
- adapter.updateView(
- view = view,
- attrs = WindowManager.LayoutParams(100, 100)
- )
-
- assertThat(adapter.isInitialized()).isTrue()
- assertThat(adapter.view()).isEqualTo(view)
- }
-
- @Test
- fun updateView_alreadyLaidOut_relayouts() {
- val view = View(context)
- adapter.prepareViewHost(context.resources.configuration)
- adapter.updateView(
- view = view,
- attrs = WindowManager.LayoutParams(100, 100)
- )
-
- val otherParams = WindowManager.LayoutParams(200, 200)
- adapter.updateView(
- view = view,
- attrs = otherParams
- )
-
- assertThat(adapter.view()).isEqualTo(view)
- assertThat(adapter.view()!!.layoutParams.width).isEqualTo(otherParams.width)
- }
-
- @Test
- fun updateView_replacingView_throws() {
- val view = View(context)
- adapter.prepareViewHost(context.resources.configuration)
- adapter.updateView(
- view = view,
- attrs = WindowManager.LayoutParams(100, 100)
- )
-
- val otherView = View(context)
- assertThrows(Exception::class.java) {
- adapter.updateView(
- view = otherView,
- attrs = WindowManager.LayoutParams(100, 100)
- )
- }
- }
-
- @Test
- fun release() {
- adapter.prepareViewHost(context.resources.configuration)
- adapter.updateView(
- view = View(context),
- attrs = WindowManager.LayoutParams(100, 100)
- )
-
- val mockT = mock(SurfaceControl.Transaction::class.java)
- adapter.release(mockT)
-
- verify(adapter.viewHost!!).release()
- verify(mockT).remove(adapter.rootSurface)
- }
-
- private fun SurfaceControlViewHostAdapter.view(): View? = viewHost?.view
-}
diff --git a/libs/appfunctions/Android.bp b/libs/appfunctions/Android.bp
new file mode 100644
index 0000000..09e2f42
--- /dev/null
+++ b/libs/appfunctions/Android.bp
@@ -0,0 +1,31 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_sdk_library {
+ name: "com.google.android.appfunctions.sidecar",
+ owner: "google",
+ srcs: ["java/**/*.java"],
+ api_packages: ["com.google.android.appfunctions.sidecar"],
+ dex_preopt: {
+ enabled: false,
+ },
+ system_ext_specific: true,
+ no_dist: true,
+ unsafe_ignore_missing_latest_api: true,
+}
diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt
new file mode 100644
index 0000000..504e329
--- /dev/null
+++ b/libs/appfunctions/api/current.txt
@@ -0,0 +1,49 @@
+// Signature format: 2.0
+package com.google.android.appfunctions.sidecar {
+
+ public final class AppFunctionManager {
+ ctor public AppFunctionManager(android.content.Context);
+ method public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
+ }
+
+ public abstract class AppFunctionService extends android.app.Service {
+ ctor public AppFunctionService();
+ method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
+ method @MainThread public abstract void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
+ field @NonNull public static final String BIND_APP_FUNCTION_SERVICE = "android.permission.BIND_APP_FUNCTION_SERVICE";
+ field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
+ }
+
+ public final class ExecuteAppFunctionRequest {
+ method @NonNull public android.os.Bundle getExtras();
+ method @NonNull public String getFunctionIdentifier();
+ method @NonNull public android.app.appsearch.GenericDocument getParameters();
+ method @NonNull public String getTargetPackageName();
+ }
+
+ public static final class ExecuteAppFunctionRequest.Builder {
+ ctor public ExecuteAppFunctionRequest.Builder(@NonNull String, @NonNull String);
+ method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest build();
+ method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder setExtras(@NonNull android.os.Bundle);
+ method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder setParameters(@NonNull android.app.appsearch.GenericDocument);
+ }
+
+ public final class ExecuteAppFunctionResponse {
+ method @Nullable public String getErrorMessage();
+ method @NonNull public android.os.Bundle getExtras();
+ method public int getResultCode();
+ method @NonNull public android.app.appsearch.GenericDocument getResultDocument();
+ method public boolean isSuccess();
+ method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newFailure(int, @Nullable String, @Nullable android.os.Bundle);
+ method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle);
+ field public static final String PROPERTY_RETURN_VALUE = "returnValue";
+ field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2
+ field public static final int RESULT_DENIED = 1; // 0x1
+ field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3
+ field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4
+ field public static final int RESULT_OK = 0; // 0x0
+ field public static final int RESULT_TIMED_OUT = 5; // 0x5
+ }
+
+}
+
diff --git a/libs/appfunctions/api/removed.txt b/libs/appfunctions/api/removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/libs/appfunctions/api/removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/libs/appfunctions/api/system-current.txt b/libs/appfunctions/api/system-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/libs/appfunctions/api/system-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/libs/appfunctions/api/system-removed.txt b/libs/appfunctions/api/system-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/libs/appfunctions/api/system-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/libs/appfunctions/api/test-current.txt b/libs/appfunctions/api/test-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/libs/appfunctions/api/test-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/libs/appfunctions/api/test-removed.txt b/libs/appfunctions/api/test-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/libs/appfunctions/api/test-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java
new file mode 100644
index 0000000..b1dd467
--- /dev/null
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.appfunctions.sidecar;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.content.Context;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+
+/**
+ * Provides app functions related functionalities.
+ *
+ * <p>App function is a specific piece of functionality that an app offers to the system. These
+ * functionalities can be integrated into various system features.
+ *
+ * <p>This class wraps {@link android.app.appfunctions.AppFunctionManager} functionalities and
+ * exposes it here as a sidecar library (avoiding direct dependency on the platform API).
+ */
+// TODO(b/357551503): Implement get and set enabled app function APIs.
+// TODO(b/367329899): Add sidecar library to Android B builds.
+public final class AppFunctionManager {
+ private final android.app.appfunctions.AppFunctionManager mManager;
+ private final Context mContext;
+
+ /**
+ * Creates an instance.
+ *
+ * @param context A {@link Context}.
+ * @throws java.lang.IllegalStateException if the underlying {@link
+ * android.app.appfunctions.AppFunctionManager} is not found.
+ */
+ public AppFunctionManager(Context context) {
+ mContext = Objects.requireNonNull(context);
+ mManager = context.getSystemService(android.app.appfunctions.AppFunctionManager.class);
+ if (mManager == null) {
+ throw new IllegalStateException(
+ "Underlying AppFunctionManager system service not found.");
+ }
+ }
+
+ /**
+ * Executes the app function.
+ *
+ * <p>Proxies request and response to the underlying {@link
+ * android.app.appfunctions.AppFunctionManager#executeAppFunction}, converting the request and
+ * response in the appropriate type required by the function.
+ */
+ public void executeAppFunction(
+ @NonNull ExecuteAppFunctionRequest sidecarRequest,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<ExecuteAppFunctionResponse> callback) {
+ Objects.requireNonNull(sidecarRequest);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ android.app.appfunctions.ExecuteAppFunctionRequest platformRequest =
+ SidecarConverter.getPlatformExecuteAppFunctionRequest(sidecarRequest);
+ mManager.executeAppFunction(
+ platformRequest, executor, (platformResponse) -> {
+ callback.accept(SidecarConverter.getSidecarExecuteAppFunctionResponse(
+ platformResponse));
+ });
+ }
+}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
new file mode 100644
index 0000000..65959df
--- /dev/null
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
@@ -0,0 +1,114 @@
+/*
+ * 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.google.android.appfunctions.sidecar;
+
+import static android.Manifest.permission.BIND_APP_FUNCTION_SERVICE;
+
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+
+import java.util.function.Consumer;
+
+/**
+ * Abstract base class to provide app functions to the system.
+ *
+ * <p>Include the following in the manifest:
+ *
+ * <pre>
+ * {@literal
+ * <service android:name=".YourService"
+ * android:permission="android.permission.BIND_APP_FUNCTION_SERVICE">
+ * <intent-filter>
+ * <action android:name="android.app.appfunctions.AppFunctionService" />
+ * </intent-filter>
+ * </service>
+ * }
+ * </pre>
+ *
+ * <p>This class wraps {@link android.app.appfunctions.AppFunctionService} functionalities and
+ * exposes it here as a sidecar library (avoiding direct dependency on the platform API).
+ *
+ * @see AppFunctionManager
+ */
+public abstract class AppFunctionService extends Service {
+ /**
+ * The permission to only allow system access to the functions through {@link
+ * AppFunctionManagerService}.
+ */
+ @NonNull
+ public static final String BIND_APP_FUNCTION_SERVICE =
+ "android.permission.BIND_APP_FUNCTION_SERVICE";
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service. To be supported, the
+ * service must also require the {@link BIND_APP_FUNCTION_SERVICE} permission so that other
+ * applications can not abuse it.
+ */
+ @NonNull
+ public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
+
+ private final Binder mBinder =
+ android.app.appfunctions.AppFunctionService.createBinder(
+ /* context= */ this,
+ /* onExecuteFunction= */ (platformRequest, callback) -> {
+ AppFunctionService.this.onExecuteFunction(
+ SidecarConverter.getSidecarExecuteAppFunctionRequest(
+ platformRequest),
+ (sidecarResponse) -> {
+ callback.accept(
+ SidecarConverter.getPlatformExecuteAppFunctionResponse(
+ sidecarResponse));
+ });
+ }
+ );
+
+ @NonNull
+ @Override
+ public final IBinder onBind(@Nullable Intent intent) {
+ return mBinder;
+ }
+
+ /**
+ * Called by the system to execute a specific app function.
+ *
+ * <p>This method is triggered when the system requests your AppFunctionService to handle a
+ * particular function you have registered and made available.
+ *
+ * <p>To ensure proper routing of function requests, assign a unique identifier to each
+ * function. This identifier doesn't need to be globally unique, but it must be unique within
+ * your app. For example, a function to order food could be identified as "orderFood". In most
+ * cases this identifier should come from the ID automatically generated by the AppFunctions
+ * SDK. You can determine the specific function to invoke by calling {@link
+ * ExecuteAppFunctionRequest#getFunctionIdentifier()}.
+ *
+ * <p>This method is always triggered in the main thread. You should run heavy tasks on a worker
+ * thread and dispatch the result with the given callback. You should always report back the
+ * result using the callback, no matter if the execution was successful or not.
+ *
+ * @param request The function execution request.
+ * @param callback A callback to report back the result.
+ */
+ @MainThread
+ public abstract void onExecuteFunction(
+ @NonNull ExecuteAppFunctionRequest request,
+ @NonNull Consumer<ExecuteAppFunctionResponse> callback);
+}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java
new file mode 100644
index 0000000..fa6d2ff
--- /dev/null
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.google.android.appfunctions.sidecar;
+
+import android.annotation.NonNull;
+import android.app.appsearch.GenericDocument;
+import android.os.Bundle;
+
+import java.util.Objects;
+
+/**
+ * A request to execute an app function.
+ *
+ * <p>This class copies {@link android.app.appfunctions.ExecuteAppFunctionRequest} without parcel
+ * functionality and exposes it here as a sidecar library (avoiding direct dependency on the
+ * platform API).
+ */
+public final class ExecuteAppFunctionRequest {
+ /** Returns the package name of the app that hosts the function. */
+ @NonNull private final String mTargetPackageName;
+
+ /**
+ * Returns the unique string identifier of the app function to be executed. TODO(b/357551503):
+ * Document how callers can get the available function identifiers.
+ */
+ @NonNull private final String mFunctionIdentifier;
+
+ /** Returns additional metadata relevant to this function execution request. */
+ @NonNull private final Bundle mExtras;
+
+ /**
+ * Returns the parameters required to invoke this function. Within this [GenericDocument], the
+ * property names are the names of the function parameters and the property values are the
+ * values of those parameters.
+ *
+ * <p>The document may have missing parameters. Developers are advised to implement defensive
+ * handling measures.
+ *
+ * <p>TODO(b/357551503): Document how function parameters can be obtained for function execution
+ */
+ @NonNull private final GenericDocument mParameters;
+
+ private ExecuteAppFunctionRequest(
+ @NonNull String targetPackageName,
+ @NonNull String functionIdentifier,
+ @NonNull Bundle extras,
+ @NonNull GenericDocument parameters) {
+ mTargetPackageName = Objects.requireNonNull(targetPackageName);
+ mFunctionIdentifier = Objects.requireNonNull(functionIdentifier);
+ mExtras = Objects.requireNonNull(extras);
+ mParameters = Objects.requireNonNull(parameters);
+ }
+
+ /** Returns the package name of the app that hosts the function. */
+ @NonNull
+ public String getTargetPackageName() {
+ return mTargetPackageName;
+ }
+
+ /** Returns the unique string identifier of the app function to be executed. */
+ @NonNull
+ public String getFunctionIdentifier() {
+ return mFunctionIdentifier;
+ }
+
+ /**
+ * Returns the function parameters. The key is the parameter name, and the value is the
+ * parameter value.
+ *
+ * <p>The bundle may have missing parameters. Developers are advised to implement defensive
+ * handling measures.
+ */
+ @NonNull
+ public GenericDocument getParameters() {
+ return mParameters;
+ }
+
+ /** Returns the additional data relevant to this function execution. */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ /** Builder for {@link ExecuteAppFunctionRequest}. */
+ public static final class Builder {
+ @NonNull private final String mTargetPackageName;
+ @NonNull private final String mFunctionIdentifier;
+ @NonNull private Bundle mExtras = Bundle.EMPTY;
+
+ @NonNull
+ private GenericDocument mParameters = new GenericDocument.Builder<>("", "", "").build();
+
+ public Builder(@NonNull String targetPackageName, @NonNull String functionIdentifier) {
+ mTargetPackageName = Objects.requireNonNull(targetPackageName);
+ mFunctionIdentifier = Objects.requireNonNull(functionIdentifier);
+ }
+
+ /** Sets the additional data relevant to this function execution. */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = Objects.requireNonNull(extras);
+ return this;
+ }
+
+ /** Sets the function parameters. */
+ @NonNull
+ public Builder setParameters(@NonNull GenericDocument parameters) {
+ Objects.requireNonNull(parameters);
+ mParameters = parameters;
+ return this;
+ }
+
+ /** Builds the {@link ExecuteAppFunctionRequest}. */
+ @NonNull
+ public ExecuteAppFunctionRequest build() {
+ return new ExecuteAppFunctionRequest(
+ mTargetPackageName,
+ mFunctionIdentifier,
+ mExtras,
+ mParameters);
+ }
+ }
+}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
new file mode 100644
index 0000000..60c25fa
--- /dev/null
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
@@ -0,0 +1,240 @@
+/*
+ * 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.google.android.appfunctions.sidecar;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.appsearch.GenericDocument;
+import android.os.Bundle;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * The response to an app function execution.
+ *
+ * <p>This class copies {@link android.app.appfunctions.ExecuteAppFunctionResponse} without parcel
+ * functionality and exposes it here as a sidecar library (avoiding direct dependency on the
+ * platform API).
+ */
+public final class ExecuteAppFunctionResponse {
+ /**
+ * The name of the property that stores the function return value within the {@code
+ * resultDocument}.
+ *
+ * <p>See {@link GenericDocument#getProperty(String)} for more information.
+ *
+ * <p>If the function returns {@code void} or throws an error, the {@code resultDocument} will
+ * be empty {@link GenericDocument}.
+ *
+ * <p>If the {@code resultDocument} is empty, {@link GenericDocument#getProperty(String)} will
+ * return {@code null}.
+ *
+ * <p>See {@link #getResultDocument} for more information on extracting the return value.
+ */
+ public static final String PROPERTY_RETURN_VALUE = "returnValue";
+
+ /** The call was successful. */
+ public static final int RESULT_OK = 0;
+
+ /** The caller does not have the permission to execute an app function. */
+ public static final int RESULT_DENIED = 1;
+
+ /** An unknown error occurred while processing the call in the AppFunctionService. */
+ public static final int RESULT_APP_UNKNOWN_ERROR = 2;
+
+ /**
+ * An internal error occurred within AppFunctionManagerService.
+ *
+ * <p>This error may be considered similar to {@link IllegalStateException}
+ */
+ public static final int RESULT_INTERNAL_ERROR = 3;
+
+ /**
+ * The caller supplied invalid arguments to the call.
+ *
+ * <p>This error may be considered similar to {@link IllegalArgumentException}.
+ */
+ public static final int RESULT_INVALID_ARGUMENT = 4;
+
+ /** The operation was timed out. */
+ public static final int RESULT_TIMED_OUT = 5;
+
+ /** The result code of the app function execution. */
+ @ResultCode private final int mResultCode;
+
+ /**
+ * The error message associated with the result, if any. This is {@code null} if the result code
+ * is {@link #RESULT_OK}.
+ */
+ @Nullable private final String mErrorMessage;
+
+ /**
+ * Returns the return value of the executed function.
+ *
+ * <p>The return value is stored in a {@link GenericDocument} with the key {@link
+ * #PROPERTY_RETURN_VALUE}.
+ *
+ * <p>See {@link #getResultDocument} for more information on extracting the return value.
+ */
+ @NonNull private final GenericDocument mResultDocument;
+
+ /** Returns the additional metadata data relevant to this function execution response. */
+ @NonNull private final Bundle mExtras;
+
+ private ExecuteAppFunctionResponse(
+ @NonNull GenericDocument resultDocument,
+ @NonNull Bundle extras,
+ @ResultCode int resultCode,
+ @Nullable String errorMessage) {
+ mResultDocument = Objects.requireNonNull(resultDocument);
+ mExtras = Objects.requireNonNull(extras);
+ mResultCode = resultCode;
+ mErrorMessage = errorMessage;
+ }
+
+ /**
+ * Returns result codes from throwable.
+ *
+ * @hide
+ */
+ static @ResultCode int getResultCode(@NonNull Throwable t) {
+ if (t instanceof IllegalArgumentException) {
+ return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT;
+ }
+ return ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR;
+ }
+
+ /**
+ * Returns a successful response.
+ *
+ * @param resultDocument The return value of the executed function.
+ * @param extras The additional metadata data relevant to this function execution response.
+ */
+ @NonNull
+ public static ExecuteAppFunctionResponse newSuccess(
+ @NonNull GenericDocument resultDocument, @Nullable Bundle extras) {
+ Objects.requireNonNull(resultDocument);
+ Bundle actualExtras = getActualExtras(extras);
+
+ return new ExecuteAppFunctionResponse(
+ resultDocument, actualExtras, RESULT_OK, /* errorMessage= */ null);
+ }
+
+ /**
+ * Returns a failure response.
+ *
+ * @param resultCode The result code of the app function execution.
+ * @param extras The additional metadata data relevant to this function execution response.
+ * @param errorMessage The error message associated with the result, if any.
+ */
+ @NonNull
+ public static ExecuteAppFunctionResponse newFailure(
+ @ResultCode int resultCode, @Nullable String errorMessage, @Nullable Bundle extras) {
+ if (resultCode == RESULT_OK) {
+ throw new IllegalArgumentException("resultCode must not be RESULT_OK");
+ }
+ Bundle actualExtras = getActualExtras(extras);
+ GenericDocument emptyDocument = new GenericDocument.Builder<>("", "", "").build();
+ return new ExecuteAppFunctionResponse(
+ emptyDocument, actualExtras, resultCode, errorMessage);
+ }
+
+ private static Bundle getActualExtras(@Nullable Bundle extras) {
+ if (extras == null) {
+ return Bundle.EMPTY;
+ }
+ return extras;
+ }
+
+ /**
+ * Returns a generic document containing the return value of the executed function.
+ *
+ * <p>The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value.
+ *
+ * <p>An empty document is returned if {@link #isSuccess} is {@code false} or if the executed
+ * function does not produce a return value.
+ *
+ * <p>Sample code for extracting the return value:
+ *
+ * <pre>
+ * GenericDocument resultDocument = response.getResultDocument();
+ * Object returnValue = resultDocument.getProperty(PROPERTY_RETURN_VALUE);
+ * if (returnValue != null) {
+ * // Cast returnValue to expected type, or use {@link GenericDocument#getPropertyString},
+ * // {@link GenericDocument#getPropertyLong} etc.
+ * // Do something with the returnValue
+ * }
+ * </pre>
+ */
+ @NonNull
+ public GenericDocument getResultDocument() {
+ return mResultDocument;
+ }
+
+ /** Returns the extras of the app function execution. */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ /**
+ * Returns {@code true} if {@link #getResultCode} equals {@link
+ * ExecuteAppFunctionResponse#RESULT_OK}.
+ */
+ public boolean isSuccess() {
+ return getResultCode() == RESULT_OK;
+ }
+
+ /**
+ * Returns one of the {@code RESULT} constants defined in {@link ExecuteAppFunctionResponse}.
+ */
+ @ResultCode
+ public int getResultCode() {
+ return mResultCode;
+ }
+
+ /**
+ * Returns the error message associated with this result.
+ *
+ * <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}.
+ */
+ @Nullable
+ public String getErrorMessage() {
+ return mErrorMessage;
+ }
+
+ /**
+ * Result codes.
+ *
+ * @hide
+ */
+ @IntDef(
+ prefix = {"RESULT_"},
+ value = {
+ RESULT_OK,
+ RESULT_DENIED,
+ RESULT_APP_UNKNOWN_ERROR,
+ RESULT_INTERNAL_ERROR,
+ RESULT_INVALID_ARGUMENT,
+ RESULT_TIMED_OUT,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ResultCode {}
+}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java
new file mode 100644
index 0000000..b1b05f7
--- /dev/null
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.appfunctions.sidecar;
+
+import android.annotation.NonNull;
+
+/**
+ * Utility class containing methods to convert Sidecar objects of AppFunctions API into the
+ * underlying platform classes.
+ *
+ * @hide
+ */
+public final class SidecarConverter {
+ private SidecarConverter() {}
+
+ /**
+ * Converts sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest}
+ * into platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest}
+ *
+ * @hide
+ */
+ @NonNull
+ public static android.app.appfunctions.ExecuteAppFunctionRequest
+ getPlatformExecuteAppFunctionRequest(@NonNull ExecuteAppFunctionRequest request) {
+ return new
+ android.app.appfunctions.ExecuteAppFunctionRequest.Builder(
+ request.getTargetPackageName(),
+ request.getFunctionIdentifier())
+ .setExtras(request.getExtras())
+ .setParameters(request.getParameters())
+ .build();
+ }
+
+ /**
+ * Converts sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse}
+ * into platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse}
+ *
+ * @hide
+ */
+ @NonNull
+ public static android.app.appfunctions.ExecuteAppFunctionResponse
+ getPlatformExecuteAppFunctionResponse(@NonNull ExecuteAppFunctionResponse response) {
+ if (response.isSuccess()) {
+ return android.app.appfunctions.ExecuteAppFunctionResponse.newSuccess(
+ response.getResultDocument(), response.getExtras());
+ } else {
+ return android.app.appfunctions.ExecuteAppFunctionResponse.newFailure(
+ response.getResultCode(),
+ response.getErrorMessage(),
+ response.getExtras());
+ }
+ }
+
+ /**
+ * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest}
+ * into sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest}
+ *
+ * @hide
+ */
+ @NonNull
+ public static ExecuteAppFunctionRequest getSidecarExecuteAppFunctionRequest(
+ @NonNull android.app.appfunctions.ExecuteAppFunctionRequest request) {
+ return new ExecuteAppFunctionRequest.Builder(
+ request.getTargetPackageName(),
+ request.getFunctionIdentifier())
+ .setExtras(request.getExtras())
+ .setParameters(request.getParameters())
+ .build();
+ }
+
+ /**
+ * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse}
+ * into sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse}
+ *
+ * @hide
+ */
+ @NonNull
+ public static ExecuteAppFunctionResponse getSidecarExecuteAppFunctionResponse(
+ @NonNull android.app.appfunctions.ExecuteAppFunctionResponse response) {
+ if (response.isSuccess()) {
+ return ExecuteAppFunctionResponse.newSuccess(
+ response.getResultDocument(), response.getExtras());
+ } else {
+ return ExecuteAppFunctionResponse.newFailure(
+ response.getResultCode(),
+ response.getErrorMessage(),
+ response.getExtras());
+ }
+ }
+}
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index faea6d4..ab052b9 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -121,3 +121,11 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "iso_gainmap_apis"
+ is_exported: true
+ namespace: "core_graphics"
+ description: "APIs that expose gainmap metadata corresponding to those defined in ISO 21496-1"
+ bug: "349357636"
+}
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index 1854361..84bd45d 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -264,6 +264,7 @@
, mPixelStorageType(PixelStorageType::Heap) {
mPixelStorage.heap.address = address;
mPixelStorage.heap.size = size;
+ traceBitmapCreate();
}
Bitmap::Bitmap(SkPixelRef& pixelRef, const SkImageInfo& info)
@@ -272,6 +273,7 @@
, mPixelStorageType(PixelStorageType::WrappedPixelRef) {
pixelRef.ref();
mPixelStorage.wrapped.pixelRef = &pixelRef;
+ traceBitmapCreate();
}
Bitmap::Bitmap(void* address, int fd, size_t mappedSize, const SkImageInfo& info, size_t rowBytes)
@@ -281,6 +283,7 @@
mPixelStorage.ashmem.address = address;
mPixelStorage.ashmem.fd = fd;
mPixelStorage.ashmem.size = mappedSize;
+ traceBitmapCreate();
}
#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
@@ -297,10 +300,12 @@
setImmutable(); // HW bitmaps are always immutable
mImage = SkImages::DeferredFromAHardwareBuffer(buffer, mInfo.alphaType(),
mInfo.refColorSpace());
+ traceBitmapCreate();
}
#endif
Bitmap::~Bitmap() {
+ traceBitmapDelete();
switch (mPixelStorageType) {
case PixelStorageType::WrappedPixelRef:
mPixelStorage.wrapped.pixelRef->unref();
@@ -572,4 +577,28 @@
mGainmap = std::move(gainmap);
}
+std::mutex Bitmap::mLock{};
+size_t Bitmap::mTotalBitmapBytes = 0;
+size_t Bitmap::mTotalBitmapCount = 0;
+
+void Bitmap::traceBitmapCreate() {
+ if (ATRACE_ENABLED()) {
+ std::lock_guard lock{mLock};
+ mTotalBitmapBytes += getAllocationByteCount();
+ mTotalBitmapCount++;
+ ATRACE_INT64("Bitmap Memory", mTotalBitmapBytes);
+ ATRACE_INT64("Bitmap Count", mTotalBitmapCount);
+ }
+}
+
+void Bitmap::traceBitmapDelete() {
+ if (ATRACE_ENABLED()) {
+ std::lock_guard lock{mLock};
+ mTotalBitmapBytes -= getAllocationByteCount();
+ mTotalBitmapCount--;
+ ATRACE_INT64("Bitmap Memory", mTotalBitmapBytes);
+ ATRACE_INT64("Bitmap Count", mTotalBitmapCount);
+ }
+}
+
} // namespace android
diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h
index dd344e2..3d55d85 100644
--- a/libs/hwui/hwui/Bitmap.h
+++ b/libs/hwui/hwui/Bitmap.h
@@ -25,6 +25,7 @@
#include <cutils/compiler.h>
#include <utils/StrongPointer.h>
+#include <mutex>
#include <optional>
#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
@@ -227,6 +228,13 @@
} mPixelStorage;
sk_sp<SkImage> mImage; // Cache is used only for HW Bitmaps with Skia pipeline.
+
+ // for tracing total number and memory usage of bitmaps
+ static std::mutex mLock;
+ static size_t mTotalBitmapBytes;
+ static size_t mTotalBitmapCount;
+ void traceBitmapCreate();
+ void traceBitmapDelete();
};
} // namespace android
diff --git a/libs/hwui/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h
index d7bf201..e13e1365 100644
--- a/libs/hwui/hwui/DrawTextFunctor.h
+++ b/libs/hwui/hwui/DrawTextFunctor.h
@@ -73,6 +73,7 @@
}
paint->setStrokeJoin(SkPaint::kRound_Join);
paint->setLooper(nullptr);
+ paint->setBlendMode(SkBlendMode::kSrcOver);
}
class DrawTextFunctor {
diff --git a/libs/hwui/jni/Gainmap.cpp b/libs/hwui/jni/Gainmap.cpp
index 0fffee7..71972d0 100644
--- a/libs/hwui/jni/Gainmap.cpp
+++ b/libs/hwui/jni/Gainmap.cpp
@@ -16,6 +16,9 @@
#include <Gainmap.h>
+#include "SkColorType.h"
+#include "SkGainmapInfo.h"
+
#ifdef __ANDROID__
#include <binder/Parcel.h>
#endif
@@ -36,6 +39,28 @@
return reinterpret_cast<Gainmap*>(gainmap);
}
+static SkGainmapInfo::BaseImageType baseImageTypeFromJava(jint direction) {
+ switch (direction) {
+ case 0:
+ return SkGainmapInfo::BaseImageType::kSDR;
+ case 1:
+ return SkGainmapInfo::BaseImageType::kHDR;
+ default:
+ LOG_ALWAYS_FATAL("Unrecognized Gainmap direction: %d", direction);
+ }
+}
+
+static jint baseImageTypeToJava(SkGainmapInfo::BaseImageType type) {
+ switch (type) {
+ case SkGainmapInfo::BaseImageType::kSDR:
+ return 0;
+ case SkGainmapInfo::BaseImageType::kHDR:
+ return 1;
+ default:
+ LOG_ALWAYS_FATAL("Unrecognized base image: %d", type);
+ }
+}
+
static int getCreateFlags(const sk_sp<Bitmap>& bitmap) {
int flags = 0;
if (bitmap->info().alphaType() == kPremul_SkAlphaType) {
@@ -169,6 +194,36 @@
return fromJava(gainmapPtr)->info.fDisplayRatioSdr;
}
+static void Gainmap_setAlternativeColorSpace(JNIEnv*, jobject, jlong gainmapPtr,
+ jlong colorSpacePtr) {
+ auto colorSpace = GraphicsJNI::getNativeColorSpace(colorSpacePtr);
+ fromJava(gainmapPtr)->info.fGainmapMathColorSpace = colorSpace;
+}
+
+static jobject Gainmap_getAlternativeColorSpace(JNIEnv* env, jobject, jlong gainmapPtr) {
+ const auto javaGainmap = fromJava(gainmapPtr);
+ auto colorSpace = javaGainmap->info.fGainmapMathColorSpace.get();
+ if (colorSpace == nullptr) {
+ return nullptr;
+ }
+
+ auto colorType = javaGainmap->bitmap->colorType();
+ // A8 bitmaps don't support colorspaces, but an alternative colorspace is
+ // still valid for configuring the gainmap math, so use RGBA8888 instead.
+ if (colorType == kAlpha_8_SkColorType) {
+ colorType = kRGBA_8888_SkColorType;
+ }
+ return GraphicsJNI::getColorSpace(env, colorSpace, colorType);
+}
+
+static void Gainmap_setDirection(JNIEnv*, jobject, jlong gainmapPtr, jint direction) {
+ fromJava(gainmapPtr)->info.fBaseImageType = baseImageTypeFromJava(direction);
+}
+
+static jint Gainmap_getDirection(JNIEnv* env, jobject, jlong gainmapPtr) {
+ return baseImageTypeToJava(fromJava(gainmapPtr)->info.fBaseImageType);
+}
+
// ----------------------------------------------------------------------------
// Serialization
// ----------------------------------------------------------------------------
@@ -260,6 +315,11 @@
{"nGetDisplayRatioHdr", "(J)F", (void*)Gainmap_getDisplayRatioHdr},
{"nSetDisplayRatioSdr", "(JF)V", (void*)Gainmap_setDisplayRatioSdr},
{"nGetDisplayRatioSdr", "(J)F", (void*)Gainmap_getDisplayRatioSdr},
+ {"nSetAlternativeColorSpace", "(JJ)V", (void*)Gainmap_setAlternativeColorSpace},
+ {"nGetAlternativeColorSpace", "(J)Landroid/graphics/ColorSpace;",
+ (void*)Gainmap_getAlternativeColorSpace},
+ {"nSetDirection", "(JI)V", (void*)Gainmap_setDirection},
+ {"nGetDirection", "(J)I", (void*)Gainmap_getDirection},
{"nWriteGainmapToParcel", "(JLandroid/os/Parcel;)V", (void*)Gainmap_writeToParcel},
{"nReadGainmapFromParcel", "(JLandroid/os/Parcel;)V", (void*)Gainmap_readFromParcel},
};
diff --git a/libs/hwui/platform/host/thread/ThreadBase.h b/libs/hwui/platform/host/thread/ThreadBase.h
index d709430..b4e7bd3 100644
--- a/libs/hwui/platform/host/thread/ThreadBase.h
+++ b/libs/hwui/platform/host/thread/ThreadBase.h
@@ -48,7 +48,7 @@
nsecs_t nextWakeup = mQueue.nextWakeup(lock);
std::chrono::nanoseconds duration = std::chrono::nanoseconds::max();
if (nextWakeup < std::numeric_limits<nsecs_t>::max()) {
- int timeout = nextWakeup - WorkQueue::clock::now();
+ nsecs_t timeout = nextWakeup - WorkQueue::clock::now();
if (timeout < 0) timeout = 0;
duration = std::chrono::nanoseconds(timeout);
}
diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp
index 1afef75..d993b87 100644
--- a/libs/input/MouseCursorController.cpp
+++ b/libs/input/MouseCursorController.cpp
@@ -64,25 +64,6 @@
mLocked.pointerSprite.clear();
}
-std::optional<FloatRect> MouseCursorController::getBounds() const {
- std::scoped_lock lock(mLock);
-
- return getBoundsLocked();
-}
-
-std::optional<FloatRect> MouseCursorController::getBoundsLocked() const REQUIRES(mLock) {
- if (!mLocked.viewport.isValid()) {
- return {};
- }
-
- return FloatRect{
- static_cast<float>(mLocked.viewport.logicalLeft),
- static_cast<float>(mLocked.viewport.logicalTop),
- static_cast<float>(mLocked.viewport.logicalRight - 1),
- static_cast<float>(mLocked.viewport.logicalBottom - 1),
- };
-}
-
void MouseCursorController::move(float deltaX, float deltaY) {
#if DEBUG_MOUSE_CURSOR_UPDATES
ALOGD("Move pointer by deltaX=%0.3f, deltaY=%0.3f", deltaX, deltaY);
@@ -105,11 +86,20 @@
}
void MouseCursorController::setPositionLocked(float x, float y) REQUIRES(mLock) {
- const auto bounds = getBoundsLocked();
- if (!bounds) return;
+ const auto& v = mLocked.viewport;
+ if (!v.isValid()) return;
- mLocked.pointerX = std::max(bounds->left, std::min(bounds->right, x));
- mLocked.pointerY = std::max(bounds->top, std::min(bounds->bottom, y));
+ // The valid bounds for a mouse cursor. Since the right and bottom edges are considered outside
+ // the display, clip the bounds by one pixel instead of letting the cursor get arbitrarily
+ // close to the outside edge.
+ const FloatRect bounds{
+ static_cast<float>(mLocked.viewport.logicalLeft),
+ static_cast<float>(mLocked.viewport.logicalTop),
+ static_cast<float>(mLocked.viewport.logicalRight - 1),
+ static_cast<float>(mLocked.viewport.logicalBottom - 1),
+ };
+ mLocked.pointerX = std::max(bounds.left, std::min(bounds.right, x));
+ mLocked.pointerY = std::max(bounds.top, std::min(bounds.bottom, y));
updatePointerLocked();
}
@@ -216,9 +206,11 @@
// Reset cursor position to center if size or display changed.
if (oldViewport.displayId != viewport.displayId || oldDisplayWidth != newDisplayWidth ||
oldDisplayHeight != newDisplayHeight) {
- if (const auto bounds = getBoundsLocked(); bounds) {
- mLocked.pointerX = (bounds->left + bounds->right) * 0.5f;
- mLocked.pointerY = (bounds->top + bounds->bottom) * 0.5f;
+ if (viewport.isValid()) {
+ // Use integer coordinates as the starting point for the cursor location.
+ // We usually expect display sizes to be even numbers, so the flooring is precautionary.
+ mLocked.pointerX = std::floor((viewport.logicalLeft + viewport.logicalRight) / 2);
+ mLocked.pointerY = std::floor((viewport.logicalTop + viewport.logicalBottom) / 2);
// Reload icon resources for density may be changed.
loadResourcesLocked(getAdditionalMouseResources);
} else {
diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h
index 8600341..12b31a8 100644
--- a/libs/input/MouseCursorController.h
+++ b/libs/input/MouseCursorController.h
@@ -43,7 +43,6 @@
MouseCursorController(PointerControllerContext& context);
~MouseCursorController();
- std::optional<FloatRect> getBounds() const;
void move(float deltaX, float deltaY);
void setPosition(float x, float y);
FloatPoint getPosition() const;
@@ -104,7 +103,6 @@
} mLocked GUARDED_BY(mLock);
- std::optional<FloatRect> getBoundsLocked() const;
void setPositionLocked(float x, float y);
void updatePointerLocked();
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 5ae967b..78d7d3a 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -138,10 +138,6 @@
return mDisplayInfoListener->mLock;
}
-std::optional<FloatRect> PointerController::getBounds() const {
- return mCursorController.getBounds();
-}
-
void PointerController::move(float deltaX, float deltaY) {
const ui::LogicalDisplayId displayId = mCursorController.getDisplayId();
vec2 transformed;
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 4d1e1d7..ee8d121 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -51,7 +51,6 @@
~PointerController() override;
- std::optional<FloatRect> getBounds() const override;
void move(float deltaX, float deltaY) override;
void setPosition(float x, float y) override;
FloatPoint getPosition() const override;
@@ -166,9 +165,6 @@
~TouchPointerController() override;
- std::optional<FloatRect> getBounds() const override {
- LOG_ALWAYS_FATAL("Should not be called");
- }
void move(float, float) override {
LOG_ALWAYS_FATAL("Should not be called");
}
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 6342489..306643d 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -451,7 +451,7 @@
private static final long MAX_SINGLE_LOCATION_TIMEOUT_MS = 30 * 1000;
private static final String CACHE_KEY_LOCATION_ENABLED_PROPERTY =
- "cache_key.location_enabled";
+ PropertyInvalidatedCache.createSystemCacheKey("location_enabled");
static ILocationManager getService() throws RemoteException {
try {
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index dcf5c5b..c3cb492 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -94,6 +94,16 @@
}
flag {
+ name: "use_legacy_ntp_time"
+ namespace: "location"
+ description: "Flag for switching to legacy NtpNetworkTimeHelper"
+ bug: "368034558"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "subscriptions_changed_listener_thread"
namespace: "location"
description: "Flag for running onSubscriptionsChangedListener on FgThread"
@@ -111,6 +121,17 @@
}
flag {
+ name: "enable_ni_supl_message_injection_by_carrier_config_bugfix"
+ namespace: "location"
+ description: "Flag for enabling NI SUPL message injection by carrier config"
+ bug: "242105192"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "enable_ni_supl_message_injection_by_carrier_config"
namespace: "location"
description: "Flag for enabling NI SUPL message injection by carrier config"
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index e2e7a46..cdb517b3 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -6318,7 +6318,14 @@
/**
* @hide
* Get the audio devices that would be used for the routing of the given audio attributes.
- * @param attributes the {@link AudioAttributes} for which the routing is being queried
+ * @param attributes the {@link AudioAttributes} for which the routing is being queried.
+ * For queries about output devices (playback use cases), a valid usage must be specified in
+ * the audio attributes via AudioAttributes.Builder.setUsage(). The capture preset MUST NOT
+ * be changed from default.
+ * For queries about input devices (capture use case), a valid capture preset MUST be
+ * specified in the audio attributes via AudioAttributes.Builder.setCapturePreset(). If a
+ * capture preset is present, then this has precedence over any usage or content type also
+ * present in the audio attrirutes.
* @return an empty list if there was an issue with the request, a list of audio devices
* otherwise (typically one device, except for duplicated paths).
*/
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index 3cc0ad2..31f8996 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -90,24 +90,24 @@
mDisplayManager = displayManager;
}
- /**
- * Register a listener to receive notifications about when the {@link MediaProjection} or captured
- * content changes state.
- *
- * <p>The callback must be registered before invoking {@link #createVirtualDisplay(String, int,
- * int, int, int, Surface, VirtualDisplay.Callback, Handler)} to ensure that any notifications on
- * the callback are not missed. The client must implement {@link Callback#onStop()} and clean up
- * any resources it is holding, e.g. the {@link VirtualDisplay} and {@link Surface}. This should
- * also update any application UI indicating the MediaProjection status as MediaProjection has
- * stopped.
- *
- * @param callback The callback to call.
- * @param handler The handler on which the callback should be invoked, or null if the callback
- * should be invoked on the calling thread's looper.
- * @throws NullPointerException If the given callback is null.
- * @see #unregisterCallback
- */
- public void registerCallback(@NonNull Callback callback, @Nullable Handler handler) {
+ /**
+ * Register a listener to receive notifications about when the {@link MediaProjection} or
+ * captured content changes state.
+ *
+ * <p>The callback must be registered before invoking {@link #createVirtualDisplay(String, int,
+ * int, int, int, Surface, VirtualDisplay.Callback, Handler)} to ensure that any notifications
+ * on the callback are not missed. The client must implement {@link Callback#onStop()} to
+ * properly handle MediaProjection clean up any resources it is holding, e.g. the {@link
+ * VirtualDisplay} and {@link Surface}. This should also update any application UI indicating
+ * the MediaProjection status as MediaProjection has stopped.
+ *
+ * @param callback The callback to call.
+ * @param handler The handler on which the callback should be invoked, or null if the callback
+ * should be invoked on the calling thread's looper.
+ * @throws NullPointerException If the given callback is null.
+ * @see #unregisterCallback
+ */
+ public void registerCallback(@NonNull Callback callback, @Nullable Handler handler) {
try {
final Callback c = Objects.requireNonNull(callback);
if (handler == null) {
@@ -313,7 +313,7 @@
*/
public abstract static class Callback {
/**
- * Called when the MediaProjection session is no longer valid.
+ * Called when the MediaProjection session has been stopped and is no longer valid.
*
* <p>Once a MediaProjection has been stopped, it's up to the application to release any
* resources it may be holding (e.g. releasing the {@link VirtualDisplay} and {@link
@@ -321,9 +321,9 @@
* it should be updated to indicate that MediaProjection is no longer active.
*
* <p>MediaProjection stopping can be a result of the system stopping the ongoing
- * MediaProjection due to various reasons, such as another MediaProjection session starting.
- * MediaProjection may also stop due to the user explicitly stopping ongoing MediaProjection
- * via any available system-level UI.
+ * MediaProjection due to various reasons, such as another MediaProjection session starting,
+ * a user stopping the session via UI affordances in system-level UI, or the screen being
+ * locked.
*
* <p>After this callback any call to {@link MediaProjection#createVirtualDisplay} will
* fail, even if no such {@link VirtualDisplay} was ever created for this MediaProjection
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index 03fd2c6..dc55e41 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -60,13 +60,14 @@
* <li>Register a {@link MediaProjection.Callback} by calling {@link
* MediaProjection#registerCallback(MediaProjection.Callback, Handler)}. This is required to
* receive notifications about when the {@link MediaProjection} or captured content changes
- * state. When receiving an `onStop()` callback, the client must clean up any resources it is
- * holding, e.g. the {@link VirtualDisplay} and {@link Surface}. The MediaProjection may
- * further no longer create any new {@link VirtualDisplay}s via {@link
- * MediaProjection#createVirtualDisplay(String, int, int, int, int, Surface,
- * VirtualDisplay.Callback, Handler)}. Note that the `onStop()` callback can be a result of
- * the system stopping MediaProjection due to various reasons or the user stopping the
- * MediaProjection via UI affordances in system-level UI.
+ * state. When receiving an `onStop()` callback the {@link MediaProjection} session has been
+ * finished and the client must clean up any resources it is holding, e.g. the {@link
+ * VirtualDisplay} and {@link Surface}. The MediaProjection may further no longer create any
+ * new {@link VirtualDisplay}s via {@link MediaProjection#createVirtualDisplay(String, int,
+ * int, int, int, Surface, VirtualDisplay.Callback, Handler)}. Note that the `onStop()`
+ * callback can be a result of the system stopping MediaProjection due to various reasons.
+ * This includes the user stopping the MediaProjection via UI affordances in system-level UI,
+ * the screen being locked, or another {@link MediaProjection} session starting.
* <li>Start the screen capture session for media projection by calling {@link
* MediaProjection#createVirtualDisplay(String, int, int, int, int, Surface,
* android.hardware.display.VirtualDisplay.Callback, Handler)}.
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 25c063d6..202535d 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -273,6 +273,7 @@
ASurfaceTransaction_fromJava; # introduced=34
ASurfaceTransaction_reparent; # introduced=29
ASurfaceTransaction_setBuffer; # introduced=29
+ ASurfaceTransaction_setBufferWithRelease; # introduced=36
ASurfaceTransaction_setBufferAlpha; # introduced=29
ASurfaceTransaction_setBufferDataSpace; # introduced=29
ASurfaceTransaction_setBufferTransparency; # introduced=29
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 6ce83cd..e46db6b 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -416,6 +416,35 @@
transaction->setBuffer(surfaceControl, graphic_buffer, fence);
}
+void ASurfaceTransaction_setBufferWithRelease(
+ ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl,
+ AHardwareBuffer* buffer, int acquire_fence_fd, void* _Null_unspecified context,
+ ASurfaceTransaction_OnBufferRelease aReleaseCallback) {
+ CHECK_NOT_NULL(aSurfaceTransaction);
+ CHECK_NOT_NULL(aSurfaceControl);
+ CHECK_NOT_NULL(aReleaseCallback);
+
+ sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+ Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+ sp<GraphicBuffer> graphic_buffer(GraphicBuffer::fromAHardwareBuffer(buffer));
+
+ std::optional<sp<Fence>> fence = std::nullopt;
+ if (acquire_fence_fd != -1) {
+ fence = new Fence(acquire_fence_fd);
+ }
+
+ ReleaseBufferCallback releaseBufferCallback =
+ [context,
+ aReleaseCallback](const ReleaseCallbackId&, const sp<Fence>& releaseFence,
+ std::optional<uint32_t> /* currentMaxAcquiredBufferCount */) {
+ (*aReleaseCallback)(context, (releaseFence) ? releaseFence->dup() : -1);
+ };
+
+ transaction->setBuffer(surfaceControl, graphic_buffer, fence, /* frameNumber */ std::nullopt,
+ /* producerId */ 0, releaseBufferCallback);
+}
+
void ASurfaceTransaction_setGeometry(ASurfaceTransaction* aSurfaceTransaction,
ASurfaceControl* aSurfaceControl, const ARect& source,
const ARect& destination, int32_t transform) {
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index bc8a7af..9603c0a 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -60,8 +60,13 @@
method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull public java.util.List<java.lang.String> getActiveNfceeList();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void maybeTriggerFirmwareUpdate();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcOemExtension.Callback);
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void setControllerAlwaysOn(int);
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void synchronizeScreenState();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void unregisterCallback(@NonNull android.nfc.NfcOemExtension.Callback);
+ field @FlaggedApi("android.nfc.nfc_oem_extension") public static final int DISABLE = 0; // 0x0
+ field @FlaggedApi("android.nfc.nfc_oem_extension") public static final int ENABLE_DEFAULT = 1; // 0x1
+ field @FlaggedApi("android.nfc.nfc_oem_extension") public static final int ENABLE_EE = 3; // 0x3
+ field @FlaggedApi("android.nfc.nfc_oem_extension") public static final int ENABLE_TRANSPARENT = 2; // 0x2
field public static final int HCE_ACTIVATE = 1; // 0x1
field public static final int HCE_DATA_TRANSFERRED = 2; // 0x2
field public static final int HCE_DEACTIVATE = 3; // 0x3
diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
index e2ec952..246efc7 100644
--- a/nfc/java/android/nfc/INfcAdapter.aidl
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -73,7 +73,7 @@
boolean setNfcSecure(boolean enable);
NfcAntennaInfo getNfcAntennaInfo();
- boolean setControllerAlwaysOn(boolean value);
+ void setControllerAlwaysOn(int mode);
boolean isControllerAlwaysOn();
boolean isControllerAlwaysOnSupported();
void registerControllerAlwaysOnListener(in INfcControllerAlwaysOnListener listener);
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index f478793..de85f1e 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -559,6 +559,18 @@
@Retention(RetentionPolicy.SOURCE)
public @interface TagIntentAppPreferenceResult {}
+ /**
+ * Mode Type for {@link NfcOemExtension#setControllerAlwaysOn(int)}.
+ * @hide
+ */
+ public static final int CONTROLLER_ALWAYS_ON_MODE_DEFAULT = 1;
+
+ /**
+ * Mode Type for {@link NfcOemExtension#setControllerAlwaysOn(int)}.
+ * @hide
+ */
+ public static final int CONTROLLER_ALWAYS_ON_DISABLE = 0;
+
// Guarded by sLock
static boolean sIsInitialized = false;
static boolean sHasNfcFeature;
@@ -2330,7 +2342,8 @@
* FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
* FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC and FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE
* are unavailable
- * @return void
+ * @return true if feature is supported by the device and operation has bee initiated,
+ * false if the feature is not supported by the device.
* @hide
*/
@SystemApi
@@ -2339,8 +2352,13 @@
if (!sHasNfcFeature && !sHasCeFeature) {
throw new UnsupportedOperationException();
}
- return callServiceReturn(() -> sService.setControllerAlwaysOn(value), false);
-
+ int mode = value ? CONTROLLER_ALWAYS_ON_MODE_DEFAULT : CONTROLLER_ALWAYS_ON_DISABLE;
+ try {
+ callService(() -> sService.setControllerAlwaysOn(mode));
+ } catch (UnsupportedOperationException e) {
+ return false;
+ }
+ return true;
}
/**
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index d51b704..45038d4 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -70,6 +70,58 @@
private boolean mRfDiscoveryStarted = false;
/**
+ * Mode Type for {@link #setControllerAlwaysOn(int)}.
+ * Enables the controller in default mode when NFC is disabled (existing API behavior).
+ * works same as {@link NfcAdapter#setControllerAlwaysOn(boolean)}.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ public static final int ENABLE_DEFAULT = NfcAdapter.CONTROLLER_ALWAYS_ON_MODE_DEFAULT;
+
+ /**
+ * Mode Type for {@link #setControllerAlwaysOn(int)}.
+ * Enables the controller in transparent mode when NFC is disabled.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ public static final int ENABLE_TRANSPARENT = 2;
+
+ /**
+ * Mode Type for {@link #setControllerAlwaysOn(int)}.
+ * Enables the controller and initializes and enables the EE subsystem when NFC is disabled.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ public static final int ENABLE_EE = 3;
+
+ /**
+ * Mode Type for {@link #setControllerAlwaysOn(int)}.
+ * Disable the Controller Always On Mode.
+ * works same as {@link NfcAdapter#setControllerAlwaysOn(boolean)}.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ public static final int DISABLE = NfcAdapter.CONTROLLER_ALWAYS_ON_DISABLE;
+
+ /**
+ * Possible controller modes for {@link #setControllerAlwaysOn(int)}.
+ *
+ * @hide
+ */
+ @IntDef(prefix = { "" }, value = {
+ ENABLE_DEFAULT,
+ ENABLE_TRANSPARENT,
+ ENABLE_EE,
+ DISABLE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ControllerMode{}
+
+ /**
* Event that Host Card Emulation is activated.
*/
public static final int HCE_ACTIVATE = 1;
@@ -389,6 +441,32 @@
NfcAdapter.sService.fetchActiveNfceeList(), new ArrayList<String>());
}
+ /**
+ * Sets NFC controller always on feature.
+ * <p>This API is for the NFCC internal state management. It allows to discriminate
+ * the controller function from the NFC function by keeping the NFC controller on without
+ * any NFC RF enabled if necessary.
+ * <p>This call is asynchronous, register listener {@link NfcAdapter.ControllerAlwaysOnListener}
+ * by {@link NfcAdapter#registerControllerAlwaysOnListener} to find out when the operation is
+ * complete.
+ * @param mode one of {@link ControllerMode} modes
+ * @throws UnsupportedOperationException if
+ * <li> if FEATURE_NFC, FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
+ * FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC and FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE
+ * are unavailable </li>
+ * <li> if the feature is unavailable @see NfcAdapter#isNfcControllerAlwaysOnSupported() </li>
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
+ public void setControllerAlwaysOn(@ControllerMode int mode) {
+ if (!NfcAdapter.sHasNfcFeature && !NfcAdapter.sHasCeFeature) {
+ throw new UnsupportedOperationException();
+ }
+ NfcAdapter.callService(() -> NfcAdapter.sService.setControllerAlwaysOn(mode));
+ }
+
private final class NfcOemExtensionCallback extends INfcOemExtensionCallback.Stub {
@Override
diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp
index bd84b58..a30c0c3 100644
--- a/packages/PackageInstaller/Android.bp
+++ b/packages/PackageInstaller/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_framework_android_packages",
default_applicable_licenses: [
"frameworks_base_packages_PackageInstaller_license",
],
diff --git a/packages/PackageInstaller/TEST_MAPPING b/packages/PackageInstaller/TEST_MAPPING
index 717ec02..91882fd 100644
--- a/packages/PackageInstaller/TEST_MAPPING
+++ b/packages/PackageInstaller/TEST_MAPPING
@@ -65,6 +65,17 @@
"name": "CtsIntentSignatureTestCases"
},
{
+ "name": "CtsPackageInstallerCUJDeviceAdminTestCases",
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ }
+ ]
+ },
+ {
"name": "CtsPackageInstallerCUJInstallationTestCases",
"options":[
{
@@ -76,6 +87,17 @@
]
},
{
+ "name": "CtsPackageInstallerCUJMultiUsersTestCases",
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ }
+ ]
+ },
+ {
"name": "CtsPackageInstallerCUJUninstallationTestCases",
"options":[
{
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
index 022dded..265864e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
@@ -17,11 +17,16 @@
package com.android.settingslib.spa.widget.dialog
import android.content.res.Configuration
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Button
+import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
@@ -32,9 +37,11 @@
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogProperties
+import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled
data class AlertDialogButton(
val text: String,
@@ -85,27 +92,41 @@
AlertDialog(
onDismissRequest = ::close,
modifier = Modifier.width(getDialogWidth()),
- confirmButton = { confirmButton?.let { Button(it) } },
- dismissButton = dismissButton?.let { { Button(it) } },
- title = title?.let { { Text(it) } },
- text = text?.let {
- {
- Column(Modifier.verticalScroll(rememberScrollState())) {
- text()
- }
- }
+ confirmButton = {
+ confirmButton?.let { if (isSpaExpressiveEnabled) ConfirmButton(it) else Button(it) }
},
+ dismissButton =
+ dismissButton?.let {
+ { if (isSpaExpressiveEnabled) DismissButton(it) else Button(it) }
+ },
+ title = title?.let { { CenterRow { Text(it) } } },
+ text =
+ text?.let {
+ { CenterRow { Column(Modifier.verticalScroll(rememberScrollState())) { text() } } }
+ },
properties = DialogProperties(usePlatformDefaultWidth = false),
)
}
@Composable
+internal fun CenterRow(content: @Composable (() -> Unit)) {
+ if (isSpaExpressiveEnabled) {
+ Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
+ content()
+ }
+ } else {
+ content()
+ }
+}
+
+@Composable
fun getDialogWidth(): Dp {
val configuration = LocalConfiguration.current
- return configuration.screenWidthDp.dp * when (configuration.orientation) {
- Configuration.ORIENTATION_LANDSCAPE -> 0.65f
- else -> 0.85f
- }
+ return configuration.screenWidthDp.dp *
+ when (configuration.orientation) {
+ Configuration.ORIENTATION_LANDSCAPE -> 0.65f
+ else -> 0.85f
+ }
}
@Composable
@@ -120,3 +141,47 @@
Text(button.text)
}
}
+
+@Composable
+private fun AlertDialogPresenter.DismissButton(button: AlertDialogButton) {
+ OutlinedButton(
+ onClick = {
+ close()
+ button.onClick()
+ },
+ enabled = button.enabled,
+ ) {
+ Text(button.text)
+ }
+}
+
+@Composable
+private fun AlertDialogPresenter.ConfirmButton(button: AlertDialogButton) {
+ Button(
+ onClick = {
+ close()
+ button.onClick()
+ },
+ enabled = button.enabled,
+ ) {
+ Text(button.text)
+ }
+}
+
+@Preview
+@Composable
+private fun AlertDialogPreview() {
+ val alertDialogPresenter = remember {
+ object : AlertDialogPresenter {
+ override fun open() {}
+
+ override fun close() {}
+ }
+ }
+ alertDialogPresenter.SettingsAlertDialog(
+ confirmButton = AlertDialogButton("Ok"),
+ dismissButton = AlertDialogButton("Cancel"),
+ title = "Title",
+ text = { Text("Text") },
+ )
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt
index 030522d..58a83fa 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt
@@ -40,10 +40,7 @@
dismissButton: AlertDialogButton?,
title: String?,
icon: @Composable (() -> Unit)? = {
- Icon(
- Icons.Default.WarningAmber,
- contentDescription = null
- )
+ Icon(Icons.Default.WarningAmber, contentDescription = null)
},
text: @Composable (() -> Unit)?,
) {
@@ -52,43 +49,22 @@
icon = icon,
modifier = Modifier.width(getDialogWidth()),
confirmButton = {
- confirmButton?.let {
- Button(
- onClick = {
- it.onClick()
- },
- ) {
- Text(it.text)
+ confirmButton?.let { Button(onClick = { it.onClick() }) { Text(it.text) } }
+ },
+ dismissButton =
+ dismissButton?.let { { OutlinedButton(onClick = { it.onClick() }) { Text(it.text) } } },
+ title =
+ title?.let {
+ {
+ CenterRow {
+ Text(it, modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center)
+ }
}
- }
- },
- dismissButton = dismissButton?.let {
- {
- OutlinedButton(
- onClick = {
- it.onClick()
- },
- ) {
- Text(it.text)
- }
- }
- },
- title = title?.let {
- {
- Text(
- it,
- modifier = Modifier.fillMaxWidth(),
- textAlign = TextAlign.Center
- )
- }
- },
- text = text?.let {
- {
- Column(Modifier.verticalScroll(rememberScrollState())) {
- text()
- }
- }
- },
+ },
+ text =
+ text?.let {
+ { CenterRow { Column(Modifier.verticalScroll(rememberScrollState())) { text() } } }
+ },
properties = DialogProperties(usePlatformDefaultWidth = false),
)
-}
\ No newline at end of file
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlterDialogContent.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlterDialogContent.kt
index bef0bca..9f2210d 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlterDialogContent.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlterDialogContent.kt
@@ -58,10 +58,7 @@
dismissButton: AlertDialogButton?,
title: String?,
icon: @Composable (() -> Unit)? = {
- Icon(
- Icons.Default.WarningAmber,
- contentDescription = null
- )
+ Icon(Icons.Default.WarningAmber, contentDescription = null)
},
text: @Composable (() -> Unit)?,
) {
@@ -69,42 +66,22 @@
buttons = {
AlertDialogFlowRow(
mainAxisSpacing = ButtonsMainAxisSpacing,
- crossAxisSpacing = ButtonsCrossAxisSpacing
+ crossAxisSpacing = ButtonsCrossAxisSpacing,
) {
- dismissButton?.let {
- OutlinedButton(onClick = it.onClick) {
- Text(it.text)
- }
- }
- confirmButton?.let {
- Button(
- onClick = {
- it.onClick()
- },
- ) {
- Text(it.text)
- }
- }
+ dismissButton?.let { OutlinedButton(onClick = it.onClick) { Text(it.text) } }
+ confirmButton?.let { Button(onClick = { it.onClick() }) { Text(it.text) } }
}
},
icon = icon,
modifier = Modifier.width(getDialogWidth()),
- title = title?.let {
- {
- Text(
- it,
- modifier = Modifier.fillMaxWidth(),
- textAlign = TextAlign.Center
- )
- }
- },
- text = text?.let {
- {
- Column(Modifier.verticalScroll(rememberScrollState())) {
- text()
- }
- }
- },
+ title =
+ title?.let {
+ { Text(it, modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center) }
+ },
+ text =
+ text?.let {
+ { CenterRow { Column(Modifier.verticalScroll(rememberScrollState())) { text() } } }
+ },
)
}
@@ -121,18 +98,12 @@
shape = SettingsShape.CornerExtraLarge,
color = MaterialTheme.colorScheme.surfaceContainerHigh,
) {
- Column(
- modifier = Modifier.padding(DialogPadding)
- ) {
+ Column(modifier = Modifier.padding(DialogPadding)) {
icon?.let {
CompositionLocalProvider(
- LocalContentColor provides AlertDialogDefaults.iconContentColor,
+ LocalContentColor provides AlertDialogDefaults.iconContentColor
) {
- Box(
- Modifier
- .padding(IconPadding)
- .align(Alignment.CenterHorizontally)
- ) {
+ Box(Modifier.padding(IconPadding).align(Alignment.CenterHorizontally)) {
icon()
}
}
@@ -144,8 +115,7 @@
) {
Box(
// Align the title to the center when an icon is present.
- Modifier
- .padding(TitlePadding)
+ Modifier.padding(TitlePadding)
.align(
if (icon == null) {
Alignment.Start
@@ -161,11 +131,10 @@
text?.let {
ProvideContentColorTextStyle(
contentColor = AlertDialogDefaults.textContentColor,
- textStyle = MaterialTheme.typography.bodyMedium
+ textStyle = MaterialTheme.typography.bodyMedium,
) {
Box(
- Modifier
- .weight(weight = 1f, fill = false)
+ Modifier.weight(weight = 1f, fill = false)
.padding(TextPadding)
.align(Alignment.Start)
) {
@@ -177,7 +146,7 @@
ProvideContentColorTextStyle(
contentColor = MaterialTheme.colorScheme.primary,
textStyle = MaterialTheme.typography.labelLarge,
- content = buttons
+ content = buttons,
)
}
}
@@ -188,7 +157,7 @@
internal fun AlertDialogFlowRow(
mainAxisSpacing: Dp,
crossAxisSpacing: Dp,
- content: @Composable () -> Unit
+ content: @Composable () -> Unit,
) {
Layout(content) { measurables, constraints ->
val sequences = mutableListOf<List<Placeable>>()
@@ -204,8 +173,9 @@
// Return whether the placeable can be added to the current sequence.
fun canAddToCurrentSequence(placeable: Placeable) =
- currentSequence.isEmpty() || currentMainAxisSize + mainAxisSpacing.roundToPx() +
- placeable.width <= constraints.maxWidth
+ currentSequence.isEmpty() ||
+ currentMainAxisSize + mainAxisSpacing.roundToPx() + placeable.width <=
+ constraints.maxWidth
// Store current sequence information and start a new sequence.
fun startNewSequence() {
@@ -213,8 +183,7 @@
crossAxisSpace += crossAxisSpacing.roundToPx()
}
// Ensures that confirming actions appear above dismissive actions.
- @Suppress("ListIterator")
- sequences.add(0, currentSequence.toList())
+ @Suppress("ListIterator") sequences.add(0, currentSequence.toList())
crossAxisSizes += currentCrossAxisSize
crossAxisPositions += crossAxisSpace
@@ -254,23 +223,23 @@
layout(layoutWidth, layoutHeight) {
sequences.fastForEachIndexed { i, placeables ->
- val childrenMainAxisSizes = IntArray(placeables.size) { j ->
- placeables[j].width +
- if (j < placeables.lastIndex) mainAxisSpacing.roundToPx() else 0
- }
+ val childrenMainAxisSizes =
+ IntArray(placeables.size) { j ->
+ placeables[j].width +
+ if (j < placeables.lastIndex) mainAxisSpacing.roundToPx() else 0
+ }
val arrangement = Arrangement.End
val mainAxisPositions = IntArray(childrenMainAxisSizes.size) { 0 }
with(arrangement) {
arrange(
- mainAxisLayoutSize, childrenMainAxisSizes,
- layoutDirection, mainAxisPositions
+ mainAxisLayoutSize,
+ childrenMainAxisSizes,
+ layoutDirection,
+ mainAxisPositions,
)
}
placeables.fastForEachIndexed { j, placeable ->
- placeable.place(
- x = mainAxisPositions[j],
- y = crossAxisPositions[i]
- )
+ placeable.place(x = mainAxisPositions[j], y = crossAxisPositions[i])
}
}
}
@@ -289,8 +258,8 @@
/**
* ProvideContentColorTextStyle
*
- * A convenience method to provide values to both LocalContentColor and LocalTextStyle in
- * one call. This is less expensive than nesting calls to CompositionLocalProvider.
+ * A convenience method to provide values to both LocalContentColor and LocalTextStyle in one call.
+ * This is less expensive than nesting calls to CompositionLocalProvider.
*
* Text styles will be merged with the current value of LocalTextStyle.
*/
@@ -298,12 +267,12 @@
private fun ProvideContentColorTextStyle(
contentColor: Color,
textStyle: TextStyle,
- content: @Composable () -> Unit
+ content: @Composable () -> Unit,
) {
val mergedStyle = LocalTextStyle.current.merge(textStyle)
CompositionLocalProvider(
LocalContentColor provides contentColor,
LocalTextStyle provides mergedStyle,
- content = content
+ content = content,
)
}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index feee89a..34e33c0 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1409,6 +1409,8 @@
<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_tablet">This tablet</string>
+ <!-- Name of the internal speaker. [CHAR LIMIT=30] -->
+ <string name="media_transfer_this_device_name_desktop">This computer (internal)</string>
<!-- Name of the default media output of the TV. [CHAR LIMIT=30] -->
<string name="media_transfer_this_device_name_tv">@string/tv_media_transfer_default</string>
<!-- Name of the internal mic. [CHAR LIMIT=30] -->
@@ -1637,7 +1639,13 @@
<string name="cached_apps_freezer_reboot_dialog_text">Your device must be rebooted for this change to apply. Reboot now or cancel.</string>
<!-- Name of the 3.5mm and usb audio device. [CHAR LIMIT=50] -->
- <string name="media_transfer_wired_usb_device_name">Wired headphone</string>
+ <string name="media_transfer_wired_headphone_name">Wired headphone</string>
+
+ <!-- Name of the 3.5mm headphone, used in desktop devices. [CHAR LIMIT=50] -->
+ <string name="media_transfer_headphone_name">Headphone</string>
+
+ <!-- Name of the usb audio device speaker, used in desktop devices. [CHAR LIMIT=50] -->
+ <string name="media_transfer_usb_speaker_name">USB speaker</string>
<!-- Name of the 3.5mm audio device mic. [CHAR LIMIT=50] -->
<string name="media_transfer_wired_device_mic_name">Mic jack</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 6f2567b..a3f9e51 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -80,7 +80,9 @@
"com.android.settings.action.BLUETOOTH_LE_AUDIO_SHARING_STATE_CHANGE";
public static final String EXTRA_LE_AUDIO_SHARING_STATE = "BLUETOOTH_LE_AUDIO_SHARING_STATE";
public static final String EXTRA_BLUETOOTH_DEVICE = "BLUETOOTH_DEVICE";
+ public static final String EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE = "BT_DEVICE_TO_AUTO_ADD_SOURCE";
public static final String EXTRA_START_LE_AUDIO_SHARING = "START_LE_AUDIO_SHARING";
+ public static final String EXTRA_PAIR_AND_JOIN_SHARING = "PAIR_AND_JOIN_SHARING";
public static final int BROADCAST_STATE_UNKNOWN = 0;
public static final int BROADCAST_STATE_ON = 1;
public static final int BROADCAST_STATE_OFF = 2;
@@ -1224,7 +1226,7 @@
Intent intent = new Intent(ACTION_LE_AUDIO_SHARING_STATE_CHANGE);
intent.putExtra(EXTRA_LE_AUDIO_SHARING_STATE, state);
intent.setPackage(mContext.getPackageName());
- Log.e(TAG, "notifyBroadcastStateChange for state = " + state);
+ Log.d(TAG, "notifyBroadcastStateChange for state = " + state);
mContext.sendBroadcast(intent);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
index 148e164..c9f9d1b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
@@ -151,7 +151,19 @@
Log.d(TAG, "The BluetoothLeBroadcastAssistant is null");
return;
}
- mService.addSource(sink, metadata, isGroupOp);
+ try {
+ mService.addSource(sink, metadata, isGroupOp);
+ } catch (IllegalStateException e) {
+ // BT will check callback registration before add source.
+ // If it throw callback exception when bt is disabled, then the failure is intended,
+ // just catch it here.
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ if (adapter != null && adapter.isEnabled()) {
+ throw e;
+ } else {
+ Log.d(TAG, "Catch addSource failure when bt is disabled: " + e);
+ }
+ }
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreference.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreference.java
index 01bb6f0..7ee7180 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreference.java
@@ -33,6 +33,7 @@
private final String mTitle;
private final ImmutableList<ToggleInfo> mToggleInfos;
private final int mState;
+ private final boolean mIsActive;
private final boolean mIsAllowedChangingState;
private final Bundle mExtras;
@@ -40,6 +41,7 @@
@NonNull String title,
List<ToggleInfo> toggleInfos,
int state,
+ boolean isActive,
boolean allowChangingState,
Bundle extras) {
super(DeviceSettingType.DEVICE_SETTING_TYPE_MULTI_TOGGLE);
@@ -47,6 +49,7 @@
mTitle = title;
mToggleInfos = ImmutableList.copyOf(toggleInfos);
mState = state;
+ mIsActive = isActive;
mIsAllowedChangingState = allowChangingState;
mExtras = extras;
}
@@ -67,9 +70,11 @@
List<ToggleInfo> toggleInfos = new ArrayList<>();
in.readTypedList(toggleInfos, ToggleInfo.CREATOR);
int state = in.readInt();
+ boolean isActive = in.readBoolean();
boolean allowChangingState = in.readBoolean();
Bundle extras = in.readBundle(Bundle.class.getClassLoader());
- return new MultiTogglePreference(title, toggleInfos, state, allowChangingState, extras);
+ return new MultiTogglePreference(
+ title, toggleInfos, state, isActive, allowChangingState, extras);
}
public static final Creator<MultiTogglePreference> CREATOR =
@@ -99,6 +104,7 @@
dest.writeString(mTitle);
dest.writeTypedList(mToggleInfos, flags);
dest.writeInt(mState);
+ dest.writeBoolean(mIsActive);
dest.writeBoolean(mIsAllowedChangingState);
dest.writeBundle(mExtras);
}
@@ -108,6 +114,7 @@
private String mTitle;
private ImmutableList.Builder<ToggleInfo> mToggleInfos = new ImmutableList.Builder<>();
private int mState;
+ private boolean mIsActive;
private boolean mAllowChangingState;
private Bundle mExtras = Bundle.EMPTY;
@@ -148,6 +155,19 @@
}
/**
+ * Sets whether the current state is considered as an "active" state. If it's set to true,
+ * the toggle will be highlighted in UI.
+ *
+ * @param isActive The active state.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setIsActive(boolean isActive) {
+ mIsActive = isActive;
+ return this;
+ }
+
+ /**
* Sets whether state can be changed by user.
*
* @param allowChangingState Whether user is allowed to change state.
@@ -178,7 +198,7 @@
@NonNull
public MultiTogglePreference build() {
return new MultiTogglePreference(
- mTitle, mToggleInfos.build(), mState, mAllowChangingState, mExtras);
+ mTitle, mToggleInfos.build(), mState, mIsActive, mAllowChangingState, mExtras);
}
}
@@ -202,6 +222,16 @@
}
/**
+ * Whether the current state is considered as an active state. If it's set to true, the toggle
+ * will be highlighted in UI.
+ *
+ * @return Returns the active state.
+ */
+ public boolean isActive() {
+ return mIsActive;
+ }
+
+ /**
* Gets the toggle list in the preference.
*
* @return the toggle list.
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
index 29664f6..851b614 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
@@ -159,7 +159,7 @@
title = pref.title,
toggles = pref.toggleInfos.map { it.toModel() },
isAllowedChangingState = pref.isAllowedChangingState,
- isActive = true,
+ isActive = pref.isActive,
state = DeviceSettingStateModel.MultiTogglePreferenceState(pref.state),
updateState = { newState ->
coroutineScope.launch(backgroundCoroutineContext) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
index 7eae5b2..3d8ff86 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
@@ -56,11 +56,13 @@
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
+import kotlinx.coroutines.plus
import kotlinx.coroutines.withContext
@OptIn(ExperimentalCoroutinesApi::class)
@@ -101,8 +103,7 @@
} else if (allStatus.all { it is ServiceConnectionStatus.Connected }) {
allStatus
.filterIsInstance<
- ServiceConnectionStatus.Connected<
- IDeviceSettingsProviderService>
+ ServiceConnectionStatus.Connected<IDeviceSettingsProviderService>
>()
.all { it.service.serviceStatus?.enabled == true }
} else {
@@ -232,7 +233,7 @@
IDeviceSettingsProviderService.Stub::asInterface,
)
.stateIn(
- coroutineScope,
+ coroutineScope.plus(backgroundCoroutineContext),
SharingStarted.WhileSubscribed(),
ServiceConnectionStatus.Connecting,
)
@@ -263,21 +264,30 @@
transform: ((IBinder) -> T),
): Flow<ServiceConnectionStatus<T>> {
return callbackFlow {
- val serviceConnection =
- object : ServiceConnection {
- override fun onServiceConnected(name: ComponentName, service: IBinder) {
- launch { send(ServiceConnectionStatus.Connected(transform(service))) }
- }
+ val serviceConnection =
+ object : ServiceConnection {
+ override fun onServiceConnected(name: ComponentName, service: IBinder) {
+ launch { send(ServiceConnectionStatus.Connected(transform(service))) }
+ }
- override fun onServiceDisconnected(name: ComponentName?) {
- launch { send(ServiceConnectionStatus.Connecting) }
+ override fun onServiceDisconnected(name: ComponentName?) {
+ launch { send(ServiceConnectionStatus.Connecting) }
+ }
}
+ if (
+ !context.bindService(
+ intent,
+ Context.BIND_AUTO_CREATE,
+ { launch { it.run() } },
+ serviceConnection,
+ )
+ ) {
+ Log.w(TAG, "Fail to bind service $intent")
+ launch { send(ServiceConnectionStatus.Failed) }
}
- if (!context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)) {
- launch { send(ServiceConnectionStatus.Failed) }
+ awaitClose { context.unbindService(serviceConnection) }
}
- awaitClose { context.unbindService(serviceConnection) }
- }
+ .flowOn(backgroundCoroutineContext)
}
private suspend fun tryGetEndpointFromMetadata(cachedDevice: CachedBluetoothDevice): EndPoint? =
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
index 766cd43..9dd2dbb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
@@ -80,6 +80,10 @@
context, id, audioDeviceInfoType, maxVolume, currentVolume, isVolumeFixed);
}
+ public @AudioDeviceType int getAudioDeviceInfoType() {
+ return mAudioDeviceInfoType;
+ }
+
public static boolean isSupportedInputDevice(@AudioDeviceType int audioDeviceInfoType) {
return switch (audioDeviceInfoType) {
case TYPE_BUILTIN_MIC,
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
index 548eb3f..0c50166 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
@@ -15,13 +15,20 @@
*/
package com.android.settingslib.media;
+import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED;
+
import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceCallback;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
+import android.media.MediaRecorder;
import android.os.Handler;
+import android.util.Slog;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
@@ -35,12 +42,18 @@
private static final String TAG = "InputRouteManager";
+ @VisibleForTesting
+ static final AudioAttributes INPUT_ATTRIBUTES =
+ new AudioAttributes.Builder().setCapturePreset(MediaRecorder.AudioSource.MIC).build();
+
private final Context mContext;
private final AudioManager mAudioManager;
@VisibleForTesting final List<MediaDevice> mInputMediaDevices = new CopyOnWriteArrayList<>();
+ private MediaDevice mSelectedInputDevice;
+
private final Collection<InputDeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
@VisibleForTesting
@@ -57,7 +70,7 @@
}
};
- /* package */ InputRouteManager(@NonNull Context context, @NonNull AudioManager audioManager) {
+ public InputRouteManager(@NonNull Context context, @NonNull AudioManager audioManager) {
mContext = context;
mAudioManager = audioManager;
Handler handler = new Handler(context.getMainLooper());
@@ -76,8 +89,27 @@
mCallbacks.remove(callback);
}
+ public @Nullable MediaDevice getSelectedInputDevice() {
+ return mSelectedInputDevice;
+ }
+
private void dispatchInputDeviceListUpdate() {
- // TODO (b/360175574): Get selected input device.
+ // Get selected input device.
+ List<AudioDeviceAttributes> attributesOfSelectedInputDevices =
+ mAudioManager.getDevicesForAttributes(INPUT_ATTRIBUTES);
+ int selectedInputDeviceAttributesType;
+ if (attributesOfSelectedInputDevices.isEmpty()) {
+ Slog.e(TAG, "Unexpected empty list of input devices. Using built-in mic.");
+ selectedInputDeviceAttributesType = AudioDeviceInfo.TYPE_BUILTIN_MIC;
+ } else {
+ if (attributesOfSelectedInputDevices.size() > 1) {
+ Slog.w(
+ TAG,
+ "AudioManager.getDevicesForAttributes returned more than one element."
+ + " Using the first one.");
+ }
+ selectedInputDeviceAttributesType = attributesOfSelectedInputDevices.get(0).getType();
+ }
// Get all input devices.
AudioDeviceInfo[] audioDeviceInfos =
@@ -93,6 +125,10 @@
getCurrentInputGain(),
isInputGainFixed());
if (mediaDevice != null) {
+ if (info.getType() == selectedInputDeviceAttributesType) {
+ mediaDevice.setState(STATE_SELECTED);
+ mSelectedInputDevice = mediaDevice;
+ }
mInputMediaDevices.add(mediaDevice);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index 9eaf8d3..0b8fb22ce 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -72,6 +72,8 @@
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 if (inputRoutingEnabledAndIsDesktop()) {
+ return context.getString(R.string.media_transfer_this_device_name_desktop);
} else {
return context.getString(R.string.media_transfer_this_device_name);
}
@@ -85,10 +87,18 @@
switch (routeInfo.getType()) {
case TYPE_WIRED_HEADSET:
case TYPE_WIRED_HEADPHONES:
+ name =
+ inputRoutingEnabledAndIsDesktop()
+ ? context.getString(R.string.media_transfer_headphone_name)
+ : context.getString(R.string.media_transfer_wired_headphone_name);
+ break;
case TYPE_USB_DEVICE:
case TYPE_USB_HEADSET:
case TYPE_USB_ACCESSORY:
- name = context.getString(R.string.media_transfer_wired_usb_device_name);
+ name =
+ inputRoutingEnabledAndIsDesktop()
+ ? context.getString(R.string.media_transfer_usb_speaker_name)
+ : context.getString(R.string.media_transfer_wired_headphone_name);
break;
case TYPE_DOCK:
name = context.getString(R.string.media_transfer_dock_speaker_device_name);
@@ -139,6 +149,16 @@
.contains("tablet");
}
+ static boolean isDesktop() {
+ return Arrays.asList(SystemProperties.get("ro.build.characteristics").split(","))
+ .contains("desktop");
+ }
+
+ static boolean inputRoutingEnabledAndIsDesktop() {
+ return com.android.media.flags.Flags.enableAudioInputDeviceRoutingAndVolumeControl()
+ && isDesktop();
+ }
+
// 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/notification/ConversationIconFactory.java b/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java
index 251cd36..9a9a960 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java
@@ -87,7 +87,7 @@
* Returns the conversation info drawable
*/
public Drawable getBaseIconDrawable(ShortcutInfo shortcutInfo) {
- return mLauncherApps.getShortcutIconDrawable(shortcutInfo, mFillResIconDpi);
+ return mLauncherApps.getShortcutIconDrawable(shortcutInfo, mFullResIconDpi);
}
/**
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceTest.java
index 62fcb5e..1c7b5bc 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceTest.java
@@ -120,6 +120,7 @@
.addToggleInfo(TOGGLE_INFO_1)
.addToggleInfo(TOGGLE_INFO_2)
.setState(123)
+ .setIsActive(true)
.setAllowChangingState(true)
.setExtras(buildBundle("key1", "value1"))
.build();
@@ -130,6 +131,7 @@
assertThat(fromParcel.getToggleInfos().stream().map(ToggleInfo::getLabel).toList())
.containsExactly("label1", "label2");
assertThat(fromParcel.getState()).isEqualTo(preference.getState());
+ assertThat(fromParcel.isActive()).isEqualTo(preference.isActive());
assertThat(fromParcel.isAllowedChangingState())
.isEqualTo(preference.isAllowedChangingState());
assertThat(fromParcel.getExtras().getString("key1"))
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
index 81b5634..0cb6bc1 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
@@ -102,9 +102,9 @@
`when`(settingProviderService2.queryLocalInterface(anyString()))
.thenReturn(settingProviderService2)
- `when`(context.bindService(any(), any(), anyInt())).then { input ->
+ `when`(context.bindService(any(), anyInt(), any(), any())).then { input ->
val intent = input.getArgument<Intent?>(0)
- val connection = input.getArgument<ServiceConnection>(1)
+ val connection = input.getArgument<ServiceConnection>(3)
when (intent?.action) {
CONFIG_SERVICE_INTENT_ACTION ->
@@ -210,7 +210,7 @@
fun getDeviceSettingsConfig_bindingServiceFail_returnNull() {
testScope.runTest {
`when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
- doReturn(false).`when`(context).bindService(any(), any(), anyInt())
+ doReturn(false).`when`(context).bindService(any(), anyInt(), any(), any())
val config = underTest.getDeviceSettingsConfig(cachedDevice)
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java
index 2501ae6..8a18d07 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java
@@ -16,6 +16,8 @@
package com.android.settingslib.media;
+import static com.android.settingslib.media.InputRouteManager.INPUT_ATTRIBUTES;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
@@ -23,6 +25,7 @@
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
@@ -36,6 +39,10 @@
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowRouter2Manager.class})
public class InputRouteManagerTest {
@@ -124,6 +131,97 @@
}
@Test
+ public void getSelectedInputDevice_returnOneFromAudioManager() {
+ final AudioDeviceInfo info1 = mock(AudioDeviceInfo.class);
+ when(info1.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET);
+ when(info1.getId()).thenReturn(INPUT_WIRED_HEADSET_ID);
+
+ final AudioDeviceInfo info2 = mock(AudioDeviceInfo.class);
+ when(info2.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_MIC);
+ when(info2.getId()).thenReturn(BUILTIN_MIC_ID);
+
+ final AudioManager audioManager = mock(AudioManager.class);
+ AudioDeviceInfo[] devices = {info1, info2};
+ when(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(devices);
+
+ // Mock audioManager.getDevicesForAttributes returns exactly one audioDeviceAttributes.
+ AudioDeviceAttributes audioDeviceAttributes = new AudioDeviceAttributes(info1);
+ when(audioManager.getDevicesForAttributes(INPUT_ATTRIBUTES))
+ .thenReturn(Collections.singletonList(audioDeviceAttributes));
+
+ InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager);
+ inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices);
+
+ // The selected input device has the same type as the one returned from AudioManager.
+ InputMediaDevice selectedInputDevice =
+ (InputMediaDevice) inputRouteManager.getSelectedInputDevice();
+ assertThat(selectedInputDevice.getAudioDeviceInfoType())
+ .isEqualTo(AudioDeviceInfo.TYPE_WIRED_HEADSET);
+ }
+
+ @Test
+ public void getSelectedInputDevice_returnMoreThanOneFromAudioManager() {
+ final AudioDeviceInfo info1 = mock(AudioDeviceInfo.class);
+ when(info1.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET);
+ when(info1.getId()).thenReturn(INPUT_WIRED_HEADSET_ID);
+
+ final AudioDeviceInfo info2 = mock(AudioDeviceInfo.class);
+ when(info2.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_MIC);
+ when(info2.getId()).thenReturn(BUILTIN_MIC_ID);
+
+ final AudioManager audioManager = mock(AudioManager.class);
+ AudioDeviceInfo[] devices = {info1, info2};
+ when(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(devices);
+
+ // Mock audioManager.getDevicesForAttributes returns more than one audioDeviceAttributes.
+ AudioDeviceAttributes audioDeviceAttributes1 = new AudioDeviceAttributes(info1);
+ AudioDeviceAttributes audioDeviceAttributes2 = new AudioDeviceAttributes(info2);
+ List<AudioDeviceAttributes> attributesOfSelectedInputDevices = new ArrayList<>();
+ attributesOfSelectedInputDevices.add(audioDeviceAttributes1);
+ attributesOfSelectedInputDevices.add(audioDeviceAttributes2);
+ when(audioManager.getDevicesForAttributes(INPUT_ATTRIBUTES))
+ .thenReturn(attributesOfSelectedInputDevices);
+
+ InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager);
+ inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices);
+
+ // The selected input device has the same type as the first one returned from AudioManager.
+ InputMediaDevice selectedInputDevice =
+ (InputMediaDevice) inputRouteManager.getSelectedInputDevice();
+ assertThat(selectedInputDevice.getAudioDeviceInfoType())
+ .isEqualTo(AudioDeviceInfo.TYPE_WIRED_HEADSET);
+ }
+
+ @Test
+ public void getSelectedInputDevice_returnEmptyFromAudioManager() {
+ final AudioDeviceInfo info1 = mock(AudioDeviceInfo.class);
+ when(info1.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET);
+ when(info1.getId()).thenReturn(INPUT_WIRED_HEADSET_ID);
+
+ final AudioDeviceInfo info2 = mock(AudioDeviceInfo.class);
+ when(info2.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_MIC);
+ when(info2.getId()).thenReturn(BUILTIN_MIC_ID);
+
+ final AudioManager audioManager = mock(AudioManager.class);
+ AudioDeviceInfo[] devices = {info1, info2};
+ when(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(devices);
+
+ // Mock audioManager.getDevicesForAttributes returns empty list of audioDeviceAttributes.
+ List<AudioDeviceAttributes> attributesOfSelectedInputDevices = new ArrayList<>();
+ when(audioManager.getDevicesForAttributes(INPUT_ATTRIBUTES))
+ .thenReturn(attributesOfSelectedInputDevices);
+
+ InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager);
+ inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices);
+
+ // The selected input device has default type AudioDeviceInfo.TYPE_BUILTIN_MIC.
+ InputMediaDevice selectedInputDevice =
+ (InputMediaDevice) inputRouteManager.getSelectedInputDevice();
+ assertThat(selectedInputDevice.getAudioDeviceInfoType())
+ .isEqualTo(AudioDeviceInfo.TYPE_BUILTIN_MIC);
+ }
+
+ @Test
public void getMaxInputGain_returnMaxInputGain() {
assertThat(mInputRouteManager.getMaxInputGain()).isEqualTo(15);
}
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 e2d58d6..da5f428 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
@@ -47,6 +47,7 @@
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowSystemProperties;
@RunWith(RobolectricTestRunner.class)
public class PhoneMediaDeviceTest {
@@ -105,12 +106,37 @@
when(mInfo.getName()).thenReturn(deviceName);
assertThat(mPhoneMediaDevice.getName())
- .isEqualTo(mContext.getString(R.string.media_transfer_wired_usb_device_name));
+ .isEqualTo(mContext.getString(R.string.media_transfer_wired_headphone_name));
when(mInfo.getType()).thenReturn(TYPE_USB_DEVICE);
assertThat(mPhoneMediaDevice.getName())
- .isEqualTo(mContext.getString(R.string.media_transfer_wired_usb_device_name));
+ .isEqualTo(mContext.getString(R.string.media_transfer_wired_headphone_name));
+
+ when(mInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER);
+
+ assertThat(mPhoneMediaDevice.getName()).isEqualTo(getMediaTransferThisDeviceName(mContext));
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
+ @Test
+ public void getName_returnCorrectName_desktop() {
+ ShadowSystemProperties.override("ro.build.characteristics", "desktop");
+
+ when(mInfo.getType()).thenReturn(TYPE_WIRED_HEADPHONES);
+
+ assertThat(mPhoneMediaDevice.getName())
+ .isEqualTo(mContext.getString(R.string.media_transfer_headphone_name));
+
+ when(mInfo.getType()).thenReturn(TYPE_WIRED_HEADSET);
+
+ assertThat(mPhoneMediaDevice.getName())
+ .isEqualTo(mContext.getString(R.string.media_transfer_headphone_name));
+
+ when(mInfo.getType()).thenReturn(TYPE_USB_DEVICE);
+
+ assertThat(mPhoneMediaDevice.getName())
+ .isEqualTo(mContext.getString(R.string.media_transfer_usb_speaker_name));
when(mInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER);
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp
index c107ff5..1a99d25 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -36,6 +36,7 @@
"aconfig_new_storage_flags_lib",
"aconfigd_java_utils",
"aconfig_demo_flags_java_lib",
+ "configinfra_framework_flags_java_lib",
"device_config_service_flags_java",
"libaconfig_java_proto_lite",
"SettingsLibDeviceStateRotationLock",
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 40a8199..d710939 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -86,6 +86,7 @@
Settings.Secure.DOUBLE_TAP_TO_WAKE,
Settings.Secure.WAKE_GESTURE_ENABLED,
Settings.Secure.LONG_PRESS_TIMEOUT,
+ Settings.Secure.KEY_REPEAT_ENABLED,
Settings.Secure.KEY_REPEAT_TIMEOUT_MS,
Settings.Secure.KEY_REPEAT_DELAY_MS,
Settings.Secure.CAMERA_GESTURE_DISABLED,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 3b9c683..fa16a44 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -133,6 +133,7 @@
VALIDATORS.put(Secure.DOUBLE_TAP_TO_WAKE, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.WAKE_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.LONG_PRESS_TIMEOUT, NON_NEGATIVE_INTEGER_VALIDATOR);
+ VALIDATORS.put(Secure.KEY_REPEAT_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.KEY_REPEAT_TIMEOUT_MS, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.KEY_REPEAT_DELAY_MS, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.CAMERA_GESTURE_DISABLED, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index c9ad5a5..fbce6ca 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -99,13 +99,23 @@
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.print("SyncDisabledForTests: ");
- MyShellCommand.getSyncDisabledForTests(pw, pw);
+ if (android.provider.flags.Flags.dumpImprovements()) {
+ pw.print("SyncDisabledForTests: ");
+ MyShellCommand.getSyncDisabledForTests(pw, pw);
- pw.print("Is mainline: ");
- pw.println(UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService());
+ pw.print("UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService(): ");
+ pw.println(UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService());
- final IContentProvider iprovider = mProvider.getIContentProvider();
+ pw.println("DeviceConfig provider: ");
+ try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(fd)) {
+ DeviceConfig.dump(pfd, pw, /* prefix= */ " ", args);
+ } catch (IOException e) {
+ pw.print("IOException creating ParcelFileDescriptor: ");
+ pw.println(e);
+ }
+ }
+
+ IContentProvider iprovider = mProvider.getIContentProvider();
pw.println("DeviceConfig flags:");
for (String line : MyShellCommand.listAll(iprovider)) {
pw.println(line);
@@ -251,22 +261,13 @@
public static HashMap<String, String> getAllFlags(IContentProvider provider) {
HashMap<String, String> allFlags = new HashMap<String, String>();
- try {
- Bundle args = new Bundle();
- args.putInt(Settings.CALL_METHOD_USER_KEY,
- ActivityManager.getService().getCurrentUser().id);
- Bundle b = provider.call(new AttributionSource(Process.myUid(),
- resolveCallingPackage(), null), Settings.AUTHORITY,
- Settings.CALL_METHOD_LIST_CONFIG, null, args);
- if (b != null) {
- Map<String, String> flagsToValues =
- (HashMap) b.getSerializable(Settings.NameValueTable.VALUE);
- allFlags.putAll(flagsToValues);
+ for (DeviceConfig.Properties properties : DeviceConfig.getAllProperties()) {
+ List<String> keys = new ArrayList<>(properties.getKeyset());
+ for (String flagName : properties.getKeyset()) {
+ String fullName = properties.getNamespace() + "/" + flagName;
+ allFlags.put(fullName, properties.getString(flagName, null));
}
- } catch (RemoteException e) {
- throw new RuntimeException("Failed in IPC", e);
}
-
return allFlags;
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index ba59ce8..0ae4da5 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -545,6 +545,10 @@
reportDeviceConfigAccess(prefix);
return result;
}
+ case Settings.CALL_METHOD_LIST_NAMESPACES_CONFIG -> {
+ Bundle result = packageNamespacesForCallResult(getAllConfigFlagNamespaces());
+ return result;
+ }
case Settings.CALL_METHOD_REGISTER_MONITOR_CALLBACK_CONFIG -> {
RemoteCallback callback = args.getParcelable(
Settings.CALL_METHOD_MONITOR_CALLBACK_KEY);
@@ -1337,6 +1341,23 @@
}
@NonNull
+ private HashSet<String> getAllConfigFlagNamespaces() {
+ Set<String> flagNames = getAllConfigFlags(null).keySet();
+ HashSet<String> namespaces = new HashSet();
+ for (String name : flagNames) {
+ int slashIndex = name.indexOf("/");
+ boolean validSlashIndex = slashIndex != -1
+ && slashIndex != 0
+ && slashIndex != name.length();
+ if (validSlashIndex) {
+ String namespace = name.substring(0, slashIndex);
+ namespaces.add(namespace);
+ }
+ }
+ return namespaces;
+ }
+
+ @NonNull
private HashMap<String, String> getAllConfigFlags(@Nullable String prefix) {
if (DEBUG) {
Slog.v(LOG_TAG, "getAllConfigFlags() for " + prefix);
@@ -2561,6 +2582,12 @@
return result;
}
+ private Bundle packageNamespacesForCallResult(@NonNull HashSet<String> namespaces) {
+ Bundle result = new Bundle();
+ result.putSerializable(Settings.NameValueTable.VALUE, namespaces);
+ return result;
+ }
+
private void setMonitorCallback(RemoteCallback callback) {
if (callback == null) {
return;
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index f59eab0..cd16af7 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -452,7 +452,7 @@
"tests/src/**/systemui/clipboardoverlay/ClipboardListenerTest.java",
"tests/src/**/systemui/doze/DozeScreenStateTest.java",
"tests/src/**/systemui/keyguard/WorkLockActivityControllerTest.java",
- "tests/src/**/systemui/media/dialog/MediaOutputControllerTest.java",
+ "tests/src/**/systemui/media/dialog/MediaSwitchingControllerTest.java",
"tests/src/**/systemui/navigationbar/views/NavigationBarTest.java",
"tests/src/**/systemui/power/PowerNotificationWarningsTest.java",
"tests/src/**/systemui/power/PowerUITest.java",
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 7974f92..c49ffb4 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -149,6 +149,16 @@
}
flag {
+ name: "modes_dialog_single_rows"
+ namespace: "systemui"
+ description: "[Experiment] Display one entry per grid row in the Modes Dialog."
+ bug: "366034002"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "pss_app_selector_recents_split_screen"
namespace: "systemui"
description: "Allows recent apps selected for partial screenshare to be launched in split screen mode"
@@ -528,6 +538,13 @@
}
flag {
+ name: "status_bar_connected_displays"
+ namespace: "systemui"
+ description: "Shows the status bar on connected displays"
+ bug: "362720336"
+}
+
+flag {
name: "status_bar_switch_to_spn_from_data_spn"
namespace: "systemui"
description: "Fix usage of the SPN broadcast extras"
@@ -538,10 +555,10 @@
}
flag {
- name: "haptic_volume_slider"
+ name: "status_bar_simple_fragment"
namespace: "systemui"
- description: "Adds haptic feedback to the volume slider."
- bug: "316953430"
+ description: "Feature flag for refactoring the collapsed status bar fragment"
+ bug: "364360986"
}
flag {
@@ -668,13 +685,6 @@
}
flag {
- name: "compose_lockscreen"
- namespace: "systemui"
- description: "Enables the compose version of lockscreen that runs standalone, outside of Flexiglass."
- 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"
@@ -1053,6 +1063,13 @@
}
flag {
+ name: "communal_widget_resizing"
+ namespace: "systemui"
+ description: "Allow resizing of widgets on glanceable hub"
+ bug: "368053818"
+}
+
+flag {
name: "app_clips_backlinks"
namespace: "systemui"
description: "Enables Backlinks improvement feature in App Clips"
@@ -1405,3 +1422,13 @@
description: "Allow non-touchscreen devices to bypass falsing"
bug: "319809270"
}
+
+flag {
+ name: "media_projection_dialog_behind_lockscreen"
+ namespace: "systemui"
+ description: "Ensure MediaProjection Dialog appears behind the lockscreen"
+ bug: "351409536"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
index 9d0b095..d025275 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
@@ -57,6 +57,7 @@
import com.android.systemui.Flags.translucentOccludingActivityFix
import com.android.systemui.animation.TransitionAnimator.Companion.toTransitionState
import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary
+import com.android.systemui.shared.Flags.returnAnimationFrameworkLongLived
import com.android.wm.shell.shared.IShellTransitions
import com.android.wm.shell.shared.ShellTransitions
import java.util.concurrent.Executor
@@ -607,8 +608,8 @@
* this registration.
*/
fun register(controller: Controller) {
- check(returnAnimationFrameworkLibrary()) {
- "Long-lived registrations cannot be used when the returnAnimationFrameworkLibrary " +
+ check(returnAnimationFrameworkLongLived()) {
+ "Long-lived registrations cannot be used when the returnAnimationFrameworkLongLived " +
"flag is disabled"
}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt b/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt
index c01396a..4674d6e 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt
@@ -16,16 +16,15 @@
package com.android.compose.windowsizeclass
-import android.view.WindowManager
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
import androidx.compose.material3.windowsizeclass.WindowSizeClass
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.toComposeRect
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
+import androidx.window.layout.WindowMetricsCalculator
val LocalWindowSizeClass =
staticCompositionLocalOf<WindowSizeClass> {
@@ -42,10 +41,7 @@
LocalConfiguration.current
val density = LocalDensity.current
val context = LocalContext.current
- val metrics =
- remember(context) {
- context.getSystemService(WindowManager::class.java)!!.currentWindowMetrics
- }
+ val metrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context)
val size = with(density) { metrics.bounds.toComposeRect().size.toDpSize() }
return WindowSizeClass.calculateFromSize(size)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
index 163b355..8321238 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
@@ -16,7 +16,6 @@
package com.android.systemui.bouncer.ui.composable
-import android.view.HapticFeedbackConstants
import androidx.annotation.VisibleForTesting
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationVector1D
@@ -133,10 +132,7 @@
// Perform haptic feedback, but only if the current dot is not null, so we don't perform it
// when the UI first shows up or when the user lifts their pointer/finger.
if (currentDot != null) {
- view.performHapticFeedback(
- HapticFeedbackConstants.VIRTUAL_KEY,
- HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING,
- )
+ viewModel.performDotFeedback(view)
}
if (!isAnimationEnabled) {
@@ -206,10 +202,7 @@
// Show the failure animation if the user entered the wrong input.
LaunchedEffect(animateFailure) {
if (animateFailure) {
- showFailureAnimation(
- dots = dots,
- scalingAnimatables = dotScalingAnimatables,
- )
+ showFailureAnimation(dots = dots, scalingAnimatables = dotScalingAnimatables)
viewModel.onFailureAnimationShown()
}
}
@@ -358,15 +351,10 @@
(1 - checkNotNull(dotAppearMoveUpAnimatables[dot]).value) * initialOffset
drawCircle(
center =
- pixelOffset(
- dot,
- spacing,
- horizontalOffset,
- verticalOffset + appearOffset,
- ),
+ pixelOffset(dot, spacing, horizontalOffset, verticalOffset + appearOffset),
color =
dotColor.copy(alpha = checkNotNull(dotAppearFadeInAnimatables[dot]).value),
- radius = dotRadius * checkNotNull(dotScalingAnimatables[dot]).value
+ radius = dotRadius * checkNotNull(dotScalingAnimatables[dot]).value,
)
}
}
@@ -387,7 +375,7 @@
delayMillis = 33 * dot.y,
durationMillis = 450,
easing = Easings.LegacyDecelerate,
- )
+ ),
)
}
}
@@ -400,7 +388,7 @@
delayMillis = 0,
durationMillis = 450 + (33 * dot.y),
easing = Easings.StandardDecelerate,
- )
+ ),
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
index 489e24e..0830c9b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
@@ -16,8 +16,8 @@
package com.android.systemui.bouncer.ui.composable
-import android.view.HapticFeedbackConstants
import android.view.MotionEvent
+import android.view.View
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationSpec
@@ -72,11 +72,7 @@
/** Renders the PIN button pad. */
@Composable
-fun PinPad(
- viewModel: PinBouncerViewModel,
- verticalSpacing: Dp,
- modifier: Modifier = Modifier,
-) {
+fun PinPad(viewModel: PinBouncerViewModel, verticalSpacing: Dp, modifier: Modifier = Modifier) {
DisposableEffect(Unit) { onDispose { viewModel.onHidden() } }
val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsStateWithLifecycle()
@@ -104,7 +100,7 @@
columns = columns,
verticalSpacing = verticalSpacing,
horizontalSpacing = calculateHorizontalSpacingBetweenColumns(gridWidth = 300.dp),
- modifier = modifier.focusRequester(focusRequester).sysuiResTag("pin_pad_grid")
+ modifier = modifier.focusRequester(focusRequester).sysuiResTag("pin_pad_grid"),
) {
repeat(9) { index ->
DigitButton(
@@ -126,10 +122,11 @@
),
isInputEnabled = isInputEnabled,
onClicked = viewModel::onBackspaceButtonClicked,
+ onPointerDown = viewModel::onBackspaceButtonPressed,
onLongPressed = viewModel::onBackspaceButtonLongPressed,
appearance = backspaceButtonAppearance,
scaling = buttonScaleAnimatables[9]::value,
- elementId = "delete_button"
+ elementId = "delete_button",
)
DigitButton(
@@ -138,7 +135,7 @@
onClicked = viewModel::onPinButtonClicked,
scaling = buttonScaleAnimatables[10]::value,
isAnimationEnabled = isDigitButtonAnimationEnabled,
- onPointerDown = viewModel::onDigitButtonDown
+ onPointerDown = viewModel::onDigitButtonDown,
)
ActionButton(
@@ -152,7 +149,7 @@
onClicked = viewModel::onAuthenticateButtonClicked,
appearance = confirmButtonAppearance,
scaling = buttonScaleAnimatables[11]::value,
- elementId = "key_enter"
+ elementId = "key_enter",
)
}
}
@@ -162,7 +159,7 @@
digit: Int,
isInputEnabled: Boolean,
onClicked: (Int) -> Unit,
- onPointerDown: () -> Unit,
+ onPointerDown: (View?) -> Unit,
scaling: () -> Float,
isAnimationEnabled: Boolean,
) {
@@ -178,7 +175,7 @@
val scale = if (isAnimationEnabled) scaling() else 1f
scaleX = scale
scaleY = scale
- }
+ },
) { contentColor ->
// TODO(b/281878426): once "color: () -> Color" (added to BasicText in aosp/2568972) makes
// it into Text, use that here, to animate more efficiently.
@@ -197,6 +194,7 @@
onClicked: () -> Unit,
elementId: String,
onLongPressed: (() -> Unit)? = null,
+ onPointerDown: ((View?) -> Unit)? = null,
appearance: ActionButtonAppearance,
scaling: () -> Float,
) {
@@ -222,18 +220,16 @@
foregroundColor = foregroundColor,
isAnimationEnabled = true,
elementId = elementId,
+ onPointerDown = onPointerDown,
modifier =
Modifier.graphicsLayer {
alpha = hiddenAlpha
val scale = scaling()
scaleX = scale
scaleY = scale
- }
+ },
) { contentColor ->
- Icon(
- icon = icon,
- tint = contentColor(),
- )
+ Icon(icon = icon, tint = contentColor())
}
}
@@ -247,22 +243,13 @@
modifier: Modifier = Modifier,
elementId: String? = null,
onLongPressed: (() -> Unit)? = null,
- onPointerDown: (() -> Unit)? = null,
+ onPointerDown: ((View?) -> Unit)? = null,
content: @Composable (contentColor: () -> Color) -> Unit,
) {
val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState()
val indication = LocalIndication.current.takeUnless { isPressed }
-
val view = LocalView.current
- LaunchedEffect(isPressed) {
- if (isPressed) {
- view.performHapticFeedback(
- HapticFeedbackConstants.VIRTUAL_KEY,
- HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING,
- )
- }
- }
// Pin button animation specification is asymmetric: fast animation to the pressed state, and a
// slow animation upon release. Note that isPressed is guaranteed to be true for at least the
@@ -277,7 +264,7 @@
animateDpAsState(
if (isAnimationEnabled && isPressed) 24.dp else pinButtonMaxSize / 2,
label = "PinButton round corners",
- animationSpec = tween(animDurationMillis, easing = animEasing)
+ animationSpec = tween(animDurationMillis, easing = animEasing),
)
val colorAnimationSpec: AnimationSpec<Color> = tween(animDurationMillis, easing = animEasing)
val containerColor: Color by
@@ -287,7 +274,7 @@
else -> backgroundColor
},
label = "Pin button container color",
- animationSpec = colorAnimationSpec
+ animationSpec = colorAnimationSpec,
)
val contentColor =
animateColorAsState(
@@ -296,7 +283,7 @@
else -> foregroundColor
},
label = "Pin button container color",
- animationSpec = colorAnimationSpec
+ animationSpec = colorAnimationSpec,
)
Box(
@@ -319,11 +306,11 @@
interactionSource = interactionSource,
indication = indication,
onClick = onClicked,
- onLongClick = onLongPressed
+ onLongClick = onLongPressed,
)
.pointerInteropFilter { motionEvent ->
if (motionEvent.action == MotionEvent.ACTION_DOWN) {
- onPointerDown?.let { it() }
+ onPointerDown?.let { it(view) }
}
false
}
@@ -353,10 +340,7 @@
animatable.animateTo(
targetValue = 1f,
animationSpec =
- tween(
- durationMillis = pinButtonErrorRevertMs,
- easing = Easings.Legacy,
- ),
+ tween(durationMillis = pinButtonErrorRevertMs, easing = Easings.Legacy),
)
}
}
@@ -364,9 +348,7 @@
}
/** Returns the amount of horizontal spacing between columns, in dips. */
-private fun calculateHorizontalSpacingBetweenColumns(
- gridWidth: Dp,
-): Dp {
+private fun calculateHorizontalSpacingBetweenColumns(gridWidth: Dp): Dp {
return (gridWidth - (pinButtonMaxSize * columns)) / (columns - 1)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt
index 296fc27..dcf32b2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt
@@ -16,15 +16,10 @@
package com.android.systemui.common.ui.compose.windowinsets
-import androidx.compose.foundation.layout.WindowInsets
-import androidx.compose.foundation.layout.asPaddingValues
-import androidx.compose.foundation.layout.displayCutout
-import androidx.compose.foundation.layout.systemBars
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.staticCompositionLocalOf
-import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -36,9 +31,6 @@
/** The corner radius in px of the current display. */
val LocalScreenCornerRadius = staticCompositionLocalOf { 0.dp }
-/** The screen height in px without accounting for any screen insets (cutouts, status/nav bars) */
-val LocalRawScreenHeight = staticCompositionLocalOf { 0f }
-
@Composable
fun ScreenDecorProvider(
displayCutout: StateFlow<DisplayCutout>,
@@ -48,22 +40,9 @@
val cutout by displayCutout.collectAsStateWithLifecycle()
val screenCornerRadiusDp = with(LocalDensity.current) { screenCornerRadius.toDp() }
- val density = LocalDensity.current
- val navBarHeight =
- with(density) { WindowInsets.systemBars.asPaddingValues().calculateBottomPadding().toPx() }
- val statusBarHeight = WindowInsets.systemBars.asPaddingValues().calculateTopPadding()
- val displayCutoutHeight = WindowInsets.displayCutout.asPaddingValues().calculateTopPadding()
- val screenHeight =
- with(density) {
- (LocalConfiguration.current.screenHeightDp.dp +
- maxOf(statusBarHeight, displayCutoutHeight))
- .toPx()
- } + navBarHeight
-
CompositionLocalProvider(
LocalScreenCornerRadius provides screenCornerRadiusDp,
LocalDisplayCutout provides cutout,
- LocalRawScreenHeight provides screenHeight,
) {
content()
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
index 5f7b1ad..f4d9e82 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
@@ -35,7 +35,6 @@
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.res.R
import com.android.systemui.util.animation.MeasurementInput
-import kotlinx.coroutines.ExperimentalCoroutinesApi
object MediaCarousel {
object Elements {
@@ -47,7 +46,6 @@
}
}
-@ExperimentalCoroutinesApi
@Composable
fun SceneScope.MediaCarousel(
isVisible: Boolean,
@@ -79,7 +77,7 @@
offsetProvider?.invoke() ?: IntOffset.Zero
)
}
- }
+ },
)
.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
@@ -89,7 +87,7 @@
MeasurementInput(placeable.width, placeable.height)
carouselController.setSceneContainerSize(
placeable.width,
- placeable.height
+ placeable.height,
)
layout(placeable.width, placeable.height) {
@@ -106,7 +104,7 @@
}
},
update = { it.setView(carouselController.mediaFrame) },
- onRelease = { it.removeAllViews() }
+ onRelease = { it.removeAllViews() },
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
index 897a861..a2ae8bb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
@@ -24,9 +24,11 @@
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
import com.android.compose.nestedscroll.PriorityNestedScrollConnection
-import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
import kotlin.math.max
import kotlin.math.roundToInt
import kotlin.math.tanh
@@ -36,9 +38,10 @@
@Composable
fun Modifier.stackVerticalOverscroll(
coroutineScope: CoroutineScope,
- canScrollForward: () -> Boolean
+ canScrollForward: () -> Boolean,
): Modifier {
- val screenHeight = LocalRawScreenHeight.current
+ val screenHeight =
+ with(LocalDensity.current) { LocalConfiguration.current.screenHeightDp.dp.toPx() }
val overscrollOffset = remember { Animatable(0f) }
val stackNestedScrollConnection = remember {
NotificationStackNestedScrollConnection(
@@ -60,10 +63,10 @@
overscrollOffset.animateTo(
targetValue = 0f,
initialVelocity = velocityAvailable,
- animationSpec = tween()
+ animationSpec = tween(),
)
}
- }
+ },
)
}
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 91ecfc1..1b99a96 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
@@ -19,6 +19,7 @@
import android.util.Log
import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background
@@ -29,6 +30,8 @@
import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.absoluteOffset
@@ -36,9 +39,11 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.imeAnimationTarget
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars
+import androidx.compose.foundation.layout.windowInsetsBottomHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
@@ -68,6 +73,7 @@
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.res.dimensionResource
import androidx.compose.ui.unit.Dp
@@ -81,7 +87,6 @@
import com.android.compose.animation.scene.NestedScrollBehavior
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.thenIf
-import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
import com.android.systemui.res.R
import com.android.systemui.scene.session.ui.composable.SaveableSession
@@ -96,6 +101,7 @@
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import kotlin.math.roundToInt
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
object Notifications {
@@ -171,7 +177,7 @@
setCurrent = { scrollOffset = it },
min = minScrollOffset,
max = maxScrollOffset,
- delta
+ delta,
)
}
@@ -209,8 +215,8 @@
calculateHeadsUpPlaceholderYOffset(
scrollOffset.roundToInt(),
minScrollOffset.roundToInt(),
- stackScrollView.topHeadsUpHeight
- )
+ stackScrollView.topHeadsUpHeight,
+ ),
)
}
.thenIf(isHeadsUp) {
@@ -218,11 +224,8 @@
bottomBehavior = NestedScrollBehavior.EdgeAlways
)
.nestedScroll(nestedScrollConnection)
- .scrollable(
- orientation = Orientation.Vertical,
- state = scrollableState,
- )
- }
+ .scrollable(orientation = Orientation.Vertical, state = scrollableState)
+ },
)
}
@@ -259,6 +262,7 @@
* Adds the space where notification stack should appear in the scene, with a scrim and nested
* scrolling.
*/
+@OptIn(ExperimentalLayoutApi::class)
@Composable
fun SceneScope.NotificationScrollingStack(
shadeSession: SaveableSession,
@@ -291,7 +295,7 @@
val navBarHeight = WindowInsets.systemBars.asPaddingValues().calculateBottomPadding()
val bottomPadding = if (shouldReserveSpaceForNavBar) navBarHeight else 0.dp
- val screenHeight = LocalRawScreenHeight.current
+ val screenHeight = with(density) { LocalConfiguration.current.screenHeightDp.dp.toPx() }
/**
* The height in px of the contents of notification stack. Depending on the number of
@@ -325,6 +329,14 @@
screenHeight - maxScrimTop() - with(density) { navBarHeight.toPx() }
}
+ val isRemoteInputActive by viewModel.isRemoteInputActive.collectAsStateWithLifecycle(false)
+
+ // The bottom Y bound of the currently focused remote input notification.
+ val remoteInputRowBottom by viewModel.remoteInputRowBottomBound.collectAsStateWithLifecycle(0f)
+
+ // The top y bound of the IME.
+ val imeTop = remember { mutableFloatStateOf(0f) }
+
// we are not scrolled to the top unless the scrim is at its maximum offset.
LaunchedEffect(viewModel, scrimOffset) {
snapshotFlow { scrimOffset.value >= 0f }
@@ -342,15 +354,34 @@
LaunchedEffect(syntheticScroll, scrimOffset, scrollState) {
snapshotFlow { syntheticScroll.value }
.collect { delta ->
- val minOffset = minScrimOffset()
- if (scrimOffset.value > minOffset) {
- val remainingDelta = (minOffset - (scrimOffset.value - delta)).coerceAtLeast(0f)
- scrimOffset.snapTo((scrimOffset.value - delta).coerceAtLeast(minOffset))
- if (remainingDelta > 0f) {
- scrollState.scrollBy(remainingDelta)
- }
- } else {
- scrollState.scrollTo(delta.roundToInt())
+ scrollNotificationStack(
+ scope = coroutineScope,
+ delta = delta,
+ animate = false,
+ scrimOffset = scrimOffset,
+ minScrimOffset = minScrimOffset,
+ scrollState = scrollState,
+ )
+ }
+ }
+
+ // if remote input state changes, compare the row and IME's overlap and offset the scrim and
+ // placeholder accordingly.
+ LaunchedEffect(isRemoteInputActive, remoteInputRowBottom, imeTop) {
+ imeTop.floatValue = 0f
+ snapshotFlow { imeTop.floatValue }
+ .collect { imeTopValue ->
+ // only scroll the stack if ime value has been populated (ime placeholder has been
+ // composed at least once), and our remote input row overlaps with the ime bounds.
+ if (isRemoteInputActive && imeTopValue > 0f && remoteInputRowBottom > imeTopValue) {
+ scrollNotificationStack(
+ scope = coroutineScope,
+ delta = remoteInputRowBottom - imeTopValue,
+ animate = true,
+ scrimOffset = scrimOffset,
+ minScrimOffset = minScrimOffset,
+ scrollState = scrollState,
+ )
}
}
}
@@ -394,12 +425,12 @@
scrimOffset.value < 0 &&
layoutState.isTransitioning(
from = Scenes.Shade,
- to = Scenes.QuickSettings
+ to = Scenes.QuickSettings,
)
) {
IntOffset(
x = 0,
- y = (scrimOffset.value * (1 - shadeToQsFraction)).roundToInt()
+ y = (scrimOffset.value * (1 - shadeToQsFraction)).roundToInt(),
)
} else {
IntOffset(x = 0, y = scrimOffset.value.roundToInt())
@@ -458,13 +489,11 @@
.thenIf(shouldFillMaxSize) { Modifier.fillMaxSize() }
.debugBackground(viewModel, DEBUG_BOX_COLOR)
) {
- NotificationPlaceholder(
- stackScrollView = stackScrollView,
- viewModel = viewModel,
+ Column(
modifier =
Modifier.verticalNestedScrollToScene(
topBehavior = NestedScrollBehavior.EdgeWithPreview,
- isExternalOverscrollGesture = { isCurrentGestureOverscroll.value }
+ isExternalOverscrollGesture = { isCurrentGestureOverscroll.value },
)
.thenIf(shadeMode == ShadeMode.Single) {
Modifier.nestedScroll(scrimNestedScrollConnection)
@@ -473,18 +502,31 @@
.verticalScroll(scrollState)
.padding(top = topPadding)
.fillMaxWidth()
- .notificationStackHeight(
- view = stackScrollView,
- totalVerticalPadding = topPadding + bottomPadding,
- )
- .onSizeChanged { size -> stackHeight.intValue = size.height },
- )
+ ) {
+ NotificationPlaceholder(
+ stackScrollView = stackScrollView,
+ viewModel = viewModel,
+ modifier =
+ Modifier.notificationStackHeight(
+ view = stackScrollView,
+ totalVerticalPadding = topPadding + bottomPadding,
+ )
+ .onSizeChanged { size -> stackHeight.intValue = size.height },
+ )
+ Spacer(
+ modifier =
+ Modifier.windowInsetsBottomHeight(WindowInsets.imeAnimationTarget)
+ .onGloballyPositioned { coordinates: LayoutCoordinates ->
+ imeTop.floatValue = screenHeight - coordinates.size.height
+ }
+ )
+ }
}
if (shouldIncludeHeadsUpSpace) {
HeadsUpNotificationSpace(
stackScrollView = stackScrollView,
viewModel = viewModel,
- modifier = Modifier.padding(top = topPadding)
+ modifier = Modifier.padding(top = topPadding),
)
}
}
@@ -572,6 +614,42 @@
)
}
+private suspend fun scrollNotificationStack(
+ scope: CoroutineScope,
+ delta: Float,
+ animate: Boolean,
+ scrimOffset: Animatable<Float, AnimationVector1D>,
+ minScrimOffset: () -> Float,
+ scrollState: ScrollState,
+) {
+ val minOffset = minScrimOffset()
+ if (scrimOffset.value > minOffset) {
+ val remainingDelta =
+ (minOffset - (scrimOffset.value - delta)).coerceAtLeast(0f).roundToInt()
+ if (remainingDelta > 0) {
+ if (animate) {
+ // launch a new coroutine for the remainder animation so that it doesn't suspend the
+ // scrim animation, allowing both to play simultaneously.
+ scope.launch { scrollState.animateScrollTo(remainingDelta) }
+ } else {
+ scrollState.scrollTo(remainingDelta)
+ }
+ }
+ val newScrimOffset = (scrimOffset.value - delta).coerceAtLeast(minOffset)
+ if (animate) {
+ scrimOffset.animateTo(newScrimOffset)
+ } else {
+ scrimOffset.snapTo(newScrimOffset)
+ }
+ } else {
+ if (animate) {
+ scrollState.animateScrollBy(delta)
+ } else {
+ scrollState.scrollBy(delta)
+ }
+ }
+}
+
private fun calculateCornerRadius(
scrimCornerRadius: Dp,
screenCornerRadius: Dp,
@@ -618,7 +696,7 @@
setCurrent: (Float) -> Unit,
min: Float,
max: Float,
- delta: Float
+ delta: Float,
): Float {
return if (delta < 0 && current > min) {
val remainder = (current + delta - min).coerceAtMost(0f)
@@ -631,10 +709,7 @@
} else 0f
}
-private inline fun debugLog(
- viewModel: NotificationsPlaceholderViewModel,
- msg: () -> Any,
-) {
+private inline fun debugLog(viewModel: NotificationsPlaceholderViewModel, msg: () -> Any) {
if (viewModel.isDebugLoggingEnabled) {
Log.d(TAG, msg().toString())
}
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 fa92bef34..d91958a 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
@@ -61,6 +61,7 @@
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.layoutId
+import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.colorResource
@@ -79,7 +80,6 @@
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout
-import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.lifecycle.ExclusiveActivatable
@@ -229,17 +229,16 @@
}
.thenIf(cutoutLocation != CutoutLocation.CENTER) { Modifier.displayCutoutPadding() }
) {
+ val density = LocalDensity.current
val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsStateWithLifecycle()
val isCustomizerShowing by
viewModel.qsSceneAdapter.isCustomizerShowing.collectAsStateWithLifecycle()
val customizingAnimationDuration by
viewModel.qsSceneAdapter.customizerAnimationDuration.collectAsStateWithLifecycle()
- val screenHeight = LocalRawScreenHeight.current
+ val screenHeight = with(density) { LocalConfiguration.current.screenHeightDp.dp.toPx() }
BackHandler(enabled = isCustomizing) { viewModel.qsSceneAdapter.requestCloseCustomizer() }
- val collapsedHeaderHeight =
- with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() }
val lifecycleOwner = LocalLifecycleOwner.current
val footerActionsViewModel =
remember(lifecycleOwner, viewModel) {
@@ -268,7 +267,6 @@
val navBarBottomHeight =
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
- val density = LocalDensity.current
val bottomPadding by
animateDpAsState(
targetValue = if (isCustomizing) 0.dp else navBarBottomHeight,
@@ -394,7 +392,7 @@
{ mediaOffset.roundToPx() },
)
}
- Box(modifier = Modifier.padding(horizontal = shadeHorizontalPadding)) {
+ Column(modifier = Modifier.padding(horizontal = shadeHorizontalPadding)) {
if (mediaInRow) {
Layout(content = content, measurePolicy = landscapeQsMediaMeasurePolicy)
} else {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
index b85523b..6c4edf4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
@@ -18,6 +18,7 @@
package com.android.systemui.shade.ui.composable
+import android.view.HapticFeedbackConstants
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
@@ -39,17 +40,20 @@
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.LowestZIndexContentPicker
import com.android.compose.animation.scene.SceneScope
import com.android.compose.windowsizeclass.LocalWindowSizeClass
+import com.android.systemui.scene.shared.model.Scenes
/** Renders a lightweight shade UI container, as an overlay. */
@Composable
@@ -58,6 +62,13 @@
modifier: Modifier = Modifier,
content: @Composable () -> Unit,
) {
+ val view = LocalView.current
+ LaunchedEffect(Unit) {
+ if (layoutState.currentTransition?.fromContent == Scenes.Gone) {
+ view.performHapticFeedback(HapticFeedbackConstants.GESTURE_START)
+ }
+ }
+
Box(modifier) {
Scrim(onClicked = onScrimClicked)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index fb9dde3..0bb1d92 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -51,6 +51,7 @@
import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.util.fastAll
import androidx.compose.ui.util.fastAny
+import androidx.compose.ui.util.fastFilter
import androidx.compose.ui.util.fastFirstOrNull
import androidx.compose.ui.util.fastSumBy
import com.android.compose.ui.util.SpaceVectorConverter
@@ -234,8 +235,15 @@
pointersDown == 0 -> {
startedPosition = null
- val lastPointerUp = changes.single { it.id == velocityPointerId }
- velocityTracker.addPointerInputChange(lastPointerUp)
+ // In case of multiple events with 0 pointers down (not pressed) we may have
+ // already removed the velocityPointer
+ val lastPointerUp = changes.fastFilter { it.id == velocityPointerId }
+ check(lastPointerUp.isEmpty() || lastPointerUp.size == 1) {
+ "There are ${lastPointerUp.size} pointers up: $lastPointerUp"
+ }
+ if (lastPointerUp.size == 1) {
+ velocityTracker.addPointerInputChange(lastPointerUp.first())
+ }
}
// The first pointer down, startedPosition was not set.
diff --git a/packages/SystemUI/docs/scene.md b/packages/SystemUI/docs/scene.md
index 0ac15c5..234c7a0 100644
--- a/packages/SystemUI/docs/scene.md
+++ b/packages/SystemUI/docs/scene.md
@@ -68,15 +68,13 @@
1. Set a collection of **aconfig flags** to `true` by running the following
commands:
```console
- $ adb shell device_config override systemui com.android.systemui.scene_container true
- $ adb shell device_config override systemui com.android.systemui.compose_lockscreen true
$ adb shell device_config override systemui com.android.systemui.keyguard_bottom_area_refactor true
$ adb shell device_config override systemui com.android.systemui.keyguard_wm_state_refactor true
- $ adb shell device_config override systemui com.android.systemui.media_in_scene_container true
$ adb shell device_config override systemui com.android.systemui.migrate_clocks_to_blueprint true
- $ adb shell device_config override systemui com.android.systemui.notifications_heads_up_refactor true
+ $ adb shell device_config override systemui com.android.systemui.notification_avalanche_throttle_hun true
$ adb shell device_config override systemui com.android.systemui.predictive_back_sysui true
$ adb shell device_config override systemui com.android.systemui.device_entry_udfps_refactor true
+ $ adb shell device_config override systemui com.android.systemui.scene_container true
```
2. **Restart** System UI by issuing the following command:
```console
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 156e068..312e62d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -196,6 +196,28 @@
}
@Test
+ public void testUserSwitcherToOneHandedRemovesViews() {
+ // Can happen when a SIM is inserted into a large screen device
+ initMode(MODE_USER_SWITCHER);
+ {
+ View view1 = mKeyguardSecurityContainer.findViewById(
+ R.id.keyguard_bouncer_user_switcher);
+ View view2 = mKeyguardSecurityContainer.findViewById(R.id.user_switcher_header);
+ assertThat(view1).isNotNull();
+ assertThat(view2).isNotNull();
+ }
+
+ initMode(MODE_ONE_HANDED);
+ {
+ View view1 = mKeyguardSecurityContainer.findViewById(
+ R.id.keyguard_bouncer_user_switcher);
+ View view2 = mKeyguardSecurityContainer.findViewById(R.id.user_switcher_header);
+ assertThat(view1).isNull();
+ assertThat(view2).isNull();
+ }
+ }
+
+ @Test
public void updatePosition_movesKeyguard() {
setupForUpdateKeyguardPosition(/* oneHandedMode= */ true);
mKeyguardSecurityContainer.updatePositionByTouchX(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
index deef652..9552564 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -16,18 +16,26 @@
package com.android.systemui.bouncer.ui.viewmodel
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.keyguard.AuthInteractionProperties
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.haptics.msdl.bouncerHapticPlayer
+import com.android.systemui.haptics.msdl.fakeMSDLPlayer
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.testKosmos
+import com.google.android.msdl.data.model.MSDLToken
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -39,11 +47,15 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
+ private val msdlPlayer = kosmos.fakeMSDLPlayer
+ private val bouncerHapticPlayer = kosmos.bouncerHapticPlayer
+ private val authInteractionProperties = AuthInteractionProperties()
private val underTest =
kosmos.pinBouncerViewModelFactory.create(
isInputEnabled = MutableStateFlow(true),
onIntentionalUserInput = {},
authenticationMethod = AuthenticationMethodModel.Pin,
+ bouncerHapticPlayer = bouncerHapticPlayer,
)
@Before
@@ -77,4 +89,42 @@
underTest.onAuthenticateButtonClicked()
assertThat(animateFailure).isFalse()
}
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun onAuthenticationResult_playUnlockTokenIfSuccessful() =
+ testScope.runTest {
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ // Correct PIN:
+ FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
+ underTest.onPinButtonClicked(digit)
+ }
+ underTest.onAuthenticateButtonClicked()
+ runCurrent()
+
+ assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.UNLOCK)
+ assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun onAuthenticationResult_playFailureTokenIfFailure() =
+ testScope.runTest {
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ // Wrong PIN:
+ FakeAuthenticationRepository.DEFAULT_PIN.drop(2).forEach { digit ->
+ underTest.onPinButtonClicked(digit)
+ }
+ underTest.onAuthenticateButtonClicked()
+ runCurrent()
+
+ assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.FAILURE)
+ assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index 7c773a9..c163c6f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -16,9 +16,11 @@
package com.android.systemui.bouncer.ui.viewmodel
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.data.repository.authenticationRepository
@@ -27,12 +29,16 @@
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate as Point
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.haptics.msdl.FakeMSDLPlayer
+import com.android.systemui.haptics.msdl.bouncerHapticPlayer
+import com.android.systemui.haptics.msdl.fakeMSDLPlayer
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
+import com.google.android.msdl.data.model.MSDLToken
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -55,10 +61,13 @@
private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val bouncerViewModel by lazy { kosmos.bouncerSceneContentViewModel }
+ private val msdlPlayer: FakeMSDLPlayer = kosmos.fakeMSDLPlayer
+ private val bouncerHapticHelper = kosmos.bouncerHapticPlayer
private val underTest =
kosmos.patternBouncerViewModelFactory.create(
isInputEnabled = MutableStateFlow(true).asStateFlow(),
onIntentionalUserInput = {},
+ bouncerHapticPlayer = bouncerHapticHelper,
)
private val containerSize = 90 // px
@@ -115,10 +124,7 @@
.that(selectedDots)
.isEqualTo(
CORRECT_PATTERN.subList(0, index + 1).map {
- PatternDotViewModel(
- x = it.x,
- y = it.y,
- )
+ PatternDotViewModel(x = it.x, y = it.y)
}
)
assertWithMessage("Wrong current dot for index $index")
@@ -174,7 +180,7 @@
listOf(
PatternDotViewModel(0, 0),
PatternDotViewModel(1, 0),
- PatternDotViewModel(2, 0)
+ PatternDotViewModel(2, 0),
)
)
}
@@ -200,7 +206,7 @@
listOf(
PatternDotViewModel(1, 0),
PatternDotViewModel(1, 1),
- PatternDotViewModel(1, 2)
+ PatternDotViewModel(1, 2),
)
)
}
@@ -228,7 +234,7 @@
listOf(
PatternDotViewModel(2, 0),
PatternDotViewModel(1, 1),
- PatternDotViewModel(0, 2)
+ PatternDotViewModel(0, 2),
)
)
}
@@ -300,10 +306,7 @@
val attempts = FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT + 1
repeat(attempts) { attempt ->
underTest.onDragStart()
- CORRECT_PATTERN.subList(
- 0,
- kosmos.authenticationRepository.minPatternLength - 1,
- )
+ CORRECT_PATTERN.subList(0, kosmos.authenticationRepository.minPatternLength - 1)
.forEach { coordinate ->
underTest.onDrag(
xPx = 30f * coordinate.x + 15,
@@ -341,6 +344,16 @@
assertThat(authResult).isTrue()
}
+ @Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun performDotFeedback_deliversDragToken() =
+ testScope.runTest {
+ underTest.performDotFeedback(null)
+
+ assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.DRAG_INDICATOR)
+ assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+ }
+
private fun dragOverCoordinates(vararg coordinatesDragged: Point) {
underTest.onDragStart()
coordinatesDragged.forEach(::dragToCoordinate)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 2ee4aee..af5f2ac 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -27,6 +27,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -35,12 +36,15 @@
import com.android.systemui.bouncer.data.repository.fakeSimBouncerRepository
import com.android.systemui.classifier.fakeFalsingCollector
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.haptics.msdl.bouncerHapticPlayer
+import com.android.systemui.haptics.msdl.fakeMSDLPlayer
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
+import com.google.android.msdl.data.model.MSDLToken
import com.google.common.truth.Truth.assertThat
import kotlin.random.Random
import kotlin.random.nextInt
@@ -64,11 +68,14 @@
private val testScope = kosmos.testScope
private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
+ private val msdlPlayer = kosmos.fakeMSDLPlayer
+ private val bouncerHapticPlayer = kosmos.bouncerHapticPlayer
private val underTest by lazy {
kosmos.pinBouncerViewModelFactory.create(
isInputEnabled = MutableStateFlow(true),
onIntentionalUserInput = {},
authenticationMethod = AuthenticationMethodModel.Pin,
+ bouncerHapticPlayer = bouncerHapticPlayer,
)
}
@@ -97,6 +104,7 @@
isInputEnabled = MutableStateFlow(true),
onIntentionalUserInput = {},
authenticationMethod = AuthenticationMethodModel.Sim,
+ bouncerHapticPlayer = bouncerHapticPlayer,
)
assertThat(underTest.isSimAreaVisible).isTrue()
@@ -122,6 +130,7 @@
isInputEnabled = MutableStateFlow(true),
onIntentionalUserInput = {},
authenticationMethod = AuthenticationMethodModel.Pin,
+ bouncerHapticPlayer = bouncerHapticPlayer,
)
kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
@@ -487,11 +496,39 @@
testScope.runTest {
lockDeviceAndOpenPinBouncer()
- underTest.onDigitButtonDown()
+ underTest.onDigitButtonDown(null)
assertTrue(kosmos.fakeFalsingCollector.wasLastGestureAvoided())
}
+ @Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun onDigiButtonDown_deliversKeyStandardToken() =
+ testScope.runTest {
+ underTest.onDigitButtonDown(null)
+
+ assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.KEYPRESS_STANDARD)
+ assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun onBackspaceButtonPressed_deliversKeyDeleteToken() {
+ underTest.onBackspaceButtonPressed(null)
+
+ assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.KEYPRESS_DELETE)
+ assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun onBackspaceButtonLongPressed_deliversLongPressToken() {
+ underTest.onBackspaceButtonLongPressed()
+
+ assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.LONG_PRESS)
+ assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+ }
+
private fun TestScope.switchToScene(toScene: SceneKey) {
val currentScene by collectLastValue(sceneInteractor.currentScene)
val bouncerHidden = currentScene == Scenes.Bouncer && toScene != Scenes.Bouncer
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
index 956c129..2028d288 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
@@ -25,6 +25,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.view.MotionEvent;
import android.view.accessibility.AccessibilityManager;
@@ -33,6 +35,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.testing.FakeMetricsLogger;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -201,6 +204,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING)
public void testTrackpadGesture() {
assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue();
when(mFalsingDataProvider.isFromTrackpad()).thenReturn(true);
@@ -208,6 +212,14 @@
}
@Test
+ @EnableFlags(Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING)
+ public void testTrackpadGesture_touchScreenSource_false() {
+ assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue();
+ when(mFalsingDataProvider.isTouchScreenSource()).thenReturn(false);
+ assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse();
+ }
+
+ @Test
public void testAddAndRemoveFalsingBeliefListener() {
verify(mHistoryTracker, never()).addBeliefListener(any());
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingDataProviderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
index df4b048..5a4799c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
@@ -312,6 +312,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING)
public void test_IsFromTrackpad() {
MotionEvent motionEventOrigin = appendTrackpadDownEvent(0, 0);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index 70529cc..ee65fbd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -21,6 +21,8 @@
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.internal.logging.uiEventLogger
+import com.android.internal.logging.uiEventLoggerFake
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
import com.android.systemui.SysuiTestCase
@@ -28,6 +30,7 @@
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
+import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.coroutines.collectLastValue
@@ -35,7 +38,6 @@
import com.android.systemui.dock.fakeDockManager
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
import com.android.systemui.flags.fakeFeatureFlagsClassic
-import com.android.systemui.flags.featureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -93,6 +95,7 @@
bgScope = applicationCoroutineScope,
mainDispatcher = testDispatcher,
centralSurfacesOpt = centralSurfacesOptional,
+ uiEventLogger = uiEventLoggerFake,
)
.apply { start() }
@@ -119,7 +122,7 @@
fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.GONE,
- testScope = this
+ testScope = this,
)
assertThat(scene).isEqualTo(CommunalScenes.Communal)
@@ -140,7 +143,7 @@
fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.GONE,
- testScope = this
+ testScope = this,
)
assertThat(scene).isEqualTo(CommunalScenes.Communal)
@@ -161,7 +164,7 @@
fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.GONE,
- testScope = this
+ testScope = this,
)
assertThat(scene).isEqualTo(CommunalScenes.Blank)
@@ -181,7 +184,7 @@
fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.ALTERNATE_BOUNCER,
to = KeyguardState.GONE,
- testScope = this
+ testScope = this,
)
// Scene change will be handled in EditWidgetsActivity not here
assertThat(scene).isEqualTo(CommunalScenes.Communal)
@@ -200,7 +203,7 @@
fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.GONE,
to = KeyguardState.LOCKSCREEN,
- testScope = this
+ testScope = this,
)
assertThat(scene).isEqualTo(CommunalScenes.Communal)
}
@@ -220,7 +223,7 @@
fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.GLANCEABLE_HUB,
to = KeyguardState.OCCLUDED,
- testScope = this
+ testScope = this,
)
assertThat(scene).isEqualTo(CommunalScenes.Blank)
}
@@ -240,7 +243,7 @@
fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.GLANCEABLE_HUB,
to = KeyguardState.OCCLUDED,
- testScope = this
+ testScope = this,
)
assertThat(scene).isEqualTo(CommunalScenes.Communal)
}
@@ -258,7 +261,7 @@
fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.GLANCEABLE_HUB,
to = KeyguardState.LOCKSCREEN,
- testScope = this
+ testScope = this,
)
assertThat(scene).isEqualTo(CommunalScenes.Blank)
}
@@ -276,7 +279,7 @@
fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.GLANCEABLE_HUB,
to = KeyguardState.OFF,
- testScope = this
+ testScope = this,
)
assertThat(scene).isEqualTo(CommunalScenes.Communal)
@@ -298,7 +301,7 @@
fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.GLANCEABLE_HUB,
to = KeyguardState.OFF,
- testScope = this
+ testScope = this,
)
assertThat(scene).isEqualTo(CommunalScenes.Communal)
advanceTimeBy(CommunalSceneStartable.AWAKE_DEBOUNCE_DELAY / 2)
@@ -307,7 +310,7 @@
fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.OFF,
to = KeyguardState.GLANCEABLE_HUB,
- testScope = this
+ testScope = this,
)
advanceTimeBy(CommunalSceneStartable.AWAKE_DEBOUNCE_DELAY)
@@ -327,7 +330,7 @@
fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.GLANCEABLE_HUB,
to = KeyguardState.LOCKSCREEN,
- testScope = this
+ testScope = this,
)
updateDocked(true)
@@ -349,7 +352,7 @@
fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.GLANCEABLE_HUB,
to = KeyguardState.LOCKSCREEN,
- testScope = this
+ testScope = this,
)
updateDocked(true)
@@ -361,7 +364,7 @@
fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.DREAMING,
- testScope = this
+ testScope = this,
)
advanceTimeBy(CommunalSceneStartable.DOCK_DEBOUNCE_DELAY)
assertThat(scene).isEqualTo(CommunalScenes.Blank)
@@ -511,6 +514,9 @@
advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
assertThat(scene).isEqualTo(CommunalScenes.Blank)
+ assertThat(uiEventLoggerFake.logs.first().eventId)
+ .isEqualTo(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT.id)
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
}
}
@@ -526,7 +532,7 @@
fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.DOZING,
to = KeyguardState.GLANCEABLE_HUB,
- testScope = this
+ testScope = this,
)
assertThat(scene).isEqualTo(CommunalScenes.Communal)
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 c2acc5f..160865d 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
@@ -31,8 +31,11 @@
import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeTrustRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.AuthenticationFlags
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
@@ -211,7 +214,7 @@
val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
kosmos.fakeUserRepository.setSelectedUserInfo(
primaryUser,
- SelectionStatus.SELECTION_COMPLETE
+ SelectionStatus.SELECTION_COMPLETE,
)
kosmos.fakeTrustRepository.setCurrentUserTrusted(true)
@@ -240,6 +243,49 @@
}
@Test
+ fun deviceUnlockStatus_becomesUnlocked_whenFingerprintUnlocked_whileDeviceAsleepInAod() =
+ testScope.runTest {
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ testScope = this,
+ )
+ kosmos.powerInteractor.setAsleepForTest()
+ runCurrent()
+
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+ }
+
+ @Test
+ fun deviceUnlockStatus_staysLocked_whenFingerprintUnlocked_whileDeviceAsleep() =
+ testScope.runTest {
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+ assertThat(kosmos.keyguardTransitionInteractor.getCurrentState())
+ .isEqualTo(KeyguardState.LOCKSCREEN)
+
+ kosmos.powerInteractor.setAsleepForTest()
+ runCurrent()
+
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+ }
+
+ @Test
fun deviceEntryRestrictionReason_whenFaceOrFingerprintOrTrust_alwaysNull() =
testScope.runTest {
kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
@@ -273,7 +319,7 @@
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
DeviceEntryRestrictionReason.UserLockdown,
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
- DeviceEntryRestrictionReason.PolicyLockdown
+ DeviceEntryRestrictionReason.PolicyLockdown,
)
}
@@ -285,7 +331,7 @@
kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
kosmos.fakeSystemPropertiesHelper.set(
DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
- "not mainline reboot"
+ "not mainline reboot",
)
runCurrent()
@@ -321,7 +367,7 @@
kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
kosmos.fakeSystemPropertiesHelper.set(
DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
- "not mainline reboot"
+ "not mainline reboot",
)
runCurrent()
@@ -358,7 +404,7 @@
kosmos.fakeTrustRepository.setCurrentUserTrustManaged(false)
kosmos.fakeSystemPropertiesHelper.set(
DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
- "not mainline reboot"
+ "not mainline reboot",
)
runCurrent()
@@ -394,12 +440,12 @@
collectLastValue(underTest.deviceEntryRestrictionReason)
kosmos.fakeSystemPropertiesHelper.set(
DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
- DeviceUnlockedInteractor.REBOOT_MAINLINE_UPDATE
+ DeviceUnlockedInteractor.REBOOT_MAINLINE_UPDATE,
)
kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
AuthenticationFlags(
userId = 1,
- flag = LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+ flag = LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT,
)
)
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
index 50b727c..9cfd328 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.education.data.model.EduDeviceConnectionTime
import com.android.systemui.education.data.model.GestureEduModel
+import com.android.systemui.education.domain.interactor.mockEduInputManager
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
@@ -62,7 +63,13 @@
// Create TestContext here because TemporaryFolder.create() is called in @Before. It is
// needed before calling TemporaryFolder.newFolder().
val testContext = TestContext(context, tmpFolder.newFolder())
- underTest = UserContextualEducationRepository(testContext, dsScopeProvider)
+ underTest =
+ UserContextualEducationRepository(
+ testContext,
+ dsScopeProvider,
+ kosmos.mockEduInputManager,
+ kosmos.testDispatcher
+ )
underTest.setUser(testUserId)
}
@@ -99,7 +106,8 @@
lastShortcutTriggeredTime = kosmos.fakeEduClock.instant(),
lastEducationTime = kosmos.fakeEduClock.instant(),
usageSessionStartTime = kosmos.fakeEduClock.instant(),
- userId = testUserId
+ userId = testUserId,
+ gestureType = BACK
)
underTest.updateGestureEduModel(BACK) { newModel }
val model by collectLastValue(underTest.readGestureEduModelFlow(BACK))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
index 64915fb..8201bbe 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
@@ -17,15 +17,13 @@
package com.android.systemui.education.domain.interactor
import android.content.pm.UserInfo
-import android.hardware.input.InputManager
-import android.hardware.input.KeyGestureEvent
-import android.view.KeyEvent
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.contextualeducation.GestureType
import com.android.systemui.contextualeducation.GestureType.ALL_APPS
import com.android.systemui.contextualeducation.GestureType.BACK
+import com.android.systemui.contextualeducation.GestureType.HOME
+import com.android.systemui.contextualeducation.GestureType.OVERVIEW
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.education.data.model.GestureEduModel
@@ -40,20 +38,21 @@
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.kotlin.any
-import org.mockito.kotlin.verify
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(ParameterizedAndroidJunit4::class)
@kotlinx.coroutines.ExperimentalCoroutinesApi
-class KeyboardTouchpadEduInteractorTest : SysuiTestCase() {
+class KeyboardTouchpadEduInteractorTest(private val gestureType: GestureType) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val contextualEduInteractor = kosmos.contextualEducationInteractor
@@ -71,21 +70,27 @@
underTest.start()
contextualEduInteractor.start()
userRepository.setUserInfos(USER_INFOS)
+ testScope.launch {
+ contextualEduInteractor.updateKeyboardFirstConnectionTime()
+ contextualEduInteractor.updateTouchpadFirstConnectionTime()
+ }
}
@Test
fun newEducationInfoOnMaxSignalCountReached() =
testScope.runTest {
- triggerMaxEducationSignals(BACK)
+ triggerMaxEducationSignals(gestureType)
val model by collectLastValue(underTest.educationTriggered)
- assertThat(model?.gestureType).isEqualTo(BACK)
+
+ assertThat(model?.gestureType).isEqualTo(gestureType)
}
@Test
fun newEducationToastOn1stEducation() =
testScope.runTest {
val model by collectLastValue(underTest.educationTriggered)
- triggerMaxEducationSignals(BACK)
+ triggerMaxEducationSignals(gestureType)
+
assertThat(model?.educationUiType).isEqualTo(EducationUiType.Toast)
}
@@ -93,12 +98,12 @@
fun newEducationNotificationOn2ndEducation() =
testScope.runTest {
val model by collectLastValue(underTest.educationTriggered)
- triggerMaxEducationSignals(BACK)
+ triggerMaxEducationSignals(gestureType)
// runCurrent() to trigger 1st education
runCurrent()
eduClock.offset(minDurationForNextEdu)
- triggerMaxEducationSignals(BACK)
+ triggerMaxEducationSignals(gestureType)
assertThat(model?.educationUiType).isEqualTo(EducationUiType.Notification)
}
@@ -106,7 +111,7 @@
@Test
fun noEducationInfoBeforeMaxSignalCountReached() =
testScope.runTest {
- contextualEduInteractor.incrementSignalCount(BACK)
+ contextualEduInteractor.incrementSignalCount(gestureType)
val model by collectLastValue(underTest.educationTriggered)
assertThat(model).isNull()
}
@@ -115,8 +120,8 @@
fun noEducationInfoWhenShortcutTriggeredPreviously() =
testScope.runTest {
val model by collectLastValue(underTest.educationTriggered)
- contextualEduInteractor.updateShortcutTriggerTime(BACK)
- triggerMaxEducationSignals(BACK)
+ contextualEduInteractor.updateShortcutTriggerTime(gestureType)
+ triggerMaxEducationSignals(gestureType)
assertThat(model).isNull()
}
@@ -124,12 +129,12 @@
fun no2ndEducationBeforeMinEduIntervalReached() =
testScope.runTest {
val models by collectValues(underTest.educationTriggered)
- triggerMaxEducationSignals(BACK)
+ triggerMaxEducationSignals(gestureType)
runCurrent()
// Offset a duration that is less than the required education interval
eduClock.offset(1.seconds)
- triggerMaxEducationSignals(BACK)
+ triggerMaxEducationSignals(gestureType)
runCurrent()
assertThat(models.filterNotNull().size).isEqualTo(1)
@@ -140,15 +145,15 @@
testScope.runTest {
val models by collectValues(underTest.educationTriggered)
// Trigger 2 educations
- triggerMaxEducationSignals(BACK)
+ triggerMaxEducationSignals(gestureType)
runCurrent()
eduClock.offset(minDurationForNextEdu)
- triggerMaxEducationSignals(BACK)
+ triggerMaxEducationSignals(gestureType)
runCurrent()
// Try triggering 3rd education
eduClock.offset(minDurationForNextEdu)
- triggerMaxEducationSignals(BACK)
+ triggerMaxEducationSignals(gestureType)
assertThat(models.filterNotNull().size).isEqualTo(2)
}
@@ -157,18 +162,21 @@
fun startNewUsageSessionWhen2ndSignalReceivedAfterSessionDeadline() =
testScope.runTest {
val model by
- collectLastValue(kosmos.contextualEducationRepository.readGestureEduModelFlow(BACK))
- contextualEduInteractor.incrementSignalCount(BACK)
+ collectLastValue(
+ kosmos.contextualEducationRepository.readGestureEduModelFlow(gestureType)
+ )
+ contextualEduInteractor.incrementSignalCount(gestureType)
eduClock.offset(KeyboardTouchpadEduInteractor.usageSessionDuration.plus(1.seconds))
val secondSignalReceivedTime = eduClock.instant()
- contextualEduInteractor.incrementSignalCount(BACK)
+ contextualEduInteractor.incrementSignalCount(gestureType)
assertThat(model)
.isEqualTo(
GestureEduModel(
signalCount = 1,
usageSessionStartTime = secondSignalReceivedTime,
- userId = 0
+ userId = 0,
+ gestureType = gestureType
)
)
}
@@ -252,22 +260,9 @@
@Test
fun updateShortcutTimeOnKeyboardShortcutTriggered() =
testScope.runTest {
- // runCurrent() to trigger inputManager#registerKeyGestureEventListener in the
- // interactor
- runCurrent()
- val listenerCaptor =
- ArgumentCaptor.forClass(InputManager.KeyGestureEventListener::class.java)
- verify(kosmos.mockEduInputManager)
- .registerKeyGestureEventListener(any(), listenerCaptor.capture())
-
- val allAppsKeyGestureEvent =
- KeyGestureEvent.Builder()
- .setDeviceId(1)
- .setModifierState(KeyEvent.META_META_ON)
- .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS)
- .setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- .build()
- listenerCaptor.value.onKeyGestureEvent(allAppsKeyGestureEvent)
+ // Only All Apps needs to update the keyboard shortcut
+ assumeTrue(gestureType == ALL_APPS)
+ kosmos.contextualEducationRepository.setKeyboardShortcutTriggered(ALL_APPS)
val model by
collectLastValue(
@@ -293,10 +288,18 @@
runCurrent()
}
+ private suspend fun setUpForDeviceConnection() {
+ contextualEduInteractor.updateKeyboardFirstConnectionTime()
+ contextualEduInteractor.updateTouchpadFirstConnectionTime()
+ }
+
companion object {
- private val USER_INFOS =
- listOf(
- UserInfo(101, "Second User", 0),
- )
+ private val USER_INFOS = listOf(UserInfo(101, "Second User", 0))
+
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getGestureTypes(): List<GestureType> {
+ return listOf(BACK, HOME, OVERVIEW, ALL_APPS)
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
index c4ac585..ab33269 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
@@ -37,6 +37,7 @@
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -69,6 +70,11 @@
@Before
fun setUp() {
+ testScope.launch {
+ interactor.updateKeyboardFirstConnectionTime()
+ interactor.updateTouchpadFirstConnectionTime()
+ }
+
val viewModel =
ContextualEduViewModel(
kosmos.applicationContext.resources,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
index 686b518..366b55d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
@@ -23,6 +23,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.classifier.falsingManager
import com.android.systemui.haptics.fakeVibratorHelper
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.core.FakeLogBuffer
@@ -68,11 +69,13 @@
vibratorHelper.primitiveDurations[VibrationEffect.Composition.PRIMITIVE_SPIN] = spinDuration
whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(true)
+ kosmos.falsingManager.setFalseLongTap(false)
longPressEffect =
QSLongPressEffect(
vibratorHelper,
kosmos.keyguardStateController,
+ kosmos.falsingManager,
FakeLogBuffer.Factory.create(),
)
longPressEffect.callback = callback
@@ -180,11 +183,7 @@
// THEN the expected texture is played
val reverseHaptics =
- LongPressHapticBuilder.createReversedEffect(
- progress,
- lowTickDuration,
- effectDuration,
- )
+ LongPressHapticBuilder.createReversedEffect(progress, lowTickDuration, effectDuration)
assertThat(reverseHaptics).isNotNull()
assertThat(vibratorHelper.hasVibratedWithEffects(reverseHaptics!!)).isTrue()
}
@@ -224,6 +223,20 @@
}
@Test
+ fun onAnimationComplete_isFalseLongClick_effectEndsInIdleWithReset() =
+ testWhileInState(QSLongPressEffect.State.RUNNING_FORWARD) {
+ // GIVEN that the long-click is false
+ kosmos.falsingManager.setFalseLongTap(true)
+
+ // GIVEN that the animation completes
+ longPressEffect.handleAnimationComplete()
+
+ // THEN the long-press effect ends in the idle state and the properties are reset
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
+ verify(callback, times(1)).onResetProperties()
+ }
+
+ @Test
fun onAnimationComplete_whenRunningBackwardsFromUp_endsWithFinishedReversingAndClick() =
testWhileInState(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_UP) {
// GIVEN that the animation completes
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt
index 0c716137..639737b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger
import com.android.systemui.inputdevice.tutorial.domain.interactor.KeyboardTouchpadConnectionInteractor
import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEY
import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEYBOARD
@@ -53,6 +54,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
+import org.mockito.kotlin.mock
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -81,6 +83,7 @@
Optional.of(kosmos.touchpadGesturesInteractor),
KeyboardTouchpadConnectionInteractor(keyboardRepo, touchpadRepo),
hasTouchpadTutorialScreens,
+ mock<InputDeviceTutorialLogger>(),
SavedStateHandle(mapOf(INTENT_TUTORIAL_TYPE_KEY to startingPeripheral))
)
lifecycle.addObserver(viewModel)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
index 361e768..8f9e238 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
@@ -17,11 +17,13 @@
package com.android.systemui.keyboard.data.repository
-import android.hardware.input.InputManager
+import android.hardware.input.FakeInputManager
+import android.hardware.input.InputManager.InputDeviceListener
import android.hardware.input.InputManager.KeyboardBacklightListener
import android.hardware.input.KeyboardBacklightState
+import android.hardware.input.fakeInputManager
import android.testing.TestableLooper
-import android.view.InputDevice
+import android.view.InputDevice.SOURCE_KEYBOARD
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -30,10 +32,7 @@
import com.android.systemui.coroutines.collectValues
import com.android.systemui.inputdevice.data.repository.InputDeviceRepository
import com.android.systemui.keyboard.data.model.Keyboard
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.nullable
-import com.android.systemui.util.mockito.whenever
+import com.android.systemui.testKosmos
import com.android.systemui.utils.os.FakeHandler
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineDispatcher
@@ -50,9 +49,10 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
-import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -60,10 +60,9 @@
@RunWith(AndroidJUnit4::class)
class KeyboardRepositoryTest : SysuiTestCase() {
- @Captor
- private lateinit var deviceListenerCaptor: ArgumentCaptor<InputManager.InputDeviceListener>
+ @Captor private lateinit var deviceListenerCaptor: ArgumentCaptor<InputDeviceListener>
@Captor private lateinit var backlightListenerCaptor: ArgumentCaptor<KeyboardBacklightListener>
- @Mock private lateinit var inputManager: InputManager
+ private lateinit var fakeInputManager: FakeInputManager
private lateinit var underTest: KeyboardRepository
private lateinit var dispatcher: CoroutineDispatcher
@@ -73,16 +72,14 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf())
- whenever(inputManager.getInputDevice(any())).then { invocation ->
- val id = invocation.arguments.first()
- INPUT_DEVICES_MAP[id]
- }
+ fakeInputManager = testKosmos().fakeInputManager
dispatcher = StandardTestDispatcher()
testScope = TestScope(dispatcher)
val handler = FakeHandler(TestableLooper.get(this).looper)
- inputDeviceRepo = InputDeviceRepository(handler, testScope.backgroundScope, inputManager)
- underTest = KeyboardRepositoryImpl(dispatcher, inputManager, inputDeviceRepo)
+ inputDeviceRepo =
+ InputDeviceRepository(handler, testScope.backgroundScope, fakeInputManager.inputManager)
+ underTest =
+ KeyboardRepositoryImpl(dispatcher, fakeInputManager.inputManager, inputDeviceRepo)
}
@Test
@@ -95,7 +92,7 @@
@Test
fun emitsConnected_ifKeyboardAlreadyConnectedAtTheStart() =
testScope.runTest {
- whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(PHYSICAL_FULL_KEYBOARD_ID))
+ fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID)
val initialValue = underTest.isAnyKeyboardConnected.first()
assertThat(initialValue).isTrue()
}
@@ -103,74 +100,77 @@
@Test
fun emitsConnected_whenNewPhysicalKeyboardConnects() =
testScope.runTest {
- val deviceListener = captureDeviceListener()
+ captureDeviceListener()
val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected)
- deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID)
assertThat(isKeyboardConnected).isTrue()
}
@Test
- fun emitsDisconnected_whenDeviceWithIdDoesNotExist() =
+ fun emitsDisconnected_whenDeviceNotFound() =
testScope.runTest {
- val deviceListener = captureDeviceListener()
+ captureDeviceListener()
val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected)
-
- deviceListener.onInputDeviceAdded(NULL_DEVICE_ID)
+ fakeInputManager.addDevice(NULL_DEVICE_ID, SOURCE_KEYBOARD, isNotFound = true)
assertThat(isKeyboardConnected).isFalse()
}
@Test
fun emitsDisconnected_whenKeyboardDisconnects() =
testScope.runTest {
- val deviceListener = captureDeviceListener()
+ captureDeviceListener()
val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected)
- deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID)
assertThat(isKeyboardConnected).isTrue()
- deviceListener.onInputDeviceRemoved(PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.removeDevice(PHYSICAL_FULL_KEYBOARD_ID)
assertThat(isKeyboardConnected).isFalse()
}
- private suspend fun captureDeviceListener(): InputManager.InputDeviceListener {
+ private suspend fun captureDeviceListener() {
underTest.isAnyKeyboardConnected.first()
- verify(inputManager).registerInputDeviceListener(deviceListenerCaptor.capture(), nullable())
- return deviceListenerCaptor.value
+ verify(fakeInputManager.inputManager)
+ .registerInputDeviceListener(deviceListenerCaptor.capture(), anyOrNull())
+ fakeInputManager.registerInputDeviceListener(deviceListenerCaptor.value)
}
@Test
fun emitsDisconnected_whenVirtualOrNotFullKeyboardConnects() =
testScope.runTest {
- val deviceListener = captureDeviceListener()
+ captureDeviceListener()
val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected)
- deviceListener.onInputDeviceAdded(PHYSICAL_NOT_FULL_KEYBOARD_ID)
+ fakeInputManager.addPhysicalKeyboard(
+ PHYSICAL_NOT_FULL_KEYBOARD_ID,
+ isFullKeyboard = false
+ )
assertThat(isKeyboardConnected).isFalse()
- deviceListener.onInputDeviceAdded(VIRTUAL_FULL_KEYBOARD_ID)
+ fakeInputManager.addDevice(VIRTUAL_FULL_KEYBOARD_ID, SOURCE_KEYBOARD)
assertThat(isKeyboardConnected).isFalse()
}
@Test
fun emitsDisconnected_whenKeyboardDisconnectsAndWasAlreadyConnectedAtTheStart() =
testScope.runTest {
- val deviceListener = captureDeviceListener()
+ captureDeviceListener()
val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected)
- deviceListener.onInputDeviceRemoved(PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.removeDevice(PHYSICAL_FULL_KEYBOARD_ID)
assertThat(isKeyboardConnected).isFalse()
}
@Test
fun emitsConnected_whenAnotherDeviceDisconnects() =
testScope.runTest {
- val deviceListener = captureDeviceListener()
+ captureDeviceListener()
val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected)
- deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID)
- deviceListener.onInputDeviceRemoved(VIRTUAL_FULL_KEYBOARD_ID)
+ fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.removeDevice(VIRTUAL_FULL_KEYBOARD_ID)
assertThat(isKeyboardConnected).isTrue()
}
@@ -178,12 +178,12 @@
@Test
fun emitsConnected_whenOnePhysicalKeyboardDisconnectsButAnotherRemainsConnected() =
testScope.runTest {
- val deviceListener = captureDeviceListener()
+ captureDeviceListener()
val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected)
- deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID)
- deviceListener.onInputDeviceAdded(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID)
- deviceListener.onInputDeviceRemoved(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.addPhysicalKeyboard(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.removeDevice(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID)
assertThat(isKeyboardConnected).isTrue()
}
@@ -195,7 +195,7 @@
// subscribed to and listener is actually registered in inputManager
val backlight by collectLastValueImmediately(underTest.backlight)
- verify(inputManager)
+ verify(fakeInputManager.inputManager)
.registerKeyboardBacklightListener(any(), backlightListenerCaptor.capture())
backlightListenerCaptor.value.onBacklightChanged(current = 1, max = 5)
@@ -217,7 +217,7 @@
fun keyboardBacklightValuesNotPassed_fromBacklightListener_whenNotTriggeredByKeyPress() {
testScope.runTest {
val backlight by collectLastValueImmediately(underTest.backlight)
- verify(inputManager)
+ verify(fakeInputManager.inputManager)
.registerKeyboardBacklightListener(any(), backlightListenerCaptor.capture())
backlightListenerCaptor.value.onBacklightChanged(
@@ -233,7 +233,7 @@
fun passesKeyboardBacklightValues_fromBacklightListener_whenTriggeredByKeyPress() {
testScope.runTest {
val backlight by collectLastValueImmediately(underTest.backlight)
- verify(inputManager)
+ verify(fakeInputManager.inputManager)
.registerKeyboardBacklightListener(any(), backlightListenerCaptor.capture())
backlightListenerCaptor.value.onBacklightChanged(
@@ -248,14 +248,11 @@
@Test
fun passessAllKeyboards_thatWereAlreadyConnectedOnInitialization() {
testScope.runTest {
- whenever(inputManager.inputDeviceIds)
- .thenReturn(
- intArrayOf(
- PHYSICAL_FULL_KEYBOARD_ID,
- ANOTHER_PHYSICAL_FULL_KEYBOARD_ID,
- VIRTUAL_FULL_KEYBOARD_ID // not a physical keyboard - that's why result is 2
- )
- )
+ fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.addPhysicalKeyboard(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID)
+ // not a physical keyboard - that's why result is 2
+ fakeInputManager.addDevice(VIRTUAL_FULL_KEYBOARD_ID, SOURCE_KEYBOARD)
+
val keyboards by collectValues(underTest.newlyConnectedKeyboard)
assertThat(keyboards).hasSize(2)
@@ -265,9 +262,9 @@
@Test
fun passesNewlyConnectedKeyboard() {
testScope.runTest {
- val deviceListener = captureDeviceListener()
+ captureDeviceListener()
- deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID, VENDOR_ID, PRODUCT_ID)
assertThat(underTest.newlyConnectedKeyboard.first())
.isEqualTo(Keyboard(VENDOR_ID, PRODUCT_ID))
@@ -277,13 +274,12 @@
@Test
fun emitsOnlyNewlyConnectedKeyboards() {
testScope.runTest {
- whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(PHYSICAL_FULL_KEYBOARD_ID))
+ fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID)
underTest.newlyConnectedKeyboard.first()
- verify(inputManager)
- .registerInputDeviceListener(deviceListenerCaptor.capture(), nullable())
- val deviceListener = deviceListenerCaptor.value
- deviceListener.onInputDeviceAdded(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID)
+ captureDeviceListener()
+
+ fakeInputManager.addPhysicalKeyboard(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID)
val keyboards by collectValues(underTest.newlyConnectedKeyboard)
assertThat(keyboards).hasSize(1)
@@ -293,14 +289,11 @@
@Test
fun stillEmitsNewKeyboardEvenIfFlowWasSubscribedAfterOtherFlows() {
testScope.runTest {
- whenever(inputManager.inputDeviceIds)
- .thenReturn(
- intArrayOf(
- PHYSICAL_FULL_KEYBOARD_ID,
- ANOTHER_PHYSICAL_FULL_KEYBOARD_ID,
- VIRTUAL_FULL_KEYBOARD_ID // not a physical keyboard - that's why result is 2
- )
- )
+ fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.addPhysicalKeyboard(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID)
+ // not a physical keyboard - that's why result is 2
+ fakeInputManager.addDevice(VIRTUAL_FULL_KEYBOARD_ID, SOURCE_KEYBOARD)
+
collectLastValueImmediately(underTest.isAnyKeyboardConnected)
// let's pretend second flow is subscribed after some delay
@@ -314,12 +307,12 @@
@Test
fun emitsKeyboardWhenItWasReconnected() {
testScope.runTest {
- val deviceListener = captureDeviceListener()
+ captureDeviceListener()
val keyboards by collectValues(underTest.newlyConnectedKeyboard)
- deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID)
- deviceListener.onInputDeviceRemoved(PHYSICAL_FULL_KEYBOARD_ID)
- deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.removeDevice(PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID)
assertThat(keyboards).hasSize(2)
}
@@ -339,30 +332,13 @@
private companion object {
private const val PHYSICAL_FULL_KEYBOARD_ID = 1
- private const val VIRTUAL_FULL_KEYBOARD_ID = 2
+ private const val VIRTUAL_FULL_KEYBOARD_ID = -2 // Virtual keyboards has id with minus value
private const val PHYSICAL_NOT_FULL_KEYBOARD_ID = 3
private const val ANOTHER_PHYSICAL_FULL_KEYBOARD_ID = 4
- private const val NULL_DEVICE_ID = 5
+ private const val NULL_DEVICE_ID = -5
private const val VENDOR_ID = 99
private const val PRODUCT_ID = 101
-
- private val INPUT_DEVICES_MAP: Map<Int, InputDevice> =
- mapOf(
- PHYSICAL_FULL_KEYBOARD_ID to inputDevice(virtual = false, fullKeyboard = true),
- VIRTUAL_FULL_KEYBOARD_ID to inputDevice(virtual = true, fullKeyboard = true),
- PHYSICAL_NOT_FULL_KEYBOARD_ID to inputDevice(virtual = false, fullKeyboard = false),
- ANOTHER_PHYSICAL_FULL_KEYBOARD_ID to
- inputDevice(virtual = false, fullKeyboard = true)
- )
-
- private fun inputDevice(virtual: Boolean, fullKeyboard: Boolean): InputDevice =
- mock<InputDevice>().also {
- whenever(it.isVirtual).thenReturn(virtual)
- whenever(it.isFullKeyboard).thenReturn(fullKeyboard)
- whenever(it.vendorId).thenReturn(VENDOR_ID)
- whenever(it.productId).thenReturn(PRODUCT_ID)
- }
}
private class TestBacklightState(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
index 6c3c7ef..fcf4662 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
@@ -16,7 +16,10 @@
*/
package com.android.systemui.keyguard.data.quickaffordance
+import android.app.Flags
import android.net.Uri
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
import android.provider.Settings.Global.ZEN_MODE_OFF
@@ -25,6 +28,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.notification.modes.EnableZenModeDialog
+import com.android.settingslib.notification.modes.TestModeBuilder
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.ContentDescription
@@ -35,7 +39,11 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.settings.data.repository.secureSettingsRepository
import com.android.systemui.statusbar.policy.ZenModeController
+import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
+import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
@@ -43,6 +51,7 @@
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
+import java.time.Duration
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -66,8 +75,13 @@
private val kosmos = testKosmos()
private val testDispatcher = kosmos.testDispatcher
private val testScope = kosmos.testScope
+
private val settings = kosmos.fakeSettings
+ private val zenModeRepository = kosmos.fakeZenModeRepository
+ private val deviceProvisioningRepository = kosmos.fakeDeviceProvisioningRepository
+ private val secureSettingsRepository = kosmos.secureSettingsRepository
+
@Mock private lateinit var zenModeController: ZenModeController
@Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var conditionUri: Uri
@@ -85,17 +99,36 @@
DoNotDisturbQuickAffordanceConfig(
context,
zenModeController,
+ kosmos.zenModeInteractor,
settings,
userTracker,
testDispatcher,
+ testScope.backgroundScope,
conditionUri,
enableZenModeDialog,
)
}
@Test
+ @EnableFlags(Flags.FLAG_MODES_UI)
fun dndNotAvailable_pickerStateHidden() =
testScope.runTest {
+ deviceProvisioningRepository.setDeviceProvisioned(false)
+ runCurrent()
+
+ val result = underTest.getPickerScreenState()
+ runCurrent()
+
+ assertEquals(
+ KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice,
+ result,
+ )
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MODES_UI)
+ fun controllerDndNotAvailable_pickerStateHidden() =
+ testScope.runTest {
// given
whenever(zenModeController.isZenAvailable).thenReturn(false)
@@ -105,13 +138,33 @@
// then
assertEquals(
KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice,
- result
+ result,
)
}
@Test
+ @EnableFlags(Flags.FLAG_MODES_UI)
fun dndAvailable_pickerStateVisible() =
testScope.runTest {
+ deviceProvisioningRepository.setDeviceProvisioned(true)
+ runCurrent()
+
+ val result = underTest.getPickerScreenState()
+ runCurrent()
+
+ assertThat(result)
+ .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java)
+ val defaultPickerState =
+ result as KeyguardQuickAffordanceConfig.PickerScreenState.Default
+ assertThat(defaultPickerState.configureIntent).isNotNull()
+ assertThat(defaultPickerState.configureIntent?.action)
+ .isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MODES_UI)
+ fun controllerDndAvailable_pickerStateVisible() =
+ testScope.runTest {
// given
whenever(zenModeController.isZenAvailable).thenReturn(true)
@@ -129,7 +182,27 @@
}
@Test
- fun onTriggered_dndModeIsNotZEN_MODE_OFF_setToZEN_MODE_OFF() =
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ fun onTriggered_dndModeIsNotOff_setToOff() =
+ testScope.runTest {
+ val currentModes by collectLastValue(zenModeRepository.modes)
+
+ zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_ACTIVE)
+ secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, -2)
+ collectLastValue(underTest.lockScreenState)
+ runCurrent()
+
+ val result = underTest.onTriggered(null)
+ runCurrent()
+
+ val dndMode = currentModes!!.single()
+ assertThat(dndMode.isActive).isFalse()
+ assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MODES_UI)
+ fun onTriggered_controllerDndModeIsNotZEN_MODE_OFF_setToZEN_MODE_OFF() =
testScope.runTest {
// given
whenever(zenModeController.isZenAvailable).thenReturn(true)
@@ -140,11 +213,12 @@
// when
val result = underTest.onTriggered(null)
+
verify(zenModeController)
.setZen(
spyZenMode.capture(),
spyConditionId.capture(),
- eq(DoNotDisturbQuickAffordanceConfig.TAG)
+ eq(DoNotDisturbQuickAffordanceConfig.TAG),
)
// then
@@ -154,7 +228,28 @@
}
@Test
- fun onTriggered_dndModeIsZEN_MODE_OFF_settingFOREVER_setZenWithoutCondition() =
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ fun onTriggered_dndModeIsOff_settingFOREVER_setZenWithoutCondition() =
+ testScope.runTest {
+ val currentModes by collectLastValue(zenModeRepository.modes)
+
+ zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
+ secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, ZEN_DURATION_FOREVER)
+ collectLastValue(underTest.lockScreenState)
+ runCurrent()
+
+ val result = underTest.onTriggered(null)
+ runCurrent()
+
+ val dndMode = currentModes!!.single()
+ assertThat(dndMode.isActive).isTrue()
+ assertThat(zenModeRepository.getModeActiveDuration(dndMode.id)).isNull()
+ assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MODES_UI)
+ fun onTriggered_controllerDndModeIsZEN_MODE_OFF_settingFOREVER_setZenWithoutCondition() =
testScope.runTest {
// given
whenever(zenModeController.isZenAvailable).thenReturn(true)
@@ -169,7 +264,7 @@
.setZen(
spyZenMode.capture(),
spyConditionId.capture(),
- eq(DoNotDisturbQuickAffordanceConfig.TAG)
+ eq(DoNotDisturbQuickAffordanceConfig.TAG),
)
// then
@@ -179,7 +274,27 @@
}
@Test
- fun onTriggered_dndZEN_MODE_OFF_settingNotFOREVERorPROMPT_zenWithCondition() =
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ fun onTriggered_dndModeIsOff_settingNotFOREVERorPROMPT_dndWithDuration() =
+ testScope.runTest {
+ val currentModes by collectLastValue(zenModeRepository.modes)
+ zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
+ secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, -900)
+ runCurrent()
+
+ val result = underTest.onTriggered(null)
+ runCurrent()
+
+ assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
+ val dndMode = currentModes!!.single()
+ assertThat(dndMode.isActive).isTrue()
+ assertThat(zenModeRepository.getModeActiveDuration(dndMode.id))
+ .isEqualTo(Duration.ofMinutes(-900))
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MODES_UI)
+ fun onTriggered_controllerDndZEN_MODE_OFF_settingNotFOREVERorPROMPT_zenWithCondition() =
testScope.runTest {
// given
whenever(zenModeController.isZenAvailable).thenReturn(true)
@@ -194,7 +309,7 @@
.setZen(
spyZenMode.capture(),
spyConditionId.capture(),
- eq(DoNotDisturbQuickAffordanceConfig.TAG)
+ eq(DoNotDisturbQuickAffordanceConfig.TAG),
)
// then
@@ -204,7 +319,28 @@
}
@Test
- fun onTriggered_dndModeIsZEN_MODE_OFF_settingIsPROMPT_showDialog() =
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ fun onTriggered_dndModeIsOff_settingIsPROMPT_showDialog() =
+ testScope.runTest {
+ val expandable: Expandable = mock()
+ zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
+ secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, ZEN_DURATION_PROMPT)
+ whenever(enableZenModeDialog.createDialog()).thenReturn(mock())
+ collectLastValue(underTest.lockScreenState)
+ runCurrent()
+
+ val result = underTest.onTriggered(expandable)
+
+ assertTrue(result is KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog)
+ assertEquals(
+ expandable,
+ (result as KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog).expandable,
+ )
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MODES_UI)
+ fun onTriggered_controllerDndModeIsZEN_MODE_OFF_settingIsPROMPT_showDialog() =
testScope.runTest {
// given
val expandable: Expandable = mock()
@@ -222,13 +358,31 @@
assertTrue(result is KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog)
assertEquals(
expandable,
- (result as KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog).expandable
+ (result as KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog).expandable,
)
}
@Test
+ @EnableFlags(Flags.FLAG_MODES_UI)
fun lockScreenState_dndAvailableStartsAsTrue_changeToFalse_StateIsHidden() =
testScope.runTest {
+ deviceProvisioningRepository.setDeviceProvisioned(true)
+ val valueSnapshot = collectLastValue(underTest.lockScreenState)
+ val secondLastValue = valueSnapshot()
+ runCurrent()
+
+ deviceProvisioningRepository.setDeviceProvisioned(false)
+ runCurrent()
+ val lastValue = valueSnapshot()
+
+ assertTrue(secondLastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
+ assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MODES_UI)
+ fun lockScreenState_controllerDndAvailableStartsAsTrue_changeToFalse_StateIsHidden() =
+ testScope.runTest {
// given
whenever(zenModeController.isZenAvailable).thenReturn(true)
val callbackCaptor: ArgumentCaptor<ZenModeController.Callback> = argumentCaptor()
@@ -246,7 +400,44 @@
}
@Test
- fun lockScreenState_dndModeStartsAsZEN_MODE_OFF_changeToNotOFF_StateVisible() =
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ fun lockScreenState_dndModeStartsAsOff_changeToOn_StateVisible() =
+ testScope.runTest {
+ val lockScreenState by collectLastValue(underTest.lockScreenState)
+
+ zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
+ runCurrent()
+
+ assertThat(lockScreenState)
+ .isEqualTo(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ Icon.Resource(
+ R.drawable.qs_dnd_icon_off,
+ ContentDescription.Resource(R.string.dnd_is_off),
+ ),
+ ActivationState.Inactive,
+ )
+ )
+
+ zenModeRepository.removeMode(TestModeBuilder.MANUAL_DND_INACTIVE.id)
+ zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_ACTIVE)
+ runCurrent()
+
+ assertThat(lockScreenState)
+ .isEqualTo(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ Icon.Resource(
+ R.drawable.qs_dnd_icon_on,
+ ContentDescription.Resource(R.string.dnd_is_on),
+ ),
+ ActivationState.Active,
+ )
+ )
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MODES_UI)
+ fun lockScreenState_controllerDndModeStartsAsZEN_MODE_OFF_changeToNotOFF_StateVisible() =
testScope.runTest {
// given
whenever(zenModeController.isZenAvailable).thenReturn(true)
@@ -265,9 +456,9 @@
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
Icon.Resource(
R.drawable.qs_dnd_icon_off,
- ContentDescription.Resource(R.string.dnd_is_off)
+ ContentDescription.Resource(R.string.dnd_is_off),
),
- ActivationState.Inactive
+ ActivationState.Inactive,
),
secondLastValue,
)
@@ -275,9 +466,9 @@
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
Icon.Resource(
R.drawable.qs_dnd_icon_on,
- ContentDescription.Resource(R.string.dnd_is_on)
+ ContentDescription.Resource(R.string.dnd_is_on),
),
- ActivationState.Active
+ ActivationState.Active,
),
lastValue,
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
index c18deb1..fac9312 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -14,22 +14,6 @@
* limitations under the License.
*/
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
package com.android.systemui.keyguard.domain.interactor
import android.os.PowerManager
@@ -47,7 +31,6 @@
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.communal.shared.model.CommunalTransitionKeys
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -129,7 +112,7 @@
transitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.DOZING,
- testScope
+ testScope,
)
kosmos.fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockMode.NONE)
reset(transitionRepository)
@@ -145,10 +128,7 @@
// Under default conditions, we should transition to LOCKSCREEN when waking up.
assertThat(transitionRepository)
- .startedTransition(
- from = KeyguardState.DOZING,
- to = KeyguardState.LOCKSCREEN,
- )
+ .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.LOCKSCREEN)
}
@Test
@@ -166,10 +146,7 @@
// If dreaming is possible and communal is available, then we should transition to
// GLANCEABLE_HUB when waking up due to power button press.
assertThat(transitionRepository)
- .startedTransition(
- from = KeyguardState.DOZING,
- to = KeyguardState.GLANCEABLE_HUB,
- )
+ .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.GLANCEABLE_HUB)
}
@Test
@@ -186,8 +163,7 @@
// If dreaming is possible and communal is available, then we should transition to
// GLANCEABLE_HUB when waking up due to power button press.
- verify(kosmos.fakeCommunalSceneRepository)
- .changeScene(CommunalScenes.Communal, CommunalTransitionKeys.Immediately)
+ verify(kosmos.fakeCommunalSceneRepository).snapToScene(CommunalScenes.Communal)
}
@Test
@@ -204,10 +180,7 @@
// If dreaming is NOT possible but communal is available, then we should transition to
// LOCKSCREEN when waking up due to power button press.
assertThat(transitionRepository)
- .startedTransition(
- from = KeyguardState.DOZING,
- to = KeyguardState.LOCKSCREEN,
- )
+ .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.LOCKSCREEN)
}
@Test
@@ -224,10 +197,7 @@
// If dreaming is possible but communal is NOT available, then we should transition to
// LOCKSCREEN when waking up due to power button press.
assertThat(transitionRepository)
- .startedTransition(
- from = KeyguardState.DOZING,
- to = KeyguardState.LOCKSCREEN,
- )
+ .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.LOCKSCREEN)
}
@Test
@@ -245,10 +215,7 @@
// Under default conditions, we should transition to LOCKSCREEN when waking up.
assertThat(transitionRepository)
- .startedTransition(
- from = KeyguardState.DOZING,
- to = KeyguardState.GLANCEABLE_HUB,
- )
+ .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.GLANCEABLE_HUB)
}
@Test
@@ -261,10 +228,7 @@
// Waking with a SHOW_WHEN_LOCKED activity on top should transition to OCCLUDED.
assertThat(transitionRepository)
- .startedTransition(
- from = KeyguardState.DOZING,
- to = KeyguardState.OCCLUDED,
- )
+ .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.OCCLUDED)
}
@Test
@@ -282,10 +246,7 @@
// Waking with a SHOW_WHEN_LOCKED activity on top should transition to OCCLUDED.
assertThat(transitionRepository)
- .startedTransition(
- from = KeyguardState.DOZING,
- to = KeyguardState.OCCLUDED,
- )
+ .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.OCCLUDED)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
index 59f16d7..84b7f5c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
@@ -17,17 +17,16 @@
package com.android.systemui.keyguard.domain.interactor
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
import com.android.systemui.keyguard.data.repository.keyguardBlueprintRepository
import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint
@@ -59,8 +58,8 @@
class KeyguardBlueprintInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val underTest = kosmos.keyguardBlueprintInteractor
- private val keyguardBlueprintRepository = kosmos.keyguardBlueprintRepository
+ private val underTest by lazy { kosmos.keyguardBlueprintInteractor }
+ private val keyguardBlueprintRepository by lazy { kosmos.keyguardBlueprintRepository }
private val clockRepository by lazy { kosmos.fakeKeyguardClockRepository }
private val configurationRepository by lazy { kosmos.fakeConfigurationRepository }
private val fingerprintPropertyRepository by lazy { kosmos.fakeFingerprintPropertyRepository }
@@ -75,7 +74,7 @@
sensorId = 1,
strength = SensorStrength.STRONG,
sensorType = FingerprintSensorType.POWER_BUTTON,
- sensorLocations = mapOf()
+ sensorLocations = mapOf(),
)
}
@@ -93,7 +92,7 @@
}
@Test
- @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
+ @DisableSceneContainer
fun testAppliesSplitShadeBlueprint() {
testScope.runTest {
val blueprintId by collectLastValue(underTest.blueprintId)
@@ -107,7 +106,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
+ @EnableSceneContainer
fun testDoesNotApplySplitShadeBlueprint() {
testScope.runTest {
val blueprintId by collectLastValue(underTest.blueprintId)
@@ -122,7 +121,7 @@
}
@Test
- @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
+ @DisableSceneContainer
fun fingerprintPropertyInitialized_updatesBlueprint() {
testScope.runTest {
underTest.start()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
index 41c5b73..ff6ea3a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
@@ -25,6 +25,8 @@
import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
@@ -44,6 +46,7 @@
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.test.runTest
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Answers
@@ -71,10 +74,8 @@
private val burnInFlow = MutableStateFlow(BurnInModel())
@Before
- @DisableFlags(
- AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
- AConfigFlags.FLAG_COMPOSE_LOCKSCREEN
- )
+ @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+ @DisableSceneContainer
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(burnInInteractor.burnIn(anyInt(), anyInt())).thenReturn(burnInFlow)
@@ -112,18 +113,13 @@
from = KeyguardState.AOD,
to = KeyguardState.LOCKSCREEN,
value = 1f,
- transitionState = TransitionState.FINISHED
+ transitionState = TransitionState.FINISHED,
),
validateStep = false,
)
// Trigger a change to the burn-in model
- burnInFlow.value =
- BurnInModel(
- translationX = 20,
- translationY = 30,
- scale = 0.5f,
- )
+ burnInFlow.value = BurnInModel(translationX = 20, translationY = 30, scale = 0.5f)
assertThat(movement?.translationX).isEqualTo(0)
assertThat(movement?.translationY).isEqualTo(0)
@@ -143,17 +139,12 @@
from = KeyguardState.GONE,
to = KeyguardState.AOD,
value = 1f,
- transitionState = TransitionState.FINISHED
+ transitionState = TransitionState.FINISHED,
),
validateStep = false,
)
// Trigger a change to the burn-in model
- burnInFlow.value =
- BurnInModel(
- translationX = 20,
- translationY = 30,
- scale = 0.5f,
- )
+ burnInFlow.value = BurnInModel(translationX = 20, translationY = 30, scale = 0.5f)
assertThat(movement?.translationX).isEqualTo(20)
assertThat(movement?.translationY).isEqualTo(30)
@@ -166,7 +157,7 @@
from = KeyguardState.GONE,
to = KeyguardState.AOD,
value = 0f,
- transitionState = TransitionState.STARTED
+ transitionState = TransitionState.STARTED,
),
validateStep = false,
)
@@ -180,11 +171,7 @@
@DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
fun translationAndScale_whenFullyDozing_MigrationFlagOff_staysOutOfTopInset() =
testScope.runTest {
- burnInParameters =
- burnInParameters.copy(
- minViewY = 100,
- topInset = 80,
- )
+ burnInParameters = burnInParameters.copy(minViewY = 100, topInset = 80)
val movement by collectLastValue(underTest.movement(burnInParameters))
// Set to dozing (on AOD)
@@ -193,18 +180,13 @@
from = KeyguardState.GONE,
to = KeyguardState.AOD,
value = 1f,
- transitionState = TransitionState.FINISHED
+ transitionState = TransitionState.FINISHED,
),
validateStep = false,
)
// Trigger a change to the burn-in model
- burnInFlow.value =
- BurnInModel(
- translationX = 20,
- translationY = -30,
- scale = 0.5f,
- )
+ burnInFlow.value = BurnInModel(translationX = 20, translationY = -30, scale = 0.5f)
assertThat(movement?.translationX).isEqualTo(20)
// -20 instead of -30, due to inset of 80
assertThat(movement?.translationY).isEqualTo(-20)
@@ -217,7 +199,7 @@
from = KeyguardState.GONE,
to = KeyguardState.AOD,
value = 0f,
- transitionState = TransitionState.STARTED
+ transitionState = TransitionState.STARTED,
),
validateStep = false,
)
@@ -231,11 +213,7 @@
@EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
fun translationAndScale_whenFullyDozing_MigrationFlagOn_staysOutOfTopInset() =
testScope.runTest {
- burnInParameters =
- burnInParameters.copy(
- minViewY = 100,
- topInset = 80,
- )
+ burnInParameters = burnInParameters.copy(minViewY = 100, topInset = 80)
val movement by collectLastValue(underTest.movement(burnInParameters))
// Set to dozing (on AOD)
@@ -244,18 +222,13 @@
from = KeyguardState.GONE,
to = KeyguardState.AOD,
value = 1f,
- transitionState = TransitionState.FINISHED
+ transitionState = TransitionState.FINISHED,
),
validateStep = false,
)
// Trigger a change to the burn-in model
- burnInFlow.value =
- BurnInModel(
- translationX = 20,
- translationY = -30,
- scale = 0.5f,
- )
+ burnInFlow.value = BurnInModel(translationX = 20, translationY = -30, scale = 0.5f)
assertThat(movement?.translationX).isEqualTo(20)
// -20 instead of -30, due to inset of 80
assertThat(movement?.translationY).isEqualTo(-20)
@@ -268,7 +241,7 @@
from = KeyguardState.GONE,
to = KeyguardState.AOD,
value = 0f,
- transitionState = TransitionState.STARTED
+ transitionState = TransitionState.STARTED,
),
validateStep = false,
)
@@ -291,18 +264,13 @@
from = KeyguardState.GONE,
to = KeyguardState.AOD,
value = 1f,
- transitionState = TransitionState.FINISHED
+ transitionState = TransitionState.FINISHED,
),
validateStep = false,
)
// Trigger a change to the burn-in model
- burnInFlow.value =
- BurnInModel(
- translationX = 20,
- translationY = 30,
- scale = 0.5f,
- )
+ burnInFlow.value = BurnInModel(translationX = 20, translationY = 30, scale = 0.5f)
assertThat(movement?.translationX).isEqualTo(20)
assertThat(movement?.translationY).isEqualTo(30)
@@ -311,9 +279,9 @@
}
@Test
- @DisableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN)
+ @DisableSceneContainer
@EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
- fun translationAndScale_composeFlagOff_weatherLargeClock() =
+ fun translationAndScale_sceneContainerOff_weatherLargeClock() =
testBurnInViewModelForClocks(
isSmallClock = false,
isWeatherClock = true,
@@ -321,9 +289,9 @@
)
@Test
- @DisableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN)
+ @DisableSceneContainer
@EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
- fun translationAndScale_composeFlagOff_weatherSmallClock() =
+ fun translationAndScale_sceneContainerOff_weatherSmallClock() =
testBurnInViewModelForClocks(
isSmallClock = true,
isWeatherClock = true,
@@ -331,9 +299,9 @@
)
@Test
- @DisableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN)
+ @DisableSceneContainer
@EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
- fun translationAndScale_composeFlagOff_nonWeatherLargeClock() =
+ fun translationAndScale_sceneContainerOff_nonWeatherLargeClock() =
testBurnInViewModelForClocks(
isSmallClock = false,
isWeatherClock = false,
@@ -341,9 +309,9 @@
)
@Test
- @DisableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN)
+ @DisableSceneContainer
@EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
- fun translationAndScale_composeFlagOff_nonWeatherSmallClock() =
+ fun translationAndScale_sceneContainerOff_nonWeatherSmallClock() =
testBurnInViewModelForClocks(
isSmallClock = true,
isWeatherClock = false,
@@ -351,11 +319,9 @@
)
@Test
- @EnableFlags(
- AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
- AConfigFlags.FLAG_COMPOSE_LOCKSCREEN
- )
- fun translationAndScale_composeFlagOn_weatherLargeClock() =
+ @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+ @EnableSceneContainer
+ fun translationAndScale_sceneContainerOn_weatherLargeClock() =
testBurnInViewModelForClocks(
isSmallClock = false,
isWeatherClock = true,
@@ -363,11 +329,9 @@
)
@Test
- @EnableFlags(
- AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
- AConfigFlags.FLAG_COMPOSE_LOCKSCREEN
- )
- fun translationAndScale_composeFlagOn_weatherSmallClock() =
+ @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+ @EnableSceneContainer
+ fun translationAndScale_sceneContainerOn_weatherSmallClock() =
testBurnInViewModelForClocks(
isSmallClock = true,
isWeatherClock = true,
@@ -375,11 +339,9 @@
)
@Test
- @EnableFlags(
- AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
- AConfigFlags.FLAG_COMPOSE_LOCKSCREEN
- )
- fun translationAndScale_composeFlagOn_nonWeatherLargeClock() =
+ @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+ @EnableSceneContainer
+ fun translationAndScale_sceneContainerOn_nonWeatherLargeClock() =
testBurnInViewModelForClocks(
isSmallClock = false,
isWeatherClock = false,
@@ -387,11 +349,10 @@
)
@Test
- @EnableFlags(
- AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
- AConfigFlags.FLAG_COMPOSE_LOCKSCREEN
- )
- fun translationAndScale_composeFlagOn_nonWeatherSmallClock() =
+ @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+ @EnableSceneContainer
+ @Ignore("b/367659687")
+ fun translationAndScale_sceneContainerOn_nonWeatherSmallClock() =
testBurnInViewModelForClocks(
isSmallClock = true,
isWeatherClock = false,
@@ -421,18 +382,13 @@
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.AOD,
value = 1f,
- transitionState = TransitionState.FINISHED
+ transitionState = TransitionState.FINISHED,
),
validateStep = false,
)
// Trigger a change to the burn-in model
- burnInFlow.value =
- BurnInModel(
- translationX = 20,
- translationY = 30,
- scale = 0.5f,
- )
+ burnInFlow.value = BurnInModel(translationX = 20, translationY = 30, scale = 0.5f)
assertThat(movement?.translationX).isEqualTo(20)
assertThat(movement?.translationY).isEqualTo(30)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
index 17e1b53..05a6b87 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
@@ -16,14 +16,13 @@
package com.android.systemui.keyguard.ui.viewmodel
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.BrokenWithSceneContainer
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
import com.android.systemui.keyguard.data.repository.keyguardClockRepository
@@ -229,8 +228,8 @@
}
@Test
- @EnableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
- fun testSmallClockTop_splitShade_composeLockscreenOn() =
+ @EnableSceneContainer
+ fun testSmallClockTop_splitShade_sceneContainerOn() =
testScope.runTest {
with(kosmos) {
shadeRepository.setShadeLayoutWide(true)
@@ -244,8 +243,8 @@
}
@Test
- @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
- fun testSmallClockTop_splitShade_composeLockscreenOff() =
+ @DisableSceneContainer
+ fun testSmallClockTop_splitShade_sceneContainerOff() =
testScope.runTest {
with(kosmos) {
shadeRepository.setShadeLayoutWide(true)
@@ -257,8 +256,8 @@
}
@Test
- @EnableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
- fun testSmallClockTop_nonSplitShade_composeLockscreenOn() =
+ @EnableSceneContainer
+ fun testSmallClockTop_nonSplitShade_sceneContainerOn() =
testScope.runTest {
with(kosmos) {
shadeRepository.setShadeLayoutWide(false)
@@ -270,8 +269,8 @@
}
@Test
- @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
- fun testSmallClockTop_nonSplitShade_composeLockscreenOff() =
+ @DisableSceneContainer
+ fun testSmallClockTop_nonSplitShade_sceneContainerOff() =
testScope.runTest {
with(kosmos) {
shadeRepository.setShadeLayoutWide(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
index d594f3a..62d0625 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
@@ -121,7 +121,7 @@
MediaData(
userId = USER_ID,
instanceId = InstanceId.fakeInstanceId(2),
- artist = ARTIST
+ artist = ARTIST,
)
mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData)
@@ -145,10 +145,17 @@
val clickIntent = mock<PendingIntent> { whenever(it.isActivity).thenReturn(true) }
val expandable = mock<Expandable>()
+ val activityController = mock<ActivityTransitionAnimator.Controller>()
+ whenever(expandable.activityTransitionController(any())).thenReturn(activityController)
underTest.startClickIntent(expandable, clickIntent, SMARTSPACE_CARD_CLICK_EVENT, 1)
- verify(clickIntent).send(any<Bundle>())
+ verify(activityStarter)
+ .startPendingIntentMaybeDismissingKeyguard(
+ eq(clickIntent),
+ eq(null),
+ eq(activityController),
+ )
}
@Test
@@ -174,7 +181,7 @@
mediaData.appUid,
surface = SURFACE,
cardinality = 2,
- rank = 1
+ rank = 1,
)
verify(activityStarter)
.postStartActivityDismissingKeyguard(eq(clickIntent), eq(activityController))
@@ -232,7 +239,7 @@
eq(true),
eq(dialogTransitionController),
eq(null),
- eq(null)
+ eq(null),
)
}
@@ -248,7 +255,7 @@
.createBroadcastDialogWithController(
eq(APP_NAME),
eq(PACKAGE_NAME),
- eq(dialogTransitionController)
+ eq(dialogTransitionController),
)
}
@@ -279,7 +286,7 @@
anyInt(),
anyInt(),
anyInt(),
- anyBoolean()
+ anyBoolean(),
)
verify(listener).onMediaDataRemoved(eq(KEY), eq(true))
}
@@ -307,7 +314,7 @@
mediaData.appUid,
surface = SURFACE,
cardinality = 2,
- rank = 1
+ rank = 1,
)
verify(listener).onMediaDataRemoved(eq(KEY), eq(true))
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt
index 69ccc58..7da2e9a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt
@@ -35,6 +35,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.Flags.MEDIA_RESUME_PROGRESS
import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.graphics.ImageLoader
import com.android.systemui.graphics.imageLoader
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
@@ -45,12 +46,18 @@
import com.android.systemui.statusbar.SbnBuilder
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
private const val KEY = "KEY"
@@ -88,12 +95,13 @@
mediaControllerFactory,
mediaFlags,
kosmos.imageLoader,
- statusBarManager
+ statusBarManager,
)
@Before
fun setUp() {
mediaControllerFactory.setControllerForToken(session.sessionToken, mediaController)
+ whenever(mediaController.metadata).then { metadataBuilder.build() }
}
@Test
@@ -115,7 +123,7 @@
0,
0,
AudioAttributes.Builder().build(),
- null
+ null,
)
)
whenever(mediaController.metadata)
@@ -126,7 +134,7 @@
.putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, albumArt)
.putLong(
MediaConstants.METADATA_KEY_IS_EXPLICIT,
- MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
+ MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT,
)
.build()
)
@@ -161,12 +169,12 @@
val extras = Bundle()
extras.putInt(
MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
- MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED,
)
extras.putDouble(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.3)
extras.putLong(
MediaConstants.METADATA_KEY_IS_EXPLICIT,
- MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
+ MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT,
)
val description =
@@ -189,7 +197,7 @@
session.sessionToken,
APP_NAME,
intent,
- PACKAGE_NAME
+ PACKAGE_NAME,
)
assertThat(result).isNotNull()
assertThat(result?.appName).isEqualTo(APP_NAME)
@@ -372,9 +380,37 @@
assertThat(result).isNotNull()
}
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testLoadMediaDataInBg_cancelMultipleScheduledTasks() =
+ testScope.runTest {
+ val mockImageLoader = mock<ImageLoader>()
+ val mediaDataLoader =
+ MediaDataLoader(
+ context,
+ testDispatcher,
+ testScope,
+ mediaControllerFactory,
+ mediaFlags,
+ mockImageLoader,
+ statusBarManager,
+ )
+ metadataBuilder.putString(
+ MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
+ "content://album_art_uri",
+ )
+
+ testScope.launch { mediaDataLoader.loadMediaData(KEY, createMediaNotification()) }
+ testScope.launch { mediaDataLoader.loadMediaData(KEY, createMediaNotification()) }
+ testScope.launch { mediaDataLoader.loadMediaData(KEY, createMediaNotification()) }
+ testScope.advanceUntilIdle()
+
+ verify(mockImageLoader, times(1)).loadBitmap(any(), anyInt(), anyInt(), anyInt())
+ }
+
private fun createMediaNotification(
mediaSession: MediaSession? = session,
- applicationInfo: ApplicationInfo? = null
+ applicationInfo: ApplicationInfo? = null,
): StatusBarNotification =
SbnBuilder().run {
setPkg(PACKAGE_NAME)
@@ -385,7 +421,7 @@
val bundle = Bundle()
bundle.putParcelable(
Notification.EXTRA_BUILDER_APPLICATION_INFO,
- applicationInfo
+ applicationInfo,
)
it.addExtras(bundle)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt
index 9558e5d..0122028 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt
@@ -24,26 +24,25 @@
import android.media.session.PlaybackState
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.internal.logging.InstanceId
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
-import com.android.systemui.media.controls.domain.pipeline.MediaDataFilterImpl
import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.MediaDeviceData
import com.android.systemui.media.controls.util.mediaInstanceId
import com.android.systemui.statusbar.notificationLockscreenUserManager
import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.any
import org.mockito.Mockito
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -52,30 +51,31 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val mediaDataFilter: MediaDataFilterImpl = kosmos.mediaDataFilter
+ private val mediaDataFilter = kosmos.mediaDataFilter
private val notificationLockscreenUserManager = kosmos.notificationLockscreenUserManager
private val packageManager = kosmos.packageManager
private val drawable = context.getDrawable(R.drawable.ic_media_play)
- private val instanceId: InstanceId = kosmos.mediaInstanceId
-
+ private val instanceId = kosmos.mediaInstanceId
private val underTest: MediaControlViewModel = kosmos.mediaControlViewModel
+ @Before
+ fun setUp() {
+ whenever(packageManager.getApplicationIcon(Mockito.anyString())).thenReturn(drawable)
+ whenever(packageManager.getApplicationIcon(any(ApplicationInfo::class.java)))
+ .thenReturn(drawable)
+ whenever(packageManager.getApplicationInfo(eq(PACKAGE_NAME), ArgumentMatchers.anyInt()))
+ .thenReturn(ApplicationInfo())
+ whenever(packageManager.getApplicationLabel(any())).thenReturn(PACKAGE_NAME)
+ whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
+ whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
+ context.setMockPackageManager(packageManager)
+ }
+
@Test
fun addMediaControl_mediaControlViewModelIsLoaded() =
testScope.runTest {
- whenever(packageManager.getApplicationIcon(Mockito.anyString())).thenReturn(drawable)
- whenever(packageManager.getApplicationIcon(any(ApplicationInfo::class.java)))
- .thenReturn(drawable)
- whenever(packageManager.getApplicationInfo(eq(PACKAGE_NAME), ArgumentMatchers.anyInt()))
- .thenReturn(ApplicationInfo())
- whenever(packageManager.getApplicationLabel(any())).thenReturn(PACKAGE_NAME)
- whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
- whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
val playerModel by collectLastValue(underTest.player)
-
- context.setMockPackageManager(packageManager)
-
- val mediaData = initMediaData()
+ val mediaData = initMediaData(ARTIST, TITLE)
mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData)
@@ -88,7 +88,51 @@
assertThat(playerModel?.playTurbulenceNoise).isFalse()
}
- private fun initMediaData(): MediaData {
+ @Test
+ fun emitDuplicateMediaControls_mediaControlIsNotBound() =
+ testScope.runTest {
+ val playerModel by collectLastValue(underTest.player)
+ val mediaData = initMediaData(ARTIST, TITLE)
+
+ mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData)
+
+ assertThat(playerModel).isNotNull()
+ assertThat(playerModel?.titleName).isEqualTo(TITLE)
+ assertThat(playerModel?.artistName).isEqualTo(ARTIST)
+ assertThat(underTest.isNewPlayer(playerModel!!)).isTrue()
+
+ mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData)
+
+ assertThat(playerModel).isNotNull()
+ assertThat(playerModel?.titleName).isEqualTo(TITLE)
+ assertThat(playerModel?.artistName).isEqualTo(ARTIST)
+ assertThat(underTest.isNewPlayer(playerModel!!)).isFalse()
+ }
+
+ @Test
+ fun emitDifferentMediaControls_mediaControlIsBound() =
+ testScope.runTest {
+ val playerModel by collectLastValue(underTest.player)
+ var mediaData = initMediaData(ARTIST, TITLE)
+
+ mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData)
+
+ assertThat(playerModel).isNotNull()
+ assertThat(playerModel?.titleName).isEqualTo(TITLE)
+ assertThat(playerModel?.artistName).isEqualTo(ARTIST)
+ assertThat(underTest.isNewPlayer(playerModel!!)).isTrue()
+
+ mediaData = initMediaData(ARTIST_2, TITLE_2)
+
+ mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData)
+
+ assertThat(playerModel).isNotNull()
+ assertThat(playerModel?.titleName).isEqualTo(TITLE_2)
+ assertThat(playerModel?.artistName).isEqualTo(ARTIST_2)
+ assertThat(underTest.isNewPlayer(playerModel!!)).isTrue()
+ }
+
+ private fun initMediaData(artist: String, title: String): MediaData {
val device = MediaDeviceData(true, null, DEVICE_NAME, null, showBroadcastButton = true)
// Create media session
@@ -111,12 +155,12 @@
return MediaData(
userId = USER_ID,
- artist = ARTIST,
- song = TITLE,
+ artist = artist,
+ song = title,
packageName = PACKAGE,
token = session.sessionToken,
device = device,
- instanceId = instanceId
+ instanceId = instanceId,
)
}
@@ -127,6 +171,8 @@
private const val PACKAGE = "PKG"
private const val ARTIST = "ARTIST"
private const val TITLE = "TITLE"
+ private const val ARTIST_2 = "ARTIST_2"
+ private const val TITLE_2 = "TITLE_2"
private const val DEVICE_NAME = "DEVICE_NAME"
private const val SESSION_KEY = "SESSION_KEY"
private const val SESSION_ARTIST = "SESSION_ARTIST"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarInflaterViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarInflaterViewTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarInflaterViewTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarInflaterViewTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/KeyButtonViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/KeyButtonViewTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/KeyButtonViewTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/KeyButtonViewTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/NavigationBarContextTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/NavigationBarContextTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/NavigationBarContextTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/NavigationBarContextTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/NearestTouchFrameTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/NearestTouchFrameTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/NearestTouchFrameTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/NearestTouchFrameTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/NotificationHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/people/NotificationHelperTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/people/NotificationHelperTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/people/NotificationHelperTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleBackupFollowUpJobTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/people/PeopleBackupFollowUpJobTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/people/PeopleBackupFollowUpJobTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/people/PeopleBackupFollowUpJobTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTestable.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/people/PeopleProviderTestable.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTestable.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/people/PeopleProviderTestable.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/SharedPreferencesHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/people/SharedPreferencesHelperTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/people/SharedPreferencesHelperTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/people/SharedPreferencesHelperTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleBackupHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/people/widget/PeopleBackupHelperTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleBackupHelperTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/people/widget/PeopleBackupHelperTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyChipBuilderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/privacy/PrivacyChipBuilderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyChipBuilderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/privacy/PrivacyChipBuilderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/privacy/PrivacyDialogTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/privacy/PrivacyDialogTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/PagedTileLayoutTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/PagedTileLayoutTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSContainerImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSContainerImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDisableFlagsLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSDisableFlagsLoggerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/QSDisableFlagsLoggerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSDisableFlagsLoggerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseSceneContainerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseSceneContainerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseSceneContainerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseSceneContainerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelSwitchToParentTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelSwitchToParentTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelSwitchToParentTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelSwitchToParentTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/TileStateToProtoTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/TileStateToProtoTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/TouchAnimatorTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/TouchAnimatorTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/UserSettingObserverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/UserSettingObserverTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/UserSettingObserverTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/UserSettingObserverTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileAdapterTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileAdapterTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/CustomTileTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/CustomTileTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileColorPickerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileColorPickerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/external/TileColorPickerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileColorPickerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
deleted file mode 100644
index 42db96e..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.panels.domain.interactor
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
-import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
-import com.android.systemui.qs.panels.data.repository.gridLayoutTypeRepository
-import com.android.systemui.qs.panels.shared.model.GridLayoutType
-import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
-import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository
-import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
-import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.testKosmos
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class GridConsistencyInteractorTest : SysuiTestCase() {
-
- data object NoopGridLayoutType : GridLayoutType
-
- private val kosmos =
- testKosmos().apply {
- defaultLargeTilesRepository =
- object : DefaultLargeTilesRepository {
- override val defaultLargeTiles =
- setOf(
- TileSpec.create("largeA"),
- TileSpec.create("largeB"),
- TileSpec.create("largeC"),
- TileSpec.create("largeD"),
- )
- }
- gridConsistencyInteractorsMap =
- mapOf(
- Pair(NoopGridLayoutType, noopGridConsistencyInteractor),
- Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor)
- )
- }
-
- private val underTest = with(kosmos) { gridConsistencyInteractor }
-
- @Before
- fun setUp() {
- // Mostly testing InfiniteGridConsistencyInteractor because it reorders tiles
- with(kosmos) { gridLayoutTypeRepository.setLayout(InfiniteGridLayoutType) }
- underTest.start()
- }
-
- @OptIn(ExperimentalCoroutinesApi::class)
- @Test
- fun changeLayoutType_usesCorrectGridConsistencyInteractor() =
- with(kosmos) {
- testScope.runTest {
- // Using the no-op grid consistency interactor
- gridLayoutTypeRepository.setLayout(NoopGridLayoutType)
-
- // Setting an invalid layout with holes
- // [ Large A ] [ sa ]
- // [ Large B ] [ Large C ]
- // [ sb ] [ Large D ]
- val newTiles =
- listOf(
- TileSpec.create("largeA"),
- TileSpec.create("smallA"),
- TileSpec.create("largeB"),
- TileSpec.create("largeC"),
- TileSpec.create("smallB"),
- TileSpec.create("largeD"),
- )
- tileSpecRepository.setTiles(0, newTiles)
-
- runCurrent()
-
- val tiles = currentTilesInteractor.currentTiles.value
- val tileSpecs = tiles.map { it.spec }
-
- // Saved tiles should be unchanged
- assertThat(tileSpecs).isEqualTo(newTiles)
- }
- }
-
- @Test
- fun validTilesWithInfiniteGridConsistencyInteractor_unchangedList() =
- with(kosmos) {
- testScope.runTest {
- // Setting a valid layout with holes
- // [ Large A ] [ sa ][ sb ]
- // [ Large B ] [ Large C ]
- // [ Large D ]
- val newTiles =
- listOf(
- TileSpec.create("largeA"),
- TileSpec.create("smallA"),
- TileSpec.create("smallB"),
- TileSpec.create("largeB"),
- TileSpec.create("largeC"),
- TileSpec.create("largeD"),
- )
- tileSpecRepository.setTiles(0, newTiles)
-
- runCurrent()
-
- val tiles = currentTilesInteractor.currentTiles.value
- val tileSpecs = tiles.map { it.spec }
-
- // Saved tiles should be unchanged
- assertThat(tileSpecs).isEqualTo(newTiles)
- }
- }
-
- @Test
- fun invalidTilesWithInfiniteGridConsistencyInteractor_savesNewList() =
- with(kosmos) {
- testScope.runTest {
- // Setting an invalid layout with holes
- // [ sa ] [ Large A ]
- // [ Large B ] [ sb ] [ sc ]
- // [ sd ] [ se ] [ Large C ]
- val newTiles =
- listOf(
- TileSpec.create("smallA"),
- TileSpec.create("largeA"),
- TileSpec.create("largeB"),
- TileSpec.create("smallB"),
- TileSpec.create("smallC"),
- TileSpec.create("smallD"),
- TileSpec.create("smallE"),
- TileSpec.create("largeC"),
- )
- tileSpecRepository.setTiles(0, newTiles)
-
- runCurrent()
-
- val tiles = currentTilesInteractor.currentTiles.value
- val tileSpecs = tiles.map { it.spec }
-
- // Expected grid
- // [ sa ] [ Large A ] [ sb ]
- // [ Large B ] [ sc ] [ sd ]
- // [ se ] [ Large C ]
- val expectedTiles =
- listOf(
- TileSpec.create("smallA"),
- TileSpec.create("largeA"),
- TileSpec.create("smallB"),
- TileSpec.create("largeB"),
- TileSpec.create("smallC"),
- TileSpec.create("smallD"),
- TileSpec.create("smallE"),
- TileSpec.create("largeC"),
- )
-
- // Saved tiles should be unchanged
- assertThat(tileSpecs).isEqualTo(expectedTiles)
- }
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt
deleted file mode 100644
index ea51398..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.panels.domain.interactor
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
-import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
-import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.testKosmos
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class InfiniteGridConsistencyInteractorTest : SysuiTestCase() {
-
- private val kosmos =
- testKosmos().apply {
- defaultLargeTilesRepository =
- object : DefaultLargeTilesRepository {
- override val defaultLargeTiles: Set<TileSpec> =
- setOf(
- TileSpec.create("largeA"),
- TileSpec.create("largeB"),
- TileSpec.create("largeC"),
- TileSpec.create("largeD"),
- )
- }
- }
- private val underTest = with(kosmos) { infiniteGridConsistencyInteractor }
-
- @Test
- fun validTiles_returnsUnchangedList() =
- with(kosmos) {
- testScope.runTest {
- // Original grid
- // [ Large A ] [ sa ][ sb ]
- // [ Large B ] [ Large C ]
- // [ Large D ]
- val tiles =
- listOf(
- TileSpec.create("largeA"),
- TileSpec.create("smallA"),
- TileSpec.create("smallB"),
- TileSpec.create("largeB"),
- TileSpec.create("largeC"),
- TileSpec.create("largeD"),
- )
-
- val newTiles = underTest.reconcileTiles(tiles)
-
- assertThat(newTiles).isEqualTo(tiles)
- }
- }
-
- @Test
- fun invalidTiles_moveIconTileForward() =
- with(kosmos) {
- testScope.runTest {
- // Original grid
- // [ Large A ] [ sa ]
- // [ Large B ] [ Large C ]
- // [ sb ] [ Large D ]
- val tiles =
- listOf(
- TileSpec.create("largeA"),
- TileSpec.create("smallA"),
- TileSpec.create("largeB"),
- TileSpec.create("largeC"),
- TileSpec.create("smallB"),
- TileSpec.create("largeD"),
- )
- // Expected grid
- // [ Large A ] [ sa ][ sb ]
- // [ Large B ] [ Large C ]
- // [ Large D ]
- val expectedTiles =
- listOf(
- TileSpec.create("largeA"),
- TileSpec.create("smallA"),
- TileSpec.create("smallB"),
- TileSpec.create("largeB"),
- TileSpec.create("largeC"),
- TileSpec.create("largeD"),
- )
-
- val newTiles = underTest.reconcileTiles(tiles)
-
- assertThat(newTiles).isEqualTo(expectedTiles)
- }
- }
-
- @Test
- fun invalidTiles_moveIconTileBack() =
- with(kosmos) {
- testScope.runTest {
- // Original grid
- // [ sa ] [ Large A ]
- // [ Large B ] [ Large C ]
- // [ Large D ]
- val tiles =
- listOf(
- TileSpec.create("smallA"),
- TileSpec.create("largeA"),
- TileSpec.create("largeB"),
- TileSpec.create("largeC"),
- TileSpec.create("largeD"),
- )
- // Expected grid
- // [ Large A ] [ Large B ]
- // [ Large C ] [ Large D ]
- // [ sa ]
- val expectedTiles =
- listOf(
- TileSpec.create("largeA"),
- TileSpec.create("largeB"),
- TileSpec.create("largeC"),
- TileSpec.create("largeD"),
- TileSpec.create("smallA"),
- )
-
- val newTiles = underTest.reconcileTiles(tiles)
-
- assertThat(newTiles).isEqualTo(expectedTiles)
- }
- }
-
- @Test
- fun invalidTiles_multipleCorrections() =
- with(kosmos) {
- testScope.runTest {
- // Original grid
- // [ sa ] [ Large A ]
- // [ Large B ] [ sb ] [ sc ]
- // [ sd ] [ se ] [ Large C ]
- val tiles =
- listOf(
- TileSpec.create("smallA"),
- TileSpec.create("largeA"),
- TileSpec.create("largeB"),
- TileSpec.create("smallB"),
- TileSpec.create("smallC"),
- TileSpec.create("smallD"),
- TileSpec.create("smallE"),
- TileSpec.create("largeC"),
- )
- // Expected grid
- // [ sa ] [ Large A ] [ sb ]
- // [ Large B ] [ sc ] [ sd ]
- // [ se ] [ Large C ]
- val expectedTiles =
- listOf(
- TileSpec.create("smallA"),
- TileSpec.create("largeA"),
- TileSpec.create("smallB"),
- TileSpec.create("largeB"),
- TileSpec.create("smallC"),
- TileSpec.create("smallD"),
- TileSpec.create("smallE"),
- TileSpec.create("largeC"),
- )
-
- val newTiles = underTest.reconcileTiles(tiles)
-
- assertThat(newTiles).isEqualTo(expectedTiles)
- }
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt
index 53384af..9e90090 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt
@@ -22,6 +22,7 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.InfiniteGridLayout
import com.android.systemui.qs.panels.ui.viewmodel.MockTileViewModel
import com.android.systemui.qs.panels.ui.viewmodel.fixedColumnsSizeViewModel
import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel
@@ -44,12 +45,7 @@
}
private val underTest =
- with(kosmos) {
- InfiniteGridLayout(
- iconTilesViewModel,
- fixedColumnsSizeViewModel,
- )
- }
+ with(kosmos) { InfiniteGridLayout(iconTilesViewModel, fixedColumnsSizeViewModel) }
@Test
fun correctPagination_underOnePage_sameOrder() =
@@ -65,7 +61,7 @@
smallTile(),
largeTile(),
largeTile(),
- smallTile()
+ smallTile(),
)
val pages = underTest.splitIntoPages(tiles, rows = rows, columns = columns)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/ResourceIconTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/ResourceIconTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/ResourceIconTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/ResourceIconTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CastTileTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CastTileTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/NfcTileTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/NfcTileTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/WifiStateWorkerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/WifiStateWorkerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/WifiStateWorkerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/WifiStateWorkerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/CustomTraceStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/CustomTraceStateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/recordissue/CustomTraceStateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/CustomTraceStateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/IssueRecordingServiceCommandHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceCommandHandlerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/recordissue/IssueRecordingServiceCommandHandlerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceCommandHandlerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/IssueRecordingStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingStateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/recordissue/IssueRecordingStateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingStateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt
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 763a1a9..3850891 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
@@ -27,6 +27,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.OverlayKey
import com.android.compose.animation.scene.SceneKey
import com.android.internal.logging.uiEventLoggerFake
import com.android.internal.policy.IKeyguardDismissCallback
@@ -88,9 +89,11 @@
import com.android.systemui.scene.data.repository.Transition
import com.android.systemui.scene.domain.interactor.sceneBackInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.shared.system.QuickStepContract
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
@@ -161,6 +164,7 @@
}
@Test
+ @DisableFlags(DualShade.FLAG_NAME)
fun hydrateVisibility() =
testScope.runTest {
val currentDesiredSceneKey by collectLastValue(sceneInteractor.currentScene)
@@ -221,6 +225,87 @@
}
@Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun hydrateVisibility_dualShade() =
+ testScope.runTest {
+ val currentDesiredSceneKey by collectLastValue(sceneInteractor.currentScene)
+ val currentDesiredOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ val isVisible by collectLastValue(sceneInteractor.isVisible)
+ val transitionStateFlow =
+ prepareState(
+ authenticationMethod = AuthenticationMethodModel.Pin,
+ isDeviceUnlocked = true,
+ initialSceneKey = Scenes.Gone,
+ )
+ assertThat(currentDesiredSceneKey).isEqualTo(Scenes.Gone)
+ assertThat(currentDesiredOverlays).isEmpty()
+ assertThat(isVisible).isTrue()
+
+ underTest.start()
+ assertThat(isVisible).isFalse()
+
+ // Expand the notifications shade.
+ fakeSceneDataSource.pause()
+ sceneInteractor.showOverlay(Overlays.NotificationsShade, "reason")
+ transitionStateFlow.value =
+ ObservableTransitionState.Transition.ShowOrHideOverlay(
+ overlay = Overlays.NotificationsShade,
+ fromContent = Scenes.Gone,
+ toContent = Overlays.NotificationsShade,
+ currentScene = Scenes.Gone,
+ currentOverlays = flowOf(emptySet()),
+ progress = flowOf(0.5f),
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ previewProgress = flowOf(0f),
+ isInPreviewStage = flowOf(false),
+ )
+ assertThat(isVisible).isTrue()
+ fakeSceneDataSource.unpause(expectedScene = Scenes.Gone)
+ transitionStateFlow.value =
+ ObservableTransitionState.Idle(
+ currentScene = Scenes.Gone,
+ currentOverlays = setOf(Overlays.NotificationsShade),
+ )
+ assertThat(isVisible).isTrue()
+
+ // Collapse the notifications shade.
+ fakeSceneDataSource.pause()
+ sceneInteractor.hideOverlay(Overlays.NotificationsShade, "reason")
+ transitionStateFlow.value =
+ ObservableTransitionState.Transition.ShowOrHideOverlay(
+ overlay = Overlays.NotificationsShade,
+ fromContent = Overlays.NotificationsShade,
+ toContent = Scenes.Gone,
+ currentScene = Scenes.Gone,
+ currentOverlays = flowOf(setOf(Overlays.NotificationsShade)),
+ progress = flowOf(0.5f),
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ previewProgress = flowOf(0f),
+ isInPreviewStage = flowOf(false),
+ )
+ assertThat(isVisible).isTrue()
+ fakeSceneDataSource.unpause(expectedScene = Scenes.Gone)
+ transitionStateFlow.value =
+ ObservableTransitionState.Idle(
+ currentScene = Scenes.Gone,
+ currentOverlays = emptySet(),
+ )
+ assertThat(isVisible).isFalse()
+
+ kosmos.headsUpNotificationRepository.setNotifications(
+ buildNotificationRows(isPinned = true)
+ )
+ assertThat(isVisible).isTrue()
+
+ kosmos.headsUpNotificationRepository.setNotifications(
+ buildNotificationRows(isPinned = false)
+ )
+ assertThat(isVisible).isFalse()
+ }
+
+ @Test
fun hydrateVisibility_basedOnDeviceProvisioning() =
testScope.runTest {
val isVisible by collectLastValue(sceneInteractor.isVisible)
@@ -1621,6 +1706,7 @@
}
@Test
+ @DisableFlags(DualShade.FLAG_NAME)
fun hydrateInteractionState_whileLocked() =
testScope.runTest {
val transitionStateFlow = prepareState(initialSceneKey = Scenes.Lockscreen)
@@ -1707,6 +1793,7 @@
}
@Test
+ @DisableFlags(DualShade.FLAG_NAME)
fun hydrateInteractionState_whileUnlocked() =
testScope.runTest {
val transitionStateFlow =
@@ -1795,6 +1882,186 @@
}
@Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun hydrateInteractionState_dualShade_whileLocked() =
+ testScope.runTest {
+ val currentDesiredOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ val transitionStateFlow = prepareState(initialSceneKey = Scenes.Lockscreen)
+ underTest.start()
+ runCurrent()
+ verify(centralSurfaces).setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true)
+ assertThat(currentDesiredOverlays).isEmpty()
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = Scenes.Bouncer,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces)
+ .setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false)
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = Scenes.Lockscreen,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces).setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true)
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateOverlayTransition(
+ transitionStateFlow = transitionStateFlow,
+ toOverlay = Overlays.NotificationsShade,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces)
+ .setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false)
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = Scenes.Lockscreen,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces).setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true)
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateOverlayTransition(
+ transitionStateFlow = transitionStateFlow,
+ toOverlay = Overlays.QuickSettingsShade,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ )
+ }
+
+ @Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun hydrateInteractionState_dualShade_whileUnlocked() =
+ testScope.runTest {
+ val currentDesiredOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ val transitionStateFlow =
+ prepareState(
+ authenticationMethod = AuthenticationMethodModel.Pin,
+ isDeviceUnlocked = true,
+ initialSceneKey = Scenes.Gone,
+ )
+ underTest.start()
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ assertThat(currentDesiredOverlays).isEmpty()
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = Scenes.Bouncer,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = Scenes.Lockscreen,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = Scenes.Shade,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = Scenes.Lockscreen,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = Scenes.QuickSettings,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ )
+ }
+
+ @Test
fun respondToFalsingDetections() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene)
@@ -2131,19 +2398,40 @@
verifyAfterTransition: (() -> Unit)? = null,
) {
val fromScene = sceneInteractor.currentScene.value
+ val fromOverlays = sceneInteractor.currentOverlays.value
sceneInteractor.changeScene(toScene, "reason")
runCurrent()
verifyBeforeTransition?.invoke()
transitionStateFlow.value =
- ObservableTransitionState.Transition(
- fromScene = fromScene,
- toScene = toScene,
- currentScene = flowOf(fromScene),
- progress = flowOf(0.5f),
- isInitiatedByUserInput = true,
- isUserInputOngoing = flowOf(true),
- )
+ if (fromOverlays.isEmpty()) {
+ // Regular scene-to-scene transition.
+ ObservableTransitionState.Transition.ChangeScene(
+ fromScene = fromScene,
+ toScene = toScene,
+ currentScene = flowOf(fromScene),
+ currentOverlays = fromOverlays,
+ progress = flowOf(0.5f),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(true),
+ previewProgress = flowOf(0f),
+ isInPreviewStage = flowOf(false),
+ )
+ } else {
+ // An overlay is present; hide it.
+ ObservableTransitionState.Transition.ShowOrHideOverlay(
+ overlay = fromOverlays.first(),
+ fromContent = fromOverlays.first(),
+ toContent = toScene,
+ currentScene = fromScene,
+ currentOverlays = sceneInteractor.currentOverlays,
+ progress = flowOf(0.5f),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(true),
+ previewProgress = flowOf(0f),
+ isInPreviewStage = flowOf(false),
+ )
+ }
runCurrent()
verifyDuringTransition?.invoke()
@@ -2152,6 +2440,60 @@
verifyAfterTransition?.invoke()
}
+ private fun TestScope.emulateOverlayTransition(
+ transitionStateFlow: MutableStateFlow<ObservableTransitionState>,
+ toOverlay: OverlayKey,
+ verifyBeforeTransition: (() -> Unit)? = null,
+ verifyDuringTransition: (() -> Unit)? = null,
+ verifyAfterTransition: (() -> Unit)? = null,
+ ) {
+ val fromScene = sceneInteractor.currentScene.value
+ val fromOverlays = sceneInteractor.currentOverlays.value
+ sceneInteractor.showOverlay(toOverlay, "reason")
+ runCurrent()
+ verifyBeforeTransition?.invoke()
+
+ transitionStateFlow.value =
+ if (fromOverlays.isEmpty()) {
+ // Show a new overlay.
+ ObservableTransitionState.Transition.ShowOrHideOverlay(
+ overlay = toOverlay,
+ fromContent = fromScene,
+ toContent = toOverlay,
+ currentScene = fromScene,
+ currentOverlays = sceneInteractor.currentOverlays,
+ progress = flowOf(0.5f),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(true),
+ previewProgress = flowOf(0f),
+ isInPreviewStage = flowOf(false),
+ )
+ } else {
+ // Overlay-to-overlay transition.
+ ObservableTransitionState.Transition.ReplaceOverlay(
+ fromOverlay = fromOverlays.first(),
+ toOverlay = toOverlay,
+ currentScene = fromScene,
+ currentOverlays = sceneInteractor.currentOverlays,
+ progress = flowOf(0.5f),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(true),
+ previewProgress = flowOf(0f),
+ isInPreviewStage = flowOf(false),
+ )
+ }
+ runCurrent()
+ verifyDuringTransition?.invoke()
+
+ transitionStateFlow.value =
+ ObservableTransitionState.Idle(
+ currentScene = fromScene,
+ currentOverlays = setOf(toOverlay),
+ )
+ runCurrent()
+ verifyAfterTransition?.invoke()
+ }
+
private fun TestScope.prepareState(
isDeviceUnlocked: Boolean = false,
isBypassEnabled: Boolean = false,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.kt
index 4d69f0d..f86337e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.kt
@@ -19,8 +19,8 @@
import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_COMPOSE_LOCKSCREEN
import com.android.systemui.Flags.FLAG_EXAMPLE_FLAG
+import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.andSceneContainer
@@ -66,7 +66,7 @@
@Test
fun oneDependencyAndSceneContainer() {
- val dependentFlag = FLAG_COMPOSE_LOCKSCREEN
+ val dependentFlag = FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
val result = FlagsParameterization.allCombinationsOf(dependentFlag).andSceneContainer()
Truth.assertThat(result).hasSize(3)
Truth.assertThat(result[0].mOverrides[dependentFlag]).isFalse()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 40fb769..614d51e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -39,6 +39,7 @@
import android.app.IActivityManager;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
+import android.graphics.Rect;
import android.platform.test.flag.junit.FlagsParameterization;
import android.testing.TestableLooper.RunWithLooper;
import android.view.View;
@@ -53,6 +54,7 @@
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.communal.domain.interactor.CommunalInteractor;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.EnableSceneContainer;
import com.android.systemui.flags.SceneContainerFlagParameterizationKt;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.kosmos.KosmosJavaAdapter;
@@ -466,6 +468,32 @@
assertThat(lp.preferredMinDisplayRefreshRate).isEqualTo(0);
}
+ @Test
+ @EnableSceneContainer
+ public void configChanged_boundsUpdate() {
+ when(mNotificationShadeWindowView.getWidth()).thenReturn(1600);
+ when(mNotificationShadeWindowView.getHeight()).thenReturn(800);
+ when(mNotificationShadeWindowView.getVisibility()).thenReturn(View.INVISIBLE);
+ Configuration newConfig = new Configuration();
+ // swap width and height in new bounds to simulate auto-rotate
+ newConfig.windowConfiguration.setBounds(new Rect(0, 0, 800, 1600));
+ mNotificationShadeWindowController.onConfigChanged(newConfig);
+ verify(mWindowManager, atLeastOnce()).updateViewLayout(any(), any());
+ }
+
+ @Test
+ @EnableSceneContainer
+ public void configChanged_boundsDontUpdate() {
+ when(mNotificationShadeWindowView.getWidth()).thenReturn(1600);
+ when(mNotificationShadeWindowView.getHeight()).thenReturn(800);
+ when(mNotificationShadeWindowView.getVisibility()).thenReturn(View.INVISIBLE);
+ Configuration newConfig = new Configuration();
+ // same bounds as view's current bounds
+ newConfig.windowConfiguration.setBounds(new Rect(0, 0, 1600, 800));
+ mNotificationShadeWindowController.onConfigChanged(newConfig);
+ verify(mWindowManager, never()).updateViewLayout(any(), any());
+ }
+
private void setKeyguardShowing() {
mNotificationShadeWindowController.setKeyguardShowing(true);
mNotificationShadeWindowController.setKeyguardGoingAway(false);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
index fb32855..0f6dc07 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
@@ -17,7 +17,9 @@
package com.android.systemui.statusbar.policy.domain.interactor
import android.app.AutomaticZenRule
+import android.app.Flags
import android.app.NotificationManager.Policy
+import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import android.provider.Settings.Secure.ZEN_DURATION
import android.provider.Settings.Secure.ZEN_DURATION_FOREVER
@@ -32,6 +34,7 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.shared.settings.data.repository.secureSettingsRepository
+import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -50,10 +53,31 @@
private val testScope = kosmos.testScope
private val zenModeRepository = kosmos.fakeZenModeRepository
private val settingsRepository = kosmos.secureSettingsRepository
+ private val deviceProvisioningRepository = kosmos.fakeDeviceProvisioningRepository
private val underTest = kosmos.zenModeInteractor
@Test
+ fun isZenAvailable_off() =
+ testScope.runTest {
+ val isZenAvailable by collectLastValue(underTest.isZenAvailable)
+ deviceProvisioningRepository.setDeviceProvisioned(false)
+ runCurrent()
+
+ assertThat(isZenAvailable).isFalse()
+ }
+
+ @Test
+ fun isZenAvailable_on() =
+ testScope.runTest {
+ val isZenAvailable by collectLastValue(underTest.isZenAvailable)
+ deviceProvisioningRepository.setDeviceProvisioned(true)
+ runCurrent()
+
+ assertThat(isZenAvailable).isTrue()
+ }
+
+ @Test
fun isZenModeEnabled_off() =
testScope.runTest {
val enabled by collectLastValue(underTest.isZenModeEnabled)
@@ -337,4 +361,22 @@
runCurrent()
assertThat(mainActiveMode).isNull()
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ fun dndMode_flows() =
+ testScope.runTest {
+ val dndMode by collectLastValue(underTest.dndMode)
+
+ zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
+ runCurrent()
+
+ assertThat(dndMode!!.isActive).isFalse()
+
+ zenModeRepository.removeMode(TestModeBuilder.MANUAL_DND_INACTIVE.id)
+ zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_ACTIVE)
+ runCurrent()
+
+ assertThat(dndMode!!.isActive).isTrue()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
index 21a45ec..9dcbe1b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
@@ -616,6 +616,71 @@
}
@Test
+ fun angleDecreaseAfterCancelAnimation_emitsStartClosingEvent() {
+ setFoldState(folded = true)
+ sendHingeAngleEvent(0)
+ foldUpdates.clear()
+
+ setFoldState(folded = false)
+ rotationListener.value.onRotationChanged(1)
+ sendHingeAngleEvent(180)
+ screenOnStatusProvider.notifyScreenTurningOn()
+ screenOnStatusProvider.notifyScreenTurnedOn()
+
+ // Start folding
+ (180 downTo 60).forEach {
+ sendHingeAngleEvent(it)
+ }
+ // Stopped folding and simulate timeout in this posture
+ simulateTimeout()
+
+ assertThat(foldUpdates)
+ .containsExactly(
+ FOLD_UPDATE_START_OPENING, // unfolded
+ FOLD_UPDATE_FINISH_HALF_OPEN, // force-finished the animation because of rotation
+ FOLD_UPDATE_START_CLOSING, // start closing the device
+ FOLD_UPDATE_FINISH_HALF_OPEN, // finished closing and timed-out in this state
+ )
+
+ }
+
+ @Test
+ fun angleIncreaseDecreaseAfterHalfUnfold_emitsStartClosingEvent() {
+ setFoldState(folded = true)
+ sendHingeAngleEvent(0)
+ foldUpdates.clear()
+
+ setFoldState(folded = false)
+ sendHingeAngleEvent(90)
+ screenOnStatusProvider.notifyScreenTurningOn()
+ screenOnStatusProvider.notifyScreenTurnedOn()
+
+ // Stopped folding and simulate timeout in this posture
+ simulateTimeout()
+
+ // Unfold further
+ (90 until 180).forEach {
+ sendHingeAngleEvent(it)
+ }
+ // Start folding
+ (180 downTo 90).forEach {
+ sendHingeAngleEvent(it)
+ }
+
+ // Stopped folding and simulate timeout in this posture
+ simulateTimeout()
+
+ assertThat(foldUpdates)
+ .containsExactly(
+ FOLD_UPDATE_START_OPENING, // unfolded
+ FOLD_UPDATE_FINISH_HALF_OPEN, // force-finished the animation because of rotation
+ FOLD_UPDATE_START_CLOSING, // start closing the device
+ FOLD_UPDATE_FINISH_HALF_OPEN, // finished closing and timed-out in this state
+ )
+
+ }
+
+ @Test
fun onUnfold_onSmallScreen_emitsStartOpening() {
// the new display state might arrive later, so it shouldn't be used to decide to send the
// start opening event, but only for the closing.
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index f9c2aef..ba3822b 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3114,6 +3114,10 @@
<string name="media_output_group_title_speakers_and_displays">Speakers & Displays</string>
<!-- Title for Suggested Devices group. [CHAR LIMIT=NONE] -->
<string name="media_output_group_title_suggested_device">Suggested Devices</string>
+ <!-- Title for input device group. [CHAR LIMIT=NONE] -->
+ <string name="media_input_group_title">Input</string>
+ <!-- Title for output device group. [CHAR LIMIT=NONE] -->
+ <string name="media_output_group_title">Output</string>
<!-- Summary for end session dialog. [CHAR LIMIT=NONE] -->
<string name="media_output_end_session_dialog_summary">Stop your shared session to move media to another device</string>
<!-- Button text for stopping session [CHAR LIMIT=60] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 7efe2dd..ffbc85c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -968,6 +968,15 @@
constraintSet.constrainWidth(mViewFlipper.getId(), MATCH_CONSTRAINT);
constraintSet.applyTo(mView);
}
+
+ @Override
+ public void onDestroy() {
+ if (mView == null) return;
+ ConstraintSet constraintSet = new ConstraintSet();
+ constraintSet.clone(mView);
+ constraintSet.clear(mViewFlipper.getId());
+ constraintSet.applyTo(mView);
+ }
}
/**
@@ -1043,12 +1052,20 @@
@Override
public void onDensityOrFontScaleChanged() {
mView.removeView(mUserSwitcherViewGroup);
+ mView.removeView(mUserSwitcher);
inflateUserSwitcher();
}
@Override
public void onDestroy() {
- mUserSwitcherController.removeUserSwitchCallback(mUserSwitchCallback);
+ ConstraintSet constraintSet = new ConstraintSet();
+ constraintSet.clone(mView);
+ constraintSet.clear(mUserSwitcherViewGroup.getId());
+ constraintSet.clear(mViewFlipper.getId());
+ constraintSet.applyTo(mView);
+
+ mView.removeView(mUserSwitcherViewGroup);
+ mView.removeView(mUserSwitcher);
}
private Drawable findLargeUserIcon(int userId) {
@@ -1344,5 +1361,13 @@
constraintSet.constrainPercentWidth(mViewFlipper.getId(), 0.5f);
constraintSet.applyTo(mView);
}
+
+ @Override
+ public void onDestroy() {
+ ConstraintSet constraintSet = new ConstraintSet();
+ constraintSet.clone(mView);
+ constraintSet.clear(mViewFlipper.getId());
+ constraintSet.applyTo(mView);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt
index 19e7537..b8c30fe 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt
@@ -76,11 +76,7 @@
/** Deliver MSDL feedback when the delete key of the pin bouncer is pressed */
fun playDeleteKeyPressFeedback() = msdlPlayer.get().playToken(MSDLToken.KEYPRESS_DELETE)
- /**
- * Deliver MSDL feedback when the delete key of the pin bouncer is long-pressed
- *
- * @return whether MSDL feedback is allowed to play.
- */
+ /** Deliver MSDL feedback when the delete key of the pin bouncer is long-pressed. */
fun playDeleteKeyLongPressedFeedback() = msdlPlayer.get().playToken(MSDLToken.LONG_PRESS)
/** Deliver MSDL feedback when a numpad key is pressed on the pin bouncer */
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
index c67b354..873d1b3 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
@@ -21,6 +21,7 @@
import com.android.systemui.authentication.domain.interactor.AuthenticationResult
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer
import com.android.systemui.lifecycle.ExclusiveActivatable
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.channels.Channel
@@ -42,6 +43,7 @@
/** Name to use for performance tracing purposes. */
val traceName: String,
+ protected val bouncerHapticPlayer: BouncerHapticPlayer? = null,
) : ExclusiveActivatable() {
private val _animateFailure = MutableStateFlow(false)
@@ -80,6 +82,8 @@
return@collectLatest
}
+ performAuthenticationHapticFeedback(authenticationResult)
+
_animateFailure.value = authenticationResult != AuthenticationResult.SUCCEEDED
clearInput()
}
@@ -112,20 +116,23 @@
/** Returns the input entered so far. */
protected abstract fun getInput(): List<Any>
+ /** Perform authentication result haptics */
+ private fun performAuthenticationHapticFeedback(result: AuthenticationResult) {
+ if (result == AuthenticationResult.SKIPPED) return
+
+ bouncerHapticPlayer?.playAuthenticationFeedback(
+ authenticationSucceeded = result == AuthenticationResult.SUCCEEDED
+ )
+ }
+
/**
* Attempts to authenticate the user using the current input value.
*
* @see BouncerInteractor.authenticate
*/
- protected fun tryAuthenticate(
- input: List<Any> = getInput(),
- useAutoConfirm: Boolean = false,
- ) {
+ protected fun tryAuthenticate(input: List<Any> = getInput(), useAutoConfirm: Boolean = false) {
authenticationRequests.trySend(AuthenticationRequest(input, useAutoConfirm))
}
- private data class AuthenticationRequest(
- val input: List<Any>,
- val useAutoConfirm: Boolean,
- )
+ private data class AuthenticationRequest(val input: List<Any>, val useAutoConfirm: Boolean)
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
index 0aada06..0bcb58d 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
@@ -30,6 +30,7 @@
import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
+import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.qualifiers.Application
@@ -61,6 +62,7 @@
private val pinViewModelFactory: PinBouncerViewModel.Factory,
private val patternViewModelFactory: PatternBouncerViewModel.Factory,
private val passwordViewModelFactory: PasswordBouncerViewModel.Factory,
+ private val bouncerHapticPlayer: BouncerHapticPlayer,
) : ExclusiveActivatable() {
private val _selectedUserImage = MutableStateFlow<Bitmap?>(null)
val selectedUserImage: StateFlow<Bitmap?> = _selectedUserImage.asStateFlow()
@@ -162,10 +164,7 @@
}
launch {
- combine(
- userSwitcher.users,
- userSwitcher.menu,
- ) { users, actions ->
+ combine(userSwitcher.users, userSwitcher.menu) { users, actions ->
users.map { user ->
UserSwitcherDropdownItemViewModel(
icon = Icon.Loaded(user.image, contentDescription = null),
@@ -178,7 +177,7 @@
icon =
Icon.Resource(
action.iconResourceId,
- contentDescription = null
+ contentDescription = null,
),
text = Text.Resource(action.textResourceId),
onClick = action.onClicked,
@@ -226,7 +225,7 @@
}
private fun getChildViewModel(
- authenticationMethod: AuthenticationMethodModel,
+ authenticationMethod: AuthenticationMethodModel
): AuthMethodBouncerViewModel? {
// If the current child view-model matches the authentication method, reuse it instead of
// creating a new instance.
@@ -241,12 +240,14 @@
authenticationMethod = authenticationMethod,
onIntentionalUserInput = ::onIntentionalUserInput,
isInputEnabled = isInputEnabled,
+ bouncerHapticPlayer = bouncerHapticPlayer,
)
is AuthenticationMethodModel.Sim ->
pinViewModelFactory.create(
authenticationMethod = authenticationMethod,
onIntentionalUserInput = ::onIntentionalUserInput,
isInputEnabled = isInputEnabled,
+ bouncerHapticPlayer = bouncerHapticPlayer,
)
is AuthenticationMethodModel.Password ->
passwordViewModelFactory.create(
@@ -257,6 +258,7 @@
patternViewModelFactory.create(
onIntentionalUserInput = ::onIntentionalUserInput,
isInputEnabled = isInputEnabled,
+ bouncerHapticPlayer = bouncerHapticPlayer,
)
else -> null
}
@@ -317,10 +319,7 @@
return when {
// The wipe dialog takes priority over the lockout dialog.
wipeText != null ->
- DialogViewModel(
- text = wipeText,
- onDismiss = { wipeDialogMessage.value = null },
- )
+ DialogViewModel(text = wipeText, onDismiss = { wipeDialogMessage.value = null })
lockoutText != null ->
DialogViewModel(
text = lockoutText,
@@ -338,7 +337,7 @@
fun onKeyEvent(keyEvent: KeyEvent): Boolean {
return (authMethodViewModel.value as? PinBouncerViewModel)?.onKeyEvent(
keyEvent.type,
- keyEvent.nativeKeyEvent.keyCode
+ keyEvent.nativeKeyEvent.keyCode,
) ?: false
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
index 0a866b4..158f102 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
@@ -18,9 +18,11 @@
import android.content.Context
import android.util.TypedValue
+import android.view.View
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer
import com.android.systemui.res.R
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -35,7 +37,6 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
/** Holds UI state and handles user input for the pattern bouncer UI. */
@@ -44,6 +45,7 @@
constructor(
private val applicationContext: Context,
interactor: BouncerInteractor,
+ @Assisted bouncerHapticPlayer: BouncerHapticPlayer,
@Assisted isInputEnabled: StateFlow<Boolean>,
@Assisted private val onIntentionalUserInput: () -> Unit,
) :
@@ -51,6 +53,7 @@
interactor = interactor,
isInputEnabled = isInputEnabled,
traceName = "PatternBouncerViewModel",
+ bouncerHapticPlayer = bouncerHapticPlayer,
) {
/** The number of columns in the dot grid. */
@@ -190,14 +193,7 @@
private fun defaultDots(): List<PatternDotViewModel> {
return buildList {
(0 until columnCount).forEach { x ->
- (0 until rowCount).forEach { y ->
- add(
- PatternDotViewModel(
- x = x,
- y = y,
- )
- )
- }
+ (0 until rowCount).forEach { y -> add(PatternDotViewModel(x = x, y = y)) }
}
}
}
@@ -207,14 +203,17 @@
applicationContext.resources.getValue(
com.android.internal.R.dimen.lock_pattern_dot_hit_factor,
outValue,
- true
+ true,
)
max(min(outValue.float, 1f), MIN_DOT_HIT_FACTOR)
}
+ fun performDotFeedback(view: View?) = bouncerHapticPlayer?.playPatternDotFeedback(view)
+
@AssistedFactory
interface Factory {
fun create(
+ bouncerHapticPlayer: BouncerHapticPlayer,
isInputEnabled: StateFlow<Boolean>,
onIntentionalUserInput: () -> Unit,
): PatternBouncerViewModel
@@ -231,7 +230,7 @@
*/
private fun PatternDotViewModel.isOnLineSegment(
first: PatternDotViewModel,
- second: PatternDotViewModel
+ second: PatternDotViewModel,
): Boolean {
val anotherPoint = this
// No need to consider any points outside the bounds of two end points
@@ -253,14 +252,8 @@
return (this in a..b) || (this in b..a)
}
-data class PatternDotViewModel(
- val x: Int,
- val y: Int,
-) {
+data class PatternDotViewModel(val x: Int, val y: Int) {
fun toCoordinate(): AuthenticationPatternCoordinate {
- return AuthenticationPatternCoordinate(
- x = x,
- y = y,
- )
+ return AuthenticationPatternCoordinate(x = x, y = y)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index da29c62..0cb4260 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -19,12 +19,14 @@
package com.android.systemui.bouncer.ui.viewmodel
import android.content.Context
+import android.view.HapticFeedbackConstants
import android.view.KeyEvent.KEYCODE_0
import android.view.KeyEvent.KEYCODE_9
import android.view.KeyEvent.KEYCODE_DEL
import android.view.KeyEvent.KEYCODE_NUMPAD_0
import android.view.KeyEvent.KEYCODE_NUMPAD_9
import android.view.KeyEvent.isConfirmKey
+import android.view.View
import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.input.key.KeyEventType
import com.android.keyguard.PinShapeAdapter
@@ -32,6 +34,7 @@
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor
import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
+import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer
import com.android.systemui.res.R
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -56,6 +59,7 @@
applicationContext: Context,
interactor: BouncerInteractor,
private val simBouncerInteractor: SimBouncerInteractor,
+ @Assisted bouncerHapticPlayer: BouncerHapticPlayer,
@Assisted isInputEnabled: StateFlow<Boolean>,
@Assisted private val onIntentionalUserInput: () -> Unit,
@Assisted override val authenticationMethod: AuthenticationMethodModel,
@@ -64,6 +68,7 @@
interactor = interactor,
isInputEnabled = isInputEnabled,
traceName = "PinBouncerViewModel",
+ bouncerHapticPlayer = bouncerHapticPlayer,
) {
/**
* Whether the sim-related UI in the pin view is showing.
@@ -126,10 +131,9 @@
.collect { _hintedPinLength.value = it }
}
launch {
- combine(
- mutablePinInput,
- interactor.isAutoConfirmEnabled,
- ) { mutablePinEntries, isAutoConfirmEnabled ->
+ combine(mutablePinInput, interactor.isAutoConfirmEnabled) {
+ mutablePinEntries,
+ isAutoConfirmEnabled ->
computeBackspaceButtonAppearance(
pinInput = mutablePinEntries,
isAutoConfirmEnabled = isAutoConfirmEnabled,
@@ -183,8 +187,22 @@
mutablePinInput.value = mutablePinInput.value.deleteLast()
}
+ fun onBackspaceButtonPressed(view: View?) {
+ if (bouncerHapticPlayer?.isEnabled == true) {
+ bouncerHapticPlayer.playDeleteKeyPressFeedback()
+ } else {
+ view?.performHapticFeedback(
+ HapticFeedbackConstants.VIRTUAL_KEY,
+ HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING,
+ )
+ }
+ }
+
/** Notifies that the user long-pressed the backspace button. */
fun onBackspaceButtonLongPressed() {
+ if (bouncerHapticPlayer?.isEnabled == true) {
+ bouncerHapticPlayer.playDeleteKeyLongPressedFeedback()
+ }
clearInput()
}
@@ -266,13 +284,24 @@
}
}
- /** Notifies that the user has pressed down on a digit button. */
- fun onDigitButtonDown() {
+ /**
+ * Notifies that the user has pressed down on a digit button. This function also performs haptic
+ * feedback on the view.
+ */
+ fun onDigitButtonDown(view: View?) {
if (ComposeBouncerFlags.isOnlyComposeBouncerEnabled()) {
// Current PIN bouncer informs FalsingInteractor#avoidGesture() upon every Pin button
// touch.
super.onDown()
}
+ if (bouncerHapticPlayer?.isEnabled == true) {
+ bouncerHapticPlayer.playNumpadKeyFeedback()
+ } else {
+ view?.performHapticFeedback(
+ HapticFeedbackConstants.VIRTUAL_KEY,
+ HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING,
+ )
+ }
}
@AssistedFactory
@@ -281,6 +310,7 @@
isInputEnabled: StateFlow<Boolean>,
onIntentionalUserInput: () -> Unit,
authenticationMethod: AuthenticationMethodModel,
+ bouncerHapticPlayer: BouncerHapticPlayer,
): PinBouncerViewModel
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
index 769976e..ae4b679d 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
@@ -282,6 +282,8 @@
return !mRecentKeyEvents.isEmpty();
}
+ // Deprecated in favor of {@code isTouchScreenSource}, b/329221787
+ @Deprecated
public boolean isFromTrackpad() {
if (Flags.nonTouchscreenDevicesBypassFalsing()) {
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index c7a47b1..1ada56d 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -30,6 +30,7 @@
import android.content.ClipboardManager;
import android.content.Context;
import android.os.Build;
+import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
@@ -37,6 +38,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.user.utils.UserScopedService;
import javax.inject.Inject;
import javax.inject.Provider;
@@ -67,13 +69,13 @@
public ClipboardListener(Context context,
Provider<ClipboardOverlayController> clipboardOverlayControllerProvider,
ClipboardToast clipboardToast,
- ClipboardManager clipboardManager,
+ UserScopedService<ClipboardManager> clipboardManager,
KeyguardManager keyguardManager,
UiEventLogger uiEventLogger) {
mContext = context;
mOverlayProvider = clipboardOverlayControllerProvider;
mClipboardToast = clipboardToast;
- mClipboardManager = clipboardManager;
+ mClipboardManager = clipboardManager.forUser(UserHandle.CURRENT);
mKeyguardManager = keyguardManager;
mUiEventLogger = uiEventLogger;
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index 6e01393..08a7c39 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -19,11 +19,13 @@
import android.provider.Settings
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.TransitionKey
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.CoreStartable
import com.android.systemui.Flags.communalSceneKtfRefactor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
+import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
import com.android.systemui.communal.shared.model.EditModeState
@@ -84,6 +86,7 @@
@Application private val applicationScope: CoroutineScope,
@Background private val bgScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
+ private val uiEventLogger: UiEventLogger,
) : CoreStartable {
private var screenTimeout: Int = DEFAULT_SCREEN_TIMEOUT
@@ -146,7 +149,7 @@
screenTimeout =
systemSettings.getInt(
Settings.System.SCREEN_OFF_TIMEOUT,
- DEFAULT_SCREEN_TIMEOUT
+ DEFAULT_SCREEN_TIMEOUT,
)
}
.launchIn(bgScope)
@@ -160,7 +163,7 @@
combine(
communalSceneInteractor.currentScene,
// Emit a value on start so the combine starts.
- communalInteractor.userActivity.emitOnStart()
+ communalInteractor.userActivity.emitOnStart(),
) { scene, _ ->
// Only timeout if we're on the hub is open.
scene == CommunalScenes.Communal
@@ -184,6 +187,7 @@
CommunalScenes.Blank,
"dream started after timeout",
)
+ uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT)
}
}
}
@@ -212,6 +216,7 @@
newScene = CommunalScenes.Blank,
loggingReason = "hub timeout",
)
+ uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT)
}
timeoutJob = null
}
@@ -219,7 +224,7 @@
}
private suspend fun determineSceneAfterTransition(
- lastStartedTransition: TransitionStep,
+ lastStartedTransition: TransitionStep
): Pair<SceneKey, TransitionKey>? {
val to = lastStartedTransition.to
val from = lastStartedTransition.from
@@ -251,9 +256,8 @@
Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade)
}
from == KeyguardState.DOZING && to == KeyguardState.GLANCEABLE_HUB -> {
- // Make sure the communal hub is showing (immediately, not fading in) when
- // transitioning from dozing to hub.
- Pair(CommunalScenes.Communal, CommunalTransitionKeys.Immediately)
+ // Make sure the communal hub is showing when transitioning from dozing to hub.
+ Pair(CommunalScenes.Communal, CommunalTransitionKeys.SimpleFade)
}
else -> null
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt
index 11fb233..78156db 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt
@@ -30,6 +30,4 @@
val ToEditMode = TransitionKey("ToEditMode")
/** Transition to the glanceable hub after exiting edit mode */
val FromEditMode = TransitionKey("FromEditMode")
- /** Immediately transitions without any delay */
- val Immediately = TransitionKey("Immediately")
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 8818c3a..78a8a42 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -113,6 +113,7 @@
import android.view.inputmethod.InputMethodManager;
import android.view.textclassifier.TextClassificationManager;
+import androidx.annotation.NonNull;
import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
import androidx.core.app.NotificationManagerCompat;
@@ -718,6 +719,19 @@
@Provides
@Singleton
+ static ViewCaptureAwareWindowManager.Factory viewCaptureAwareWindowManagerFactory(
+ Lazy<ViewCapture> daggerLazyViewCapture) {
+ return new ViewCaptureAwareWindowManager.Factory() {
+ @NonNull
+ @Override
+ public ViewCaptureAwareWindowManager create(@NonNull WindowManager windowManager) {
+ return provideViewCaptureAwareWindowManager(windowManager, daggerLazyViewCapture);
+ }
+ };
+ }
+
+ @Provides
+ @Singleton
static PermissionManager providePermissionManager(Context context) {
PermissionManager pm = context.getSystemService(PermissionManager.class);
if (pm != null) {
@@ -733,9 +747,8 @@
}
@Provides
- @Singleton
- static ClipboardManager provideClipboardManager(Context context) {
- return context.getSystemService(ClipboardManager.class);
+ static UserScopedService<ClipboardManager> provideClipboardManager(Context context) {
+ return new UserScopedServiceImpl<>(context, ClipboardManager.class);
}
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
index e17e530..5259c5d 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
@@ -26,7 +26,9 @@
import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource
import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus
import com.android.systemui.flags.SystemPropertiesHelper
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.TrustInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import javax.inject.Inject
@@ -36,6 +38,7 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
@@ -57,6 +60,7 @@
private val powerInteractor: PowerInteractor,
private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
private val systemPropertiesHelper: SystemPropertiesHelper,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
) {
private val deviceUnlockSource =
@@ -74,7 +78,7 @@
trustInteractor.isTrusted.filter { it }.map { DeviceUnlockSource.TrustAgent },
authenticationInteractor.onAuthenticationResult
.filter { it }
- .map { DeviceUnlockSource.BouncerInput }
+ .map { DeviceUnlockSource.BouncerInput },
)
private val faceEnrolledAndEnabled = biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled
@@ -170,10 +174,20 @@
combine(
powerInteractor.isAsleep,
isInLockdown,
- ::Pair,
+ keyguardTransitionInteractor
+ .transitionValue(KeyguardState.AOD)
+ .map { it == 1f }
+ .distinctUntilChanged(),
+ ::Triple,
)
- .flatMapLatestConflated { (isAsleep, isInLockdown) ->
- if (isAsleep || isInLockdown) {
+ .flatMapLatestConflated { (isAsleep, isInLockdown, isAod) ->
+ val isForceLocked =
+ when {
+ isAsleep && !isAod -> true
+ isInLockdown -> true
+ else -> false
+ }
+ if (isForceLocked) {
flowOf(DeviceUnlockStatus(false, null))
} else {
deviceUnlockSource.map { DeviceUnlockStatus(true, it) }
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt b/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
index 1daaa11..500c5b3 100644
--- a/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.education.data.model
+import com.android.systemui.contextualeducation.GestureType
import java.time.Instant
/**
@@ -23,6 +24,7 @@
* gesture stores its own model separately.
*/
data class GestureEduModel(
+ val gestureType: GestureType,
val signalCount: Int = 0,
val educationShownCount: Int = 0,
val lastShortcutTriggeredTime: Instant? = null,
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
index 01f838f..2978595 100644
--- a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
@@ -17,6 +17,8 @@
package com.android.systemui.education.data.repository
import android.content.Context
+import android.hardware.input.InputManager
+import android.hardware.input.KeyGestureEvent
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.MutablePreferences
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
@@ -25,23 +27,31 @@
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.preferencesDataStoreFile
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.contextualeducation.GestureType
+import com.android.systemui.contextualeducation.GestureType.ALL_APPS
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.education.dagger.ContextualEducationModule.EduDataStoreScope
import com.android.systemui.education.data.model.EduDeviceConnectionTime
import com.android.systemui.education.data.model.GestureEduModel
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import java.time.Instant
+import java.util.concurrent.Executor
import javax.inject.Inject
import javax.inject.Provider
import kotlin.properties.Delegates.notNull
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
+import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
/**
@@ -64,6 +74,8 @@
suspend fun updateEduDeviceConnectionTime(
transform: (EduDeviceConnectionTime) -> EduDeviceConnectionTime
)
+
+ val keyboardShortcutTriggered: Flow<GestureType>
}
/**
@@ -75,9 +87,13 @@
@Inject
constructor(
@Application private val applicationContext: Context,
- @EduDataStoreScope private val dataStoreScopeProvider: Provider<CoroutineScope>
+ @EduDataStoreScope private val dataStoreScopeProvider: Provider<CoroutineScope>,
+ private val inputManager: InputManager,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
) : ContextualEducationRepository {
companion object {
+ const val TAG = "UserContextualEducationRepository"
+
const val SIGNAL_COUNT_SUFFIX = "_SIGNAL_COUNT"
const val NUMBER_OF_EDU_SHOWN_SUFFIX = "_NUMBER_OF_EDU_SHOWN"
const val LAST_SHORTCUT_TRIGGERED_TIME_SUFFIX = "_LAST_SHORTCUT_TRIGGERED_TIME"
@@ -98,6 +114,30 @@
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
private val prefData: Flow<Preferences> = datastore.filterNotNull().flatMapLatest { it.data }
+ override val keyboardShortcutTriggered: Flow<GestureType> =
+ conflatedCallbackFlow {
+ val listener =
+ InputManager.KeyGestureEventListener { event ->
+ // Only store keyboard shortcut time for gestures providing keyboard
+ // education
+ val shortcutType =
+ when (event.keyGestureType) {
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS,
+ KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS -> ALL_APPS
+
+ else -> null
+ }
+
+ if (shortcutType != null) {
+ trySendWithFailureLogging(shortcutType, TAG)
+ }
+ }
+
+ inputManager.registerKeyGestureEventListener(Executor(Runnable::run), listener)
+ awaitClose { inputManager.unregisterKeyGestureEventListener(listener) }
+ }
+ .flowOn(backgroundDispatcher)
+
override fun setUser(userId: Int) {
dataStoreScope?.cancel()
val newDsScope = dataStoreScopeProvider.get()
@@ -136,7 +176,8 @@
preferences[getLastEducationTimeKey(gestureType)]?.let {
Instant.ofEpochSecond(it)
},
- userId = userId
+ userId = userId,
+ gestureType = gestureType,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
index 10be26e..c88b364 100644
--- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
@@ -18,7 +18,10 @@
import com.android.systemui.CoreStartable
import com.android.systemui.contextualeducation.GestureType
+import com.android.systemui.contextualeducation.GestureType.ALL_APPS
import com.android.systemui.contextualeducation.GestureType.BACK
+import com.android.systemui.contextualeducation.GestureType.HOME
+import com.android.systemui.contextualeducation.GestureType.OVERVIEW
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.education.dagger.ContextualEducationModule.EduClock
@@ -53,6 +56,13 @@
) : CoreStartable {
val backGestureModelFlow = readEduModelsOnSignalCountChanged(BACK)
+ val homeGestureModelFlow = readEduModelsOnSignalCountChanged(HOME)
+ val overviewGestureModelFlow = readEduModelsOnSignalCountChanged(OVERVIEW)
+ val allAppsGestureModelFlow = readEduModelsOnSignalCountChanged(ALL_APPS)
+ val eduDeviceConnectionTimeFlow =
+ repository.readEduDeviceConnectionTime().distinctUntilChanged()
+
+ val keyboardShortcutTriggered = repository.keyboardShortcutTriggered
override fun start() {
backgroundScope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
index 43855d9..faee326 100644
--- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
@@ -16,15 +16,8 @@
package com.android.systemui.education.domain.interactor
-import android.hardware.input.InputManager
-import android.hardware.input.InputManager.KeyGestureEventListener
-import android.hardware.input.KeyGestureEvent
import android.os.SystemProperties
import com.android.systemui.CoreStartable
-import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.contextualeducation.GestureType
-import com.android.systemui.contextualeducation.GestureType.ALL_APPS
-import com.android.systemui.contextualeducation.GestureType.BACK
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.education.dagger.ContextualEducationModule.EduClock
@@ -32,19 +25,19 @@
import com.android.systemui.education.shared.model.EducationInfo
import com.android.systemui.education.shared.model.EducationUiType
import com.android.systemui.inputdevice.data.repository.UserInputDeviceRepository
-import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import java.time.Clock
-import java.util.concurrent.Executor
import javax.inject.Inject
import kotlin.time.Duration
import kotlin.time.Duration.Companion.days
import kotlin.time.DurationUnit
import kotlin.time.toDuration
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.launch
/** Allow listening to new contextual education triggered */
@@ -55,7 +48,6 @@
@Background private val backgroundScope: CoroutineScope,
private val contextualEducationInteractor: ContextualEducationInteractor,
private val userInputDeviceRepository: UserInputDeviceRepository,
- private val inputManager: InputManager,
@EduClock private val clock: Clock,
) : CoreStartable {
@@ -82,34 +74,32 @@
private val _educationTriggered = MutableStateFlow<EducationInfo?>(null)
val educationTriggered = _educationTriggered.asStateFlow()
- private val keyboardShortcutTriggered: Flow<GestureType> = conflatedCallbackFlow {
- val listener = KeyGestureEventListener { event ->
- // Only store keyboard shortcut time for gestures providing keyboard education
- val shortcutType =
- when (event.keyGestureType) {
- KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS -> ALL_APPS
- else -> null
- }
-
- if (shortcutType != null) {
- trySendWithFailureLogging(shortcutType, TAG)
- }
- }
-
- inputManager.registerKeyGestureEventListener(Executor(Runnable::run), listener)
- awaitClose { inputManager.unregisterKeyGestureEventListener(listener) }
- }
-
+ @OptIn(ExperimentalCoroutinesApi::class)
override fun start() {
backgroundScope.launch {
- contextualEducationInteractor.backGestureModelFlow.collect {
- if (isUsageSessionExpired(it)) {
- contextualEducationInteractor.startNewUsageSession(BACK)
- } else if (isEducationNeeded(it)) {
- _educationTriggered.value = EducationInfo(BACK, getEduType(it), it.userId)
- contextualEducationInteractor.updateOnEduTriggered(BACK)
+ contextualEducationInteractor.eduDeviceConnectionTimeFlow
+ .flatMapLatest {
+ val gestureFlows = mutableListOf<Flow<GestureEduModel>>()
+ if (it.touchpadFirstConnectionTime != null) {
+ gestureFlows.add(contextualEducationInteractor.backGestureModelFlow)
+ gestureFlows.add(contextualEducationInteractor.homeGestureModelFlow)
+ gestureFlows.add(contextualEducationInteractor.overviewGestureModelFlow)
+ }
+
+ if (it.keyboardFirstConnectionTime != null) {
+ gestureFlows.add(contextualEducationInteractor.allAppsGestureModelFlow)
+ }
+ gestureFlows.merge()
}
- }
+ .collect {
+ if (isUsageSessionExpired(it)) {
+ contextualEducationInteractor.startNewUsageSession(it.gestureType)
+ } else if (isEducationNeeded(it)) {
+ _educationTriggered.value =
+ EducationInfo(it.gestureType, getEduType(it), it.userId)
+ contextualEducationInteractor.updateOnEduTriggered(it.gestureType)
+ }
+ }
}
backgroundScope.launch {
@@ -139,7 +129,7 @@
}
backgroundScope.launch {
- keyboardShortcutTriggered.collect {
+ contextualEducationInteractor.keyboardShortcutTriggered.collect {
contextualEducationInteractor.updateShortcutTriggerTime(it)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 6318dc0..0b775ab 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -31,9 +31,7 @@
import com.android.systemui.Flags.statusBarScreenSharingChips
import com.android.systemui.Flags.statusBarUseReposForCallChip
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.MigrateClocksToBlueprint
-import com.android.systemui.keyguard.shared.ComposeLockscreen
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag
@@ -62,10 +60,6 @@
// SceneContainer dependencies
SceneContainerFlag.getFlagDependencies().forEach { (alpha, beta) -> alpha dependsOn beta }
- // ComposeLockscreen dependencies
- ComposeLockscreen.token dependsOn KeyguardBottomAreaRefactor.token
- ComposeLockscreen.token dependsOn MigrateClocksToBlueprint.token
-
// CommunalHub dependencies
communalHub dependsOn MigrateClocksToBlueprint.token
@@ -99,7 +93,7 @@
get() =
FlagToken(
FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON,
- statusBarCallChipNotificationIcon()
+ statusBarCallChipNotificationIcon(),
)
private inline val statusBarScreenSharingChipsToken
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
index e50c05c..e09e198 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -29,6 +29,7 @@
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
import com.android.systemui.log.dagger.QSLog
+import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -44,12 +45,12 @@
* @property[vibratorHelper] The [VibratorHelper] to deliver haptic effects.
* @property[effectDuration] The duration of the effect in ms.
*/
-// TODO(b/332902869): In addition from being injectable, we can consider making it a singleton
class QSLongPressEffect
@Inject
constructor(
private val vibratorHelper: VibratorHelper?,
private val keyguardStateController: KeyguardStateController,
+ private val falsingManager: FalsingManager,
@QSLog private val logBuffer: LogBuffer,
) {
@@ -72,7 +73,7 @@
private val durations =
vibratorHelper?.getPrimitiveDurations(
VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
- VibrationEffect.Composition.PRIMITIVE_SPIN
+ VibrationEffect.Composition.PRIMITIVE_SPIN,
)
private var longPressHint: VibrationEffect? = null
@@ -152,15 +153,27 @@
logEvent(qsTile?.tileSpec, state, "animation completed")
when (state) {
State.RUNNING_FORWARD -> {
- vibrate(snapEffect)
- if (keyguardStateController.isUnlocked) {
- setState(State.LONG_CLICKED)
- } else {
+ val wasFalseLongTap = falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)
+ if (wasFalseLongTap) {
callback?.onResetProperties()
setState(State.IDLE)
+ logEvent(qsTile?.tileSpec, state, "false long click. No action triggered")
+ } else if (keyguardStateController.isUnlocked) {
+ vibrate(snapEffect)
+ setState(State.LONG_CLICKED)
+ qsTile?.longClick(expandable)
+ logEvent(qsTile?.tileSpec, state, "long click action triggered")
+ } else {
+ vibrate(snapEffect)
+ callback?.onResetProperties()
+ setState(State.IDLE)
+ qsTile?.longClick(expandable)
+ logEvent(
+ qsTile?.tileSpec,
+ state,
+ "properties reset and long click action triggered",
+ )
}
- logEvent(qsTile?.tileSpec, state, "long click action triggered")
- qsTile?.longClick(expandable)
}
State.RUNNING_BACKWARDS_FROM_UP -> {
callback?.onEffectFinishedReversing()
@@ -236,7 +249,7 @@
LongPressHapticBuilder.createLongPressHint(
durations?.get(0) ?: LongPressHapticBuilder.INVALID_DURATION,
durations?.get(1) ?: LongPressHapticBuilder.INVALID_DURATION,
- effectDuration
+ effectDuration,
)
setState(State.IDLE)
return true
@@ -265,7 +278,7 @@
}
override fun dialogTransitionController(
- cuj: DialogCuj?,
+ cuj: DialogCuj?
): DialogTransitionAnimator.Controller? =
DialogTransitionAnimator.Controller.fromView(view, cuj)
}
@@ -298,7 +311,7 @@
str2 = event
str3 = state.name
},
- { "[long-press effect on $str1 tile] $str2 on state: $str3" }
+ { "[long-press effect on $str1 tile] $str2 on state: $str3" },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/InputDeviceTutorialLogger.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/InputDeviceTutorialLogger.kt
index 9525174..48f5cb6 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/InputDeviceTutorialLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/InputDeviceTutorialLogger.kt
@@ -16,27 +16,27 @@
package com.android.systemui.inputdevice.tutorial
+import com.android.systemui.inputdevice.tutorial.domain.interactor.ConnectionState
+import com.android.systemui.inputdevice.tutorial.ui.viewmodel.Screen as KeyboardTouchpadTutorialScreen
+import com.android.systemui.log.ConstantStringsLogger
+import com.android.systemui.log.ConstantStringsLoggerImpl
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.MessageInitializer
+import com.android.systemui.log.core.MessagePrinter
import com.android.systemui.log.dagger.InputDeviceTutorialLog
-import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen
-import com.google.errorprone.annotations.CompileTimeConstant
+import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen as TouchpadTutorialScreen
import javax.inject.Inject
private const val TAG = "InputDeviceTutorial"
class InputDeviceTutorialLogger
@Inject
-constructor(@InputDeviceTutorialLog private val buffer: LogBuffer) {
+constructor(@InputDeviceTutorialLog private val buffer: LogBuffer) :
+ ConstantStringsLogger by ConstantStringsLoggerImpl(buffer, TAG) {
- fun log(@CompileTimeConstant s: String) {
- buffer.log(TAG, LogLevel.INFO, message = s)
- }
-
- fun logGoingToScreen(screen: Screen, context: TutorialContext) {
- buffer.log(
- TAG,
- LogLevel.INFO,
+ fun logGoingToScreen(screen: TouchpadTutorialScreen, context: TutorialContext) {
+ logInfo(
{
str1 = screen.toString()
str2 = context.string
@@ -46,7 +46,58 @@
}
fun logCloseTutorial(context: TutorialContext) {
- buffer.log(TAG, LogLevel.INFO, { str1 = context.string }, { "Closing $str1" })
+ logInfo({ str1 = context.string }, { "Closing $str1" })
+ }
+
+ fun logOpenTutorial(context: TutorialContext) {
+ logInfo({ str1 = context.string }, { "Opening $str1" })
+ }
+
+ fun logNextScreenMissingHardware(nextScreen: KeyboardTouchpadTutorialScreen) {
+ buffer.log(
+ TAG,
+ LogLevel.WARNING,
+ { str1 = nextScreen.toString() },
+ { "next screen should be $str1 but required hardware is missing" }
+ )
+ }
+
+ fun logNextScreen(nextScreen: KeyboardTouchpadTutorialScreen) {
+ logInfo({ str1 = nextScreen.toString() }, { "going to $str1 screen" })
+ }
+
+ fun logNewConnectionState(connectionState: ConnectionState) {
+ logInfo(
+ {
+ bool1 = connectionState.touchpadConnected
+ bool2 = connectionState.keyboardConnected
+ },
+ { "Received connection state: touchpad connected: $bool1 keyboard connected: $bool2" }
+ )
+ }
+
+ fun logMovingBetweenScreens(
+ previousScreen: KeyboardTouchpadTutorialScreen?,
+ currentScreen: KeyboardTouchpadTutorialScreen
+ ) {
+ logInfo(
+ {
+ str1 = previousScreen?.toString() ?: "NO_SCREEN"
+ str2 = currentScreen.toString()
+ },
+ { "Moving from $str1 screen to $str2 screen" }
+ )
+ }
+
+ fun logGoingBack(previousScreen: KeyboardTouchpadTutorialScreen) {
+ logInfo({ str1 = previousScreen.toString() }, { "Going back to $str1 screen" })
+ }
+
+ private inline fun logInfo(
+ messageInitializer: MessageInitializer,
+ noinline messagePrinter: MessagePrinter
+ ) {
+ buffer.log(TAG, LogLevel.INFO, messageInitializer, messagePrinter)
}
enum class TutorialContext(val string: String) {
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
index 1adc285..c130c6c 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
@@ -28,6 +28,8 @@
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import com.android.compose.theme.PlatformTheme
+import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger
+import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger.TutorialContext
import com.android.systemui.inputdevice.tutorial.TouchpadTutorialScreensProvider
import com.android.systemui.inputdevice.tutorial.ui.composable.ActionKeyTutorialScreen
import com.android.systemui.inputdevice.tutorial.ui.viewmodel.KeyboardTouchpadTutorialViewModel
@@ -48,6 +50,7 @@
constructor(
private val viewModelFactoryAssistedProvider: ViewModelFactoryAssistedProvider,
private val touchpadTutorialScreensProvider: Optional<TouchpadTutorialScreensProvider>,
+ private val logger: InputDeviceTutorialLogger,
) : ComponentActivity() {
companion object {
@@ -74,6 +77,7 @@
lifecycleScope.launch {
vm.closeActivity.collect { finish ->
if (finish) {
+ logger.logCloseTutorial(TutorialContext.KEYBOARD_TOUCHPAD_TUTORIAL)
finish()
}
}
@@ -81,6 +85,9 @@
setContent {
PlatformTheme { KeyboardTouchpadTutorialContainer(vm, touchpadTutorialScreensProvider) }
}
+ if (savedInstanceState == null) {
+ logger.logOpenTutorial(TutorialContext.KEYBOARD_TOUCHPAD_TUTORIAL)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt
index 315c102..5cf1967 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt
@@ -22,6 +22,7 @@
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger
import com.android.systemui.inputdevice.tutorial.domain.interactor.ConnectionState
import com.android.systemui.inputdevice.tutorial.domain.interactor.KeyboardTouchpadConnectionInteractor
import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEY
@@ -47,6 +48,7 @@
private val gesturesInteractor: Optional<TouchpadGesturesInteractor>,
private val keyboardTouchpadConnectionInteractor: KeyboardTouchpadConnectionInteractor,
private val hasTouchpadTutorialScreens: Boolean,
+ private val logger: InputDeviceTutorialLogger,
handle: SavedStateHandle
) : ViewModel(), DefaultLifecycleObserver {
@@ -68,7 +70,10 @@
init {
viewModelScope.launch {
- keyboardTouchpadConnectionInteractor.connectionState.collect { connectionState = it }
+ keyboardTouchpadConnectionInteractor.connectionState.collect {
+ logger.logNewConnectionState(connectionState)
+ connectionState = it
+ }
}
viewModelScope.launch {
@@ -89,7 +94,14 @@
viewModelScope.launch {
// close activity if screen requires touchpad but we don't have it. This can only happen
// when current sysui build doesn't contain touchpad module dependency
- _screen.filterNot { it.canBeShown() }.collect { _closeActivity.value = true }
+ _screen
+ .filterNot { it.canBeShown() }
+ .collect {
+ logger.e(
+ "Touchpad is connected but touchpad module is missing, something went wrong"
+ )
+ _closeActivity.value = true
+ }
}
}
@@ -114,11 +126,14 @@
if (requiredHardwarePresent(nextScreen)) {
break
}
+ logger.logNextScreenMissingHardware(nextScreen)
nextScreen = nextScreen.next()
}
if (nextScreen == null) {
+ logger.d("Final screen reached, closing tutorial")
_closeActivity.value = true
} else {
+ logger.logNextScreen(nextScreen)
_screen.value = nextScreen
screensBackStack.add(nextScreen)
}
@@ -127,6 +142,7 @@
private fun Screen.canBeShown() = requiredHardware != TOUCHPAD || hasTouchpadTutorialScreens
private fun setupDeviceState(previousScreen: Screen?, currentScreen: Screen) {
+ logger.logMovingBetweenScreens(previousScreen, currentScreen)
if (previousScreen?.requiredHardware == currentScreen.requiredHardware) return
previousScreen?.let { clearDeviceStateForScreen(it) }
when (currentScreen.requiredHardware) {
@@ -153,6 +169,7 @@
_closeActivity.value = true
} else {
screensBackStack.removeLast()
+ logger.logGoingBack(screensBackStack.last())
_screen.value = screensBackStack.last()
}
}
@@ -162,6 +179,7 @@
constructor(
private val gesturesInteractor: Optional<TouchpadGesturesInteractor>,
private val keyboardTouchpadConnected: KeyboardTouchpadConnectionInteractor,
+ private val logger: InputDeviceTutorialLogger,
@Assisted private val hasTouchpadTutorialScreens: Boolean,
) : AbstractSavedStateViewModelFactory() {
@@ -180,6 +198,7 @@
gesturesInteractor,
keyboardTouchpadConnected,
hasTouchpadTutorialScreens,
+ logger,
handle
)
as T
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index df0f10a..416eaba 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -24,12 +24,6 @@
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
-import androidx.constraintlayout.widget.ConstraintSet
-import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
-import androidx.constraintlayout.widget.ConstraintSet.END
-import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
-import androidx.constraintlayout.widget.ConstraintSet.START
-import androidx.constraintlayout.widget.ConstraintSet.TOP
import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayout
@@ -47,7 +41,6 @@
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
-import com.android.systemui.keyguard.shared.ComposeLockscreen
import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint
import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
@@ -128,7 +121,7 @@
keyguardStatusViewComponentFactory.build(
LayoutInflater.from(context).inflate(R.layout.keyguard_status_view, null)
as KeyguardStatusView,
- context.display
+ context.display,
)
val controller = statusViewComponent.keyguardStatusViewController
controller.init()
@@ -143,29 +136,12 @@
initializeViews()
if (!SceneContainerFlag.isEnabled) {
- if (ComposeLockscreen.isEnabled) {
- val composeView =
- createLockscreen(
- context = context,
- viewModelFactory = lockscreenContentViewModelFactory,
- blueprints = lockscreenSceneBlueprintsLazy.get(),
- )
- composeView.id = View.generateViewId()
- val cs = ConstraintSet()
- cs.clone(keyguardRootView)
- cs.connect(composeView.id, START, PARENT_ID, START)
- cs.connect(composeView.id, END, PARENT_ID, END)
- cs.connect(composeView.id, TOP, PARENT_ID, TOP)
- cs.connect(composeView.id, BOTTOM, PARENT_ID, BOTTOM)
- keyguardRootView.addView(composeView)
- } else {
- KeyguardBlueprintViewBinder.bind(
- keyguardRootView,
- keyguardBlueprintViewModel,
- keyguardClockViewModel,
- smartspaceViewModel,
- )
- }
+ KeyguardBlueprintViewBinder.bind(
+ keyguardRootView,
+ keyguardBlueprintViewModel,
+ keyguardClockViewModel,
+ smartspaceViewModel,
+ )
}
if (deviceEntryUnlockTrackerViewBinder.isPresent) {
deviceEntryUnlockTrackerViewBinder.get().bind(keyguardRootView)
@@ -247,7 +223,7 @@
LockscreenContent(
viewModelFactory = viewModelFactory,
blueprints = sceneBlueprints,
- clockInteractor = clockInteractor
+ clockInteractor = clockInteractor,
)
) {
Content(modifier = Modifier.fillMaxSize())
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 1a0525d..ba23eb3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -346,6 +346,7 @@
private boolean mShuttingDown;
private boolean mDozing;
private boolean mAnimatingScreenOff;
+ private boolean mIgnoreDismiss;
private final Context mContext;
private final FalsingCollector mFalsingCollector;
@@ -627,18 +628,20 @@
public void onUserSwitching(int userId) {
Log.d(TAG, String.format("onUserSwitching %d", userId));
synchronized (KeyguardViewMediator.this) {
+ mIgnoreDismiss = true;
notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(userId));
resetKeyguardDonePendingLocked();
- dismiss(null /* callback */, null /* message */);
+ resetStateLocked();
adjustStatusBarLocked();
}
}
@Override
public void onUserSwitchComplete(int userId) {
+ mIgnoreDismiss = false;
Log.d(TAG, String.format("onUserSwitchComplete %d", userId));
- // We are calling dismiss again and with a delay as there are race conditions
- // in some scenarios caused by async layout listeners
+ // We are calling dismiss with a delay as there are race conditions in some scenarios
+ // caused by async layout listeners
mHandler.postDelayed(() -> dismiss(null /* callback */, null /* message */), 500);
}
@@ -2442,6 +2445,10 @@
}
public void dismiss(IKeyguardDismissCallback callback, CharSequence message) {
+ if (mIgnoreDismiss) {
+ android.util.Log.i(TAG, "Ignoring request to dismiss (user switch in progress?)");
+ return;
+ }
mHandler.obtainMessage(DISMISS, new DismissMessage(callback, message)).sendToTarget();
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
index 406b9f6..be87334 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
@@ -25,7 +25,9 @@
import android.provider.Settings.Secure.ZEN_DURATION_FOREVER
import android.provider.Settings.Secure.ZEN_DURATION_PROMPT
import android.service.notification.ZenModeConfig
+import android.util.Log
import com.android.settingslib.notification.modes.EnableZenModeDialog
+import com.android.settingslib.notification.modes.ZenMode
import com.android.settingslib.notification.modes.ZenModeDialogMetricsLogger
import com.android.systemui.animation.Expandable
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
@@ -35,30 +37,38 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.modes.shared.ModesUi
import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.ZenModeController
+import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
@SysUISingleton
class DoNotDisturbQuickAffordanceConfig
constructor(
private val context: Context,
private val controller: ZenModeController,
+ private val interactor: ZenModeInteractor,
private val secureSettings: SecureSettings,
private val userTracker: UserTracker,
@Background private val backgroundDispatcher: CoroutineDispatcher,
+ @Background private val backgroundScope: CoroutineScope,
private val testConditionId: Uri?,
testDialog: EnableZenModeDialog?,
) : KeyguardQuickAffordanceConfig {
@@ -67,15 +77,45 @@
constructor(
context: Context,
controller: ZenModeController,
+ interactor: ZenModeInteractor,
secureSettings: SecureSettings,
userTracker: UserTracker,
@Background backgroundDispatcher: CoroutineDispatcher,
- ) : this(context, controller, secureSettings, userTracker, backgroundDispatcher, null, null)
+ @Background backgroundScope: CoroutineScope,
+ ) : this(
+ context,
+ controller,
+ interactor,
+ secureSettings,
+ userTracker,
+ backgroundDispatcher,
+ backgroundScope,
+ null,
+ null,
+ )
- private var dndMode: Int = 0
- private var isAvailable = false
+ private var zenMode: Int = 0
+ private var oldIsAvailable = false
private var settingsValue: Int = 0
+ private val dndMode: StateFlow<ZenMode?> by lazy {
+ ModesUi.assertInNewMode()
+ interactor.dndMode.stateIn(
+ scope = backgroundScope,
+ started = SharingStarted.Eagerly,
+ initialValue = null,
+ )
+ }
+
+ private val isAvailable: StateFlow<Boolean> by lazy {
+ ModesUi.assertInNewMode()
+ interactor.isZenAvailable.stateIn(
+ scope = backgroundScope,
+ started = SharingStarted.Eagerly,
+ initialValue = false,
+ )
+ }
+
private val conditionUri: Uri
get() =
testConditionId
@@ -104,42 +144,68 @@
override val pickerIconResourceId: Int = R.drawable.ic_do_not_disturb
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
- combine(
- conflatedCallbackFlow {
- val callback =
- object : ZenModeController.Callback {
- override fun onZenChanged(zen: Int) {
- dndMode = zen
- trySendWithFailureLogging(updateState(), TAG)
+ if (ModesUi.isEnabled) {
+ combine(isAvailable, dndMode) { isAvailable, dndMode ->
+ if (!isAvailable) {
+ KeyguardQuickAffordanceConfig.LockScreenState.Hidden
+ } else if (dndMode?.isActive == true) {
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ Icon.Resource(
+ R.drawable.qs_dnd_icon_on,
+ ContentDescription.Resource(R.string.dnd_is_on),
+ ),
+ ActivationState.Active,
+ )
+ } else {
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ Icon.Resource(
+ R.drawable.qs_dnd_icon_off,
+ ContentDescription.Resource(R.string.dnd_is_off),
+ ),
+ ActivationState.Inactive,
+ )
+ }
+ }
+ } else {
+ combine(
+ conflatedCallbackFlow {
+ val callback =
+ object : ZenModeController.Callback {
+ override fun onZenChanged(zen: Int) {
+ zenMode = zen
+ trySendWithFailureLogging(updateState(), TAG)
+ }
+
+ override fun onZenAvailableChanged(available: Boolean) {
+ oldIsAvailable = available
+ trySendWithFailureLogging(updateState(), TAG)
+ }
}
- override fun onZenAvailableChanged(available: Boolean) {
- isAvailable = available
- trySendWithFailureLogging(updateState(), TAG)
- }
- }
+ zenMode = controller.zen
+ oldIsAvailable = controller.isZenAvailable
+ trySendWithFailureLogging(updateState(), TAG)
- dndMode = controller.zen
- isAvailable = controller.isZenAvailable
- trySendWithFailureLogging(updateState(), TAG)
+ controller.addCallback(callback)
- controller.addCallback(callback)
-
- awaitClose { controller.removeCallback(callback) }
- },
- secureSettings
- .observerFlow(userTracker.userId, Settings.Secure.ZEN_DURATION)
- .onStart { emit(Unit) }
- .map { secureSettings.getInt(Settings.Secure.ZEN_DURATION, ZEN_MODE_OFF) }
- .flowOn(backgroundDispatcher)
- .distinctUntilChanged()
- .onEach { settingsValue = it }
- ) { callbackFlowValue, _ ->
- callbackFlowValue
+ awaitClose { controller.removeCallback(callback) }
+ },
+ secureSettings
+ .observerFlow(userTracker.userId, Settings.Secure.ZEN_DURATION)
+ .onStart { emit(Unit) }
+ .map { secureSettings.getInt(Settings.Secure.ZEN_DURATION, ZEN_MODE_OFF) }
+ .flowOn(backgroundDispatcher)
+ .distinctUntilChanged()
+ .onEach { settingsValue = it },
+ ) { callbackFlowValue, _ ->
+ callbackFlowValue
+ }
}
override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
- return if (controller.isZenAvailable) {
+ val isZenAvailable = if (ModesUi.isEnabled) isAvailable.value else controller.isZenAvailable
+
+ return if (isZenAvailable) {
KeyguardQuickAffordanceConfig.PickerScreenState.Default(
configureIntent = Intent(Settings.ACTION_ZEN_MODE_SETTINGS)
)
@@ -151,32 +217,63 @@
override fun onTriggered(
expandable: Expandable?
): KeyguardQuickAffordanceConfig.OnTriggeredResult {
- return when {
- !isAvailable -> KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
- dndMode != ZEN_MODE_OFF -> {
- controller.setZen(ZEN_MODE_OFF, null, TAG)
+ return if (ModesUi.isEnabled) {
+ if (!isAvailable.value) {
KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ } else {
+ val dnd = dndMode.value
+ if (dnd == null) {
+ Log.wtf(TAG, "Triggered DND but it's null!?")
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ }
+ if (dnd.isActive) {
+ interactor.deactivateMode(dnd)
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ } else {
+ if (interactor.shouldAskForZenDuration(dnd)) {
+ // NOTE: The dialog handles turning on the mode itself.
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog(
+ dialog.createDialog(),
+ expandable,
+ )
+ } else {
+ interactor.activateMode(dnd)
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ }
+ }
}
- settingsValue == ZEN_DURATION_PROMPT ->
- KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog(
- dialog.createDialog(),
- expandable
- )
- settingsValue == ZEN_DURATION_FOREVER -> {
- controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG)
- KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
- }
- else -> {
- controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, conditionUri, TAG)
- KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ } else {
+ when {
+ !oldIsAvailable -> KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ zenMode != ZEN_MODE_OFF -> {
+ controller.setZen(ZEN_MODE_OFF, null, TAG)
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ }
+
+ settingsValue == ZEN_DURATION_PROMPT ->
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog(
+ dialog.createDialog(),
+ expandable,
+ )
+
+ settingsValue == ZEN_DURATION_FOREVER -> {
+ controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG)
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ }
+
+ else -> {
+ controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, conditionUri, TAG)
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ }
}
}
}
private fun updateState(): KeyguardQuickAffordanceConfig.LockScreenState {
- return if (!isAvailable) {
+ ModesUi.assertInLegacyMode()
+ return if (!oldIsAvailable) {
KeyguardQuickAffordanceConfig.LockScreenState.Hidden
- } else if (dndMode == ZEN_MODE_OFF) {
+ } else if (zenMode == ZEN_MODE_OFF) {
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
Icon.Resource(
R.drawable.qs_dnd_icon_off,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 80a0cee..b0820a7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -24,7 +24,6 @@
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.communal.shared.model.CommunalTransitionKeys
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -95,10 +94,7 @@
scope.launch {
powerInteractor.isAwake
.filterRelevantKeyguardStateAnd { isAwake -> isAwake }
- .sample(
- keyguardInteractor.biometricUnlockState,
- ::Pair,
- )
+ .sample(keyguardInteractor.biometricUnlockState, ::Pair)
.collect {
(
_,
@@ -203,21 +199,21 @@
if (!SceneContainerFlag.isEnabled) {
startTransitionTo(
KeyguardState.GONE,
- ownerReason = "waking from dozing"
+ ownerReason = "waking from dozing",
)
}
} else if (primaryBouncerShowing) {
if (!SceneContainerFlag.isEnabled) {
startTransitionTo(
KeyguardState.PRIMARY_BOUNCER,
- ownerReason = "waking from dozing"
+ ownerReason = "waking from dozing",
)
}
} else if (isIdleOnCommunal && !communalSceneKtfRefactor()) {
if (!SceneContainerFlag.isEnabled) {
startTransitionTo(
KeyguardState.GLANCEABLE_HUB,
- ownerReason = "waking from dozing"
+ ownerReason = "waking from dozing",
)
}
} else if (isCommunalAvailable && dreamManager.canStartDreaming(true)) {
@@ -227,7 +223,7 @@
} else {
startTransitionTo(
KeyguardState.LOCKSCREEN,
- ownerReason = "waking from dozing"
+ ownerReason = "waking from dozing",
)
}
}
@@ -237,11 +233,9 @@
private suspend fun transitionToGlanceableHub() {
if (communalSceneKtfRefactor()) {
- communalSceneInteractor.changeScene(
+ communalSceneInteractor.snapToScene(
newScene = CommunalScenes.Communal,
loggingReason = "from dozing to hub",
- // Immediately show the hub when transitioning from dozing to hub.
- transitionKey = CommunalTransitionKeys.Immediately,
)
} else {
startTransitionTo(KeyguardState.GLANCEABLE_HUB)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index 199caa1..7759298 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -162,10 +162,9 @@
.filterRelevantKeyguardStateAnd { isAsleep -> isAsleep }
.collect {
if (communalSceneKtfRefactor()) {
- communalSceneInteractor.changeScene(
+ communalSceneInteractor.snapToScene(
newScene = CommunalScenes.Blank,
loggingReason = "hub to dozing",
- transitionKey = CommunalTransitionKeys.Immediately,
keyguardState = KeyguardState.DOZING,
)
} else {
@@ -210,12 +209,12 @@
// ends, to avoid transitioning to OCCLUDED erroneously when exiting
// the dream.
.debounce(100.milliseconds),
- ::Pair
+ ::Pair,
)
.sampleFilter(
// When launching activities from widgets on the hub, we have a
// custom occlusion animation.
- communalSceneInteractor.isLaunchingWidget,
+ communalSceneInteractor.isLaunchingWidget
) { launchingWidget ->
!launchingWidget
}
@@ -253,7 +252,7 @@
noneOf(
// When launching activities from widgets on the hub, we wait to change
// scenes until the activity launch is complete.
- communalSceneInteractor.isLaunchingWidget,
+ communalSceneInteractor.isLaunchingWidget
),
)
.filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway }
@@ -270,7 +269,7 @@
newScene = CommunalScenes.Blank,
loggingReason = "hub to gone",
transitionKey = CommunalTransitionKeys.SimpleFade,
- keyguardState = KeyguardState.GONE
+ keyguardState = KeyguardState.GONE,
)
}
}
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 7afc759..6932eb5 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
@@ -25,13 +25,13 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository
-import com.android.systemui.keyguard.shared.ComposeLockscreen
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.IntraBlueprintTransition.Config
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type
import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -64,7 +64,7 @@
/** Current BlueprintId */
val blueprintId =
shadeInteractor.isShadeLayoutWide.map { isShadeLayoutWide ->
- val useSplitShade = isShadeLayoutWide && !ComposeLockscreen.isEnabled
+ val useSplitShade = isShadeLayoutWide && !SceneContainerFlag.isEnabled
when {
useSplitShade -> SplitShadeKeyguardBlueprint.ID
else -> DefaultKeyguardBlueprint.DEFAULT
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt
deleted file mode 100644
index 601fbfa..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.shared
-
-import com.android.systemui.Flags
-import com.android.systemui.flags.FlagToken
-import com.android.systemui.flags.RefactorFlagUtils
-
-/** Helper for reading or using the compose lockscreen flag state. */
-@Suppress("NOTHING_TO_INLINE")
-object ComposeLockscreen {
- /** The aconfig flag name */
- const val FLAG_NAME = Flags.FLAG_COMPOSE_LOCKSCREEN
-
- /** A token used for dependency declaration */
- val token: FlagToken
- get() = FlagToken(FLAG_NAME, isEnabled)
-
- /** Is the refactor enabled */
- @JvmStatic
- inline val isEnabled
- get() = Flags.composeLockscreen()
-
- /**
- * Called to ensure code is only run when the flag is enabled. This protects users from the
- * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
- * build to ensure that the refactor author catches issues in testing.
- */
- @JvmStatic
- inline fun isUnexpectedlyInLegacyMode() =
- RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
-
- /**
- * Called to ensure code is only run when the flag is disabled. This will throw an exception if
- * the flag is enabled to ensure that the refactor author catches issues in testing.
- */
- @JvmStatic
- inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index ed82159..b5f6b41 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
@@ -59,7 +59,6 @@
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
-import com.android.systemui.keyguard.shared.ComposeLockscreen
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
@@ -72,6 +71,7 @@
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.CrossFadeHelper
import com.android.systemui.statusbar.VibratorHelper
@@ -241,7 +241,7 @@
disposables +=
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
- if (ComposeLockscreen.isEnabled) {
+ if (SceneContainerFlag.isEnabled) {
view.setViewTreeOnBackPressedDispatcherOwner(
object : OnBackPressedDispatcherOwner {
override val onBackPressedDispatcher =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index 73028c5..36f684e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -25,10 +25,10 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
-import com.android.systemui.keyguard.shared.ComposeLockscreen
import com.android.systemui.keyguard.shared.model.ClockSize
import com.android.systemui.keyguard.shared.model.ClockSizeSetting
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
import com.android.systemui.statusbar.ui.SystemBarUtilsProxy
@@ -56,10 +56,9 @@
var burnInLayer: Layer? = null
val clockSize: StateFlow<ClockSize> =
- combine(
- keyguardClockInteractor.selectedClockSize,
- keyguardClockInteractor.clockSize,
- ) { selectedSize, clockSize ->
+ combine(keyguardClockInteractor.selectedClockSize, keyguardClockInteractor.clockSize) {
+ selectedSize,
+ clockSize ->
if (selectedSize == ClockSizeSetting.SMALL) ClockSize.SMALL else clockSize
}
.stateIn(
@@ -80,10 +79,7 @@
val currentClock = keyguardClockInteractor.currentClock
val hasCustomWeatherDataDisplay =
- combine(
- isLargeClockVisible,
- currentClock,
- ) { isLargeClock, currentClock ->
+ combine(isLargeClockVisible, currentClock) { isLargeClock, currentClock ->
currentClock?.let { clock ->
val face = if (isLargeClock) clock.largeClock else clock.smallClock
face.config.hasCustomWeatherDataDisplay
@@ -93,14 +89,14 @@
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
initialValue =
- currentClock.value?.largeClock?.config?.hasCustomWeatherDataDisplay ?: false
+ currentClock.value?.largeClock?.config?.hasCustomWeatherDataDisplay ?: false,
)
val clockShouldBeCentered: StateFlow<Boolean> =
keyguardClockInteractor.clockShouldBeCentered.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = false
+ initialValue = false,
)
// To translate elements below smartspace in weather clock to avoid overlapping between date
@@ -111,7 +107,7 @@
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = false
+ initialValue = false,
)
val currentClockLayout: StateFlow<ClockLayout> =
@@ -145,7 +141,7 @@
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = ClockLayout.SMALL_CLOCK
+ initialValue = ClockLayout.SMALL_CLOCK,
)
val hasCustomPositionUpdatedAnimation: StateFlow<Boolean> =
@@ -156,7 +152,7 @@
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = false
+ initialValue = false,
)
/** Calculates the top margin for the small clock. */
@@ -164,10 +160,10 @@
val statusBarHeight = systemBarUtils.getStatusBarHeaderHeightKeyguard()
return if (shadeInteractor.isShadeLayoutWide.value) {
resources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin) -
- if (ComposeLockscreen.isEnabled) statusBarHeight else 0
+ if (SceneContainerFlag.isEnabled) statusBarHeight else 0
} else {
resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
- if (!ComposeLockscreen.isEnabled) statusBarHeight else 0
+ if (!SceneContainerFlag.isEnabled) statusBarHeight else 0
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt
index 53cc15b..7b55dac8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt
@@ -42,7 +42,6 @@
import android.support.v4.media.MediaMetadataCompat
import android.text.TextUtils
import android.util.Log
-import android.util.Pair
import androidx.media.utils.MediaConstants
import com.android.app.tracing.coroutines.traceCoroutine
import com.android.systemui.dagger.SysUISingleton
@@ -70,6 +69,7 @@
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.cancel
+import kotlinx.coroutines.delay
import kotlinx.coroutines.ensureActive
/** Loads media information from media style [StatusBarNotification] classes. */
@@ -85,7 +85,7 @@
private val imageLoader: ImageLoader,
private val statusBarManager: StatusBarManager,
) {
- private val mediaProcessingJobs = ConcurrentHashMap<JobKey, Job>()
+ private val mediaProcessingJobs = ConcurrentHashMap<String, Job>()
private val artworkWidth: Int =
context.resources.getDimensionPixelSize(
@@ -97,7 +97,7 @@
private val themeText =
com.android.settingslib.Utils.getColorAttr(
context,
- com.android.internal.R.attr.textColorPrimary
+ com.android.internal.R.attr.textColorPrimary,
)
.defaultColor
@@ -112,11 +112,14 @@
* load will be cancelled.
*/
suspend fun loadMediaData(key: String, sbn: StatusBarNotification): MediaDataLoaderResult? {
- logD(TAG) { "Loading media data for $key..." }
- val jobKey = JobKey(key, sbn)
val loadMediaJob = backgroundScope.async { loadMediaDataInBackground(key, sbn) }
- loadMediaJob.invokeOnCompletion { mediaProcessingJobs.remove(jobKey) }
- val existingJob = mediaProcessingJobs.put(jobKey, loadMediaJob)
+ loadMediaJob.invokeOnCompletion {
+ // We need to make sure we're removing THIS job after cancellation, not
+ // a job that we created later.
+ mediaProcessingJobs.remove(key, loadMediaJob)
+ }
+ val existingJob = mediaProcessingJobs.put(key, loadMediaJob)
+ logD(TAG) { "Loading media data for $key... / existing job: $existingJob" }
existingJob?.cancel("New processing job incoming.")
return loadMediaJob.await()
}
@@ -128,10 +131,15 @@
sbn: StatusBarNotification,
): MediaDataLoaderResult? =
traceCoroutine("MediaDataLoader#loadMediaData") {
+ // We have apps spamming us with quick notification updates which can cause
+ // us to spend significant CPU time loading duplicate data. This debounces
+ // those requests at the cost of a bit of latency.
+ delay(DEBOUNCE_DELAY_MS)
+
val token =
sbn.notification.extras.getParcelable(
Notification.EXTRA_MEDIA_SESSION,
- MediaSession.Token::class.java
+ MediaSession.Token::class.java,
)
if (token == null) {
Log.i(TAG, "Token was null, not loading media info")
@@ -144,7 +152,7 @@
val appInfo =
notification.extras.getParcelable(
Notification.EXTRA_BUILDER_APPLICATION_INFO,
- ApplicationInfo::class.java
+ ApplicationInfo::class.java,
) ?: getAppInfoFromPackage(sbn.packageName)
// App name
@@ -240,7 +248,7 @@
playbackLocation = playbackLocation,
isPlaying = isPlaying,
appUid = appUid,
- isExplicit = isExplicit
+ isExplicit = isExplicit,
)
}
@@ -258,7 +266,7 @@
token: MediaSession.Token,
appName: String,
appIntent: PendingIntent,
- packageName: String
+ packageName: String,
): MediaDataLoaderResult? {
val mediaData =
backgroundScope.async {
@@ -270,7 +278,7 @@
token,
appName,
appIntent,
- packageName
+ packageName,
)
}
return mediaData.await()
@@ -286,7 +294,7 @@
token: MediaSession.Token,
appName: String,
appIntent: PendingIntent,
- packageName: String
+ packageName: String,
): MediaDataLoaderResult? =
traceCoroutine("MediaDataLoader#loadMediaDataForResumption") {
if (desc.title.isNullOrBlank()) {
@@ -338,14 +346,14 @@
appUid = appUid,
isExplicit = isExplicit,
resumeAction = resumeAction,
- resumeProgress = progress
+ resumeProgress = progress,
)
}
private fun createActionsFromState(
packageName: String,
controller: MediaController,
- user: UserHandle
+ user: UserHandle,
): MediaButton? {
if (!mediaFlags.areMediaSessionActionsEnabled(packageName, user)) {
return null
@@ -368,7 +376,7 @@
*/
private fun getDeviceInfoForRemoteCast(
key: String,
- sbn: StatusBarNotification
+ sbn: StatusBarNotification,
): MediaDeviceData? {
val extras = sbn.notification.extras
val deviceName = extras.getCharSequence(Notification.EXTRA_MEDIA_REMOTE_DEVICE, null)
@@ -388,7 +396,7 @@
deviceDrawable,
deviceName,
deviceIntent,
- showBroadcastButton = false
+ showBroadcastButton = false,
)
}
return null
@@ -439,7 +447,7 @@
listOf(
ContentResolver.SCHEME_CONTENT,
ContentResolver.SCHEME_ANDROID_RESOURCE,
- ContentResolver.SCHEME_FILE
+ ContentResolver.SCHEME_FILE,
)
) {
Log.w(TAG, "Invalid album art uri $uri")
@@ -451,7 +459,7 @@
source,
artworkWidth,
artworkHeight,
- allocator = ImageDecoder.ALLOCATOR_SOFTWARE
+ allocator = ImageDecoder.ALLOCATOR_SOFTWARE,
)
}
@@ -459,7 +467,7 @@
uri: Uri,
userId: Int,
appUid: Int,
- packageName: String
+ packageName: String,
): Bitmap? {
try {
val ugm = UriGrantsManager.getService()
@@ -468,7 +476,7 @@
packageName,
ContentProvider.getUriWithoutUserId(uri),
Intent.FLAG_GRANT_READ_URI_PERMISSION,
- ContentProvider.getUserIdFromUri(uri, userId)
+ ContentProvider.getUserIdFromUri(uri, userId),
)
return loadBitmapFromUri(uri)
} catch (e: SecurityException) {
@@ -488,21 +496,20 @@
.loadDrawable(context),
action,
context.getString(R.string.controls_media_resume),
- context.getDrawable(R.drawable.ic_media_play_container)
+ context.getDrawable(R.drawable.ic_media_play_container),
)
}
- private data class JobKey(val key: String, val sbn: StatusBarNotification) :
- Pair<String, StatusBarNotification>(key, sbn)
-
companion object {
private const val TAG = "MediaDataLoader"
private val ART_URIS =
arrayOf(
MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
MediaMetadata.METADATA_KEY_ART_URI,
- MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI
+ MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI,
)
+
+ private const val DEBOUNCE_DELAY_MS = 200L
}
/** Returned data from loader. */
@@ -523,6 +530,6 @@
val appUid: Int,
val isExplicit: Boolean,
val resumeAction: Runnable? = null,
- val resumeProgress: Double? = null
+ val resumeProgress: Double? = null,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
index 130868d..1f339dd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
@@ -33,6 +33,7 @@
import com.android.systemui.media.controls.data.repository.MediaFilterRepository
import com.android.systemui.media.controls.domain.pipeline.MediaDataProcessor
import com.android.systemui.media.controls.domain.pipeline.getNotificationActions
+import com.android.systemui.media.controls.shared.MediaLogger
import com.android.systemui.media.controls.shared.model.MediaControlModel
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.util.MediaSmartspaceLogger
@@ -59,6 +60,7 @@
private val lockscreenUserManager: NotificationLockscreenUserManager,
private val mediaOutputDialogManager: MediaOutputDialogManager,
private val broadcastDialogController: BroadcastDialogController,
+ private val mediaLogger: MediaLogger,
) {
val mediaControl: Flow<MediaControlModel?> =
@@ -73,7 +75,7 @@
instanceId: InstanceId,
delayMs: Long,
eventId: Int,
- location: Int
+ location: Int,
): Boolean {
logSmartspaceUserEvent(eventId, location)
val dismissed =
@@ -81,7 +83,7 @@
if (!dismissed) {
Log.w(
TAG,
- "Manager failed to dismiss media of instanceId=$instanceId, Token uid=${token?.uid}"
+ "Manager failed to dismiss media of instanceId=$instanceId, Token uid=${token?.uid}",
)
}
return dismissed
@@ -120,20 +122,20 @@
expandable: Expandable,
clickIntent: PendingIntent,
eventId: Int,
- location: Int
+ location: Int,
) {
logSmartspaceUserEvent(eventId, location)
- if (!launchOverLockscreen(clickIntent)) {
+ if (!launchOverLockscreen(expandable, clickIntent)) {
activityStarter.postStartActivityDismissingKeyguard(
clickIntent,
- expandable.activityTransitionController(Cuj.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER)
+ expandable.activityTransitionController(Cuj.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER),
)
}
}
fun startDeviceIntent(deviceIntent: PendingIntent) {
if (deviceIntent.isActivity) {
- if (!launchOverLockscreen(deviceIntent)) {
+ if (!launchOverLockscreen(expandable = null, deviceIntent)) {
activityStarter.postStartActivityDismissingKeyguard(deviceIntent)
}
} else {
@@ -141,20 +143,33 @@
}
}
- private fun launchOverLockscreen(pendingIntent: PendingIntent): Boolean {
+ private fun launchOverLockscreen(
+ expandable: Expandable?,
+ pendingIntent: PendingIntent,
+ ): Boolean {
val showOverLockscreen =
keyguardStateController.isShowing &&
activityIntentHelper.wouldPendingShowOverLockscreen(
pendingIntent,
- lockscreenUserManager.currentUserId
+ lockscreenUserManager.currentUserId,
)
if (showOverLockscreen) {
try {
- val options = BroadcastOptions.makeBasic()
- options.isInteractive = true
- options.pendingIntentBackgroundActivityStartMode =
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
- pendingIntent.send(options.toBundle())
+ if (expandable != null) {
+ activityStarter.startPendingIntentMaybeDismissingKeyguard(
+ pendingIntent,
+ /* intentSentUiThreadCallback = */ null,
+ expandable.activityTransitionController(
+ Cuj.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER
+ ),
+ )
+ } else {
+ val options = BroadcastOptions.makeBasic()
+ options.isInteractive = true
+ options.pendingIntentBackgroundActivityStartMode =
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ pendingIntent.send(options.toBundle())
+ }
} catch (e: PendingIntent.CanceledException) {
Log.e(TAG, "pending intent of $instanceId was canceled")
}
@@ -166,7 +181,7 @@
fun startMediaOutputDialog(
expandable: Expandable,
packageName: String,
- token: MediaSession.Token? = null
+ token: MediaSession.Token? = null,
) {
mediaOutputDialogManager.createAndShowWithController(
packageName,
@@ -180,7 +195,7 @@
broadcastDialogController.createBroadcastDialogWithController(
broadcastApp,
packageName,
- expandable.dialogTransitionController()
+ expandable.dialogTransitionController(),
)
}
@@ -188,10 +203,14 @@
repository.logSmartspaceCardUserEvent(
eventId,
MediaSmartspaceLogger.getSurface(location),
- instanceId = instanceId
+ instanceId = instanceId,
)
}
+ fun logMediaControlIsBound(artistName: CharSequence, songName: CharSequence) {
+ mediaLogger.logMediaControlIsBound(instanceId, artistName, songName)
+ }
+
private fun Expandable.dialogController(): DialogTransitionAnimator.Controller? {
return dialogTransitionController(
cuj =
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt
index 7d20e17..88c47ba 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt
@@ -36,7 +36,7 @@
bool1 = active
str2 = reason
},
- { "add media $str1, active: $bool1, reason: $str2" }
+ { "add media $str1, active: $bool1, reason: $str2" },
)
}
@@ -48,7 +48,7 @@
str1 = instanceId.toString()
str2 = reason
},
- { "removing media $str1, reason: $str2" }
+ { "removing media $str1, reason: $str2" },
)
}
@@ -61,7 +61,7 @@
bool1 = isActive
str2 = reason
},
- { "add recommendation $str1, active $bool1, reason: $str2" }
+ { "add recommendation $str1, active $bool1, reason: $str2" },
)
}
@@ -74,7 +74,7 @@
bool1 = immediately
str2 = reason
},
- { "removing recommendation $str1, immediate=$bool1, reason: $str2" }
+ { "removing recommendation $str1, immediate=$bool1, reason: $str2" },
)
}
@@ -83,7 +83,7 @@
TAG,
LogLevel.DEBUG,
{ str1 = instanceId.toString() },
- { "adding media card $str1 to carousel" }
+ { "adding media card $str1 to carousel" },
)
}
@@ -92,7 +92,7 @@
TAG,
LogLevel.DEBUG,
{ str1 = instanceId.toString() },
- { "removing media card $str1 from carousel" }
+ { "removing media card $str1 from carousel" },
)
}
@@ -101,7 +101,7 @@
TAG,
LogLevel.DEBUG,
{ str1 = key },
- { "adding recommendation card $str1 to carousel" }
+ { "adding recommendation card $str1 to carousel" },
)
}
@@ -110,7 +110,7 @@
TAG,
LogLevel.DEBUG,
{ str1 = key },
- { "removing recommendation card $str1 from carousel" }
+ { "removing recommendation card $str1 from carousel" },
)
}
@@ -119,7 +119,24 @@
TAG,
LogLevel.DEBUG,
{ str1 = key },
- { "duplicate media notification $str1 posted" }
+ { "duplicate media notification $str1 posted" },
+ )
+ }
+
+ fun logMediaControlIsBound(
+ instanceId: InstanceId,
+ artistName: CharSequence,
+ title: CharSequence,
+ ) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = instanceId.toString()
+ str2 = artistName.toString()
+ str3 = title.toString()
+ },
+ { "binding media control, instance id= $str1, artist= $str2, title= $str3" },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
index 6373fed..4055818 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
@@ -80,16 +80,19 @@
mediaCard.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
- viewModel.player.collectLatest { playerViewModel ->
- playerViewModel?.let {
- bindMediaCard(
- viewHolder,
- viewController,
- it,
- falsingManager,
- backgroundDispatcher,
- mainDispatcher,
- )
+ viewModel.player.collectLatest { player ->
+ player?.let {
+ if (viewModel.isNewPlayer(it)) {
+ bindMediaCard(
+ viewHolder,
+ viewController,
+ it,
+ falsingManager,
+ backgroundDispatcher,
+ mainDispatcher,
+ )
+ viewModel.onMediaControlIsBound(it.artistName, it.titleName)
+ }
}
}
}
@@ -143,7 +146,7 @@
viewHolder,
viewModel.outputSwitcher,
viewController,
- falsingManager
+ falsingManager,
)
bindGutsViewModel(viewHolder, viewModel, viewController, falsingManager)
bindActionButtons(viewHolder, viewModel, viewController, falsingManager)
@@ -157,7 +160,7 @@
viewController,
backgroundDispatcher,
mainDispatcher,
- isSongUpdated
+ isSongUpdated,
)
if (viewModel.playTurbulenceNoise) {
@@ -259,12 +262,12 @@
if (buttonView.id == R.id.actionPrev) {
viewController.setUpPrevButtonInfo(
buttonModel.isEnabled,
- buttonModel.notVisibleValue
+ buttonModel.notVisibleValue,
)
} else if (buttonView.id == R.id.actionNext) {
viewController.setUpNextButtonInfo(
buttonModel.isEnabled,
- buttonModel.notVisibleValue
+ buttonModel.notVisibleValue,
)
}
val animHandler = (buttonView.tag ?: AnimationBindHandler()) as AnimationBindHandler
@@ -295,7 +298,7 @@
viewController.collapsedLayout,
visible,
buttonModel.notVisibleValue,
- buttonModel.showInCollapsed
+ buttonModel.showInCollapsed,
)
}
}
@@ -350,7 +353,7 @@
createTouchRippleAnimation(
button,
viewController.colorSchemeTransition,
- multiRippleView
+ multiRippleView,
)
)
@@ -382,12 +385,12 @@
setVisibleAndAlpha(
expandedSet,
R.id.media_explicit_indicator,
- viewModel.isExplicitVisible
+ viewModel.isExplicitVisible,
)
setVisibleAndAlpha(
collapsedSet,
R.id.media_explicit_indicator,
- viewModel.isExplicitVisible
+ viewModel.isExplicitVisible,
)
// refreshState is required here to resize the text views (and prevent ellipsis)
@@ -398,7 +401,7 @@
// something is incorrectly bound, but needs to be run if other elements were
// updated while the enter animation was running
viewController.refreshState()
- }
+ },
)
}
@@ -427,7 +430,7 @@
viewModel.backgroundCover!!,
viewModel.colorScheme,
width,
- height
+ height,
)
} else {
ColorDrawable(Color.TRANSPARENT)
@@ -493,7 +496,7 @@
transitionDrawable: TransitionDrawable,
layer: Int,
targetWidth: Int,
- targetHeight: Int
+ targetHeight: Int,
) {
val drawable = transitionDrawable.getDrawable(layer) ?: return
val width = drawable.intrinsicWidth
@@ -509,7 +512,7 @@
artworkIcon: android.graphics.drawable.Icon,
mutableColorScheme: ColorScheme,
width: Int,
- height: Int
+ height: Int,
): LayerDrawable {
val albumArt = MediaArtworkHelper.getScaledBackground(context, artworkIcon, width, height)
return MediaArtworkHelper.setUpGradientColorOnDrawable(
@@ -517,7 +520,7 @@
context.getDrawable(R.drawable.qs_media_scrim)?.mutate() as GradientDrawable,
mutableColorScheme,
MEDIA_PLAYER_SCRIM_START_ALPHA,
- MEDIA_PLAYER_SCRIM_END_ALPHA
+ MEDIA_PLAYER_SCRIM_END_ALPHA,
)
}
@@ -544,7 +547,7 @@
private fun createTouchRippleAnimation(
button: ImageButton,
colorSchemeTransition: ColorSchemeTransition,
- multiRippleView: MultiRippleView
+ multiRippleView: MultiRippleView,
): RippleAnimation {
val maxSize = (multiRippleView.width * 2).toFloat()
return RippleAnimation(
@@ -562,7 +565,7 @@
baseRingFadeParams = null,
sparkleRingFadeParams = null,
centerFillFadeParams = null,
- shouldDistort = false
+ shouldDistort = false,
)
)
}
@@ -596,7 +599,7 @@
set: ConstraintSet,
resId: Int,
visible: Boolean,
- notVisibleValue: Int
+ notVisibleValue: Int,
) {
set.setVisibility(resId, if (visible) ConstraintSet.VISIBLE else notVisibleValue)
set.setAlpha(resId, if (visible) 1.0f else 0.0f)
@@ -618,7 +621,7 @@
collapsedSet: ConstraintSet,
visible: Boolean,
notVisibleValue: Int,
- showInCollapsed: Boolean
+ showInCollapsed: Boolean,
) {
if (notVisibleValue == ConstraintSet.INVISIBLE) {
// Since time views should appear instead of buttons.
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index 8505a784..49a8758 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -123,7 +123,7 @@
* Class that is responsible for keeping the view carousel up to date. This also handles changes in
* state and applies them to the media carousel like the expansion.
*/
-@ExperimentalCoroutinesApi
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class MediaCarouselController
@Inject
@@ -252,13 +252,13 @@
fun calculateAlpha(
squishinessFraction: Float,
startPosition: Float,
- endPosition: Float
+ endPosition: Float,
): Float {
val transformFraction =
MathUtils.constrain(
(squishinessFraction - startPosition) / (endPosition - startPosition),
0F,
- 1F
+ 1F,
)
return TRANSFORM_BEZIER.getInterpolation(transformFraction)
}
@@ -354,7 +354,7 @@
this::closeGuts,
falsingManager,
this::logSmartspaceImpression,
- logger
+ logger,
)
carouselLocale = context.resources.configuration.locales.get(0)
isRtl = context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
@@ -387,7 +387,7 @@
object : MediaHostStatesManager.Callback {
override fun onHostStateChanged(
@MediaLocation location: Int,
- mediaHostState: MediaHostState
+ mediaHostState: MediaHostState,
) {
updateUserVisibility()
if (location == desiredLocation) {
@@ -412,7 +412,7 @@
bgExecutor.execute {
globalSettings.registerContentObserverSync(
Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
- animationScaleObserver
+ animationScaleObserver,
)
}
}
@@ -452,7 +452,7 @@
data: MediaData,
immediately: Boolean,
receivedSmartspaceCardLatency: Int,
- isSsReactivated: Boolean
+ isSsReactivated: Boolean,
) {
debugLogger.logMediaLoaded(key, data.active)
if (addOrUpdatePlayer(key, oldKey, data, isSsReactivated)) {
@@ -468,7 +468,7 @@
SSPACE_CARD_REPORTED__LOCKSCREEN,
SSPACE_CARD_REPORTED__DREAM_OVERLAY,
),
- rank = MediaPlayerData.getMediaPlayerIndex(key)
+ rank = MediaPlayerData.getMediaPlayerIndex(key),
)
}
if (
@@ -500,7 +500,7 @@
SSPACE_CARD_REPORTED__DREAM_OVERLAY,
),
rank = index,
- receivedLatencyMillis = receivedSmartspaceCardLatency
+ receivedLatencyMillis = receivedSmartspaceCardLatency,
)
}
}
@@ -533,7 +533,7 @@
override fun onSmartspaceMediaDataLoaded(
key: String,
data: SmartspaceMediaData,
- shouldPrioritize: Boolean
+ shouldPrioritize: Boolean,
) {
debugLogger.logRecommendationLoaded(key, data.isActive)
// Log the case where the hidden media carousel with the existed inactive resume
@@ -568,7 +568,7 @@
receivedLatencyMillis =
(systemClock.currentTimeMillis() -
data.headphoneConnectionTimeMillis)
- .toInt()
+ .toInt(),
)
}
}
@@ -589,7 +589,7 @@
receivedLatencyMillis =
(systemClock.currentTimeMillis() -
data.headphoneConnectionTimeMillis)
- .toInt()
+ .toInt(),
)
}
if (
@@ -644,10 +644,7 @@
mediaCarouselScrollHandler.onSettingsButtonUpdated(settings)
settingsButton.setOnClickListener {
logger.logCarouselSettings()
- activityStarter.startActivity(
- settingsIntent,
- /* dismissShade= */ true,
- )
+ activityStarter.startActivity(settingsIntent, /* dismissShade= */ true)
}
}
@@ -739,7 +736,7 @@
val lp =
LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT
+ ViewGroup.LayoutParams.WRAP_CONTENT,
)
when (commonViewModel) {
is MediaCommonViewModel.MediaControl -> {
@@ -785,7 +782,7 @@
) {
mediaCarouselScrollHandler.scrollToPlayer(
mediaCarouselScrollHandler.visibleMediaIndex,
- destIndex = 0
+ destIndex = 0,
)
}
mediaCarouselScrollHandler.onPlayersChanged()
@@ -799,7 +796,7 @@
mediaCarouselScrollHandler.onPlayersChanged()
onAddOrUpdateVisibleToUserCard(
position,
- commonViewModel is MediaCommonViewModel.MediaControl
+ commonViewModel is MediaCommonViewModel.MediaControl,
)
}
@@ -856,7 +853,7 @@
mediaCarouselScrollHandler.qsExpanded,
mediaCarouselScrollHandler.visibleMediaIndex,
currentEndLocation,
- isMediaCardUpdate
+ isMediaCardUpdate,
)
}
}
@@ -893,7 +890,7 @@
secureSettings.getBoolForUser(
Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
true,
- UserHandle.USER_CURRENT
+ UserHandle.USER_CURRENT,
)
}
}
@@ -926,7 +923,7 @@
private fun reorderAllPlayers(
previousVisiblePlayerKey: MediaPlayerData.MediaSortKey?,
- key: String? = null
+ key: String? = null,
) {
mediaContent.removeAllViews()
for (mediaPlayer in MediaPlayerData.players()) {
@@ -960,7 +957,7 @@
TAG,
"Size of players list and number of views in carousel are out of sync. " +
"Players size is ${MediaPlayerData.players().size}. " +
- "View count is ${mediaContent.childCount}."
+ "View count is ${mediaContent.childCount}.",
)
}
}
@@ -970,7 +967,7 @@
key: String,
oldKey: String?,
data: MediaData,
- isSsReactivated: Boolean
+ isSsReactivated: Boolean,
): Boolean =
traceSection("MediaCarouselController#addOrUpdatePlayer") {
MediaPlayerData.moveIfExists(oldKey, key)
@@ -992,7 +989,7 @@
val lp =
LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT
+ ViewGroup.LayoutParams.WRAP_CONTENT,
)
newPlayer.mediaViewHolder?.player?.setLayoutParams(lp)
newPlayer.bindPlayer(data, key)
@@ -1005,7 +1002,7 @@
newPlayer,
systemClock,
isSsReactivated,
- debugLogger
+ debugLogger,
)
updateViewControllerToState(newPlayer.mediaViewController, noAnimation = true)
// Media data added from a recommendation card should starts playing.
@@ -1025,7 +1022,7 @@
existingPlayer,
systemClock,
isSsReactivated,
- debugLogger
+ debugLogger,
)
val packageName = MediaPlayerData.smartspaceMediaData?.packageName ?: String()
// In case of recommendations hits.
@@ -1051,7 +1048,7 @@
private fun addSmartspaceMediaRecommendations(
key: String,
data: SmartspaceMediaData,
- shouldPrioritize: Boolean
+ shouldPrioritize: Boolean,
) =
traceSection("MediaCarouselController#addSmartspaceMediaRecommendations") {
if (DEBUG) Log.d(TAG, "Updating smartspace target in carousel")
@@ -1090,7 +1087,7 @@
val lp =
LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT
+ ViewGroup.LayoutParams.WRAP_CONTENT,
)
newRecs.recommendationViewHolder?.recommendations?.setLayoutParams(lp)
newRecs.bindRecommendation(data)
@@ -1117,7 +1114,7 @@
TAG,
"Size of players list and number of views in carousel are out of sync. " +
"Players size is ${MediaPlayerData.players().size}. " +
- "View count is ${mediaContent.childCount}."
+ "View count is ${mediaContent.childCount}.",
)
}
}
@@ -1173,7 +1170,7 @@
addSmartspaceMediaRecommendations(
it.targetId,
it,
- MediaPlayerData.shouldPrioritizeSs
+ MediaPlayerData.shouldPrioritizeSs,
)
}
} else {
@@ -1185,7 +1182,7 @@
key = key,
oldKey = null,
data = data,
- isSsReactivated = isSsReactivated
+ isSsReactivated = isSsReactivated,
)
}
if (recreateMedia) {
@@ -1248,7 +1245,7 @@
@MediaLocation startLocation: Int,
@MediaLocation endLocation: Int,
progress: Float,
- immediately: Boolean
+ immediately: Boolean,
) {
if (
startLocation != currentStartLocation ||
@@ -1286,7 +1283,7 @@
squishFraction,
(pageIndicator.translationY + pageIndicator.height) /
mediaCarousel.measuredHeight,
- 1F
+ 1F,
)
var alpha = 1.0f
if (!endIsVisible || !startIsVisible) {
@@ -1354,7 +1351,7 @@
currentCarouselHeight = height
mediaCarouselScrollHandler.setCarouselBounds(
currentCarouselWidth,
- currentCarouselHeight
+ currentCarouselHeight,
)
updatePageIndicatorLocation()
updatePageIndicatorAlpha()
@@ -1386,13 +1383,13 @@
private fun updateViewControllerToState(
viewController: MediaViewController,
- noAnimation: Boolean
+ noAnimation: Boolean,
) {
viewController.setCurrentState(
startLocation = currentStartLocation,
endLocation = currentEndLocation,
transitionProgress = currentTransitionProgress,
- applyImmediately = noAnimation
+ applyImmediately = noAnimation,
)
}
@@ -1411,7 +1408,7 @@
desiredHostState: MediaHostState?,
animate: Boolean,
duration: Long = 200,
- startDelay: Long = 0
+ startDelay: Long = 0,
) =
traceSection("MediaCarouselController#onDesiredLocationChanged") {
desiredHostState?.let {
@@ -1435,7 +1432,7 @@
if (animate) {
mediaPlayer.mediaViewController.animatePendingStateChange(
duration = duration,
- delay = startDelay
+ delay = startDelay,
)
}
if (shouldCloseGuts && mediaPlayer.mediaViewController.isGutsVisible) {
@@ -1506,7 +1503,7 @@
mediaCarouselViewModel.onCardVisibleToUser(
qsExpanded,
mediaCarouselScrollHandler.visibleMediaIndex,
- currentEndLocation
+ currentEndLocation,
)
return
}
@@ -1524,7 +1521,7 @@
800, // SMARTSPACE_CARD_SEEN
it.mSmartspaceId,
it.mUid,
- intArrayOf(it.surfaceForSmartspaceLogging)
+ intArrayOf(it.surfaceForSmartspaceLogging),
)
it.mIsImpressed = true
}
@@ -1559,7 +1556,7 @@
interactedSubcardCardinality: Int = 0,
rank: Int = mediaCarouselScrollHandler.visibleMediaIndex,
receivedLatencyMillis: Int = 0,
- isSwipeToDismiss: Boolean = false
+ isSwipeToDismiss: Boolean = false,
) {
if (MediaPlayerData.players().size <= rank) {
return
@@ -1600,7 +1597,7 @@
interactedSubcardCardinality,
receivedLatencyMillis,
null, // Media cards cannot have subcards.
- null // Media cards don't have dimensions today.
+ null, // Media cards don't have dimensions today.
)
if (DEBUG) {
@@ -1613,7 +1610,7 @@
"uid: $uid " +
"interactedSubcardRank: $interactedSubcardRank " +
"interactedSubcardCardinality: $interactedSubcardCardinality " +
- "received_latency_millis: $receivedLatencyMillis"
+ "received_latency_millis: $receivedLatencyMillis",
)
}
}
@@ -1633,7 +1630,7 @@
it.mUid,
intArrayOf(it.surfaceForSmartspaceLogging),
rank = index,
- isSwipeToDismiss = true
+ isSwipeToDismiss = true,
)
// Reset card impressed state when swipe to dismissed
it.mIsImpressed = false
@@ -1692,7 +1689,7 @@
active = true,
resumeAction = null,
instanceId = InstanceId.fakeInstanceId(-1),
- appUid = -1
+ appUid = -1,
)
// Whether should prioritize Smartspace card.
@@ -1741,7 +1738,7 @@
player: MediaControlPanel,
clock: SystemClock,
isSsReactivated: Boolean,
- debugLogger: MediaCarouselControllerLogger? = null
+ debugLogger: MediaCarouselControllerLogger? = null,
) {
val removedPlayer = removeMediaPlayer(key)
if (removedPlayer != null && removedPlayer != player) {
@@ -1754,7 +1751,7 @@
data,
key,
clock.currentTimeMillis(),
- isSsReactivated = isSsReactivated
+ isSsReactivated = isSsReactivated,
)
mediaData.put(key, sortKey)
mediaPlayers.put(sortKey, player)
@@ -1768,7 +1765,7 @@
shouldPrioritize: Boolean,
clock: SystemClock,
debugLogger: MediaCarouselControllerLogger? = null,
- update: Boolean = false
+ update: Boolean = false,
) {
shouldPrioritizeSs = shouldPrioritize
val removedPlayer = removeMediaPlayer(key)
@@ -1782,7 +1779,7 @@
EMPTY.copy(active = data.isActive, isPlaying = false),
key,
clock.currentTimeMillis(),
- isSsReactivated = true
+ isSsReactivated = true,
)
mediaData.put(key, sortKey)
mediaPlayers.put(sortKey, player)
@@ -1793,7 +1790,7 @@
fun moveIfExists(
oldKey: String?,
newKey: String,
- debugLogger: MediaCarouselControllerLogger? = null
+ debugLogger: MediaCarouselControllerLogger? = null,
) {
if (oldKey == null || oldKey == newKey) {
return
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaActionViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaActionViewModel.kt
index 82099e6..3f22d54 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaActionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaActionViewModel.kt
@@ -32,4 +32,16 @@
val buttonId: Int? = null,
val isEnabled: Boolean,
val onClicked: (Int) -> Unit,
-)
+) {
+ fun contentEquals(other: MediaActionViewModel?): Boolean {
+ return other?.let {
+ contentDescription == other.contentDescription &&
+ isVisibleWhenScrubbing == other.isVisibleWhenScrubbing &&
+ notVisibleValue == other.notVisibleValue &&
+ showInCollapsed == other.showInCollapsed &&
+ rebindId == other.rebindId &&
+ buttonId == other.buttonId &&
+ isEnabled == other.isEnabled
+ } ?: false
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
index 64820e0..104d155 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
@@ -69,18 +69,30 @@
mediaControl?.let { toViewModel(it) }
}
}
- .distinctUntilChanged()
+ .distinctUntilChanged { old, new ->
+ (new == null && old == null) || new?.contentEquals(old) ?: false
+ }
.flowOn(backgroundDispatcher)
private var isPlaying = false
private var isAnyButtonClicked = false
private var location = -1
+ private var playerViewModel: MediaPlayerViewModel? = null
+
+ fun isNewPlayer(viewModel: MediaPlayerViewModel): Boolean {
+ val contentEquals = playerViewModel?.contentEquals(viewModel) ?: false
+ return (!contentEquals).also { playerViewModel = viewModel }
+ }
+
+ fun onMediaControlIsBound(artistName: CharSequence, titleName: CharSequence) {
+ interactor.logMediaControlIsBound(artistName, titleName)
+ }
private fun onDismissMediaData(
token: Token?,
uid: Int,
packageName: String,
- instanceId: InstanceId
+ instanceId: InstanceId,
) {
logger.logLongPressDismiss(uid, packageName, instanceId)
interactor.removeMediaControl(
@@ -88,7 +100,7 @@
instanceId,
MEDIA_PLAYER_ANIMATION_DELAY,
SMARTSPACE_CARD_DISMISS_EVENT,
- location
+ location,
)
}
@@ -99,7 +111,7 @@
applicationContext,
backgroundDispatcher,
model.artwork,
- TAG
+ TAG,
)
val scheme =
wallpaperColors?.let { ColorScheme(it, true, Style.CONTENT) }
@@ -107,7 +119,7 @@
applicationContext,
model.packageName,
TAG,
- Style.CONTENT
+ Style.CONTENT,
)
?: return null
@@ -131,7 +143,7 @@
R.string.controls_media_playing_item_description,
model.songName,
model.artistName,
- model.appName
+ model.appName,
)
}
},
@@ -157,7 +169,7 @@
expandable,
clickIntent,
SMARTSPACE_CARD_CLICK_EVENT,
- location
+ location,
)
}
},
@@ -177,7 +189,7 @@
}
}
},
- onLocationChanged = { location = it }
+ onLocationChanged = { location = it },
)
}
@@ -191,7 +203,7 @@
device?.name?.let {
TextUtils.equals(
it,
- applicationContext.getString(R.string.broadcasting_description_is_broadcasting)
+ applicationContext.getString(R.string.broadcasting_description_is_broadcasting),
)
} ?: false
val useDisabledAlpha =
@@ -236,19 +248,19 @@
logger.logOpenBroadcastDialog(
model.uid,
model.packageName,
- model.instanceId
+ model.instanceId,
)
interactor.startBroadcastDialog(
expandable,
device?.name.toString(),
- model.packageName
+ model.packageName,
)
} else {
logger.logOpenOutputSwitcher(model.uid, model.packageName, model.instanceId)
interactor.startMediaOutputDialog(
expandable,
model.packageName,
- model.token
+ model.token,
)
}
} else {
@@ -257,10 +269,10 @@
?: interactor.startMediaOutputDialog(
expandable,
model.packageName,
- model.token
+ model.token,
)
}
- }
+ },
)
}
@@ -270,7 +282,7 @@
if (model.isDismissible) {
applicationContext.getString(
R.string.controls_media_close_session,
- model.appName
+ model.appName,
)
} else {
applicationContext.getString(R.string.controls_media_active_session)
@@ -304,7 +316,7 @@
model,
mediaButton.getActionById(buttonId),
buttonId,
- isScrubbingTimeEnabled
+ isScrubbingTimeEnabled,
)
}
}
@@ -319,7 +331,7 @@
model: MediaControlModel,
mediaAction: MediaAction?,
buttonId: Int,
- canShowScrubbingTimeViews: Boolean
+ canShowScrubbingTimeViews: Boolean,
): MediaActionViewModel {
val showInCollapsed = SEMANTIC_ACTIONS_COMPACT.contains(buttonId)
val hideWhenScrubbing = SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING.contains(buttonId)
@@ -353,7 +365,7 @@
private fun toNotifActionViewModel(
model: MediaControlModel,
mediaAction: MediaAction,
- index: Int
+ index: Int,
): MediaActionViewModel {
return MediaActionViewModel(
icon = mediaAction.icon,
@@ -375,7 +387,7 @@
uid: Int,
packageName: String,
instanceId: InstanceId,
- action: Runnable
+ action: Runnable,
) {
logger.logTapAction(id, uid, packageName, instanceId)
interactor.logSmartspaceUserEvent(SMARTSPACE_CARD_CLICK_EVENT, location)
@@ -424,7 +436,7 @@
R.id.actionPrev,
R.id.actionNext,
R.id.action0,
- R.id.action1
+ R.id.action1,
)
const val TURBULENCE_NOISE_PLAY_MS_DURATION = 7500L
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaOutputSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaOutputSwitcherViewModel.kt
index 9df9bcc..2a47a5a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaOutputSwitcherViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaOutputSwitcherViewModel.kt
@@ -29,4 +29,15 @@
val alpha: Float,
val isVisible: Boolean,
val onClicked: (Expandable) -> Unit,
-)
+) {
+ fun contentEquals(other: MediaOutputSwitcherViewModel?): Boolean {
+ return (other?.let {
+ isTapEnabled == other.isTapEnabled &&
+ deviceString == other.deviceString &&
+ isCurrentBroadcastApp == other.isCurrentBroadcastApp &&
+ isIntentValid == other.isIntentValid &&
+ alpha == other.alpha &&
+ isVisible == other.isVisible
+ } ?: false)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt
index 96e7fc7..f4b0d6e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt
@@ -43,4 +43,30 @@
val onSeek: () -> Unit,
val onBindSeekbar: (SeekBarViewModel) -> Unit,
val onLocationChanged: (Int) -> Unit,
-)
+) {
+ fun contentEquals(other: MediaPlayerViewModel?): Boolean {
+ return other?.let {
+ other.backgroundCover == backgroundCover &&
+ appIcon == other.appIcon &&
+ useGrayColorFilter == other.useGrayColorFilter &&
+ artistName == other.artistName &&
+ titleName == other.titleName &&
+ isExplicitVisible == other.isExplicitVisible &&
+ shouldAddGradient == other.shouldAddGradient &&
+ canShowTime == other.canShowTime &&
+ playTurbulenceNoise == other.playTurbulenceNoise &&
+ useSemanticActions == other.useSemanticActions &&
+ areActionsEqual(other.actionButtons) &&
+ outputSwitcher.contentEquals(other.outputSwitcher)
+ } ?: false
+ }
+
+ private fun areActionsEqual(other: List<MediaActionViewModel>): Boolean {
+ actionButtons.forEachIndexed { index, mediaActionViewModel ->
+ if (!mediaActionViewModel.contentEquals(other[index])) {
+ return false
+ }
+ }
+ return true
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index ff9495d..2961d05 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -57,7 +57,7 @@
private static final float DEVICE_CONNECTED_ALPHA = 1f;
protected List<MediaItem> mMediaItemList = new CopyOnWriteArrayList<>();
- public MediaOutputAdapter(MediaOutputController controller) {
+ public MediaOutputAdapter(MediaSwitchingController controller) {
super(controller);
setHasStableIds(true);
}
@@ -531,8 +531,10 @@
@RequiresApi(34)
private static class Api34Impl {
@DoNotInline
- static View.OnClickListener getClickListenerBasedOnSelectionBehavior(MediaDevice device,
- MediaOutputController controller, View.OnClickListener defaultTransferListener) {
+ static View.OnClickListener getClickListenerBasedOnSelectionBehavior(
+ MediaDevice device,
+ MediaSwitchingController controller,
+ View.OnClickListener defaultTransferListener) {
switch (device.getSelectionBehavior()) {
case SELECTION_BEHAVIOR_NONE:
return null;
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 5958b0a..63a7e01 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -63,7 +63,7 @@
static final int CUSTOMIZED_ITEM_GROUP = 2;
static final int CUSTOMIZED_ITEM_DYNAMIC_GROUP = 3;
- protected final MediaOutputController mController;
+ protected final MediaSwitchingController mController;
private static final int UNMUTE_DEFAULT_VOLUME = 2;
@@ -73,7 +73,7 @@
int mCurrentActivePosition;
private boolean mIsInitVolumeFirstTime;
- public MediaOutputBaseAdapter(MediaOutputController controller) {
+ public MediaOutputBaseAdapter(MediaSwitchingController controller) {
mController = controller;
mIsDragging = false;
mCurrentActivePosition = -1;
@@ -127,7 +127,7 @@
return mCurrentActivePosition;
}
- public MediaOutputController getController() {
+ public MediaSwitchingController getController() {
return mController;
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 6cc4dcb..6bc995f3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -65,11 +65,9 @@
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
-/**
- * Base dialog for media output UI
- */
-public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
- MediaOutputController.Callback, Window.Callback {
+/** Base dialog for media output UI */
+public abstract class MediaOutputBaseDialog extends SystemUIDialog
+ implements MediaSwitchingController.Callback, Window.Callback {
private static final String TAG = "MediaOutputDialog";
private static final String EMPTY_TITLE = " ";
@@ -82,7 +80,7 @@
private final RecyclerView.LayoutManager mLayoutManager;
final Context mContext;
- final MediaOutputController mMediaOutputController;
+ final MediaSwitchingController mMediaSwitchingController;
final BroadcastSender mBroadcastSender;
/**
@@ -212,22 +210,22 @@
@Override
public void onLayoutCompleted(RecyclerView.State state) {
super.onLayoutCompleted(state);
- mMediaOutputController.setRefreshing(false);
- mMediaOutputController.refreshDataSetIfNeeded();
+ mMediaSwitchingController.setRefreshing(false);
+ mMediaSwitchingController.refreshDataSetIfNeeded();
}
}
public MediaOutputBaseDialog(
Context context,
BroadcastSender broadcastSender,
- MediaOutputController mediaOutputController,
+ MediaSwitchingController mediaSwitchingController,
boolean includePlaybackAndAppMetadata) {
super(context, R.style.Theme_SystemUI_Dialog_Media);
// Save the context that is wrapped with our theme.
mContext = getContext();
mBroadcastSender = broadcastSender;
- mMediaOutputController = mediaOutputController;
+ mMediaSwitchingController = mediaSwitchingController;
mLayoutManager = new LayoutManagerWrapper(mContext);
mListMaxHeight = context.getResources().getDimensionPixelSize(
R.dimen.media_output_dialog_list_max_height);
@@ -279,9 +277,9 @@
// Init bottom buttons
mDoneButton.setOnClickListener(v -> dismiss());
mStopButton.setOnClickListener(v -> onStopButtonClick());
- mAppButton.setOnClickListener(mMediaOutputController::tryToLaunchMediaApplication);
+ mAppButton.setOnClickListener(mMediaSwitchingController::tryToLaunchMediaApplication);
mMediaMetadataSectionLayout.setOnClickListener(
- mMediaOutputController::tryToLaunchMediaApplication);
+ mMediaSwitchingController::tryToLaunchMediaApplication);
mDismissing = false;
}
@@ -298,10 +296,10 @@
@Override
public void start() {
- mMediaOutputController.start(this);
+ mMediaSwitchingController.start(this);
if (isBroadcastSupported() && !mIsLeBroadcastCallbackRegistered) {
- mMediaOutputController.registerLeBroadcastServiceCallback(mExecutor,
- mBroadcastCallback);
+ mMediaSwitchingController.registerLeBroadcastServiceCallback(
+ mExecutor, mBroadcastCallback);
mIsLeBroadcastCallbackRegistered = true;
}
}
@@ -311,11 +309,11 @@
// unregister broadcast callback should only depend on profile and registered flag
// rather than remote device or broadcast state
// otherwise it might have risks of leaking registered callback handle
- if (mMediaOutputController.isBroadcastSupported() && mIsLeBroadcastCallbackRegistered) {
- mMediaOutputController.unregisterLeBroadcastServiceCallback(mBroadcastCallback);
+ if (mMediaSwitchingController.isBroadcastSupported() && mIsLeBroadcastCallbackRegistered) {
+ mMediaSwitchingController.unregisterLeBroadcastServiceCallback(mBroadcastCallback);
mIsLeBroadcastCallbackRegistered = false;
}
- mMediaOutputController.stop();
+ mMediaSwitchingController.stop();
}
@VisibleForTesting
@@ -326,18 +324,17 @@
void refresh(boolean deviceSetChanged) {
// TODO(287191450): remove binder calls in this method from the UI thread.
// If the dialog is going away or is already refreshing, do nothing.
- if (mDismissing || mMediaOutputController.isRefreshing()) {
+ if (mDismissing || mMediaSwitchingController.isRefreshing()) {
return;
}
- mMediaOutputController.setRefreshing(true);
+ mMediaSwitchingController.setRefreshing(true);
// Update header icon
final int iconRes = getHeaderIconRes();
final IconCompat headerIcon = getHeaderIcon();
final IconCompat appSourceIcon = getAppSourceIcon();
boolean colorSetUpdated = false;
mCastAppLayout.setVisibility(
- mMediaOutputController.shouldShowLaunchSection()
- ? View.VISIBLE : View.GONE);
+ mMediaSwitchingController.shouldShowLaunchSection() ? View.VISIBLE : View.GONE);
if (iconRes != 0) {
mHeaderIcon.setVisibility(View.VISIBLE);
mHeaderIcon.setImageResource(iconRes);
@@ -371,10 +368,10 @@
mAppResourceIcon.setVisibility(View.GONE);
} else if (appSourceIcon != null) {
Icon appIcon = appSourceIcon.toIcon(mContext);
- mAppResourceIcon.setColorFilter(mMediaOutputController.getColorItemContent());
+ mAppResourceIcon.setColorFilter(mMediaSwitchingController.getColorItemContent());
mAppResourceIcon.setImageIcon(appIcon);
} else {
- Drawable appIconDrawable = mMediaOutputController.getAppSourceIconFromPackage();
+ Drawable appIconDrawable = mMediaSwitchingController.getAppSourceIconFromPackage();
if (appIconDrawable != null) {
mAppResourceIcon.setImageDrawable(appIconDrawable);
} else {
@@ -387,7 +384,7 @@
R.dimen.media_output_dialog_header_icon_padding);
mHeaderIcon.setLayoutParams(new LinearLayout.LayoutParams(size + padding, size));
}
- mAppButton.setText(mMediaOutputController.getAppSourceName());
+ mAppButton.setText(mMediaSwitchingController.getAppSourceName());
if (!mIncludePlaybackAndAppMetadata) {
mHeaderTitle.setVisibility(View.GONE);
@@ -424,23 +421,26 @@
mAdapter.updateItems();
}
} else {
- mMediaOutputController.setRefreshing(false);
- mMediaOutputController.refreshDataSetIfNeeded();
+ mMediaSwitchingController.setRefreshing(false);
+ mMediaSwitchingController.refreshDataSetIfNeeded();
}
}
private void updateButtonBackgroundColorFilter() {
- ColorFilter buttonColorFilter = new PorterDuffColorFilter(
- mMediaOutputController.getColorButtonBackground(),
- PorterDuff.Mode.SRC_IN);
+ ColorFilter buttonColorFilter =
+ new PorterDuffColorFilter(
+ mMediaSwitchingController.getColorButtonBackground(),
+ PorterDuff.Mode.SRC_IN);
mDoneButton.getBackground().setColorFilter(buttonColorFilter);
mStopButton.getBackground().setColorFilter(buttonColorFilter);
- mDoneButton.setTextColor(mMediaOutputController.getColorPositiveButtonText());
+ mDoneButton.setTextColor(mMediaSwitchingController.getColorPositiveButtonText());
}
private void updateDialogBackgroundColor() {
- getDialogView().getBackground().setTint(mMediaOutputController.getColorDialogBackground());
- mDeviceListLayout.setBackgroundColor(mMediaOutputController.getColorDialogBackground());
+ getDialogView()
+ .getBackground()
+ .setTint(mMediaSwitchingController.getColorDialogBackground());
+ mDeviceListLayout.setBackgroundColor(mMediaSwitchingController.getColorDialogBackground());
}
private Drawable resizeDrawable(Drawable drawable, int size) {
@@ -499,7 +499,7 @@
protected void startLeBroadcast() {
mStopButton.setText(R.string.media_output_broadcast_starting);
mStopButton.setEnabled(false);
- if (!mMediaOutputController.startBluetoothLeBroadcast()) {
+ if (!mMediaSwitchingController.startBluetoothLeBroadcast()) {
// If the system can't execute "broadcast start", then UI shows the error.
handleLeBroadcastStartFailed();
}
@@ -512,9 +512,10 @@
&& sharedPref.getBoolean(PREF_IS_LE_BROADCAST_FIRST_LAUNCH, true)) {
Log.d(TAG, "PREF_IS_LE_BROADCAST_FIRST_LAUNCH: true");
- mMediaOutputController.launchLeBroadcastNotifyDialog(mDialogView,
+ mMediaSwitchingController.launchLeBroadcastNotifyDialog(
+ mDialogView,
mBroadcastSender,
- MediaOutputController.BroadcastNotifyDialog.ACTION_FIRST_LAUNCH,
+ MediaSwitchingController.BroadcastNotifyDialog.ACTION_FIRST_LAUNCH,
(d, w) -> {
startLeBroadcast();
});
@@ -527,14 +528,13 @@
}
protected void startLeBroadcastDialog() {
- mMediaOutputController.launchMediaOutputBroadcastDialog(mDialogView,
- mBroadcastSender);
+ mMediaSwitchingController.launchMediaOutputBroadcastDialog(mDialogView, mBroadcastSender);
refresh();
}
protected void stopLeBroadcast() {
mStopButton.setEnabled(false);
- if (!mMediaOutputController.stopBluetoothLeBroadcast()) {
+ if (!mMediaSwitchingController.stopBluetoothLeBroadcast()) {
// If the system can't execute "broadcast stop", then UI does refresh.
mMainThreadHandler.post(() -> refresh());
}
@@ -559,7 +559,7 @@
}
public void onStopButtonClick() {
- mMediaOutputController.releaseSession();
+ mMediaSwitchingController.releaseSession();
dismiss();
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
index 1e31755..9b5b872a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
@@ -235,14 +235,17 @@
}
};
- MediaOutputBroadcastDialog(Context context, boolean aboveStatusbar,
- BroadcastSender broadcastSender, MediaOutputController mediaOutputController) {
+ MediaOutputBroadcastDialog(
+ Context context,
+ boolean aboveStatusbar,
+ BroadcastSender broadcastSender,
+ MediaSwitchingController mediaSwitchingController) {
super(
context,
broadcastSender,
- mediaOutputController, /* includePlaybackAndAppMetadata */
+ mediaSwitchingController, /* includePlaybackAndAppMetadata */
true);
- mAdapter = new MediaOutputAdapter(mMediaOutputController);
+ mAdapter = new MediaOutputAdapter(mMediaSwitchingController);
// TODO(b/226710953): Move the part to MediaOutputBaseDialog for every class
// that extends MediaOutputBaseDialog
if (!aboveStatusbar) {
@@ -262,8 +265,8 @@
super.start();
if (!mIsLeBroadcastAssistantCallbackRegistered) {
mIsLeBroadcastAssistantCallbackRegistered = true;
- mMediaOutputController.registerLeBroadcastAssistantServiceCallback(mExecutor,
- mBroadcastAssistantCallback);
+ mMediaSwitchingController.registerLeBroadcastAssistantServiceCallback(
+ mExecutor, mBroadcastAssistantCallback);
}
/* Add local source broadcast to connected capable devices that may be possible receivers
* of stream.
@@ -276,7 +279,7 @@
super.stop();
if (mIsLeBroadcastAssistantCallbackRegistered) {
mIsLeBroadcastAssistantCallbackRegistered = false;
- mMediaOutputController.unregisterLeBroadcastAssistantServiceCallback(
+ mMediaSwitchingController.unregisterLeBroadcastAssistantServiceCallback(
mBroadcastAssistantCallback);
}
}
@@ -288,7 +291,7 @@
@Override
IconCompat getHeaderIcon() {
- return mMediaOutputController.getHeaderIcon();
+ return mMediaSwitchingController.getHeaderIcon();
}
@Override
@@ -299,17 +302,17 @@
@Override
CharSequence getHeaderText() {
- return mMediaOutputController.getHeaderTitle();
+ return mMediaSwitchingController.getHeaderTitle();
}
@Override
CharSequence getHeaderSubtitle() {
- return mMediaOutputController.getHeaderSubTitle();
+ return mMediaSwitchingController.getHeaderSubTitle();
}
@Override
IconCompat getAppSourceIcon() {
- return mMediaOutputController.getNotificationSmallIcon();
+ return mMediaSwitchingController.getNotificationSmallIcon();
}
@Override
@@ -319,16 +322,16 @@
@Override
public void onStopButtonClick() {
- mMediaOutputController.stopBluetoothLeBroadcast();
+ mMediaSwitchingController.stopBluetoothLeBroadcast();
dismiss();
}
private String getBroadcastMetadataInfo(int metadata) {
switch (metadata) {
case METADATA_BROADCAST_NAME:
- return mMediaOutputController.getBroadcastName();
+ return mMediaSwitchingController.getBroadcastName();
case METADATA_BROADCAST_CODE:
- return mMediaOutputController.getBroadcastCode();
+ return mMediaSwitchingController.getBroadcastCode();
default:
return "";
}
@@ -342,13 +345,15 @@
mBroadcastQrCodeView = getDialogView().requireViewById(R.id.qrcode_view);
mBroadcastNotify = getDialogView().requireViewById(R.id.broadcast_info);
- mBroadcastNotify.setOnClickListener(v -> {
- mMediaOutputController.launchLeBroadcastNotifyDialog(
- /* view= */ null,
- /* broadcastSender= */ null,
- MediaOutputController.BroadcastNotifyDialog.ACTION_BROADCAST_INFO_ICON,
- /* onClickListener= */ null);
- });
+ mBroadcastNotify.setOnClickListener(
+ v -> {
+ mMediaSwitchingController.launchLeBroadcastNotifyDialog(
+ /* mediaOutputDialog= */ null,
+ /* broadcastSender= */ null,
+ MediaSwitchingController.BroadcastNotifyDialog
+ .ACTION_BROADCAST_INFO_ICON,
+ /* listener= */ null);
+ });
mBroadcastName = getDialogView().requireViewById(R.id.broadcast_name_summary);
mBroadcastNameEdit = getDialogView().requireViewById(R.id.broadcast_name_edit);
mBroadcastNameEdit.setOnClickListener(v -> {
@@ -409,16 +414,16 @@
return;
}
- for (BluetoothDevice sink : mMediaOutputController.getConnectedBroadcastSinkDevices()) {
+ for (BluetoothDevice sink : mMediaSwitchingController.getConnectedBroadcastSinkDevices()) {
Log.d(TAG, "The broadcastMetadata broadcastId: " + broadcastMetadata.getBroadcastId()
+ ", the device: " + sink.getAnonymizedAddress());
- if (mMediaOutputController.isThereAnyBroadcastSourceIntoSinkDevice(sink)) {
+ if (mMediaSwitchingController.isThereAnyBroadcastSourceIntoSinkDevice(sink)) {
Log.d(TAG, "The sink device has the broadcast source now.");
return;
}
- if (!mMediaOutputController.addSourceIntoSinkDeviceWithBluetoothLeAssistant(sink,
- broadcastMetadata, /*isGroupOp=*/ false)) {
+ if (!mMediaSwitchingController.addSourceIntoSinkDeviceWithBluetoothLeAssistant(
+ sink, broadcastMetadata, /* isGroupOp= */ false)) {
Log.e(TAG, "Error: Source add failed");
}
}
@@ -457,11 +462,11 @@
}
private String getLocalBroadcastMetadataQrCodeString() {
- return mMediaOutputController.getLocalBroadcastMetadataQrCodeString();
+ return mMediaSwitchingController.getLocalBroadcastMetadataQrCodeString();
}
private BluetoothLeBroadcastMetadata getBroadcastMetadata() {
- return mMediaOutputController.getBroadcastMetadata();
+ return mMediaSwitchingController.getBroadcastMetadata();
}
@VisibleForTesting
@@ -476,8 +481,8 @@
* stopped then used the new Broadcast code to start the Broadcast.
*/
mIsStopbyUpdateBroadcastCode = true;
- mMediaOutputController.setBroadcastCode(updatedString);
- if (!mMediaOutputController.stopBluetoothLeBroadcast()) {
+ mMediaSwitchingController.setBroadcastCode(updatedString);
+ if (!mMediaSwitchingController.stopBluetoothLeBroadcast()) {
handleLeBroadcastStopFailed();
return;
}
@@ -485,8 +490,8 @@
/* If the user wants to update the Broadcast Name, we don't need to stop the Broadcast
* session. Only use the new Broadcast name to update the broadcast session.
*/
- mMediaOutputController.setBroadcastName(updatedString);
- if (!mMediaOutputController.updateBluetoothLeBroadcast()) {
+ mMediaSwitchingController.setBroadcastName(updatedString);
+ if (!mMediaSwitchingController.updateBluetoothLeBroadcast()) {
handleLeBroadcastUpdateFailed();
}
}
@@ -496,12 +501,13 @@
public boolean isBroadcastSupported() {
if (!legacyLeAudioSharing()) return false;
boolean isBluetoothLeDevice = false;
- if (mMediaOutputController.getCurrentConnectedMediaDevice() != null) {
- isBluetoothLeDevice = mMediaOutputController.isBluetoothLeDevice(
- mMediaOutputController.getCurrentConnectedMediaDevice());
+ if (mMediaSwitchingController.getCurrentConnectedMediaDevice() != null) {
+ isBluetoothLeDevice =
+ mMediaSwitchingController.isBluetoothLeDevice(
+ mMediaSwitchingController.getCurrentConnectedMediaDevice());
}
- return mMediaOutputController.isBroadcastSupported() && isBluetoothLeDevice;
+ return mMediaSwitchingController.isBroadcastSupported() && isBluetoothLeDevice;
}
@Override
@@ -515,7 +521,7 @@
@Override
public void handleLeBroadcastStartFailed() {
- mMediaOutputController.setBroadcastCode(mCurrentBroadcastCode);
+ mMediaSwitchingController.setBroadcastCode(mCurrentBroadcastCode);
mRetryCount++;
handleUpdateFailedUi();
@@ -538,8 +544,8 @@
@Override
public void handleLeBroadcastUpdateFailed() {
- //Change the value in shared preferences back to it original value
- mMediaOutputController.setBroadcastName(mCurrentBroadcastName);
+ // Change the value in shared preferences back to it original value
+ mMediaSwitchingController.setBroadcastName(mCurrentBroadcastName);
mRetryCount++;
handleUpdateFailedUi();
@@ -550,7 +556,7 @@
if (mIsStopbyUpdateBroadcastCode) {
mIsStopbyUpdateBroadcastCode = false;
mRetryCount = 0;
- if (!mMediaOutputController.startBluetoothLeBroadcast()) {
+ if (!mMediaSwitchingController.startBluetoothLeBroadcast()) {
handleLeBroadcastStartFailed();
return;
}
@@ -561,8 +567,8 @@
@Override
public void handleLeBroadcastStopFailed() {
- //Change the value in shared preferences back to it original value
- mMediaOutputController.setBroadcastCode(mCurrentBroadcastCode);
+ // Change the value in shared preferences back to it original value
+ mMediaSwitchingController.setBroadcastCode(mCurrentBroadcastCode);
mRetryCount++;
handleUpdateFailedUi();
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
index 6ef9ea3..2e7e66f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
@@ -29,7 +29,7 @@
private val context: Context,
private val broadcastSender: BroadcastSender,
private val dialogTransitionAnimator: DialogTransitionAnimator,
- private val mediaOutputControllerFactory: MediaOutputController.Factory
+ private val mediaSwitchingControllerFactory: MediaSwitchingController.Factory
) {
var mediaOutputBroadcastDialog: MediaOutputBroadcastDialog? = null
@@ -41,7 +41,7 @@
// TODO: b/321969740 - Populate the userHandle parameter. The user handle is necessary to
// disambiguate the same package running on different users.
val controller =
- mediaOutputControllerFactory.create(
+ mediaSwitchingControllerFactory.create(
packageName,
/* userHandle= */ null,
/* token */ null,
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
index eb6a320..c9af7b3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
@@ -46,14 +46,14 @@
Context context,
boolean aboveStatusbar,
BroadcastSender broadcastSender,
- MediaOutputController mediaOutputController,
+ MediaSwitchingController mediaSwitchingController,
DialogTransitionAnimator dialogTransitionAnimator,
UiEventLogger uiEventLogger,
boolean includePlaybackAndAppMetadata) {
- super(context, broadcastSender, mediaOutputController, includePlaybackAndAppMetadata);
+ super(context, broadcastSender, mediaSwitchingController, includePlaybackAndAppMetadata);
mDialogTransitionAnimator = dialogTransitionAnimator;
mUiEventLogger = uiEventLogger;
- mAdapter = new MediaOutputAdapter(mMediaOutputController);
+ mAdapter = new MediaOutputAdapter(mMediaSwitchingController);
if (!aboveStatusbar) {
getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
}
@@ -72,7 +72,7 @@
@Override
IconCompat getHeaderIcon() {
- return mMediaOutputController.getHeaderIcon();
+ return mMediaSwitchingController.getHeaderIcon();
}
@Override
@@ -83,27 +83,29 @@
@Override
CharSequence getHeaderText() {
- return mMediaOutputController.getHeaderTitle();
+ return mMediaSwitchingController.getHeaderTitle();
}
@Override
CharSequence getHeaderSubtitle() {
- return mMediaOutputController.getHeaderSubTitle();
+ return mMediaSwitchingController.getHeaderSubTitle();
}
@Override
IconCompat getAppSourceIcon() {
- return mMediaOutputController.getNotificationSmallIcon();
+ return mMediaSwitchingController.getNotificationSmallIcon();
}
@Override
int getStopButtonVisibility() {
boolean isActiveRemoteDevice = false;
- if (mMediaOutputController.getCurrentConnectedMediaDevice() != null) {
- isActiveRemoteDevice = mMediaOutputController.isActiveRemoteDevice(
- mMediaOutputController.getCurrentConnectedMediaDevice());
+ if (mMediaSwitchingController.getCurrentConnectedMediaDevice() != null) {
+ isActiveRemoteDevice =
+ mMediaSwitchingController.isActiveRemoteDevice(
+ mMediaSwitchingController.getCurrentConnectedMediaDevice());
}
- boolean showBroadcastButton = isBroadcastSupported() && mMediaOutputController.isPlaying();
+ boolean showBroadcastButton =
+ isBroadcastSupported() && mMediaSwitchingController.isPlaying();
return (isActiveRemoteDevice || showBroadcastButton) ? View.VISIBLE : View.GONE;
}
@@ -115,13 +117,14 @@
boolean isBroadcastEnabled = false;
if (FeatureFlagUtils.isEnabled(mContext,
FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST)) {
- if (mMediaOutputController.getCurrentConnectedMediaDevice() != null) {
- isBluetoothLeDevice = mMediaOutputController.isBluetoothLeDevice(
- mMediaOutputController.getCurrentConnectedMediaDevice());
+ if (mMediaSwitchingController.getCurrentConnectedMediaDevice() != null) {
+ isBluetoothLeDevice =
+ mMediaSwitchingController.isBluetoothLeDevice(
+ mMediaSwitchingController.getCurrentConnectedMediaDevice());
// if broadcast is active, broadcast should be considered as supported
// there could be a valid case that broadcast is ongoing
// without active LEA device connected
- isBroadcastEnabled = mMediaOutputController.isBluetoothLeBroadcastEnabled();
+ isBroadcastEnabled = mMediaSwitchingController.isBluetoothLeBroadcastEnabled();
}
} else {
// To decouple LE Audio Broadcast and Unicast, it always displays the button when there
@@ -129,15 +132,16 @@
isBluetoothLeDevice = true;
}
- return mMediaOutputController.isBroadcastSupported()
+ return mMediaSwitchingController.isBroadcastSupported()
&& (isBluetoothLeDevice || isBroadcastEnabled);
}
@Override
public CharSequence getStopButtonText() {
int resId = R.string.media_output_dialog_button_stop_casting;
- if (isBroadcastSupported() && mMediaOutputController.isPlaying()
- && !mMediaOutputController.isBluetoothLeBroadcastEnabled()) {
+ if (isBroadcastSupported()
+ && mMediaSwitchingController.isPlaying()
+ && !mMediaSwitchingController.isBluetoothLeBroadcastEnabled()) {
resId = R.string.media_output_broadcast;
}
return mContext.getText(resId);
@@ -145,8 +149,8 @@
@Override
public void onStopButtonClick() {
- if (isBroadcastSupported() && mMediaOutputController.isPlaying()) {
- if (!mMediaOutputController.isBluetoothLeBroadcastEnabled()) {
+ if (isBroadcastSupported() && mMediaSwitchingController.isPlaying()) {
+ if (!mMediaSwitchingController.isBluetoothLeBroadcastEnabled()) {
if (startLeBroadcastDialogForFirstTime()) {
return;
}
@@ -155,7 +159,7 @@
stopLeBroadcast();
}
} else {
- mMediaOutputController.releaseSession();
+ mMediaSwitchingController.releaseSession();
mDialogTransitionAnimator.disableAllCurrentDialogsExitAnimations();
dismiss();
}
@@ -163,8 +167,9 @@
@Override
public int getBroadcastIconVisibility() {
- return (isBroadcastSupported() && mMediaOutputController.isBluetoothLeBroadcastEnabled())
- ? View.VISIBLE : View.GONE;
+ return (isBroadcastSupported() && mMediaSwitchingController.isBluetoothLeBroadcastEnabled())
+ ? View.VISIBLE
+ : View.GONE;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
index 47e0691..4e9451a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
@@ -35,7 +35,7 @@
private val broadcastSender: BroadcastSender,
private val uiEventLogger: UiEventLogger,
private val dialogTransitionAnimator: DialogTransitionAnimator,
- private val mediaOutputControllerFactory: MediaOutputController.Factory,
+ private val mediaSwitchingControllerFactory: MediaSwitchingController.Factory,
) {
companion object {
const val INTERACTION_JANK_TAG = "media_output"
@@ -118,7 +118,7 @@
// Dismiss the previous dialog, if any.
mediaOutputDialog?.dismiss()
- val controller = mediaOutputControllerFactory.create(packageName, userHandle, token)
+ val controller = mediaSwitchingControllerFactory.create(packageName, userHandle, token)
val mediaOutputDialog =
MediaOutputDialog(
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
rename to packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
index 875e505..f7b7353 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
@@ -77,6 +77,7 @@
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastMetadata;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.media.InfoMediaManager;
+import com.android.settingslib.media.InputRouteManager;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.media.flags.Flags;
@@ -116,12 +117,13 @@
import java.util.stream.Collectors;
/**
- * Controller for media output dialog
+ * Controller for a dialog that allows users to switch media output and input devices, control
+ * volume, connect to new devices, etc.
*/
-public class MediaOutputController implements LocalMediaManager.DeviceCallback,
- INearbyMediaDevicesUpdateCallback {
+public class MediaSwitchingController
+ implements LocalMediaManager.DeviceCallback, INearbyMediaDevicesUpdateCallback {
- private static final String TAG = "MediaOutputController";
+ private static final String TAG = "MediaSwitchingController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final String PAGE_CONNECTED_DEVICES_KEY =
"top_level_connected_devices";
@@ -137,10 +139,12 @@
private final DialogTransitionAnimator mDialogTransitionAnimator;
private final CommonNotifCollection mNotifCollection;
protected final Object mMediaDevicesLock = new Object();
+ protected final Object mInputMediaDevicesLock = new Object();
@VisibleForTesting
final List<MediaDevice> mGroupMediaDevices = new CopyOnWriteArrayList<>();
final List<MediaDevice> mCachedMediaDevices = new CopyOnWriteArrayList<>();
- private final List<MediaItem> mMediaItemList = new CopyOnWriteArrayList<>();
+ private final List<MediaItem> mOutputMediaItemList = new CopyOnWriteArrayList<>();
+ private final List<MediaItem> mInputMediaItemList = new CopyOnWriteArrayList<>();
private final AudioManager mAudioManager;
private final PowerExemptionManager mPowerExemptionManager;
private final KeyguardManager mKeyGuardManager;
@@ -153,6 +157,7 @@
@VisibleForTesting
boolean mNeedRefresh = false;
private MediaController mMediaController;
+ @VisibleForTesting InputRouteManager mInputRouteManager;
@VisibleForTesting
Callback mCallback;
@VisibleForTesting
@@ -181,8 +186,20 @@
ACTION_BROADCAST_INFO_ICON
}
+ @VisibleForTesting
+ final InputRouteManager.InputDeviceCallback mInputDeviceCallback =
+ new InputRouteManager.InputDeviceCallback() {
+ @Override
+ public void onInputDeviceListUpdated(@NonNull List<MediaDevice> devices) {
+ synchronized (mInputMediaDevicesLock) {
+ buildInputMediaItems(devices);
+ mCallback.onDeviceListChanged();
+ }
+ }
+ };
+
@AssistedInject
- public MediaOutputController(
+ public MediaSwitchingController(
Context context,
@Assisted String packageName,
@Assisted @Nullable UserHandle userHandle,
@@ -241,19 +258,23 @@
R.dimen.media_output_dialog_default_margin_end);
mItemMarginEndSelectable = (int) mContext.getResources().getDimension(
R.dimen.media_output_dialog_selectable_margin_end);
+
+ if (enableInputRouting()) {
+ mInputRouteManager = new InputRouteManager(mContext, audioManager);
+ }
}
@AssistedFactory
public interface Factory {
- /** Construct a MediaOutputController */
- MediaOutputController create(
+ /** Construct a MediaSwitchingController */
+ MediaSwitchingController create(
String packageName, UserHandle userHandle, MediaSession.Token token);
}
protected void start(@NonNull Callback cb) {
synchronized (mMediaDevicesLock) {
mCachedMediaDevices.clear();
- mMediaItemList.clear();
+ mOutputMediaItemList.clear();
}
mNearbyDeviceInfoMap.clear();
if (mNearbyMediaDevicesManager != null) {
@@ -277,6 +298,10 @@
mCallback = cb;
mLocalMediaManager.registerCallback(this);
mLocalMediaManager.startScan();
+
+ if (enableInputRouting()) {
+ mInputRouteManager.registerCallback(mInputDeviceCallback);
+ }
}
boolean shouldShowLaunchSection() {
@@ -300,12 +325,19 @@
mLocalMediaManager.stopScan();
synchronized (mMediaDevicesLock) {
mCachedMediaDevices.clear();
- mMediaItemList.clear();
+ mOutputMediaItemList.clear();
}
if (mNearbyMediaDevicesManager != null) {
mNearbyMediaDevicesManager.unregisterNearbyDevicesCallback(this);
}
mNearbyDeviceInfoMap.clear();
+
+ if (enableInputRouting()) {
+ mInputRouteManager.unregisterCallback(mInputDeviceCallback);
+ synchronized (mInputMediaDevicesLock) {
+ mInputMediaItemList.clear();
+ }
+ }
}
private MediaController getMediaController() {
@@ -335,7 +367,7 @@
@Override
public void onDeviceListUpdate(List<MediaDevice> devices) {
- boolean isListEmpty = mMediaItemList.isEmpty();
+ boolean isListEmpty = mOutputMediaItemList.isEmpty();
if (isListEmpty || !mIsRefreshing) {
buildMediaItems(devices);
mCallback.onDeviceListChanged();
@@ -352,7 +384,8 @@
public void onSelectedDeviceStateChanged(MediaDevice device,
@LocalMediaManager.MediaDeviceState int state) {
mCallback.onRouteChanged();
- mMetricLogger.logOutputItemSuccess(device.toString(), new ArrayList<>(mMediaItemList));
+ mMetricLogger.logOutputItemSuccess(
+ device.toString(), new ArrayList<>(mOutputMediaItemList));
}
@Override
@@ -363,7 +396,7 @@
@Override
public void onRequestFailed(int reason) {
mCallback.onRouteChanged();
- mMetricLogger.logOutputItemFailure(new ArrayList<>(mMediaItemList), reason);
+ mMetricLogger.logOutputItemFailure(new ArrayList<>(mOutputMediaItemList), reason);
}
/**
@@ -382,7 +415,7 @@
}
try {
synchronized (mMediaDevicesLock) {
- mMediaItemList.removeIf((MediaItem::isMutingExpectedDevice));
+ mOutputMediaItemList.removeIf((MediaItem::isMutingExpectedDevice));
}
mAudioManager.cancelMuteAwaitConnection(mAudioManager.getMutingExpectedDevice());
} catch (Exception e) {
@@ -638,9 +671,9 @@
private void buildMediaItems(List<MediaDevice> devices) {
synchronized (mMediaDevicesLock) {
- List<MediaItem> updatedMediaItems = buildMediaItems(mMediaItemList, devices);
- mMediaItemList.clear();
- mMediaItemList.addAll(updatedMediaItems);
+ List<MediaItem> updatedMediaItems = buildMediaItems(mOutputMediaItemList, devices);
+ mOutputMediaItemList.clear();
+ mOutputMediaItemList.addAll(updatedMediaItems);
}
}
@@ -714,6 +747,19 @@
}
}
+ private boolean enableInputRouting() {
+ return com.android.media.flags.Flags.enableAudioInputDeviceRoutingAndVolumeControl();
+ }
+
+ private void buildInputMediaItems(List<MediaDevice> devices) {
+ synchronized (mInputMediaDevicesLock) {
+ List<MediaItem> updatedInputMediaItems =
+ devices.stream().map(MediaItem::createDeviceMediaItem).toList();
+ mInputMediaItemList.clear();
+ mInputMediaItemList.addAll(updatedInputMediaItems);
+ }
+ }
+
/**
* Initial categorization of current devices, will not be called for updates to the devices
* list.
@@ -778,7 +824,6 @@
mediaDevice.setRangeZone(mNearbyDeviceInfoMap.get(mediaDevice.getId()));
}
}
-
}
boolean isCurrentConnectedDeviceRemote() {
@@ -837,8 +882,31 @@
});
}
+ private void addInputDevices(List<MediaItem> mediaItems) {
+ mediaItems.add(
+ MediaItem.createGroupDividerMediaItem(
+ mContext.getString(R.string.media_input_group_title)));
+ mediaItems.addAll(mInputMediaItemList);
+ }
+
+ private void addOutputDevices(List<MediaItem> mediaItems) {
+ mediaItems.add(
+ MediaItem.createGroupDividerMediaItem(
+ mContext.getString(R.string.media_output_group_title)));
+ mediaItems.addAll(mOutputMediaItemList);
+ }
+
public List<MediaItem> getMediaItemList() {
- return mMediaItemList;
+ // If input routing is not enabled, only return output media items.
+ if (!enableInputRouting()) {
+ return mOutputMediaItemList;
+ }
+
+ // If input routing is enabled, return both output and input media items.
+ List<MediaItem> mediaItems = new ArrayList<>();
+ addOutputDevices(mediaItems);
+ addInputDevices(mediaItems);
+ return mediaItems;
}
public MediaDevice getCurrentConnectedMediaDevice() {
@@ -859,7 +927,18 @@
}
public List<MediaDevice> getSelectedMediaDevice() {
- return mLocalMediaManager.getSelectedMediaDevice();
+ if (!enableInputRouting()) {
+ return mLocalMediaManager.getSelectedMediaDevice();
+ }
+
+ // Add selected input device if input routing is supported.
+ List<MediaDevice> selectedDevices =
+ new ArrayList<>(mLocalMediaManager.getSelectedMediaDevice());
+ MediaDevice selectedInputDevice = mInputRouteManager.getSelectedInputDevice();
+ if (selectedInputDevice != null) {
+ selectedDevices.add(selectedInputDevice);
+ }
+ return selectedDevices;
}
List<MediaDevice> getDeselectableMediaDevice() {
@@ -921,7 +1000,7 @@
public boolean isAnyDeviceTransferring() {
synchronized (mMediaDevicesLock) {
- for (MediaItem mediaItem : mMediaItemList) {
+ for (MediaItem mediaItem : mOutputMediaItemList) {
if (mediaItem.getMediaDevice().isPresent()
&& mediaItem.getMediaDevice().get().getState()
== LocalMediaManager.MediaDeviceState.STATE_CONNECTING) {
@@ -986,8 +1065,8 @@
}
void launchMediaOutputBroadcastDialog(View mediaOutputDialog, BroadcastSender broadcastSender) {
- MediaOutputController controller =
- new MediaOutputController(
+ MediaSwitchingController controller =
+ new MediaSwitchingController(
mContext,
mPackageName,
mUserHandle,
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index d596589..8351597 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -34,6 +34,7 @@
import android.app.Activity;
import android.app.ActivityOptions.LaunchCookie;
import android.app.AlertDialog;
+import android.app.KeyguardManager;
import android.app.StatusBarManager;
import android.app.compat.CompatChanges;
import android.content.Context;
@@ -53,7 +54,6 @@
import android.text.TextUtils;
import android.util.Log;
import android.view.Window;
-import android.view.WindowManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
@@ -84,6 +84,7 @@
private final StatusBarManager mStatusBarManager;
private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
private final ScreenCaptureDisabledDialogDelegate mScreenCaptureDisabledDialogDelegate;
+ private final KeyguardManager mKeyguardManager;
private String mPackageName;
private int mUid;
@@ -102,11 +103,13 @@
FeatureFlags featureFlags,
Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver,
StatusBarManager statusBarManager,
+ KeyguardManager keyguardManager,
MediaProjectionMetricsLogger mediaProjectionMetricsLogger,
ScreenCaptureDisabledDialogDelegate screenCaptureDisabledDialogDelegate) {
mFeatureFlags = featureFlags;
mScreenCaptureDevicePolicyResolver = screenCaptureDevicePolicyResolver;
mStatusBarManager = statusBarManager;
+ mKeyguardManager = keyguardManager;
mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
mScreenCaptureDisabledDialogDelegate = screenCaptureDisabledDialogDelegate;
}
@@ -209,7 +212,14 @@
}
setUpDialog(mDialog);
- mDialog.show();
+
+ boolean shouldDismissKeyguard =
+ com.android.systemui.Flags.mediaProjectionDialogBehindLockscreen();
+ if (shouldDismissKeyguard && mKeyguardManager.isDeviceLocked()) {
+ requestDeviceUnlock();
+ } else {
+ mDialog.show();
+ }
if (savedInstanceState == null) {
mMediaProjectionMetricsLogger.notifyPermissionRequestDisplayed(mUid);
@@ -309,9 +319,6 @@
private void setUpDialog(AlertDialog dialog) {
SystemUIDialog.registerDismissListener(dialog);
SystemUIDialog.applyFlags(dialog, /* showWhenLocked= */ false);
-
- final Window w = dialog.getWindow();
- w.setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
SystemUIDialog.setDialogSize(dialog);
dialog.setOnCancelListener(this::onDialogDismissedOrCancelled);
@@ -319,6 +326,7 @@
dialog.create();
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
+ final Window w = dialog.getWindow();
w.addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
}
@@ -335,6 +343,16 @@
return false;
}
+ private void requestDeviceUnlock() {
+ mKeyguardManager.requestDismissKeyguard(this,
+ new KeyguardManager.KeyguardDismissCallback() {
+ @Override
+ public void onDismissSucceeded() {
+ mDialog.show();
+ }
+ });
+ }
+
private void grantMediaProjectionPermission(
int screenShareMode, boolean hasCastingCapabilities) {
try {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/shared/StatusBarRonChips.kt b/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUi.kt
similarity index 73%
copy from packages/SystemUI/src/com/android/systemui/statusbar/chips/shared/StatusBarRonChips.kt
copy to packages/SystemUI/src/com/android/systemui/modes/shared/ModesUi.kt
index 4c0c461..a0663d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/shared/StatusBarRonChips.kt
+++ b/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUi.kt
@@ -14,26 +14,18 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.chips.shared
+package com.android.systemui.modes.shared
-import com.android.systemui.Flags
-import com.android.systemui.flags.FlagToken
+import android.app.Flags
import com.android.systemui.flags.RefactorFlagUtils
-/** Helper for reading or using the status bar ron chips flag state. */
+/** Helper for reading or using the modes ui flag state. */
@Suppress("NOTHING_TO_INLINE")
-object StatusBarRonChips {
- /** The aconfig flag name */
- const val FLAG_NAME = Flags.FLAG_STATUS_BAR_RON_CHIPS
-
- /** A token used for dependency declaration */
- val token: FlagToken
- get() = FlagToken(FLAG_NAME, isEnabled)
-
+object ModesUi {
/** Is the refactor enabled */
@JvmStatic
inline val isEnabled
- get() = Flags.statusBarRonChips()
+ get() = Flags.modesApi() && Flags.modesUi()
/**
* Called to ensure code is only run when the flag is enabled. This protects users from the
@@ -42,7 +34,7 @@
*/
@JvmStatic
inline fun isUnexpectedlyInLegacyMode() =
- RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, Flags.FLAG_MODES_UI)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
@@ -50,12 +42,13 @@
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, Flags.FLAG_MODES_UI)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
* the flag is enabled to ensure that the refactor author catches issues in testing.
*/
@JvmStatic
- inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+ inline fun assertInLegacyMode() =
+ RefactorFlagUtils.assertInLegacyMode(isEnabled, Flags.FLAG_MODES_UI)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/shared/StatusBarRonChips.kt b/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUiIcons.kt
similarity index 71%
copy from packages/SystemUI/src/com/android/systemui/statusbar/chips/shared/StatusBarRonChips.kt
copy to packages/SystemUI/src/com/android/systemui/modes/shared/ModesUiIcons.kt
index 4c0c461..032b0ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/shared/StatusBarRonChips.kt
+++ b/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUiIcons.kt
@@ -14,26 +14,18 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.chips.shared
+package com.android.systemui.modes.shared
-import com.android.systemui.Flags
-import com.android.systemui.flags.FlagToken
+import android.app.Flags
import com.android.systemui.flags.RefactorFlagUtils
-/** Helper for reading or using the status bar ron chips flag state. */
+/** Helper for reading or using the modes ui icons flag state. */
@Suppress("NOTHING_TO_INLINE")
-object StatusBarRonChips {
- /** The aconfig flag name */
- const val FLAG_NAME = Flags.FLAG_STATUS_BAR_RON_CHIPS
-
- /** A token used for dependency declaration */
- val token: FlagToken
- get() = FlagToken(FLAG_NAME, isEnabled)
-
+object ModesUiIcons {
/** Is the refactor enabled */
@JvmStatic
inline val isEnabled
- get() = Flags.statusBarRonChips()
+ get() = ModesUi.isEnabled && Flags.modesUiIcons()
/**
* Called to ensure code is only run when the flag is enabled. This protects users from the
@@ -42,7 +34,7 @@
*/
@JvmStatic
inline fun isUnexpectedlyInLegacyMode() =
- RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, Flags.FLAG_MODES_UI_ICONS)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
@@ -50,12 +42,14 @@
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ inline fun assertInNewMode() =
+ RefactorFlagUtils.assertInNewMode(isEnabled, Flags.FLAG_MODES_UI_ICONS)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
* the flag is enabled to ensure that the refactor author catches issues in testing.
*/
@JvmStatic
- inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+ inline fun assertInLegacyMode() =
+ RefactorFlagUtils.assertInLegacyMode(isEnabled, Flags.FLAG_MODES_UI_ICONS)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
index 072d322..1fe54e4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
@@ -23,17 +23,14 @@
import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepositoryImpl
import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepository
import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepositoryImpl
-import com.android.systemui.qs.panels.domain.interactor.GridTypeConsistencyInteractor
-import com.android.systemui.qs.panels.domain.interactor.InfiniteGridConsistencyInteractor
-import com.android.systemui.qs.panels.domain.interactor.NoopGridConsistencyInteractor
import com.android.systemui.qs.panels.shared.model.GridLayoutType
import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
import com.android.systemui.qs.panels.shared.model.PaginatedGridLayoutType
import com.android.systemui.qs.panels.shared.model.PanelsLog
import com.android.systemui.qs.panels.ui.compose.GridLayout
-import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout
import com.android.systemui.qs.panels.ui.compose.PaginatableGridLayout
import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.InfiniteGridLayout
import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModel
import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModelImpl
import com.android.systemui.qs.panels.ui.viewmodel.IconLabelVisibilityViewModel
@@ -56,11 +53,6 @@
@Binds
fun bindGridLayoutTypeRepository(impl: GridLayoutTypeRepositoryImpl): GridLayoutTypeRepository
- @Binds
- fun bindDefaultGridConsistencyInteractor(
- impl: NoopGridConsistencyInteractor
- ): GridTypeConsistencyInteractor
-
@Binds fun bindIconTilesViewModel(impl: IconTilesViewModelImpl): IconTilesViewModel
@Binds fun bindGridSizeViewModel(impl: FixedColumnsSizeViewModelImpl): FixedColumnsSizeViewModel
@@ -74,12 +66,6 @@
@PaginatedBaseLayoutType
fun bindPaginatedBaseGridLayout(impl: InfiniteGridLayout): PaginatableGridLayout
- @Binds
- @PaginatedBaseLayoutType
- fun bindPaginatedBaseConsistencyInteractor(
- impl: NoopGridConsistencyInteractor
- ): GridTypeConsistencyInteractor
-
@Binds @Named("Default") fun bindDefaultGridLayout(impl: PaginatedGridLayout): GridLayout
companion object {
@@ -117,28 +103,5 @@
): Set<GridLayoutType> {
return entries.map { it.first }.toSet()
}
-
- @Provides
- @IntoSet
- fun provideGridConsistencyInteractor(
- consistencyInteractor: InfiniteGridConsistencyInteractor
- ): Pair<GridLayoutType, GridTypeConsistencyInteractor> {
- return Pair(InfiniteGridLayoutType, consistencyInteractor)
- }
-
- @Provides
- @IntoSet
- fun providePaginatedGridConsistencyInteractor(
- @PaginatedBaseLayoutType consistencyInteractor: GridTypeConsistencyInteractor,
- ): Pair<GridLayoutType, GridTypeConsistencyInteractor> {
- return Pair(PaginatedGridLayoutType, consistencyInteractor)
- }
-
- @Provides
- fun provideGridConsistencyInteractorMap(
- entries: Set<@JvmSuppressWildcards Pair<GridLayoutType, GridTypeConsistencyInteractor>>
- ): Map<GridLayoutType, GridTypeConsistencyInteractor> {
- return entries.toMap()
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt
deleted file mode 100644
index a2e7ea6..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.panels.domain.interactor
-
-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.LogLevel
-import com.android.systemui.qs.panels.shared.model.GridLayoutType
-import com.android.systemui.qs.panels.shared.model.PanelsLog
-import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
-import com.android.systemui.qs.pipeline.shared.TileSpec
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.launch
-
-@SysUISingleton
-class GridConsistencyInteractor
-@Inject
-constructor(
- private val gridLayoutTypeInteractor: GridLayoutTypeInteractor,
- private val currentTilesInteractor: CurrentTilesInteractor,
- private val consistencyInteractors:
- Map<GridLayoutType, @JvmSuppressWildcards GridTypeConsistencyInteractor>,
- private val defaultConsistencyInteractor: GridTypeConsistencyInteractor,
- @PanelsLog private val logBuffer: LogBuffer,
- @Application private val applicationScope: CoroutineScope,
-) {
- fun start() {
- applicationScope.launch {
- gridLayoutTypeInteractor.layout.collectLatest { type ->
- val consistencyInteractor =
- consistencyInteractors[type] ?: defaultConsistencyInteractor
- currentTilesInteractor.currentTiles
- .map { tiles -> tiles.map { it.spec } }
- .collectLatest { tiles ->
- val newTiles = consistencyInteractor.reconcileTiles(tiles)
- if (newTiles != tiles) {
- currentTilesInteractor.setTiles(newTiles)
- logChange(newTiles)
- }
- }
- }
- }
- }
-
- private fun logChange(tiles: List<TileSpec>) {
- logBuffer.log(
- LOG_BUFFER_CURRENT_TILES_CHANGE_TAG,
- LogLevel.DEBUG,
- { str1 = tiles.toString() },
- { "Tiles reordered: $str1" }
- )
- }
-
- private companion object {
- const val LOG_BUFFER_CURRENT_TILES_CHANGE_TAG = "GridConsistencyTilesChange"
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridTypeConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridTypeConsistencyInteractor.kt
deleted file mode 100644
index 4cdabae..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridTypeConsistencyInteractor.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.panels.domain.interactor
-
-import com.android.systemui.qs.pipeline.shared.TileSpec
-
-interface GridTypeConsistencyInteractor {
- /**
- * Given a list of tiles, return the best list of the same tiles (preserving as much order as
- * possible, such that it's consistent with the current layout.
- */
- fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec>
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
deleted file mode 100644
index 874b3b0..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.panels.domain.interactor
-
-import android.util.Log
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.panels.shared.model.SizedTile
-import com.android.systemui.qs.panels.shared.model.SizedTileImpl
-import com.android.systemui.qs.panels.shared.model.TileRow
-import com.android.systemui.qs.pipeline.shared.TileSpec
-import javax.inject.Inject
-
-@SysUISingleton
-class InfiniteGridConsistencyInteractor
-@Inject
-constructor(
- private val iconTilesInteractor: IconTilesInteractor,
- private val gridSizeInteractor: FixedColumnsSizeInteractor
-) : GridTypeConsistencyInteractor {
-
- /**
- * Tries to fill in every columns of all rows (except the last row), potentially reordering
- * tiles.
- */
- override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> {
- val newTiles: MutableList<TileSpec> = mutableListOf()
- val row = TileRow<TileSpec>(columns = gridSizeInteractor.columns.value)
- val tilesQueue: ArrayDeque<SizedTile<TileSpec>> =
- ArrayDeque(
- tiles.map {
- SizedTileImpl(
- it,
- if (iconTilesInteractor.isIconTile(it)) 1 else 2,
- )
- }
- )
-
- while (tilesQueue.isNotEmpty()) {
- if (row.isFull()) {
- newTiles.addAll(row.tiles.map { it.tile })
- row.clear()
- }
-
- val tile = tilesQueue.removeFirst()
-
- // If the tile fits in the row, add it.
- if (!row.maybeAddTile(tile)) {
- // If the tile does not fit the row, find an icon tile to move.
- // We'll try to either add an icon tile from the queue to complete the row, or
- // remove an icon tile from the current row to free up space.
-
- val iconTile: SizedTile<TileSpec>? = tilesQueue.firstOrNull { it.width == 1 }
- if (iconTile != null) {
- tilesQueue.remove(iconTile)
- tilesQueue.addFirst(tile)
- row.maybeAddTile(iconTile)
- } else {
- val tileToRemove: SizedTile<TileSpec>? = row.findLastIconTile()
- if (tileToRemove != null) {
- row.removeTile(tileToRemove)
- row.maybeAddTile(tile)
-
- // Moving the icon tile to the end because there's no other
- // icon tiles in the queue.
- tilesQueue.addLast(tileToRemove)
- } else {
- // If the row does not have an icon tile, add the incomplete row.
- // Note: this shouldn't happen because an icon tile is guaranteed to be in a
- // row that doesn't have enough space for a large tile.
- val tileSpecs = row.tiles.map { it.tile }
- Log.wtf(TAG, "Uneven row does not have an icon tile to remove: $tileSpecs")
- newTiles.addAll(tileSpecs)
- row.clear()
- tilesQueue.addFirst(tile)
- }
- }
- }
- }
-
- // Add last row that might be incomplete
- newTiles.addAll(row.tiles.map { it.tile })
-
- return newTiles.toList()
- }
-
- private companion object {
- const val TAG = "InfiniteGridConsistencyInteractor"
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractor.kt
deleted file mode 100644
index 0386a6a..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractor.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.panels.domain.interactor
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.pipeline.shared.TileSpec
-import javax.inject.Inject
-
-/** [GridTypeConsistencyInteractor] implementation that doesn't do any changes to tiles. */
-@SysUISingleton
-class NoopGridConsistencyInteractor @Inject constructor() : GridTypeConsistencyInteractor {
- override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> = tiles
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
index 9a2315b..1f8a24a1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
@@ -161,9 +161,9 @@
@Composable
fun Modifier.dragAndDropTileSource(
sizedTile: SizedTile<EditTileViewModel>,
+ dragAndDropState: DragAndDropState,
onTap: (TileSpec) -> Unit,
- onDoubleTap: (TileSpec) -> Unit,
- dragAndDropState: DragAndDropState
+ onDoubleTap: (TileSpec) -> Unit = {},
): Modifier {
val state by rememberUpdatedState(dragAndDropState)
return dragAndDropSource {
@@ -181,11 +181,11 @@
ClipData(
QsDragAndDrop.CLIPDATA_LABEL,
arrayOf(QsDragAndDrop.TILESPEC_MIME_TYPE),
- ClipData.Item(sizedTile.tile.tileSpec.spec)
+ ClipData.Item(sizedTile.tile.tileSpec.spec),
)
)
)
- }
+ },
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
index fde40da..f4acbec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
@@ -25,6 +25,8 @@
import androidx.compose.ui.util.fastMap
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.systemui.compose.modifiers.sysuiResTag
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.Tile
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.TileLazyGrid
import com.android.systemui.qs.panels.ui.viewmodel.QuickQuickSettingsViewModel
@Composable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
deleted file mode 100644
index afd47a7..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
+++ /dev/null
@@ -1,926 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-@file:OptIn(ExperimentalFoundationApi::class)
-
-package com.android.systemui.qs.panels.ui.compose
-
-import android.content.res.Resources
-import android.graphics.drawable.Animatable
-import android.service.quicksettings.Tile.STATE_ACTIVE
-import android.service.quicksettings.Tile.STATE_INACTIVE
-import android.text.TextUtils
-import androidx.compose.animation.AnimatedContent
-import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.animation.core.animateDpAsState
-import androidx.compose.animation.fadeIn
-import androidx.compose.animation.fadeOut
-import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
-import androidx.compose.animation.graphics.res.animatedVectorResource
-import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
-import androidx.compose.animation.graphics.vector.AnimatedImageVector
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.LocalOverscrollConfiguration
-import androidx.compose.foundation.background
-import androidx.compose.foundation.basicMarquee
-import androidx.compose.foundation.border
-import androidx.compose.foundation.combinedClickable
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Arrangement.spacedBy
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.BoxScope
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.wrapContentSize
-import androidx.compose.foundation.lazy.grid.GridCells
-import androidx.compose.foundation.lazy.grid.LazyGridItemScope
-import androidx.compose.foundation.lazy.grid.LazyGridScope
-import androidx.compose.foundation.lazy.grid.LazyGridState
-import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
-import androidx.compose.foundation.lazy.grid.rememberLazyGridState
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Clear
-import androidx.compose.material3.Icon
-import androidx.compose.material3.LocalContentColor
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.ReadOnlyComposable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberUpdatedState
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.ColorFilter
-import androidx.compose.ui.graphics.Shape
-import androidx.compose.ui.layout.onGloballyPositioned
-import androidx.compose.ui.layout.positionInRoot
-import androidx.compose.ui.platform.LocalConfiguration
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.semantics.Role
-import androidx.compose.ui.semantics.clearAndSetSemantics
-import androidx.compose.ui.semantics.contentDescription
-import androidx.compose.ui.semantics.onClick
-import androidx.compose.ui.semantics.role
-import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.semantics.stateDescription
-import androidx.compose.ui.semantics.toggleableState
-import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import androidx.compose.ui.util.fastMap
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.compose.animation.Expandable
-import com.android.compose.modifiers.background
-import com.android.compose.modifiers.thenIf
-import com.android.systemui.animation.Expandable
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.common.ui.compose.Icon
-import com.android.systemui.common.ui.compose.load
-import com.android.systemui.compose.modifiers.sysuiResTag
-import com.android.systemui.plugins.qs.QSTile
-import com.android.systemui.qs.panels.shared.model.SizedTile
-import com.android.systemui.qs.panels.shared.model.SizedTileImpl
-import com.android.systemui.qs.panels.ui.compose.TileDefaults.longPressLabel
-import com.android.systemui.qs.panels.ui.model.GridCell
-import com.android.systemui.qs.panels.ui.model.SpacerGridCell
-import com.android.systemui.qs.panels.ui.model.TileGridCell
-import com.android.systemui.qs.panels.ui.viewmodel.AccessibilityUiState
-import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.TileUiState
-import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.toUiState
-import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
-import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.qs.shared.model.groupAndSort
-import com.android.systemui.qs.tileimpl.QSTileImpl
-import com.android.systemui.res.R
-import java.util.function.Supplier
-import kotlinx.coroutines.delay
-
-object TileType
-
-private const val TEST_TAG_SMALL = "qs_tile_small"
-private const val TEST_TAG_LARGE = "qs_tile_large"
-private const val TEST_TAG_TOGGLE = "qs_tile_toggle_target"
-
-@Composable
-fun Tile(tile: TileViewModel, iconOnly: Boolean, showLabels: Boolean = false, modifier: Modifier) {
- val state by tile.state.collectAsStateWithLifecycle(tile.currentState)
- val resources = resources()
- val uiState = remember(state, resources) { state.toUiState(resources) }
- val colors = TileDefaults.getColorForState(uiState)
-
- // TODO(b/361789146): Draw the shapes instead of clipping
- val tileShape = TileDefaults.animateTileShape(uiState.state)
-
- TileContainer(
- colors = colors,
- showLabels = showLabels,
- label = uiState.label,
- iconOnly = iconOnly,
- shape = tileShape,
- clickEnabled = true,
- onClick = tile::onClick,
- onLongClick = tile::onLongClick,
- modifier = modifier.height(tileHeight()),
- uiState = uiState,
- ) {
- val icon = getTileIcon(icon = uiState.icon)
- if (iconOnly) {
- TileIcon(icon = icon, color = colors.icon, modifier = Modifier.align(Alignment.Center))
- } else {
- val iconShape = TileDefaults.animateIconShape(uiState.state)
- LargeTileContent(
- label = uiState.label,
- secondaryLabel = uiState.secondaryLabel,
- icon = icon,
- colors = colors,
- iconShape = iconShape,
- toggleClickSupported = state.handlesSecondaryClick,
- onClick = {
- if (state.handlesSecondaryClick) {
- tile.onSecondaryClick()
- }
- },
- onLongClick = { tile.onLongClick(it) },
- accessibilityUiState = uiState.accessibilityUiState,
- )
- }
- }
-}
-
-@Composable
-private fun TileContainer(
- colors: TileColors,
- showLabels: Boolean,
- label: String,
- iconOnly: Boolean,
- shape: Shape,
- clickEnabled: Boolean = false,
- onClick: (Expandable) -> Unit = {},
- onLongClick: (Expandable) -> Unit = {},
- modifier: Modifier = Modifier,
- uiState: TileUiState? = null,
- content: @Composable BoxScope.(Expandable) -> Unit,
-) {
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement =
- spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin), Alignment.Top),
- modifier = modifier,
- ) {
- val backgroundColor =
- if (iconOnly || uiState?.handlesSecondaryClick != true) {
- colors.iconBackground
- } else {
- colors.background
- }
- Expandable(
- color = backgroundColor,
- shape = shape,
- modifier = Modifier.height(tileHeight()).clip(shape),
- ) {
- val longPressLabel = longPressLabel()
- Box(
- modifier =
- Modifier.fillMaxSize()
- .thenIf(clickEnabled) {
- Modifier.combinedClickable(
- onClick = { onClick(it) },
- onLongClick = { onLongClick(it) },
- onClickLabel = uiState?.accessibilityUiState?.clickLabel,
- onLongClickLabel = longPressLabel,
- )
- }
- .thenIf(uiState != null) {
- uiState as TileUiState
- Modifier.semantics {
- role = uiState.accessibilityUiState.accessibilityRole
- if (
- uiState.accessibilityUiState.accessibilityRole ==
- Role.Switch
- ) {
- uiState.accessibilityUiState.toggleableState?.let {
- toggleableState = it
- }
- }
- stateDescription = uiState.accessibilityUiState.stateDescription
- }
- .sysuiResTag(if (iconOnly) TEST_TAG_SMALL else TEST_TAG_LARGE)
- .thenIf(iconOnly) {
- Modifier.semantics {
- contentDescription =
- uiState.accessibilityUiState.contentDescription
- }
- }
- }
- .tilePadding()
- ) {
- content(it)
- }
- }
-
- if (showLabels && iconOnly) {
- Text(
- label,
- maxLines = 2,
- color = colors.label,
- overflow = TextOverflow.Ellipsis,
- textAlign = TextAlign.Center,
- )
- }
- }
-}
-
-@Composable
-private fun LargeTileContent(
- label: String,
- secondaryLabel: String?,
- icon: Icon,
- colors: TileColors,
- iconShape: Shape,
- accessibilityUiState: AccessibilityUiState? = null,
- toggleClickSupported: Boolean = false,
- onClick: () -> Unit = {},
- onLongClick: () -> Unit = {},
-) {
- Row(
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = tileHorizontalArrangement(),
- ) {
- // Icon
- val longPressLabel = longPressLabel()
- Box(
- modifier =
- Modifier.size(TileDefaults.ToggleTargetSize).thenIf(toggleClickSupported) {
- Modifier.clip(iconShape)
- .background(colors.iconBackground, { 1f })
- .combinedClickable(
- onClick = onClick,
- onLongClick = onLongClick,
- onLongClickLabel = longPressLabel,
- )
- .thenIf(accessibilityUiState != null) {
- accessibilityUiState as AccessibilityUiState
- Modifier.semantics {
- contentDescription = accessibilityUiState.contentDescription
- stateDescription = accessibilityUiState.stateDescription
- accessibilityUiState.toggleableState?.let {
- toggleableState = it
- }
- role = Role.Switch
- }
- .sysuiResTag(TEST_TAG_TOGGLE)
- }
- }
- ) {
- TileIcon(icon = icon, color = colors.icon, modifier = Modifier.align(Alignment.Center))
- }
-
- // Labels
- Column(verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxHeight()) {
- Text(label, color = colors.label, modifier = Modifier.tileMarquee())
- if (!TextUtils.isEmpty(secondaryLabel)) {
- Text(
- secondaryLabel ?: "",
- color = colors.secondaryLabel,
- modifier =
- Modifier.tileMarquee().thenIf(
- accessibilityUiState
- ?.stateDescription
- ?.contains(secondaryLabel ?: "") == true
- ) {
- Modifier.clearAndSetSemantics {}
- },
- )
- }
- }
- }
-}
-
-private fun Modifier.tileMarquee(): Modifier {
- return basicMarquee(iterations = 1, initialDelayMillis = 200)
-}
-
-@Composable
-fun TileLazyGrid(
- modifier: Modifier = Modifier,
- state: LazyGridState = rememberLazyGridState(),
- columns: GridCells,
- content: LazyGridScope.() -> Unit,
-) {
- LazyVerticalGrid(
- state = state,
- columns = columns,
- verticalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical)),
- horizontalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_horizontal)),
- modifier = modifier,
- content = content,
- )
-}
-
-@Composable
-fun DefaultEditTileGrid(
- currentListState: EditTileListState,
- otherTiles: List<SizedTile<EditTileViewModel>>,
- columns: Int,
- modifier: Modifier,
- onAddTile: (TileSpec, Int) -> Unit,
- onRemoveTile: (TileSpec) -> Unit,
- onSetTiles: (List<TileSpec>) -> Unit,
- onResize: (TileSpec) -> Unit,
-) {
- val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState {
- onAddTile(it, CurrentTilesInteractor.POSITION_AT_END)
- }
- val tilePadding = dimensionResource(R.dimen.qs_tile_margin_vertical)
-
- CompositionLocalProvider(LocalOverscrollConfiguration provides null) {
- Column(
- verticalArrangement =
- spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)),
- modifier = modifier.fillMaxSize().verticalScroll(rememberScrollState()),
- ) {
- AnimatedContent(
- targetState = currentListState.dragInProgress,
- modifier = Modifier.wrapContentSize(),
- ) { dragIsInProgress ->
- EditGridHeader(Modifier.dragAndDropRemoveZone(currentListState, onRemoveTile)) {
- if (dragIsInProgress) {
- RemoveTileTarget()
- } else {
- Text(text = "Hold and drag to rearrange tiles.")
- }
- }
- }
-
- CurrentTilesGrid(
- currentListState,
- columns,
- tilePadding,
- onRemoveTile,
- onResize,
- onSetTiles,
- )
-
- // Hide available tiles when dragging
- AnimatedVisibility(
- visible = !currentListState.dragInProgress,
- enter = fadeIn(),
- exit = fadeOut(),
- ) {
- Column(
- verticalArrangement =
- spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)),
- modifier = modifier.fillMaxSize(),
- ) {
- EditGridHeader { Text(text = "Hold and drag to add tiles.") }
-
- AvailableTileGrid(
- otherTiles,
- columns,
- tilePadding,
- addTileToEnd,
- currentListState,
- )
- }
- }
-
- // Drop zone to remove tiles dragged out of the tile grid
- Spacer(
- modifier =
- Modifier.fillMaxWidth()
- .weight(1f)
- .dragAndDropRemoveZone(currentListState, onRemoveTile)
- )
- }
- }
-}
-
-@Composable
-private fun EditGridHeader(
- modifier: Modifier = Modifier,
- content: @Composable BoxScope.() -> Unit,
-) {
- CompositionLocalProvider(
- LocalContentColor provides MaterialTheme.colorScheme.onBackground.copy(alpha = .5f)
- ) {
- Box(
- contentAlignment = Alignment.Center,
- modifier = modifier.fillMaxWidth().height(EditModeTileDefaults.EditGridHeaderHeight),
- ) {
- content()
- }
- }
-}
-
-@Composable
-private fun RemoveTileTarget() {
- Row(
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = tileHorizontalArrangement(),
- modifier =
- Modifier.fillMaxHeight()
- .border(1.dp, LocalContentColor.current, shape = CircleShape)
- .padding(10.dp),
- ) {
- Icon(imageVector = Icons.Default.Clear, contentDescription = null)
- Text(text = "Remove")
- }
-}
-
-@Composable
-private fun CurrentTilesContainer(content: @Composable () -> Unit) {
- Box(
- Modifier.fillMaxWidth()
- .border(
- width = 1.dp,
- color = MaterialTheme.colorScheme.onBackground.copy(alpha = .5f),
- shape = RoundedCornerShape(48.dp),
- )
- .padding(dimensionResource(R.dimen.qs_tile_margin_vertical))
- ) {
- content()
- }
-}
-
-@Composable
-private fun CurrentTilesGrid(
- listState: EditTileListState,
- columns: Int,
- tilePadding: Dp,
- onClick: (TileSpec) -> Unit,
- onResize: (TileSpec) -> Unit,
- onSetTiles: (List<TileSpec>) -> Unit,
-) {
- val currentListState by rememberUpdatedState(listState)
-
- CurrentTilesContainer {
- val tileHeight = tileHeight()
- val totalRows = listState.tiles.lastOrNull()?.row ?: 0
- val totalHeight = gridHeight(totalRows + 1, tileHeight, tilePadding)
- val gridState = rememberLazyGridState()
- var gridContentOffset by remember { mutableStateOf(Offset(0f, 0f)) }
-
- TileLazyGrid(
- state = gridState,
- modifier =
- Modifier.height(totalHeight)
- .dragAndDropTileList(gridState, gridContentOffset, listState) {
- onSetTiles(currentListState.tileSpecs())
- }
- .onGloballyPositioned { coordinates ->
- gridContentOffset = coordinates.positionInRoot()
- }
- .testTag(CURRENT_TILES_GRID_TEST_TAG),
- columns = GridCells.Fixed(columns),
- ) {
- editTiles(
- listState.tiles,
- ClickAction.REMOVE,
- onClick,
- listState,
- onResize = onResize,
- indicatePosition = true,
- )
- }
- }
-}
-
-@Composable
-private fun AvailableTileGrid(
- tiles: List<SizedTile<EditTileViewModel>>,
- columns: Int,
- tilePadding: Dp,
- onClick: (TileSpec) -> Unit,
- dragAndDropState: DragAndDropState,
-) {
- val availableTileHeight = tileHeight(true)
- val availableGridHeight = gridHeight(tiles.size, availableTileHeight, columns, tilePadding)
-
- // Available tiles aren't visible during drag and drop, so the row isn't needed
- val groupedTiles =
- remember(tiles.fastMap { it.tile.category }, tiles.fastMap { it.tile.label }) {
- groupAndSort(tiles.fastMap { TileGridCell(it, 0) })
- }
- val labelColors = TileDefaults.inactiveTileColors()
- // Available tiles
- TileLazyGrid(
- modifier = Modifier.height(availableGridHeight).testTag(AVAILABLE_TILES_GRID_TEST_TAG),
- columns = GridCells.Fixed(columns),
- ) {
- groupedTiles.forEach { category, tiles ->
- stickyHeader {
- Text(
- text = category.label.load() ?: "",
- fontSize = 20.sp,
- color = labelColors.label,
- modifier =
- Modifier.background(Color.Black)
- .padding(start = 16.dp, bottom = 8.dp, top = 8.dp),
- )
- }
- editTiles(
- tiles,
- ClickAction.ADD,
- onClick,
- dragAndDropState = dragAndDropState,
- showLabels = true,
- )
- }
- }
-}
-
-fun gridHeight(nTiles: Int, tileHeight: Dp, columns: Int, padding: Dp): Dp {
- val rows = (nTiles + columns - 1) / columns
- return gridHeight(rows, tileHeight, padding)
-}
-
-fun gridHeight(rows: Int, tileHeight: Dp, padding: Dp): Dp {
- return ((tileHeight + padding) * rows) - padding
-}
-
-private fun GridCell.key(index: Int, dragAndDropState: DragAndDropState): Any {
- return if (this is TileGridCell && !dragAndDropState.isMoving(tile.tileSpec)) {
- key
- } else {
- index
- }
-}
-
-fun LazyGridScope.editTiles(
- cells: List<GridCell>,
- clickAction: ClickAction,
- onClick: (TileSpec) -> Unit,
- dragAndDropState: DragAndDropState,
- onResize: (TileSpec) -> Unit = {},
- showLabels: Boolean = false,
- indicatePosition: Boolean = false,
-) {
- items(
- count = cells.size,
- key = { cells[it].key(it, dragAndDropState) },
- span = { cells[it].span },
- contentType = { TileType },
- ) { index ->
- when (val cell = cells[index]) {
- is TileGridCell ->
- if (dragAndDropState.isMoving(cell.tile.tileSpec)) {
- // If the tile is being moved, replace it with a visible spacer
- SpacerGridCell(
- Modifier.background(
- color = MaterialTheme.colorScheme.secondary,
- alpha = { EditModeTileDefaults.PLACEHOLDER_ALPHA },
- shape = RoundedCornerShape(TileDefaults.InactiveCornerRadius),
- )
- .animateItem()
- )
- } else {
- TileGridCell(
- cell = cell,
- index = index,
- dragAndDropState = dragAndDropState,
- clickAction = clickAction,
- onClick = onClick,
- onResize = onResize,
- showLabels = showLabels,
- indicatePosition = indicatePosition,
- )
- }
- is SpacerGridCell -> SpacerGridCell()
- }
- }
-}
-
-@Composable
-private fun LazyGridItemScope.TileGridCell(
- cell: TileGridCell,
- index: Int,
- dragAndDropState: DragAndDropState,
- clickAction: ClickAction,
- onClick: (TileSpec) -> Unit,
- onResize: (TileSpec) -> Unit = {},
- showLabels: Boolean = false,
- indicatePosition: Boolean = false,
-) {
- val tileHeight = tileHeight(cell.isIcon && showLabels)
- val onClickActionName =
- when (clickAction) {
- ClickAction.ADD -> stringResource(id = R.string.accessibility_qs_edit_tile_add_action)
- ClickAction.REMOVE ->
- stringResource(id = R.string.accessibility_qs_edit_remove_tile_action)
- }
- val stateDescription =
- if (indicatePosition) {
- stringResource(id = R.string.accessibility_qs_edit_position, index + 1)
- } else {
- ""
- }
- EditTile(
- tileViewModel = cell.tile,
- iconOnly = cell.isIcon,
- showLabels = showLabels,
- modifier =
- Modifier.height(tileHeight)
- .animateItem()
- .semantics(mergeDescendants = true) {
- onClick(onClickActionName) { false }
- this.stateDescription = stateDescription
- }
- .dragAndDropTileSource(
- SizedTileImpl(cell.tile, cell.width),
- onClick,
- onResize,
- dragAndDropState,
- ),
- )
-}
-
-@Composable
-private fun SpacerGridCell(modifier: Modifier = Modifier) {
- // By default, spacers are invisible and exist purely to catch drag movements
- Box(modifier.height(tileHeight()).fillMaxWidth().tilePadding())
-}
-
-@Composable
-fun EditTile(
- tileViewModel: EditTileViewModel,
- iconOnly: Boolean,
- showLabels: Boolean,
- modifier: Modifier = Modifier,
-) {
- val label = tileViewModel.label.text
- val colors = TileDefaults.inactiveTileColors()
-
- TileContainer(
- colors = colors,
- showLabels = showLabels,
- label = label,
- iconOnly = iconOnly,
- shape = RoundedCornerShape(TileDefaults.InactiveCornerRadius),
- modifier = modifier,
- ) {
- if (iconOnly) {
- TileIcon(
- icon = tileViewModel.icon,
- color = colors.icon,
- modifier = Modifier.align(Alignment.Center),
- )
- } else {
- LargeTileContent(
- label = label,
- secondaryLabel = tileViewModel.appName?.text,
- icon = tileViewModel.icon,
- colors = colors,
- iconShape = RoundedCornerShape(TileDefaults.InactiveCornerRadius),
- )
- }
- }
-}
-
-enum class ClickAction {
- ADD,
- REMOVE,
-}
-
-@Composable
-private fun getTileIcon(icon: Supplier<QSTile.Icon?>): Icon {
- val context = LocalContext.current
- return icon.get()?.let {
- if (it is QSTileImpl.ResourceIcon) {
- Icon.Resource(it.resId, null)
- } else {
- Icon.Loaded(it.getDrawable(context), null)
- }
- } ?: Icon.Resource(R.drawable.ic_error_outline, null)
-}
-
-@OptIn(ExperimentalAnimationGraphicsApi::class)
-@Composable
-private fun TileIcon(
- icon: Icon,
- color: Color,
- animateToEnd: Boolean = false,
- modifier: Modifier = Modifier,
-) {
- val iconModifier = modifier.size(TileDefaults.IconSize)
- val context = LocalContext.current
- val loadedDrawable =
- remember(icon, context) {
- when (icon) {
- is Icon.Loaded -> icon.drawable
- is Icon.Resource -> context.getDrawable(icon.res)
- }
- }
- if (loadedDrawable !is Animatable) {
- Icon(icon = icon, tint = color, modifier = iconModifier)
- } else if (icon is Icon.Resource) {
- val image = AnimatedImageVector.animatedVectorResource(id = icon.res)
- val painter =
- if (animateToEnd) {
- rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = true)
- } else {
- var atEnd by remember(icon.res) { mutableStateOf(false) }
- LaunchedEffect(key1 = icon.res) {
- delay(350)
- atEnd = true
- }
- rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = atEnd)
- }
- Image(
- painter = painter,
- contentDescription = icon.contentDescription?.load(),
- colorFilter = ColorFilter.tint(color = color),
- modifier = iconModifier,
- )
- }
-}
-
-private fun Modifier.tilePadding(): Modifier {
- return padding(TileDefaults.TilePadding)
-}
-
-private fun tileHorizontalArrangement(): Arrangement.Horizontal {
- return spacedBy(space = TileDefaults.TileArrangementPadding, alignment = Alignment.Start)
-}
-
-@Composable
-fun tileHeight(iconWithLabel: Boolean = false): Dp {
- return if (iconWithLabel) {
- TileDefaults.IconTileWithLabelHeight
- } else {
- TileDefaults.TileHeight
- }
-}
-
-private data class TileColors(
- val background: Color,
- val iconBackground: Color,
- val label: Color,
- val secondaryLabel: Color,
- val icon: Color,
-)
-
-private object EditModeTileDefaults {
- const val PLACEHOLDER_ALPHA = .3f
- val EditGridHeaderHeight = 60.dp
-}
-
-private object TileDefaults {
- val InactiveCornerRadius = 50.dp
- val ActiveIconCornerRadius = 16.dp
- val ActiveTileCornerRadius = 24.dp
-
- val ToggleTargetSize = 56.dp
- val IconSize = 24.dp
-
- val TilePadding = 8.dp
- val TileArrangementPadding = 6.dp
-
- val TileHeight = 72.dp
- val IconTileWithLabelHeight = 140.dp
-
- @Composable fun longPressLabel() = stringResource(id = R.string.accessibility_long_click_tile)
-
- /** An active tile without dual target uses the active color as background */
- @Composable
- fun activeTileColors(): TileColors =
- TileColors(
- background = MaterialTheme.colorScheme.primary,
- iconBackground = MaterialTheme.colorScheme.primary,
- label = MaterialTheme.colorScheme.onPrimary,
- secondaryLabel = MaterialTheme.colorScheme.onPrimary,
- icon = MaterialTheme.colorScheme.onPrimary,
- )
-
- /** An active tile with dual target only show the active color on the icon */
- @Composable
- fun activeDualTargetTileColors(): TileColors =
- TileColors(
- background = MaterialTheme.colorScheme.surfaceVariant,
- iconBackground = MaterialTheme.colorScheme.primary,
- label = MaterialTheme.colorScheme.onSurfaceVariant,
- secondaryLabel = MaterialTheme.colorScheme.onSurfaceVariant,
- icon = MaterialTheme.colorScheme.onPrimary,
- )
-
- @Composable
- fun inactiveTileColors(): TileColors =
- TileColors(
- background = MaterialTheme.colorScheme.surfaceVariant,
- iconBackground = MaterialTheme.colorScheme.surfaceVariant,
- label = MaterialTheme.colorScheme.onSurfaceVariant,
- secondaryLabel = MaterialTheme.colorScheme.onSurfaceVariant,
- icon = MaterialTheme.colorScheme.onSurfaceVariant,
- )
-
- @Composable
- fun unavailableTileColors(): TileColors =
- TileColors(
- background = MaterialTheme.colorScheme.surface,
- iconBackground = MaterialTheme.colorScheme.surface,
- label = MaterialTheme.colorScheme.onSurface,
- secondaryLabel = MaterialTheme.colorScheme.onSurface,
- icon = MaterialTheme.colorScheme.onSurface,
- )
-
- @Composable
- fun getColorForState(uiState: TileUiState): TileColors {
- return when (uiState.state) {
- STATE_ACTIVE -> {
- if (uiState.handlesSecondaryClick) {
- activeDualTargetTileColors()
- } else {
- activeTileColors()
- }
- }
- STATE_INACTIVE -> inactiveTileColors()
- else -> unavailableTileColors()
- }
- }
-
- @Composable
- fun animateIconShape(state: Int): Shape {
- return animateShape(
- state = state,
- activeCornerRadius = ActiveIconCornerRadius,
- label = "QSTileCornerRadius",
- )
- }
-
- @Composable
- fun animateTileShape(state: Int): Shape {
- return animateShape(
- state = state,
- activeCornerRadius = ActiveTileCornerRadius,
- label = "QSTileIconCornerRadius",
- )
- }
-
- @Composable
- fun animateShape(state: Int, activeCornerRadius: Dp, label: String): Shape {
- val animatedCornerRadius by
- animateDpAsState(
- targetValue =
- if (state == STATE_ACTIVE) {
- activeCornerRadius
- } else {
- InactiveCornerRadius
- },
- label = label,
- )
- return RoundedCornerShape(animatedCornerRadius)
- }
-}
-
-private const val CURRENT_TILES_GRID_TEST_TAG = "CurrentTilesGrid"
-private const val AVAILABLE_TILES_GRID_TEST_TAG = "AvailableTilesGrid"
-
-/**
- * A composable function that returns the [Resources]. It will be recomposed when [Configuration]
- * gets updated.
- */
-@Composable
-@ReadOnlyComposable
-private fun resources(): Resources {
- LocalConfiguration.current
- return LocalContext.current.resources
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
new file mode 100644
index 0000000..aeb6031
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
@@ -0,0 +1,204 @@
+/*
+ * 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.panels.ui.compose.infinitegrid
+
+import android.graphics.drawable.Animatable
+import android.text.TextUtils
+import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
+import androidx.compose.animation.graphics.res.animatedVectorResource
+import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
+import androidx.compose.animation.graphics.vector.AnimatedImageVector
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.stateDescription
+import androidx.compose.ui.semantics.toggleableState
+import androidx.compose.ui.unit.dp
+import com.android.compose.modifiers.background
+import com.android.compose.modifiers.thenIf
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.common.ui.compose.load
+import com.android.systemui.compose.modifiers.sysuiResTag
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.longPressLabel
+import com.android.systemui.qs.panels.ui.viewmodel.AccessibilityUiState
+import com.android.systemui.res.R
+import kotlinx.coroutines.delay
+
+private const val TEST_TAG_TOGGLE = "qs_tile_toggle_target"
+
+@Composable
+fun LargeTileContent(
+ label: String,
+ secondaryLabel: String?,
+ icon: Icon,
+ colors: TileColors,
+ accessibilityUiState: AccessibilityUiState? = null,
+ toggleClickSupported: Boolean = false,
+ iconShape: Shape = RoundedCornerShape(CommonTileDefaults.InactiveCornerRadius),
+ onClick: () -> Unit = {},
+ onLongClick: () -> Unit = {},
+) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = tileHorizontalArrangement(),
+ ) {
+ // Icon
+ val longPressLabel = longPressLabel()
+ Box(
+ modifier =
+ Modifier.size(CommonTileDefaults.ToggleTargetSize).thenIf(toggleClickSupported) {
+ Modifier.clip(iconShape)
+ .background(colors.iconBackground, { 1f })
+ .combinedClickable(
+ onClick = onClick,
+ onLongClick = onLongClick,
+ onLongClickLabel = longPressLabel,
+ )
+ .thenIf(accessibilityUiState != null) {
+ Modifier.semantics {
+ accessibilityUiState as AccessibilityUiState
+ contentDescription = accessibilityUiState.contentDescription
+ stateDescription = accessibilityUiState.stateDescription
+ accessibilityUiState.toggleableState?.let {
+ toggleableState = it
+ }
+ role = Role.Switch
+ }
+ .sysuiResTag(TEST_TAG_TOGGLE)
+ }
+ }
+ ) {
+ SmallTileContent(
+ icon = icon,
+ color = colors.icon,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
+
+ // Labels
+ LargeTileLabels(
+ label = label,
+ secondaryLabel = secondaryLabel,
+ colors = colors,
+ accessibilityUiState = accessibilityUiState,
+ )
+ }
+}
+
+@Composable
+private fun LargeTileLabels(
+ label: String,
+ secondaryLabel: String?,
+ colors: TileColors,
+ accessibilityUiState: AccessibilityUiState? = null,
+) {
+ Column(verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxHeight()) {
+ Text(label, color = colors.label, modifier = Modifier.tileMarquee())
+ if (!TextUtils.isEmpty(secondaryLabel)) {
+ Text(
+ secondaryLabel ?: "",
+ color = colors.secondaryLabel,
+ modifier =
+ Modifier.tileMarquee().thenIf(
+ accessibilityUiState?.stateDescription?.contains(secondaryLabel ?: "") ==
+ true
+ ) {
+ Modifier.clearAndSetSemantics {}
+ },
+ )
+ }
+ }
+}
+
+@OptIn(ExperimentalAnimationGraphicsApi::class)
+@Composable
+fun SmallTileContent(
+ modifier: Modifier = Modifier,
+ icon: Icon,
+ color: Color,
+ animateToEnd: Boolean = false,
+) {
+ val iconModifier = modifier.size(CommonTileDefaults.IconSize)
+ val context = LocalContext.current
+ val loadedDrawable =
+ remember(icon, context) {
+ when (icon) {
+ is Icon.Loaded -> icon.drawable
+ is Icon.Resource -> context.getDrawable(icon.res)
+ }
+ }
+ if (loadedDrawable !is Animatable) {
+ Icon(icon = icon, tint = color, modifier = iconModifier)
+ } else if (icon is Icon.Resource) {
+ val image = AnimatedImageVector.animatedVectorResource(id = icon.res)
+ val painter =
+ if (animateToEnd) {
+ rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = true)
+ } else {
+ var atEnd by remember(icon.res) { mutableStateOf(false) }
+ LaunchedEffect(key1 = icon.res) {
+ delay(350)
+ atEnd = true
+ }
+ rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = atEnd)
+ }
+ Image(
+ painter = painter,
+ contentDescription = icon.contentDescription?.load(),
+ colorFilter = ColorFilter.tint(color = color),
+ modifier = iconModifier,
+ )
+ }
+}
+
+object CommonTileDefaults {
+ val IconSize = 24.dp
+ val ToggleTargetSize = 56.dp
+ val TileHeight = 72.dp
+ val TilePadding = 8.dp
+ val TileArrangementPadding = 6.dp
+ val InactiveCornerRadius = 50.dp
+
+ @Composable fun longPressLabel() = stringResource(id = R.string.accessibility_long_click_tile)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
new file mode 100644
index 0000000..a43b880
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
@@ -0,0 +1,503 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalFoundationApi::class)
+
+package com.android.systemui.qs.panels.ui.compose.infinitegrid
+
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.LocalOverscrollConfiguration
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Arrangement.spacedBy
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.IntrinsicSize
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyGridItemScope
+import androidx.compose.foundation.lazy.grid.LazyGridScope
+import androidx.compose.foundation.lazy.grid.rememberLazyGridState
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Clear
+import androidx.compose.material3.Icon
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.layout.positionInRoot
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.onClick
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.stateDescription
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.util.fastMap
+import com.android.compose.modifiers.background
+import com.android.systemui.common.ui.compose.load
+import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.SizedTileImpl
+import com.android.systemui.qs.panels.ui.compose.DragAndDropState
+import com.android.systemui.qs.panels.ui.compose.EditTileListState
+import com.android.systemui.qs.panels.ui.compose.dragAndDropRemoveZone
+import com.android.systemui.qs.panels.ui.compose.dragAndDropTileList
+import com.android.systemui.qs.panels.ui.compose.dragAndDropTileSource
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius
+import com.android.systemui.qs.panels.ui.model.GridCell
+import com.android.systemui.qs.panels.ui.model.SpacerGridCell
+import com.android.systemui.qs.panels.ui.model.TileGridCell
+import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
+import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.shared.model.groupAndSort
+import com.android.systemui.res.R
+
+object TileType
+
+@Composable
+fun DefaultEditTileGrid(
+ currentListState: EditTileListState,
+ otherTiles: List<SizedTile<EditTileViewModel>>,
+ columns: Int,
+ modifier: Modifier,
+ onAddTile: (TileSpec, Int) -> Unit,
+ onRemoveTile: (TileSpec) -> Unit,
+ onSetTiles: (List<TileSpec>) -> Unit,
+ onResize: (TileSpec) -> Unit,
+) {
+ val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState {
+ onAddTile(it, CurrentTilesInteractor.POSITION_AT_END)
+ }
+
+ CompositionLocalProvider(LocalOverscrollConfiguration provides null) {
+ Column(
+ verticalArrangement =
+ spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)),
+ modifier = modifier.fillMaxSize().verticalScroll(rememberScrollState()),
+ ) {
+ AnimatedContent(
+ targetState = currentListState.dragInProgress,
+ modifier = Modifier.wrapContentSize(),
+ label = "",
+ ) { dragIsInProgress ->
+ EditGridHeader(Modifier.dragAndDropRemoveZone(currentListState, onRemoveTile)) {
+ if (dragIsInProgress) {
+ RemoveTileTarget()
+ } else {
+ Text(text = "Hold and drag to rearrange tiles.")
+ }
+ }
+ }
+
+ CurrentTilesGrid(currentListState, columns, onRemoveTile, onResize, onSetTiles)
+
+ // Hide available tiles when dragging
+ AnimatedVisibility(
+ visible = !currentListState.dragInProgress,
+ enter = fadeIn(),
+ exit = fadeOut(),
+ ) {
+ Column(
+ verticalArrangement =
+ spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)),
+ modifier = modifier.fillMaxSize(),
+ ) {
+ EditGridHeader { Text(text = "Hold and drag to add tiles.") }
+
+ AvailableTileGrid(otherTiles, columns, addTileToEnd, currentListState)
+ }
+ }
+
+ // Drop zone to remove tiles dragged out of the tile grid
+ Spacer(
+ modifier =
+ Modifier.fillMaxWidth()
+ .weight(1f)
+ .dragAndDropRemoveZone(currentListState, onRemoveTile)
+ )
+ }
+ }
+}
+
+@Composable
+private fun EditGridHeader(
+ modifier: Modifier = Modifier,
+ content: @Composable BoxScope.() -> Unit,
+) {
+ CompositionLocalProvider(
+ LocalContentColor provides MaterialTheme.colorScheme.onBackground.copy(alpha = .5f)
+ ) {
+ Box(
+ contentAlignment = Alignment.Center,
+ modifier = modifier.fillMaxWidth().height(EditModeTileDefaults.EditGridHeaderHeight),
+ ) {
+ content()
+ }
+ }
+}
+
+@Composable
+private fun RemoveTileTarget() {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = tileHorizontalArrangement(),
+ modifier =
+ Modifier.fillMaxHeight()
+ .border(1.dp, LocalContentColor.current, shape = CircleShape)
+ .padding(10.dp),
+ ) {
+ Icon(imageVector = Icons.Default.Clear, contentDescription = null)
+ Text(text = "Remove")
+ }
+}
+
+@Composable
+private fun CurrentTilesContainer(content: @Composable () -> Unit) {
+ Box(
+ Modifier.fillMaxWidth()
+ .border(
+ width = 1.dp,
+ color = MaterialTheme.colorScheme.onBackground.copy(alpha = .5f),
+ shape = RoundedCornerShape(48.dp),
+ )
+ .padding(dimensionResource(R.dimen.qs_tile_margin_vertical))
+ ) {
+ content()
+ }
+}
+
+@Composable
+private fun CurrentTilesGrid(
+ listState: EditTileListState,
+ columns: Int,
+ onClick: (TileSpec) -> Unit,
+ onResize: (TileSpec) -> Unit,
+ onSetTiles: (List<TileSpec>) -> Unit,
+) {
+ val currentListState by rememberUpdatedState(listState)
+ val tilePadding = CommonTileDefaults.TileArrangementPadding
+
+ CurrentTilesContainer {
+ val tileHeight = CommonTileDefaults.TileHeight
+ val totalRows = listState.tiles.lastOrNull()?.row ?: 0
+ val totalHeight = gridHeight(totalRows + 1, tileHeight, tilePadding)
+ val gridState = rememberLazyGridState()
+ var gridContentOffset by remember { mutableStateOf(Offset(0f, 0f)) }
+
+ TileLazyGrid(
+ state = gridState,
+ modifier =
+ Modifier.height(totalHeight)
+ .dragAndDropTileList(gridState, gridContentOffset, listState) {
+ onSetTiles(currentListState.tileSpecs())
+ }
+ .onGloballyPositioned { coordinates ->
+ gridContentOffset = coordinates.positionInRoot()
+ }
+ .testTag(CURRENT_TILES_GRID_TEST_TAG),
+ columns = GridCells.Fixed(columns),
+ ) {
+ EditTiles(listState.tiles, onClick, listState, onResize = onResize)
+ }
+ }
+}
+
+@Composable
+private fun AvailableTileGrid(
+ tiles: List<SizedTile<EditTileViewModel>>,
+ columns: Int,
+ onClick: (TileSpec) -> Unit,
+ dragAndDropState: DragAndDropState,
+) {
+ // Available tiles aren't visible during drag and drop, so the row isn't needed
+ val groupedTiles =
+ remember(tiles.fastMap { it.tile.category }, tiles.fastMap { it.tile.label }) {
+ groupAndSort(tiles.fastMap { TileGridCell(it, 0) })
+ }
+ val labelColors = EditModeTileDefaults.editTileColors()
+
+ // Available tiles
+ Column(
+ verticalArrangement = spacedBy(CommonTileDefaults.TileArrangementPadding),
+ horizontalAlignment = Alignment.Start,
+ modifier =
+ Modifier.fillMaxWidth().wrapContentHeight().testTag(AVAILABLE_TILES_GRID_TEST_TAG),
+ ) {
+ groupedTiles.forEach { (category, tiles) ->
+ Text(
+ text = category.label.load() ?: "",
+ fontSize = 20.sp,
+ color = labelColors.label,
+ modifier =
+ Modifier.fillMaxWidth()
+ .background(Color.Black)
+ .padding(start = 16.dp, bottom = 8.dp, top = 8.dp),
+ )
+ tiles.chunked(columns).forEach { row ->
+ Row(
+ horizontalArrangement = spacedBy(CommonTileDefaults.TileArrangementPadding),
+ modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Max),
+ ) {
+ row.forEachIndexed { index, tileGridCell ->
+ AvailableTileGridCell(
+ cell = tileGridCell,
+ index = index,
+ dragAndDropState = dragAndDropState,
+ onClick = onClick,
+ modifier = Modifier.weight(1f).fillMaxHeight(),
+ )
+ }
+
+ // Spacers for incomplete rows
+ repeat(columns - row.size) { Spacer(modifier = Modifier.weight(1f)) }
+ }
+ }
+ }
+ }
+}
+
+fun gridHeight(rows: Int, tileHeight: Dp, padding: Dp): Dp {
+ return ((tileHeight + padding) * rows) - padding
+}
+
+private fun GridCell.key(index: Int, dragAndDropState: DragAndDropState): Any {
+ return when (this) {
+ is TileGridCell -> {
+ if (dragAndDropState.isMoving(tile.tileSpec)) index else key
+ }
+ is SpacerGridCell -> index
+ }
+}
+
+fun LazyGridScope.EditTiles(
+ cells: List<GridCell>,
+ onClick: (TileSpec) -> Unit,
+ dragAndDropState: DragAndDropState,
+ onResize: (TileSpec) -> Unit = {},
+) {
+ items(
+ count = cells.size,
+ key = { cells[it].key(it, dragAndDropState) },
+ span = { cells[it].span },
+ contentType = { TileType },
+ ) { index ->
+ when (val cell = cells[index]) {
+ is TileGridCell ->
+ if (dragAndDropState.isMoving(cell.tile.tileSpec)) {
+ // If the tile is being moved, replace it with a visible spacer
+ SpacerGridCell(
+ Modifier.background(
+ color = MaterialTheme.colorScheme.secondary,
+ alpha = { EditModeTileDefaults.PLACEHOLDER_ALPHA },
+ shape = RoundedCornerShape(InactiveCornerRadius),
+ )
+ .animateItem()
+ )
+ } else {
+ TileGridCell(
+ cell = cell,
+ index = index,
+ dragAndDropState = dragAndDropState,
+ onClick = onClick,
+ onResize = onResize,
+ )
+ }
+ is SpacerGridCell -> SpacerGridCell()
+ }
+ }
+}
+
+@Composable
+private fun LazyGridItemScope.TileGridCell(
+ cell: TileGridCell,
+ index: Int,
+ dragAndDropState: DragAndDropState,
+ onClick: (TileSpec) -> Unit,
+ onResize: (TileSpec) -> Unit = {},
+) {
+ val onClickActionName = stringResource(id = R.string.accessibility_qs_edit_remove_tile_action)
+ val stateDescription = stringResource(id = R.string.accessibility_qs_edit_position, index + 1)
+
+ EditTile(
+ tileViewModel = cell.tile,
+ iconOnly = cell.isIcon,
+ modifier =
+ Modifier.animateItem()
+ .semantics(mergeDescendants = true) {
+ onClick(onClickActionName) { false }
+ this.stateDescription = stateDescription
+ }
+ .dragAndDropTileSource(
+ SizedTileImpl(cell.tile, cell.width),
+ dragAndDropState,
+ onClick,
+ onResize,
+ ),
+ )
+}
+
+@Composable
+private fun AvailableTileGridCell(
+ cell: TileGridCell,
+ index: Int,
+ dragAndDropState: DragAndDropState,
+ modifier: Modifier = Modifier,
+ onClick: (TileSpec) -> Unit,
+) {
+ val onClickActionName = stringResource(id = R.string.accessibility_qs_edit_tile_add_action)
+ val stateDescription = stringResource(id = R.string.accessibility_qs_edit_position, index + 1)
+ val colors = EditModeTileDefaults.editTileColors()
+
+ // Displays the tile as an icon tile with the label underneath
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = spacedBy(CommonTileDefaults.TilePadding, Alignment.Top),
+ modifier = modifier,
+ ) {
+ EditTile(
+ tileViewModel = cell.tile,
+ iconOnly = true,
+ colors = colors,
+ modifier =
+ Modifier.semantics(mergeDescendants = true) {
+ onClick(onClickActionName) { false }
+ this.stateDescription = stateDescription
+ }
+ .dragAndDropTileSource(
+ SizedTileImpl(cell.tile, cell.width),
+ dragAndDropState,
+ onTap = onClick,
+ ),
+ )
+ Box(Modifier.fillMaxSize()) {
+ Text(
+ cell.tile.label.text,
+ maxLines = 2,
+ color = colors.label,
+ overflow = TextOverflow.Ellipsis,
+ textAlign = TextAlign.Center,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
+ }
+}
+
+@Composable
+private fun SpacerGridCell(modifier: Modifier = Modifier) {
+ // By default, spacers are invisible and exist purely to catch drag movements
+ Box(modifier.height(CommonTileDefaults.TileHeight).fillMaxWidth().tilePadding())
+}
+
+@Composable
+fun EditTile(
+ tileViewModel: EditTileViewModel,
+ iconOnly: Boolean,
+ modifier: Modifier = Modifier,
+ colors: TileColors = EditModeTileDefaults.editTileColors(),
+) {
+ EditTileContainer(colors = colors, modifier = modifier) {
+ if (iconOnly) {
+ SmallTileContent(
+ icon = tileViewModel.icon,
+ color = colors.icon,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ } else {
+ LargeTileContent(
+ label = tileViewModel.label.text,
+ secondaryLabel = tileViewModel.appName?.text,
+ icon = tileViewModel.icon,
+ colors = colors,
+ )
+ }
+ }
+}
+
+@Composable
+private fun EditTileContainer(
+ colors: TileColors,
+ modifier: Modifier = Modifier,
+ content: @Composable BoxScope.() -> Unit,
+) {
+ Box(
+ modifier =
+ modifier
+ .height(CommonTileDefaults.TileHeight)
+ .fillMaxWidth()
+ .drawBehind {
+ drawRoundRect(
+ SolidColor(colors.background),
+ cornerRadius = CornerRadius(InactiveCornerRadius.toPx()),
+ )
+ }
+ .tilePadding(),
+ content = content,
+ )
+}
+
+private object EditModeTileDefaults {
+ const val PLACEHOLDER_ALPHA = .3f
+ val EditGridHeaderHeight = 60.dp
+
+ @Composable
+ fun editTileColors(): TileColors =
+ TileColors(
+ background = MaterialTheme.colorScheme.surfaceVariant,
+ iconBackground = MaterialTheme.colorScheme.surfaceVariant,
+ label = MaterialTheme.colorScheme.onSurfaceVariant,
+ secondaryLabel = MaterialTheme.colorScheme.onSurfaceVariant,
+ icon = MaterialTheme.colorScheme.onSurfaceVariant,
+ )
+}
+
+private const val CURRENT_TILES_GRID_TEST_TAG = "CurrentTilesGrid"
+private const val AVAILABLE_TILES_GRID_TEST_TAG = "AvailableTilesGrid"
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
rename to packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
index c75b601..f96c27d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.ui.compose
+package com.android.systemui.qs.panels.ui.compose.infinitegrid
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
@@ -26,6 +26,8 @@
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.panels.shared.model.SizedTileImpl
+import com.android.systemui.qs.panels.ui.compose.PaginatableGridLayout
+import com.android.systemui.qs.panels.ui.compose.rememberEditListState
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModel
import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel
@@ -61,7 +63,7 @@
Tile(
tile = sizedTiles[index].tile,
iconOnly = iconTilesViewModel.isIconTile(sizedTiles[index].tile.spec),
- modifier = Modifier
+ modifier = Modifier,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
new file mode 100644
index 0000000..45aad82
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalFoundationApi::class)
+
+package com.android.systemui.qs.panels.ui.compose.infinitegrid
+
+import android.content.res.Resources
+import android.service.quicksettings.Tile.STATE_ACTIVE
+import android.service.quicksettings.Tile.STATE_INACTIVE
+import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.basicMarquee
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Arrangement.spacedBy
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyGridScope
+import androidx.compose.foundation.lazy.grid.LazyGridState
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.foundation.lazy.grid.rememberLazyGridState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.stateDescription
+import androidx.compose.ui.semantics.toggleableState
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.compose.animation.Expandable
+import com.android.compose.modifiers.thenIf
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.compose.modifiers.sysuiResTag
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.longPressLabel
+import com.android.systemui.qs.panels.ui.viewmodel.TileUiState
+import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.toUiState
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.res.R
+import java.util.function.Supplier
+
+private const val TEST_TAG_SMALL = "qs_tile_small"
+private const val TEST_TAG_LARGE = "qs_tile_large"
+
+@Composable
+fun TileLazyGrid(
+ columns: GridCells,
+ modifier: Modifier = Modifier,
+ state: LazyGridState = rememberLazyGridState(),
+ content: LazyGridScope.() -> Unit,
+) {
+ LazyVerticalGrid(
+ state = state,
+ columns = columns,
+ verticalArrangement = spacedBy(CommonTileDefaults.TileArrangementPadding),
+ horizontalArrangement = spacedBy(CommonTileDefaults.TileArrangementPadding),
+ modifier = modifier,
+ content = content,
+ )
+}
+
+@Composable
+fun Tile(tile: TileViewModel, iconOnly: Boolean, modifier: Modifier) {
+ val state by tile.state.collectAsStateWithLifecycle(tile.currentState)
+ val resources = resources()
+ val uiState = remember(state, resources) { state.toUiState(resources) }
+ val colors = TileDefaults.getColorForState(uiState)
+
+ // TODO(b/361789146): Draw the shapes instead of clipping
+ val tileShape = TileDefaults.animateTileShape(uiState.state)
+
+ TileContainer(
+ color =
+ if (iconOnly || !uiState.handlesSecondaryClick) {
+ colors.iconBackground
+ } else {
+ colors.background
+ },
+ shape = tileShape,
+ iconOnly = iconOnly,
+ onClick = tile::onClick,
+ onLongClick = tile::onLongClick,
+ uiState = uiState,
+ modifier = modifier,
+ ) { expandable ->
+ val icon = getTileIcon(icon = uiState.icon)
+ if (iconOnly) {
+ SmallTileContent(
+ icon = icon,
+ color = colors.icon,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ } else {
+ val iconShape = TileDefaults.animateIconShape(uiState.state)
+ LargeTileContent(
+ label = uiState.label,
+ secondaryLabel = uiState.secondaryLabel,
+ icon = icon,
+ colors = colors,
+ iconShape = iconShape,
+ toggleClickSupported = state.handlesSecondaryClick,
+ onClick = {
+ if (state.handlesSecondaryClick) {
+ tile.onSecondaryClick()
+ }
+ },
+ onLongClick = { tile.onLongClick(expandable) },
+ accessibilityUiState = uiState.accessibilityUiState,
+ )
+ }
+ }
+}
+
+@Composable
+private fun TileContainer(
+ color: Color,
+ shape: Shape,
+ iconOnly: Boolean,
+ uiState: TileUiState,
+ modifier: Modifier = Modifier,
+ onClick: (Expandable) -> Unit = {},
+ onLongClick: (Expandable) -> Unit = {},
+ content: @Composable BoxScope.(Expandable) -> Unit,
+) {
+ Expandable(color = color, shape = shape, modifier = modifier.clip(shape)) {
+ val longPressLabel = longPressLabel()
+ Box(
+ modifier =
+ Modifier.height(CommonTileDefaults.TileHeight)
+ .fillMaxWidth()
+ .combinedClickable(
+ onClick = { onClick(it) },
+ onLongClick = { onLongClick(it) },
+ onClickLabel = uiState.accessibilityUiState.clickLabel,
+ onLongClickLabel = longPressLabel,
+ )
+ .semantics {
+ role = uiState.accessibilityUiState.accessibilityRole
+ if (uiState.accessibilityUiState.accessibilityRole == Role.Switch) {
+ uiState.accessibilityUiState.toggleableState?.let {
+ toggleableState = it
+ }
+ }
+ stateDescription = uiState.accessibilityUiState.stateDescription
+ }
+ .sysuiResTag(if (iconOnly) TEST_TAG_SMALL else TEST_TAG_LARGE)
+ .thenIf(iconOnly) {
+ Modifier.semantics {
+ contentDescription = uiState.accessibilityUiState.contentDescription
+ }
+ }
+ .tilePadding()
+ ) {
+ content(it)
+ }
+ }
+}
+
+@Composable
+private fun getTileIcon(icon: Supplier<QSTile.Icon?>): Icon {
+ val context = LocalContext.current
+ return icon.get()?.let {
+ if (it is QSTileImpl.ResourceIcon) {
+ Icon.Resource(it.resId, null)
+ } else {
+ Icon.Loaded(it.getDrawable(context), null)
+ }
+ } ?: Icon.Resource(R.drawable.ic_error_outline, null)
+}
+
+fun tileHorizontalArrangement(): Arrangement.Horizontal {
+ return spacedBy(space = CommonTileDefaults.TileArrangementPadding, alignment = Alignment.Start)
+}
+
+fun Modifier.tileMarquee(): Modifier {
+ return basicMarquee(iterations = 1, initialDelayMillis = 200)
+}
+
+fun Modifier.tilePadding(): Modifier {
+ return padding(CommonTileDefaults.TilePadding)
+}
+
+data class TileColors(
+ val background: Color,
+ val iconBackground: Color,
+ val label: Color,
+ val secondaryLabel: Color,
+ val icon: Color,
+)
+
+private object TileDefaults {
+ val ActiveIconCornerRadius = 16.dp
+ val ActiveTileCornerRadius = 24.dp
+
+ /** An active tile without dual target uses the active color as background */
+ @Composable
+ fun activeTileColors(): TileColors =
+ TileColors(
+ background = MaterialTheme.colorScheme.primary,
+ iconBackground = MaterialTheme.colorScheme.primary,
+ label = MaterialTheme.colorScheme.onPrimary,
+ secondaryLabel = MaterialTheme.colorScheme.onPrimary,
+ icon = MaterialTheme.colorScheme.onPrimary,
+ )
+
+ /** An active tile with dual target only show the active color on the icon */
+ @Composable
+ fun activeDualTargetTileColors(): TileColors =
+ TileColors(
+ background = MaterialTheme.colorScheme.surfaceVariant,
+ iconBackground = MaterialTheme.colorScheme.primary,
+ label = MaterialTheme.colorScheme.onSurfaceVariant,
+ secondaryLabel = MaterialTheme.colorScheme.onSurfaceVariant,
+ icon = MaterialTheme.colorScheme.onPrimary,
+ )
+
+ @Composable
+ fun inactiveTileColors(): TileColors =
+ TileColors(
+ background = MaterialTheme.colorScheme.surfaceVariant,
+ iconBackground = MaterialTheme.colorScheme.surfaceVariant,
+ label = MaterialTheme.colorScheme.onSurfaceVariant,
+ secondaryLabel = MaterialTheme.colorScheme.onSurfaceVariant,
+ icon = MaterialTheme.colorScheme.onSurfaceVariant,
+ )
+
+ @Composable
+ fun unavailableTileColors(): TileColors =
+ TileColors(
+ background = MaterialTheme.colorScheme.surface,
+ iconBackground = MaterialTheme.colorScheme.surface,
+ label = MaterialTheme.colorScheme.onSurface,
+ secondaryLabel = MaterialTheme.colorScheme.onSurface,
+ icon = MaterialTheme.colorScheme.onSurface,
+ )
+
+ @Composable
+ fun getColorForState(uiState: TileUiState): TileColors {
+ return when (uiState.state) {
+ STATE_ACTIVE -> {
+ if (uiState.handlesSecondaryClick) {
+ activeDualTargetTileColors()
+ } else {
+ activeTileColors()
+ }
+ }
+ STATE_INACTIVE -> inactiveTileColors()
+ else -> unavailableTileColors()
+ }
+ }
+
+ @Composable
+ fun animateIconShape(state: Int): Shape {
+ return animateShape(
+ state = state,
+ activeCornerRadius = ActiveIconCornerRadius,
+ label = "QSTileCornerRadius",
+ )
+ }
+
+ @Composable
+ fun animateTileShape(state: Int): Shape {
+ return animateShape(
+ state = state,
+ activeCornerRadius = ActiveTileCornerRadius,
+ label = "QSTileIconCornerRadius",
+ )
+ }
+
+ @Composable
+ fun animateShape(state: Int, activeCornerRadius: Dp, label: String): Shape {
+ val animatedCornerRadius by
+ animateDpAsState(
+ targetValue =
+ if (state == STATE_ACTIVE) {
+ activeCornerRadius
+ } else {
+ InactiveCornerRadius
+ },
+ label = label,
+ )
+ return RoundedCornerShape(animatedCornerRadius)
+ }
+}
+
+/**
+ * A composable function that returns the [Resources]. It will be recomposed when [Configuration]
+ * gets updated.
+ */
+@Composable
+@ReadOnlyComposable
+private fun resources(): Resources {
+ LocalConfiguration.current
+ return LocalContext.current.resources
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
index 08ee856..b16a707 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
@@ -24,7 +24,7 @@
import com.android.systemui.qs.shared.model.CategoryAndName
/** Represents an item from a grid associated with a row and a span */
-interface GridCell {
+sealed interface GridCell {
val row: Int
val span: GridItemSpan
}
@@ -38,30 +38,26 @@
override val tile: EditTileViewModel,
override val row: Int,
override val width: Int,
- override val span: GridItemSpan = GridItemSpan(width)
+ override val span: GridItemSpan = GridItemSpan(width),
) : GridCell, SizedTile<EditTileViewModel>, CategoryAndName by tile {
val key: String = "${tile.tileSpec.spec}-$row"
constructor(
sizedTile: SizedTile<EditTileViewModel>,
- row: Int
- ) : this(
- tile = sizedTile.tile,
- row = row,
- width = sizedTile.width,
- )
+ row: Int,
+ ) : this(tile = sizedTile.tile, row = row, width = sizedTile.width)
}
/** Represents an empty space used to fill incomplete rows. Will always display as a 1x1 tile */
@Immutable
data class SpacerGridCell(
override val row: Int,
- override val span: GridItemSpan = GridItemSpan(1)
+ override val span: GridItemSpan = GridItemSpan(1),
) : GridCell
fun List<SizedTile<EditTileViewModel>>.toGridCells(
columns: Int,
- includeSpacers: Boolean = false
+ includeSpacers: Boolean = false,
): List<GridCell> {
return splitInRowsSequence(this, columns)
.flatMapIndexed { rowIndex, sizedTiles ->
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
index 0bcb6b7..9677d47 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
@@ -18,8 +18,6 @@
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.flags.NewQsUI
-import com.android.systemui.qs.panels.domain.interactor.GridConsistencyInteractor
import com.android.systemui.qs.pipeline.domain.interactor.AccessibilityTilesInteractor
import com.android.systemui.qs.pipeline.domain.interactor.AutoAddInteractor
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
@@ -36,16 +34,11 @@
private val autoAddInteractor: AutoAddInteractor,
private val featureFlags: QSPipelineFlagsRepository,
private val restoreReconciliationInteractor: RestoreReconciliationInteractor,
- private val gridConsistencyInteractor: GridConsistencyInteractor,
) : CoreStartable {
override fun start() {
accessibilityTilesInteractor.init(currentTilesInteractor)
autoAddInteractor.init(currentTilesInteractor)
restoreReconciliationInteractor.start()
-
- if (NewQsUI.isEnabled) {
- gridConsistencyInteractor.start()
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index b927134..a4fe4e3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -49,7 +49,7 @@
import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.RefactorFlagUtils;
+import com.android.systemui.modes.shared.ModesUi;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
@@ -108,8 +108,7 @@
statusBarStateController, activityStarter, qsLogger);
// If the flag is on, this shouldn't run at all since the modes tile replaces the DND tile.
- RefactorFlagUtils.INSTANCE.assertInLegacyMode(android.app.Flags.modesUi(),
- android.app.Flags.FLAG_MODES_UI);
+ ModesUi.assertInLegacyMode();
mController = zenModeController;
mSharedPreferences = sharedPreferences;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
index 7d23fbd..cf2db6c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
@@ -16,7 +16,6 @@
package com.android.systemui.qs.tiles
-import android.app.Flags
import android.content.Intent
import android.os.Handler
import android.os.Looper
@@ -30,7 +29,8 @@
import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.RefactorFlagUtils.isUnexpectedlyInLegacyMode
+import com.android.systemui.modes.shared.ModesUi
+import com.android.systemui.modes.shared.ModesUiIcons
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.qs.QSTile
@@ -77,14 +77,14 @@
metricsLogger,
statusBarStateController,
activityStarter,
- qsLogger
+ qsLogger,
) {
private lateinit var tileState: QSTileState
private val config = qsTileConfigProvider.getConfig(TILE_SPEC)
init {
- /* Check if */ isUnexpectedlyInLegacyMode(Flags.modesUi(), Flags.FLAG_MODES_UI)
+ /* Check if */ ModesUiIcons.isUnexpectedlyInLegacyMode()
lifecycle.coroutineScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
@@ -93,7 +93,7 @@
}
}
- override fun isAvailable(): Boolean = Flags.modesUi()
+ override fun isAvailable(): Boolean = ModesUi.isEnabled
override fun getTileLabel(): CharSequence = tileState.label
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
index 483373d..5d44ead 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
@@ -16,13 +16,14 @@
package com.android.systemui.qs.tiles.impl.modes.domain.interactor
-import android.app.Flags
import android.content.Context
import android.os.UserHandle
import com.android.app.tracing.coroutines.flow.map
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.asIcon
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.modes.shared.ModesUi
+import com.android.systemui.modes.shared.ModesUiIcons
import com.android.systemui.qs.tiles.ModesTile
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
@@ -47,7 +48,7 @@
override fun tileData(
user: UserHandle,
- triggers: Flow<DataUpdateTrigger>
+ triggers: Flow<DataUpdateTrigger>,
): Flow<ModesTileModel> = tileData()
/**
@@ -64,20 +65,20 @@
suspend fun getCurrentTileModel() = buildTileData(zenModeInteractor.getActiveModes())
private fun buildTileData(activeModes: ActiveZenModes): ModesTileModel {
- if (usesModeIcons()) {
+ if (ModesUiIcons.isEnabled) {
val tileIcon = getTileIcon(activeModes.mainMode)
return ModesTileModel(
isActivated = activeModes.isAnyActive(),
icon = tileIcon.icon,
iconResId = tileIcon.resId,
- activeModes = activeModes.modeNames
+ activeModes = activeModes.modeNames,
)
} else {
return ModesTileModel(
isActivated = activeModes.isAnyActive(),
icon = context.getDrawable(ModesTile.ICON_RES_ID)!!.asIcon(),
iconResId = ModesTile.ICON_RES_ID,
- activeModes = activeModes.modeNames
+ activeModes = activeModes.modeNames,
)
}
}
@@ -97,7 +98,5 @@
}
}
- override fun availability(user: UserHandle): Flow<Boolean> = flowOf(Flags.modesUi())
-
- private fun usesModeIcons() = Flags.modesApi() && Flags.modesUi() && Flags.modesUiIcons()
+ override fun availability(user: UserHandle): Flow<Boolean> = flowOf(ModesUi.isEnabled)
}
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 e11ffcc..b7e2cf2 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
@@ -61,6 +61,7 @@
import com.android.systemui.scene.session.shared.SessionStorage
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.logger.SceneLogger
+import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.NotificationShadeWindowController
@@ -228,8 +229,10 @@
is ObservableTransitionState.Idle -> {
if (state.currentScene != Scenes.Gone) {
true to "scene is not Gone"
+ } else if (state.currentOverlays.isNotEmpty()) {
+ true to "overlay is shown"
} else {
- false to "scene is Gone"
+ false to "scene is Gone and no overlays are shown"
}
}
is ObservableTransitionState.Transition -> {
@@ -712,19 +715,21 @@
if (isDeviceLocked) {
sceneInteractor.transitionState
.mapNotNull { it as? ObservableTransitionState.Idle }
- .map { it.currentScene }
+ .map { it.currentScene to it.currentOverlays }
.distinctUntilChanged()
- .map { sceneKey ->
- when (sceneKey) {
+ .map { (sceneKey, currentOverlays) ->
+ when {
// When locked, showing the lockscreen scene should be reported
// as "interacting" while showing other scenes should report as
// "not interacting".
//
// This is done here in order to match the legacy
// implementation. The real reason why is lost to lore and myth.
- Scenes.Lockscreen -> true
- Scenes.Bouncer -> false
- Scenes.Shade -> false
+ Overlays.NotificationsShade in currentOverlays -> false
+ Overlays.QuickSettingsShade in currentOverlays -> null
+ sceneKey == Scenes.Lockscreen -> true
+ sceneKey == Scenes.Bouncer -> false
+ sceneKey == Scenes.Shade -> false
else -> null
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
index 751448f..7b6b0f6 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
@@ -26,7 +26,6 @@
import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.MigrateClocksToBlueprint
-import com.android.systemui.keyguard.shared.ComposeLockscreen
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
import com.android.systemui.statusbar.phone.PredictiveBackSysUiFlag
@@ -39,7 +38,6 @@
inline val isEnabled
get() =
sceneContainer() && // mainAconfigFlag
- ComposeLockscreen.isEnabled &&
KeyguardBottomAreaRefactor.isEnabled &&
KeyguardWmStateRefactor.isEnabled &&
MigrateClocksToBlueprint.isEnabled &&
@@ -55,7 +53,6 @@
/** The set of secondary flags which must be enabled for scene container to work properly */
inline fun getSecondaryFlags(): Sequence<FlagToken> =
sequenceOf(
- ComposeLockscreen.token,
KeyguardBottomAreaRefactor.token,
KeyguardWmStateRefactor.token,
MigrateClocksToBlueprint.token,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 42499f0..f76c5fd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -137,7 +137,6 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver;
-import com.android.systemui.keyguard.shared.ComposeLockscreen;
import com.android.systemui.keyguard.shared.model.Edge;
import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
@@ -186,6 +185,7 @@
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.ConversationNotificationManager;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
@@ -207,7 +207,6 @@
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
-import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -2511,11 +2510,6 @@
return 0;
}
- if (ComposeLockscreen.isEnabled()) {
- return (int) mKeyguardInteractor.getNotificationContainerBounds()
- .getValue().getTop();
- }
-
if (!mKeyguardBypassController.getBypassEnabled()) {
if (MigrateClocksToBlueprint.isEnabled() && !mSplitShadeEnabled) {
return (int) mKeyguardInteractor.getNotificationContainerBounds()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 3f3ad13..4f47536 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -28,6 +28,7 @@
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
+import android.graphics.Rect;
import android.graphics.Region;
import android.os.Binder;
import android.os.Build;
@@ -987,6 +988,19 @@
@Override
public void onConfigChanged(Configuration newConfig) {
+ // If the shade window is not visible, the bounds will not update until it becomes visible.
+ // Touches that should invoke shade expansion but are not within those incorrect bounds
+ // (because the shape of the shade window remains portrait after flipping to landscape) will
+ // be dropped, causing the shade expansion to fail silently. Since the shade doesn't open,
+ // it doesn't become visible, and the bounds will never update. Therefore, we must detect
+ // the incorrect bounds here and force the update so that touches are routed correctly.
+ if (SceneContainerFlag.isEnabled() && mWindowRootView.getVisibility() == View.INVISIBLE) {
+ Rect bounds = newConfig.windowConfiguration.getBounds();
+ if (mWindowRootView.getWidth() != bounds.width()) {
+ mLogger.logConfigChangeWidthAdjust(mWindowRootView.getWidth(), bounds.width());
+ updateRootViewBounds(bounds);
+ }
+ }
final boolean newScreenRotationAllowed = mKeyguardStateController
.isKeyguardScreenRotationAllowed();
@@ -996,6 +1010,16 @@
}
}
+ private void updateRootViewBounds(Rect bounds) {
+ int originalMlpWidth = mLp.width;
+ int originalMlpHeight = mLp.height;
+ mLp.width = bounds.width();
+ mLp.height = bounds.height();
+ mWindowManager.updateViewLayout(mWindowRootView, mLp);
+ mLp.width = originalMlpWidth;
+ mLp.height = originalMlpHeight;
+ }
+
/**
* When keyguard will be dismissed but didn't start animation yet.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt
index e7a397b..1693e62 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt
@@ -31,18 +31,13 @@
ConstantStringsLogger by ConstantStringsLoggerImpl(buffer, TAG) {
fun logNewState(state: Any) {
- buffer.log(
- TAG,
- DEBUG,
- { str1 = state.toString() },
- { "Applying new state: $str1" }
- )
+ buffer.log(TAG, DEBUG, { str1 = state.toString() }, { "Applying new state: $str1" })
}
private inline fun log(
logLevel: LogLevel,
initializer: LogMessage.() -> Unit,
- noinline printer: LogMessage.() -> String
+ noinline printer: LogMessage.() -> String,
) {
buffer.log(TAG, logLevel, initializer, printer)
}
@@ -52,7 +47,8 @@
TAG,
DEBUG,
{ bool1 = visible },
- { "Updating visibility, should be visible : $bool1" })
+ { "Updating visibility, should be visible : $bool1" },
+ )
}
fun logIsExpanded(
@@ -65,7 +61,7 @@
headsUpNotificationShowing: Boolean,
scrimsVisibilityNotTransparent: Boolean,
backgroundBlurRadius: Boolean,
- launchingActivityFromNotification: Boolean
+ launchingActivityFromNotification: Boolean,
) {
buffer.log(
TAG,
@@ -82,11 +78,13 @@
long2 = if (backgroundBlurRadius) 1 else 0
double1 = if (launchingActivityFromNotification) 1.0 else 0.0
},
- { "Setting isExpanded to $str1: forceWindowCollapsed $bool1, " +
+ {
+ "Setting isExpanded to $str1: forceWindowCollapsed $bool1, " +
"isKeyguardShowingAndNotOccluded $bool2, panelVisible $bool3, " +
"keyguardFadingAway $bool4, bouncerShowing $int1," +
"headsUpNotificationShowing $int2, scrimsVisibilityNotTransparent $long1," +
- "backgroundBlurRadius $long2, launchingActivityFromNotification $double1"}
+ "backgroundBlurRadius $long2, launchingActivityFromNotification $double1"
+ },
)
}
@@ -95,7 +93,7 @@
TAG,
DEBUG,
{ bool1 = visible },
- { "Updating shade, should be visible and focusable: $bool1" }
+ { "Updating shade, should be visible and focusable: $bool1" },
)
}
@@ -104,7 +102,19 @@
TAG,
DEBUG,
{ bool1 = focusable },
- { "Updating shade, should be focusable : $bool1" }
+ { "Updating shade, should be focusable : $bool1" },
+ )
+ }
+
+ fun logConfigChangeWidthAdjust(originalWidth: Int, newWidth: Int) {
+ buffer.log(
+ TAG,
+ DEBUG,
+ {
+ int1 = originalWidth
+ int2 = newWidth
+ },
+ { "Config changed. SceneWindowRootView width updating from $int1 to $int2." },
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 87f360e..ad3afd4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -61,6 +61,7 @@
import com.android.internal.statusbar.StatusBarIcon.Shape;
import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.Flags;
+import com.android.systemui.modes.shared.ModesUiIcons;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.NotificationContentDescription;
import com.android.systemui.statusbar.notification.NotificationDozeHelper;
@@ -215,7 +216,7 @@
// We scale notification icons (on the left) plus icons on the right that explicitly
// want FIXED_SPACE.
boolean useNonSystemIconScaling = isNotification()
- || (usesModeIcons() && mIcon != null && mIcon.shape == Shape.FIXED_SPACE);
+ || (ModesUiIcons.isEnabled() && mIcon != null && mIcon.shape == Shape.FIXED_SPACE);
if (useNonSystemIconScaling) {
updateIconScaleForNonSystemIcons();
@@ -415,7 +416,7 @@
if (!levelEquals) {
setImageLevel(icon.iconLevel);
}
- if (usesModeIcons() && icon.shape == Shape.FIXED_SPACE) {
+ if (ModesUiIcons.isEnabled() && icon.shape == Shape.FIXED_SPACE) {
setScaleType(ScaleType.FIT_CENTER);
}
if (!visibilityEquals) {
@@ -506,7 +507,7 @@
@Nullable
private Drawable loadDrawable(Context context, StatusBarIcon statusBarIcon) {
- if (usesModeIcons() && statusBarIcon.preloadedIcon != null) {
+ if (ModesUiIcons.isEnabled() && statusBarIcon.preloadedIcon != null) {
Drawable.ConstantState cached = statusBarIcon.preloadedIcon.getConstantState();
if (cached != null) {
return cached.newDrawable(mContext.getResources()).mutate();
@@ -1041,9 +1042,4 @@
public boolean showsConversation() {
return mShowsConversation;
}
-
- private static boolean usesModeIcons() {
- return android.app.Flags.modesApi() && android.app.Flags.modesUi()
- && android.app.Flags.modesUiIcons();
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
index 4056e7b..975b92e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
@@ -16,11 +16,12 @@
package com.android.systemui.statusbar.core
import android.app.Fragment
-import com.android.systemui.res.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.fragments.FragmentHostManager
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener
+import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewUpdatedListener
import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions
-import com.android.systemui.statusbar.phone.PhoneStatusBarView
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
@@ -33,31 +34,60 @@
* Responsible for creating the status bar window and initializing the root components of that
* window (see [CollapsedStatusBarFragment])
*/
-@SysUISingleton
-class StatusBarInitializer @Inject constructor(
- private val windowController: StatusBarWindowController,
- private val collapsedStatusBarFragmentProvider: Provider<CollapsedStatusBarFragment>,
- private val creationListeners: Set<@JvmSuppressWildcards OnStatusBarViewInitializedListener>,
-) {
+interface StatusBarInitializer {
- var statusBarViewUpdatedListener: OnStatusBarViewUpdatedListener? = null
+ var statusBarViewUpdatedListener: OnStatusBarViewUpdatedListener?
/**
* Creates the status bar window and root views, and initializes the component.
*
* TODO(b/277764509): Initialize the status bar via [CoreStartable#start].
*/
- fun initializeStatusBar() {
- windowController.fragmentHostManager.addTagListener(
+ fun initializeStatusBar()
+
+ interface OnStatusBarViewInitializedListener {
+
+ /**
+ * The status bar view has been initialized.
+ *
+ * @param component Dagger component that is created when the status bar view is created.
+ * Can be used to retrieve dependencies from that scope, including the status bar root
+ * view.
+ */
+ fun onStatusBarViewInitialized(component: StatusBarFragmentComponent)
+ }
+
+ interface OnStatusBarViewUpdatedListener {
+ fun onStatusBarViewUpdated(
+ statusBarViewController: PhoneStatusBarViewController,
+ statusBarTransitions: PhoneStatusBarTransitions,
+ )
+ }
+}
+
+@SysUISingleton
+class StatusBarInitializerImpl
+@Inject
+constructor(
+ private val windowController: StatusBarWindowController,
+ private val collapsedStatusBarFragmentProvider: Provider<CollapsedStatusBarFragment>,
+ private val creationListeners: Set<@JvmSuppressWildcards OnStatusBarViewInitializedListener>,
+) : StatusBarInitializer {
+
+ override var statusBarViewUpdatedListener: OnStatusBarViewUpdatedListener? = null
+
+ override fun initializeStatusBar() {
+ windowController.fragmentHostManager
+ .addTagListener(
CollapsedStatusBarFragment.TAG,
object : FragmentHostManager.FragmentListener {
override fun onFragmentViewCreated(tag: String, fragment: Fragment) {
- val statusBarFragmentComponent = (fragment as CollapsedStatusBarFragment)
- .statusBarFragmentComponent ?: throw IllegalStateException()
+ val statusBarFragmentComponent =
+ (fragment as CollapsedStatusBarFragment).statusBarFragmentComponent
+ ?: throw IllegalStateException()
statusBarViewUpdatedListener?.onStatusBarViewUpdated(
- statusBarFragmentComponent.phoneStatusBarView,
statusBarFragmentComponent.phoneStatusBarViewController,
- statusBarFragmentComponent.phoneStatusBarTransitions
+ statusBarFragmentComponent.phoneStatusBarTransitions,
)
creationListeners.forEach { listener ->
listener.onStatusBarViewInitialized(statusBarFragmentComponent)
@@ -67,33 +97,15 @@
override fun onFragmentViewDestroyed(tag: String?, fragment: Fragment?) {
// nop
}
- }
- ).fragmentManager
- .beginTransaction()
- .replace(
- R.id.status_bar_container,
- collapsedStatusBarFragmentProvider.get(),
- CollapsedStatusBarFragment.TAG
- )
- .commit()
- }
-
- interface OnStatusBarViewInitializedListener {
-
- /**
- * The status bar view has been initialized.
- *
- * @param component Dagger component that is created when the status bar view is created.
- * Can be used to retrieve dependencies from that scope, including the status bar root view.
- */
- fun onStatusBarViewInitialized(component: StatusBarFragmentComponent)
- }
-
- interface OnStatusBarViewUpdatedListener {
- fun onStatusBarViewUpdated(
- statusBarView: PhoneStatusBarView,
- statusBarViewController: PhoneStatusBarViewController,
- statusBarTransitions: PhoneStatusBarTransitions
- )
+ },
+ )
+ .fragmentManager
+ .beginTransaction()
+ .replace(
+ R.id.status_bar_container,
+ collapsedStatusBarFragmentProvider.get(),
+ CollapsedStatusBarFragment.TAG,
+ )
+ .commit()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/shared/StatusBarRonChips.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarSimpleFragment.kt
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/statusbar/chips/shared/StatusBarRonChips.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarSimpleFragment.kt
index 4c0c461..2141513 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/shared/StatusBarRonChips.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarSimpleFragment.kt
@@ -14,17 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.chips.shared
+package com.android.systemui.statusbar.core
import com.android.systemui.Flags
import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
-/** Helper for reading or using the status bar ron chips flag state. */
-@Suppress("NOTHING_TO_INLINE")
-object StatusBarRonChips {
- /** The aconfig flag name */
- const val FLAG_NAME = Flags.FLAG_STATUS_BAR_RON_CHIPS
+/** Helper for reading and using the status bar simple fragment flag state */
+object StatusBarSimpleFragment {
+ /** Aconfig flag for removing the fragment */
+ const val FLAG_NAME = Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT
/** A token used for dependency declaration */
val token: FlagToken
@@ -33,7 +32,7 @@
/** Is the refactor enabled */
@JvmStatic
inline val isEnabled
- get() = Flags.statusBarRonChips()
+ get() = Flags.statusBarSimpleFragment()
/**
* Called to ensure code is only run when the flag is enabled. This protects users from the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index 406a664..55943a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -16,16 +16,22 @@
package com.android.systemui.statusbar.dagger
+import android.content.Context
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.statusbar.core.StatusBarInitializer
+import com.android.systemui.statusbar.core.StatusBarInitializerImpl
import com.android.systemui.statusbar.data.StatusBarDataLayerModule
import com.android.systemui.statusbar.phone.LightBarController
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog
import com.android.systemui.statusbar.ui.SystemBarUtilsProxyImpl
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.statusbar.window.StatusBarWindowControllerImpl
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -57,7 +63,20 @@
@ClassKey(StatusBarSignalPolicy::class)
abstract fun bindStatusBarSignalPolicy(impl: StatusBarSignalPolicy): CoreStartable
+ @Binds abstract fun statusBarInitializer(impl: StatusBarInitializerImpl): StatusBarInitializer
+
companion object {
+
+ @Provides
+ @SysUISingleton
+ fun statusBarWindowController(
+ context: Context?,
+ viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager?,
+ factory: StatusBarWindowControllerImpl.Factory,
+ ): StatusBarWindowController {
+ return factory.create(context, viewCaptureAwareWindowManager)
+ }
+
@Provides
@SysUISingleton
@OngoingCallLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt
index c0302bc..9af4b8c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt
@@ -25,6 +25,7 @@
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
/**
* Repository used for tracking the state of notification remote input (e.g. when the user presses
@@ -33,14 +34,21 @@
interface RemoteInputRepository {
/** Whether remote input is currently active for any notification. */
val isRemoteInputActive: Flow<Boolean>
+
+ /**
+ * The bottom bound of the currently focused remote input notification row, or null if there
+ * isn't one.
+ */
+ val remoteInputRowBottomBound: Flow<Float?>
+
+ fun setRemoteInputRowBottomBound(bottom: Float?)
}
@SysUISingleton
class RemoteInputRepositoryImpl
@Inject
-constructor(
- private val notificationRemoteInputManager: NotificationRemoteInputManager,
-) : RemoteInputRepository {
+constructor(private val notificationRemoteInputManager: NotificationRemoteInputManager) :
+ RemoteInputRepository {
override val isRemoteInputActive: Flow<Boolean> = conflatedCallbackFlow {
trySend(false) // initial value is false
val callback =
@@ -52,6 +60,12 @@
notificationRemoteInputManager.addControllerCallback(callback)
awaitClose { notificationRemoteInputManager.removeControllerCallback(callback) }
}
+
+ override val remoteInputRowBottomBound = MutableStateFlow<Float?>(null)
+
+ override fun setRemoteInputRowBottomBound(bottom: Float?) {
+ remoteInputRowBottomBound.value = bottom
+ }
}
@Module
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt
index 68f727b..b83b0cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt
@@ -20,13 +20,24 @@
import com.android.systemui.statusbar.data.repository.RemoteInputRepository
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.mapNotNull
/**
* Interactor used for business logic pertaining to the notification remote input (e.g. when the
* user presses "reply" on a notification and the keyboard opens).
*/
@SysUISingleton
-class RemoteInputInteractor @Inject constructor(remoteInputRepository: RemoteInputRepository) {
+class RemoteInputInteractor
+@Inject
+constructor(private val remoteInputRepository: RemoteInputRepository) {
/** Is remote input currently active for a notification? */
val isRemoteInputActive: Flow<Boolean> = remoteInputRepository.isRemoteInputActive
+
+ /** The bottom bound of the currently focused remote input notification row. */
+ val remoteInputRowBottomBound: Flow<Float> =
+ remoteInputRepository.remoteInputRowBottomBound.mapNotNull { it }
+
+ fun setRemoteInputRowBottomBound(bottom: Float?) {
+ remoteInputRepository.setRemoteInputRowBottomBound(bottom)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index cb3e26b..5003a6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -21,6 +21,7 @@
import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.PARENT_DISMISSED;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
+import static com.android.systemui.statusbar.policy.RemoteInputView.FOCUS_ANIMATION_MIN_SCALE;
import static com.android.systemui.util.ColorUtilKt.hexColorString;
import android.animation.Animator;
@@ -83,6 +84,7 @@
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.StatusBarIconView;
@@ -118,6 +120,7 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
+import com.android.systemui.statusbar.policy.RemoteInputView;
import com.android.systemui.statusbar.policy.SmartReplyConstants;
import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
import com.android.systemui.util.Compile;
@@ -830,6 +833,20 @@
mPrivateLayout.setRemoteInputController(r);
}
+ /**
+ * Return the cumulative y-value that the actions container expands via its scale animator when
+ * remote input is activated.
+ */
+ public float getRemoteInputActionsContainerExpandedOffset() {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return 0f;
+ RemoteInputView expandedRemoteInput = mPrivateLayout.getExpandedRemoteInput();
+ if (expandedRemoteInput == null) return 0f;
+ View actionsContainerLayout = expandedRemoteInput.getActionsContainerLayout();
+ if (actionsContainerLayout == null) return 0f;
+
+ return actionsContainerLayout.getHeight() * (1 - FOCUS_ANIMATION_MIN_SCALE) * 0.5f;
+ }
+
public void addChildNotification(ExpandableNotificationRow row) {
addChildNotification(row, -1);
}
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 7543f3b..e7c67f9 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
@@ -99,6 +99,7 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.FakeShadowView;
+import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
import com.android.systemui.statusbar.notification.NotificationTransitionAnimatorController;
import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -120,7 +121,6 @@
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape;
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
-import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.policy.HeadsUpUtil;
import com.android.systemui.statusbar.policy.ScrollAdapter;
@@ -740,6 +740,15 @@
updateFooter();
}
+ void sendRemoteInputRowBottomBound(Float bottom) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+ if (bottom != null) {
+ bottom += getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.notification_content_margin);
+ }
+ mScrollViewFields.sendRemoteInputRowBottomBound(bottom);
+ }
+
/** Setter for filtered notifs, to be removed with the FooterViewRefactor flag. */
public void setHasFilteredOutSeenNotifications(boolean hasFilteredOutSeenNotifications) {
FooterViewRefactor.assertInLegacyMode();
@@ -1274,6 +1283,11 @@
}
@Override
+ public void setRemoteInputRowBottomBoundConsumer(@Nullable Consumer<Float> consumer) {
+ mScrollViewFields.setRemoteInputRowBottomBoundConsumer(consumer);
+ }
+
+ @Override
public void setHeadsUpHeightConsumer(@Nullable Consumer<Float> consumer) {
mScrollViewFields.setHeadsUpHeightConsumer(consumer);
}
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 e5f63c1..dad6894 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
@@ -98,6 +98,9 @@
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.HeadsUpNotificationViewControllerEmptyImpl;
+import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
+import com.android.systemui.statusbar.notification.HeadsUpTouchHelper.HeadsUpNotificationViewController;
import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
@@ -129,9 +132,6 @@
import com.android.systemui.statusbar.notification.shared.GroupHunAnimationFix;
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
-import com.android.systemui.statusbar.notification.HeadsUpNotificationViewControllerEmptyImpl;
-import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
-import com.android.systemui.statusbar.notification.HeadsUpTouchHelper.HeadsUpNotificationViewController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -1605,6 +1605,9 @@
return new RemoteInputController.Delegate() {
public void setRemoteInputActive(NotificationEntry entry,
boolean remoteInputActive) {
+ if (SceneContainerFlag.isEnabled()) {
+ sendRemoteInputRowBottomBound(entry, remoteInputActive);
+ }
mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
entry.notifyHeightChanged(true /* needsAnimation */);
if (!FooterViewRefactor.isEnabled()) {
@@ -1620,6 +1623,15 @@
mView.requestDisallowLongPress();
mView.requestDisallowDismiss();
}
+
+ private void sendRemoteInputRowBottomBound(NotificationEntry entry,
+ boolean remoteInputActive) {
+ ExpandableNotificationRow row = entry.getRow();
+ float top = row.getTranslationY();
+ int height = row.getActualHeight();
+ float bottom = top + height + row.getRemoteInputActionsContainerExpandedOffset();
+ mView.sendRemoteInputRowBottomBound(remoteInputActive ? bottom : null);
+ }
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
index aa39539..c08ed61 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
@@ -57,6 +57,13 @@
* guts off of this gesture, we can notify the placeholder through here.
*/
var currentGestureInGutsConsumer: Consumer<Boolean>? = null
+
+ /**
+ * When a notification begins remote input, its bottom Y bound is sent to the placeholder
+ * through here in order to adjust to accommodate the IME.
+ */
+ var remoteInputRowBottomBoundConsumer: Consumer<Float?>? = null
+
/**
* Any time the heads up height is recalculated, it should be updated here to be used by the
* placeholder
@@ -75,6 +82,10 @@
fun sendCurrentGestureInGuts(isCurrentGestureInGuts: Boolean) =
currentGestureInGutsConsumer?.accept(isCurrentGestureInGuts)
+ /** send [bottomY] to the [remoteInputRowBottomBoundConsumer], if present. */
+ fun sendRemoteInputRowBottomBound(bottomY: Float?) =
+ remoteInputRowBottomBoundConsumer?.accept(bottomY)
+
/** send the [headsUpHeight] to the [headsUpHeightConsumer], if present. */
fun sendHeadsUpHeight(headsUpHeight: Float) = headsUpHeightConsumer?.accept(headsUpHeight)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
index 235b4da..41c0293 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
@@ -74,6 +74,9 @@
/** Set a consumer for current gesture in guts events */
fun setCurrentGestureInGutsConsumer(consumer: Consumer<Boolean>?)
+ /** Set a consumer for current remote input notification row bottom bound events */
+ fun setRemoteInputRowBottomBoundConsumer(consumer: Consumer<Float?>?)
+
/** Set a consumer for heads up height changed events */
fun setHeadsUpHeightConsumer(consumer: Consumer<Float>?)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
index 6d5553f..2e37dea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
@@ -108,10 +108,14 @@
view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer)
view.setCurrentGestureOverscrollConsumer(viewModel.currentGestureOverscrollConsumer)
view.setCurrentGestureInGutsConsumer(viewModel.currentGestureInGutsConsumer)
+ view.setRemoteInputRowBottomBoundConsumer(
+ viewModel.remoteInputRowBottomBoundConsumer
+ )
DisposableHandle {
view.setSyntheticScrollConsumer(null)
view.setCurrentGestureOverscrollConsumer(null)
view.setCurrentGestureInGutsConsumer(null)
+ view.setRemoteInputRowBottomBoundConsumer(null)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index 8d7007b..5b2e02d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -31,6 +31,7 @@
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimClipping
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
@@ -56,6 +57,7 @@
dumpManager: DumpManager,
stackAppearanceInteractor: NotificationStackAppearanceInteractor,
shadeInteractor: ShadeInteractor,
+ private val remoteInputInteractor: RemoteInputInteractor,
private val sceneInteractor: SceneInteractor,
// TODO(b/336364825) Remove Lazy when SceneContainerFlag is released -
// while the flag is off, creating this object too early results in a crash
@@ -240,6 +242,10 @@
val currentGestureInGutsConsumer: (Boolean) -> Unit =
stackAppearanceInteractor::setCurrentGestureInGuts
+ /** Receives the bottom bound of the currently focused remote input notification row. */
+ val remoteInputRowBottomBoundConsumer: (Float?) -> Unit =
+ remoteInputInteractor::setRemoteInputRowBottomBound
+
/** Whether the notification stack is scrollable or not. */
val isScrollable: Flow<Boolean> =
combine(sceneInteractor.currentScene, sceneInteractor.currentOverlays) {
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 69c1bf3..c8e8358 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
@@ -24,6 +24,7 @@
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
@@ -49,6 +50,7 @@
private val sceneInteractor: SceneInteractor,
private val shadeInteractor: ShadeInteractor,
private val headsUpNotificationInteractor: HeadsUpNotificationInteractor,
+ remoteInputInteractor: RemoteInputInteractor,
featureFlags: FeatureFlagsClassic,
dumpManager: DumpManager,
) :
@@ -132,6 +134,12 @@
val isCurrentGestureOverscroll: Flow<Boolean> =
interactor.isCurrentGestureOverscroll.dumpWhileCollecting("isCurrentGestureOverScroll")
+ /** Whether remote input is currently active for any notification. */
+ val isRemoteInputActive = remoteInputInteractor.isRemoteInputActive
+
+ /** The bottom bound of the currently focused remote input notification row. */
+ val remoteInputRowBottomBound = remoteInputInteractor.remoteInputRowBottomBound
+
/** 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/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 50e9249..59533b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -372,7 +372,6 @@
private final Point mCurrentDisplaySize = new Point();
- protected PhoneStatusBarView mStatusBarView;
private PhoneStatusBarViewController mPhoneStatusBarViewController;
private PhoneStatusBarTransitions mStatusBarTransitions;
private final AuthRippleController mAuthRippleController;
@@ -1191,8 +1190,7 @@
// Set up CollapsedStatusBarFragment and PhoneStatusBarView
mStatusBarInitializer.setStatusBarViewUpdatedListener(
- (statusBarView, statusBarViewController, statusBarTransitions) -> {
- mStatusBarView = statusBarView;
+ (statusBarViewController, statusBarTransitions) -> {
mPhoneStatusBarViewController = statusBarViewController;
mStatusBarTransitions = statusBarTransitions;
getNotificationShadeWindowViewController()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index ba39c3b..8c03538 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -51,6 +51,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor;
+import com.android.systemui.modes.shared.ModesUiIcons;
import com.android.systemui.privacy.PrivacyItem;
import com.android.systemui.privacy.PrivacyItemController;
import com.android.systemui.privacy.PrivacyType;
@@ -360,7 +361,7 @@
mBluetooth.addCallback(this);
mProvisionedController.addCallback(this);
mCurrentUserSetup = mProvisionedController.isCurrentUserSetup();
- if (usesModeIcons()) {
+ if (ModesUiIcons.isEnabled()) {
// Note that we're not fully replacing ZenModeController with ZenModeInteractor, so
// we listen for the extra event here but still add the ZMC callback.
mJavaAdapter.alwaysCollectFlow(mZenModeInteractor.getMainActiveMode(),
@@ -397,8 +398,7 @@
}
private void onMainActiveModeChanged(@Nullable ZenModeInfo mainActiveMode) {
- if (!usesModeIcons()) {
- Log.wtf(TAG, "onMainActiveModeChanged shouldn't run if MODES_UI_ICONS is disabled");
+ if (ModesUiIcons.isUnexpectedlyInLegacyMode()) {
return;
}
@@ -458,14 +458,14 @@
private void updateVolumeZen() {
int zen = mZenController.getZen();
- if (!usesModeIcons()) {
+ if (!ModesUiIcons.isEnabled()) {
updateZenIcon(zen);
}
updateRingerAndAlarmIcons(zen);
}
private void updateZenIcon(int zen) {
- if (usesModeIcons()) {
+ if (ModesUiIcons.isEnabled()) {
Log.wtf(TAG, "updateZenIcon shouldn't be called if MODES_UI_ICONS is enabled");
return;
}
@@ -942,9 +942,4 @@
mIconController.setIconVisibility(mSlotConnectedDisplay, visible);
}
-
- private static boolean usesModeIcons() {
- return android.app.Flags.modesApi() && android.app.Flags.modesUi()
- && android.app.Flags.modesUiIcons();
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 659cee3..6584556 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -55,7 +55,7 @@
import com.android.systemui.statusbar.OperatorNameView;
import com.android.systemui.statusbar.OperatorNameViewController;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.chips.shared.StatusBarRonChips;
+import com.android.systemui.statusbar.chips.ron.shared.StatusBarRonChips;
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger.DisableState;
import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
index c0e36b2..f026b99 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
@@ -27,7 +27,6 @@
import com.android.systemui.statusbar.phone.PhoneStatusBarView;
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
import com.android.systemui.statusbar.phone.StatusBarLocation;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
import com.android.systemui.statusbar.policy.Clock;
import com.android.systemui.statusbar.window.StatusBarWindowController;
@@ -114,14 +113,6 @@
/** */
@Provides
@StatusBarFragmentScope
- static StatusBarUserSwitcherContainer provideStatusBarUserSwitcherContainer(
- @RootView PhoneStatusBarView view) {
- return view.findViewById(R.id.user_switcher_container);
- }
-
- /** */
- @Provides
- @StatusBarFragmentScope
static PhoneStatusBarViewController providePhoneStatusBarViewController(
PhoneStatusBarViewController.Factory phoneStatusBarViewControllerFactory,
@RootView PhoneStatusBarView phoneStatusBarView) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/IconManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/IconManager.java
index 91ead61..fd16c60 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/IconManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/IconManager.java
@@ -20,7 +20,6 @@
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE_NEW;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI_NEW;
-import static com.android.systemui.statusbar.phone.ui.StatusBarIconControllerImpl.usesModeIcons;
import android.annotation.Nullable;
import android.content.Context;
@@ -31,6 +30,7 @@
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.statusbar.StatusBarIcon.Shape;
import com.android.systemui.demomode.DemoModeCommandReceiver;
+import com.android.systemui.modes.shared.ModesUiIcons;
import com.android.systemui.statusbar.BaseStatusBarFrameLayout;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusIconDisplayable;
@@ -233,7 +233,7 @@
}
protected LinearLayout.LayoutParams onCreateLayoutParams(Shape shape) {
- int width = usesModeIcons() && shape == StatusBarIcon.Shape.FIXED_SPACE
+ int width = ModesUiIcons.isEnabled() && shape == StatusBarIcon.Shape.FIXED_SPACE
? mIconSize
: ViewGroup.LayoutParams.WRAP_CONTENT;
@@ -259,7 +259,7 @@
/** Called once an icon has been set. */
public void onSetIcon(int viewIndex, StatusBarIcon icon) {
StatusBarIconView view = (StatusBarIconView) mGroup.getChildAt(viewIndex);
- if (usesModeIcons()) {
+ if (ModesUiIcons.isEnabled()) {
ViewGroup.LayoutParams current = view.getLayoutParams();
ViewGroup.LayoutParams desired = onCreateLayoutParams(icon.shape);
if (desired.width != current.width || desired.height != current.height) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java
index 9b6d32b..e66e8138 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java
@@ -42,6 +42,7 @@
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.modes.shared.ModesUiIcons;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusIconDisplayable;
import com.android.systemui.statusbar.phone.StatusBarIconHolder;
@@ -242,10 +243,7 @@
public void setResourceIcon(String slot, @Nullable String resPackage,
@DrawableRes int iconResId, @Nullable Drawable preloadedIcon,
CharSequence contentDescription, StatusBarIcon.Shape shape) {
- if (!usesModeIcons()) {
- Log.wtf("TAG",
- "StatusBarIconController.setResourceIcon() should not be called without "
- + "MODES_UI & MODES_UI_ICONS!");
+ if (ModesUiIcons.isUnexpectedlyInLegacyMode()) {
// Fall back to old implementation, although it will not load the icon if it's from a
// different package.
setIcon(slot, iconResId, contentDescription);
@@ -580,9 +578,4 @@
return slot + EXTERNAL_SLOT_SUFFIX;
}
}
-
- static boolean usesModeIcons() {
- return android.app.Flags.modesApi() && android.app.Flags.modesUi()
- && android.app.Flags.modesUiIcons();
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
index cf9f9f4..3cb7090 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
@@ -21,6 +21,7 @@
import android.os.UserManager.DISALLOW_MICROPHONE_TOGGLE
import android.os.UserManager.DISALLOW_SHARE_LOCATION
import com.android.systemui.Flags
+import com.android.systemui.modes.shared.ModesUi
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.shared.model.TileCategory
@@ -142,7 +143,7 @@
dndTile: Provider<DndTile>,
modesTile: Provider<ModesTile>,
): QSTileImpl<*> {
- return if (android.app.Flags.modesUi()) modesTile.get() else dndTile.get()
+ return if (ModesUi.isEnabled) modesTile.get() else dndTile.get()
}
/** Inject flashlight config */
@@ -169,7 +170,7 @@
factory: QSTileViewModelFactory.Static<FlashlightTileModel>,
mapper: FlashlightMapper,
stateInteractor: FlashlightTileDataInteractor,
- userActionInteractor: FlashlightTileUserActionInteractor
+ userActionInteractor: FlashlightTileUserActionInteractor,
): QSTileViewModel =
factory.create(
TileSpec.create(FLASHLIGHT_TILE_SPEC),
@@ -206,7 +207,7 @@
factory: QSTileViewModelFactory.Static<LocationTileModel>,
mapper: LocationTileMapper,
stateInteractor: LocationTileDataInteractor,
- userActionInteractor: LocationTileUserActionInteractor
+ userActionInteractor: LocationTileUserActionInteractor,
): QSTileViewModel =
factory.create(
TileSpec.create(LOCATION_TILE_SPEC),
@@ -239,7 +240,7 @@
factory: QSTileViewModelFactory.Static<AlarmTileModel>,
mapper: AlarmTileMapper,
stateInteractor: AlarmTileDataInteractor,
- userActionInteractor: AlarmTileUserActionInteractor
+ userActionInteractor: AlarmTileUserActionInteractor,
): QSTileViewModel =
factory.create(
TileSpec.create(ALARM_TILE_SPEC),
@@ -272,7 +273,7 @@
factory: QSTileViewModelFactory.Static<UiModeNightTileModel>,
mapper: UiModeNightTileMapper,
stateInteractor: UiModeNightTileDataInteractor,
- userActionInteractor: UiModeNightTileUserActionInteractor
+ userActionInteractor: UiModeNightTileUserActionInteractor,
): QSTileViewModel =
factory.create(
TileSpec.create(UIMODENIGHT_TILE_SPEC),
@@ -306,7 +307,7 @@
factory: QSTileViewModelFactory.Static<WorkModeTileModel>,
mapper: WorkModeTileMapper,
stateInteractor: WorkModeTileDataInteractor,
- userActionInteractor: WorkModeTileUserActionInteractor
+ userActionInteractor: WorkModeTileUserActionInteractor,
): QSTileViewModel =
factory.create(
TileSpec.create(WORK_MODE_TILE_SPEC),
@@ -406,7 +407,7 @@
@IntoMap
@StringKey(DND_TILE_SPEC)
fun provideDndOrModesTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
- if (android.app.Flags.modesUi()) {
+ if (ModesUi.isEnabled) {
QSTileConfig(
tileSpec = TileSpec.create(DND_TILE_SPEC),
uiConfig =
@@ -438,9 +439,9 @@
factory: QSTileViewModelFactory.Static<ModesTileModel>,
mapper: ModesTileMapper,
stateInteractor: ModesTileDataInteractor,
- userActionInteractor: ModesTileUserActionInteractor
+ userActionInteractor: ModesTileUserActionInteractor,
): QSTileViewModel =
- if (android.app.Flags.modesUi() && Flags.qsNewTilesFuture())
+ if (ModesUi.isEnabled && Flags.qsNewTilesFuture())
factory.create(
TileSpec.create(DND_TILE_SPEC),
userActionInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 31776cf..16d5f8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -106,7 +106,7 @@
private static final long FOCUS_ANIMATION_CROSSFADE_DURATION = 50;
private static final long FOCUS_ANIMATION_FADE_IN_DELAY = 33;
private static final long FOCUS_ANIMATION_FADE_IN_DURATION = 83;
- private static final float FOCUS_ANIMATION_MIN_SCALE = 0.5f;
+ public static final float FOCUS_ANIMATION_MIN_SCALE = 0.5f;
private static final long DEFOCUS_ANIMATION_FADE_OUT_DELAY = 120;
private static final long DEFOCUS_ANIMATION_CROSSFADE_DELAY = 180;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
index dbeaa59..ba45942 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
@@ -27,7 +27,10 @@
import com.android.settingslib.notification.modes.ZenIconLoader
import com.android.settingslib.notification.modes.ZenMode
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.modes.shared.ModesUi
import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
+import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository
+import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
import com.android.systemui.statusbar.policy.domain.model.ActiveZenModes
import com.android.systemui.statusbar.policy.domain.model.ZenModeInfo
import java.time.Duration
@@ -51,7 +54,17 @@
private val notificationSettingsRepository: NotificationSettingsRepository,
@Background private val bgDispatcher: CoroutineDispatcher,
private val iconLoader: ZenIconLoader,
+ private val deviceProvisioningRepository: DeviceProvisioningRepository,
+ private val userSetupRepository: UserSetupRepository,
) {
+ val isZenAvailable: Flow<Boolean> =
+ combine(
+ deviceProvisioningRepository.isDeviceProvisioned,
+ userSetupRepository.isUserSetUp,
+ ) { isDeviceProvisioned, isUserSetUp ->
+ isDeviceProvisioned && isUserSetUp
+ }
+
val isZenModeEnabled: Flow<Boolean> =
zenModeRepository.globalZenMode
.map {
@@ -80,6 +93,18 @@
val modes: Flow<List<ZenMode>> = zenModeRepository.modes
+ /**
+ * Returns the special "manual DND" mode.
+ *
+ * This is only meant as a temporary solution for "legacy" UI pieces that handle DND
+ * specifically; any new or migrated features should use modes more generally, through [modes]
+ * or [activeModes].
+ */
+ val dndMode: Flow<ZenMode?> by lazy {
+ ModesUi.assertInNewMode()
+ zenModeRepository.modes.map { modes -> modes.singleOrNull { it.isManualDnd } }
+ }
+
/** Flow returning the currently active mode(s), if any. */
val activeModes: Flow<ActiveZenModes> =
modes
@@ -113,10 +138,11 @@
Log.e(
TAG,
"Interactor cannot handle showing the zen duration prompt. " +
- "Please use EnableZenModeDialog when this setting is active."
+ "Please use EnableZenModeDialog when this setting is active.",
)
null
}
+
ZEN_DURATION_FOREVER -> null
else -> Duration.ofMinutes(zenDuration.toLong())
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
index af93880..27bc6d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
@@ -59,32 +59,26 @@
)
CompositionLocalProvider(LocalContentColor provides contentColor) {
- Surface(
- color = tileColor,
- shape = RoundedCornerShape(16.dp),
- ) {
+ Surface(color = tileColor, shape = RoundedCornerShape(16.dp)) {
Row(
modifier =
Modifier.combinedClickable(
onClick = viewModel.onClick,
onLongClick = viewModel.onLongClick,
- onLongClickLabel = viewModel.onLongClickLabel
+ onLongClickLabel = viewModel.onLongClickLabel,
)
- .padding(20.dp)
+ .padding(16.dp)
.semantics { stateDescription = viewModel.stateDescription },
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement =
- Arrangement.spacedBy(
- space = 10.dp,
- alignment = Alignment.Start,
- ),
+ Arrangement.spacedBy(space = 8.dp, alignment = Alignment.Start),
) {
Icon(icon = viewModel.icon, modifier = Modifier.size(24.dp))
Column {
Text(
viewModel.text,
fontWeight = FontWeight.W500,
- modifier = Modifier.tileMarquee().testTag("name")
+ modifier = Modifier.tileMarquee().testTag("name"),
)
Text(
viewModel.subtext,
@@ -94,7 +88,7 @@
.testTag(if (viewModel.enabled) "stateOn" else "stateOff")
.clearAndSetSemantics {
contentDescription = viewModel.subtextDescription
- }
+ },
)
}
}
@@ -103,8 +97,5 @@
}
private fun Modifier.tileMarquee(): Modifier {
- return this.basicMarquee(
- iterations = 1,
- initialDelayMillis = 200,
- )
+ return this.basicMarquee(iterations = 1)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt
index 73d361f6..5953ea5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt
@@ -19,7 +19,6 @@
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
-import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.runtime.Composable
@@ -27,23 +26,20 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.Flags
import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.ModesDialogViewModel
@Composable
fun ModeTileGrid(viewModel: ModesDialogViewModel) {
val tiles by viewModel.tiles.collectAsStateWithLifecycle(initialValue = emptyList())
- // TODO(b/346519570): Handle what happens when we have more than a few modes.
LazyVerticalGrid(
- columns = GridCells.Fixed(2),
- modifier = Modifier.padding(8.dp).fillMaxWidth().heightIn(max = 300.dp),
+ columns = GridCells.Fixed(if (Flags.modesDialogSingleRows()) 1 else 2),
+ modifier = Modifier.fillMaxWidth().heightIn(max = 300.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
- items(
- tiles.size,
- key = { index -> tiles[index].id },
- ) { index ->
+ items(tiles.size, key = { index -> tiles[index].id }) { index ->
ModeTile(viewModel = tiles[index])
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt
new file mode 100644
index 0000000..421e5c4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.window
+
+import android.view.View
+import android.view.ViewGroup
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.fragments.FragmentHostManager
+import java.util.Optional
+
+/** Encapsulates all logic for the status bar window state management. */
+interface StatusBarWindowController {
+ val statusBarHeight: Int
+
+ /** Rereads the status bar height and reapplies the current state if the height is different. */
+ fun refreshStatusBarHeight()
+
+ /** Adds the status bar view to the window manager. */
+ fun attach()
+
+ /** Adds the given view to the status bar window view. */
+ fun addViewToWindow(view: View, layoutParams: ViewGroup.LayoutParams)
+
+ /** Returns the status bar window's background view. */
+ val backgroundView: View
+
+ /** Returns a fragment host manager for the status bar window view. */
+ val fragmentHostManager: FragmentHostManager
+
+ /**
+ * Provides an updated animation controller if we're animating a view in the status bar.
+ *
+ * This is needed because we have to make sure that the status bar window matches the full
+ * screen during the animation and that we are expanding the view below the other status bar
+ * text.
+ *
+ * @param rootView the root view of the animation
+ * @param animationController the default animation controller to use
+ * @return If the animation is on a view in the status bar, returns an Optional containing an
+ * updated animation controller that handles status-bar-related animation details. Returns an
+ * empty optional if the animation is *not* on a view in the status bar.
+ */
+ fun wrapAnimationControllerIfInStatusBar(
+ rootView: View,
+ animationController: ActivityTransitionAnimator.Controller,
+ ): Optional<ActivityTransitionAnimator.Controller>
+
+ /** Set force status bar visible. */
+ fun setForceStatusBarVisible(forceStatusBarVisible: Boolean)
+
+ /**
+ * Sets whether an ongoing process requires the status bar to be forced visible.
+ *
+ * This method is separate from {@link this#setForceStatusBarVisible} because the ongoing
+ * process **takes priority**. For example, if {@link this#setForceStatusBarVisible} is set to
+ * false but this method is set to true, then the status bar **will** be visible.
+ *
+ * TODO(b/195839150): We should likely merge this method and {@link
+ * this#setForceStatusBarVisible} together and use some sort of ranking system instead.
+ */
+ fun setOngoingProcessRequiresStatusBarVisible(visible: Boolean)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
similarity index 81%
rename from packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
index c30a6b7..1ee7cf3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
@@ -28,7 +28,6 @@
import static com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN;
import android.content.Context;
-import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.PixelFormat;
import android.graphics.Rect;
@@ -46,28 +45,30 @@
import android.view.WindowInsets;
import android.view.WindowManager;
+import androidx.annotation.NonNull;
+
import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.animation.DelegateTransitionAnimatorController;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
+import com.android.systemui.statusbar.window.StatusBarWindowModule.InternalWindowViewInflater;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
import com.android.systemui.unfold.util.JankMonitorTransitionProgressListener;
-import java.util.Optional;
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
-import javax.inject.Inject;
+import java.util.Optional;
/**
* Encapsulates all logic for the status bar window state management.
*/
-@SysUISingleton
-public class StatusBarWindowController {
+public class StatusBarWindowControllerImpl implements StatusBarWindowController {
private static final String TAG = "StatusBarWindowController";
private static final boolean DEBUG = false;
@@ -88,21 +89,20 @@
private final WindowManager.LayoutParams mLpChanged;
private final Binder mInsetsSourceOwner = new Binder();
- @Inject
- public StatusBarWindowController(
- Context context,
- @StatusBarWindowModule.InternalWindowView StatusBarWindowView statusBarWindowView,
- ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
+ @AssistedInject
+ public StatusBarWindowControllerImpl(
+ @Assisted Context context,
+ @InternalWindowViewInflater StatusBarWindowViewInflater statusBarWindowViewInflater,
+ @Assisted ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
IWindowManager iWindowManager,
StatusBarContentInsetsProvider contentInsetsProvider,
FragmentService fragmentService,
- @Main Resources resources,
Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider) {
mContext = context;
mWindowManager = viewCaptureAwareWindowManager;
mIWindowManager = iWindowManager;
mContentInsetsProvider = contentInsetsProvider;
- mStatusBarWindowView = statusBarWindowView;
+ mStatusBarWindowView = statusBarWindowViewInflater.inflate(context);
mFragmentService = fragmentService;
mLaunchAnimationContainer = mStatusBarWindowView.findViewById(
R.id.status_bar_launch_animation_container);
@@ -117,14 +117,12 @@
/* attachedViewProvider=*/ () -> mStatusBarWindowView)));
}
+ @Override
public int getStatusBarHeight() {
return mBarHeight;
}
- /**
- * Rereads the status bar height and reapplys the current state if the height
- * is different.
- */
+ @Override
public void refreshStatusBarHeight() {
Trace.beginSection("StatusBarWindowController#refreshStatusBarHeight");
try {
@@ -141,9 +139,7 @@
}
}
- /**
- * Adds the status bar view to the window manager.
- */
+ @Override
public void attach() {
// Now that the status bar window encompasses the sliding panel and its
// translucent backdrop, the entire thing is made TRANSLUCENT and is
@@ -161,54 +157,47 @@
apply(mCurrentState);
}
- /** Adds the given view to the status bar window view. */
- public void addViewToWindow(View view, ViewGroup.LayoutParams layoutParams) {
+ @Override
+ public void addViewToWindow(@NonNull View view, @NonNull ViewGroup.LayoutParams layoutParams) {
mStatusBarWindowView.addView(view, layoutParams);
}
- /** Returns the status bar window's background view. */
+ @NonNull
+ @Override
public View getBackgroundView() {
return mStatusBarWindowView.findViewById(R.id.status_bar_container);
}
- /** Returns a fragment host manager for the status bar window view. */
+ @NonNull
+ @Override
public FragmentHostManager getFragmentHostManager() {
return mFragmentService.getFragmentHostManager(mStatusBarWindowView);
}
- /**
- * Provides an updated animation controller if we're animating a view in the status bar.
- *
- * This is needed because we have to make sure that the status bar window matches the full
- * screen during the animation and that we are expanding the view below the other status bar
- * text.
- *
- * @param rootView the root view of the animation
- * @param animationController the default animation controller to use
- * @return If the animation is on a view in the status bar, returns an Optional containing an
- * updated animation controller that handles status-bar-related animation details. Returns an
- * empty optional if the animation is *not* on a view in the status bar.
- */
+ @NonNull
+ @Override
public Optional<ActivityTransitionAnimator.Controller> wrapAnimationControllerIfInStatusBar(
- View rootView, ActivityTransitionAnimator.Controller animationController) {
+ @NonNull View rootView,
+ @NonNull ActivityTransitionAnimator.Controller animationController) {
if (rootView != mStatusBarWindowView) {
return Optional.empty();
}
animationController.setTransitionContainer(mLaunchAnimationContainer);
- return Optional.of(new DelegateTransitionAnimatorController(animationController) {
- @Override
- public void onTransitionAnimationStart(boolean isExpandingFullyAbove) {
- getDelegate().onTransitionAnimationStart(isExpandingFullyAbove);
- setLaunchAnimationRunning(true);
- }
+ return Optional.of(
+ new DelegateTransitionAnimatorController(animationController) {
+ @Override
+ public void onTransitionAnimationStart(boolean isExpandingFullyAbove) {
+ getDelegate().onTransitionAnimationStart(isExpandingFullyAbove);
+ setLaunchAnimationRunning(true);
+ }
- @Override
- public void onTransitionAnimationEnd(boolean isExpandingFullyAbove) {
- getDelegate().onTransitionAnimationEnd(isExpandingFullyAbove);
- setLaunchAnimationRunning(false);
- }
- });
+ @Override
+ public void onTransitionAnimationEnd(boolean isExpandingFullyAbove) {
+ getDelegate().onTransitionAnimationEnd(isExpandingFullyAbove);
+ setLaunchAnimationRunning(false);
+ }
+ });
}
private WindowManager.LayoutParams getBarLayoutParams(int rotation) {
@@ -275,22 +264,13 @@
}
}
- /** Set force status bar visible. */
+ @Override
public void setForceStatusBarVisible(boolean forceStatusBarVisible) {
mCurrentState.mForceStatusBarVisible = forceStatusBarVisible;
apply(mCurrentState);
}
- /**
- * Sets whether an ongoing process requires the status bar to be forced visible.
- *
- * This method is separate from {@link this#setForceStatusBarVisible} because the ongoing
- * process **takes priority**. For example, if {@link this#setForceStatusBarVisible} is set to
- * false but this method is set to true, then the status bar **will** be visible.
- *
- * TODO(b/195839150): We should likely merge this method and
- * {@link this#setForceStatusBarVisible} together and use some sort of ranking system instead.
- */
+ @Override
public void setOngoingProcessRequiresStatusBarVisible(boolean visible) {
mCurrentState.mOngoingProcessRequiresStatusBarVisible = visible;
apply(mCurrentState);
@@ -372,4 +352,13 @@
mLpChanged.forciblyShownTypes &= ~WindowInsets.Type.statusBars();
}
}
+
+ @AssistedFactory
+ public interface Factory {
+ /** Creates a new instance. */
+ StatusBarWindowControllerImpl create(
+ Context context,
+ ViewCaptureAwareWindowManager viewCaptureAwareWindowManager);
+ }
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowModule.kt
index 1c7debc..ebfbac7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowModule.kt
@@ -1,47 +1,36 @@
package com.android.systemui.statusbar.window
-import android.view.LayoutInflater
-import com.android.systemui.res.R
import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
import dagger.Module
-import dagger.Provides
import javax.inject.Qualifier
/** Module providing dependencies related to the status bar window. */
@Module
abstract class StatusBarWindowModule {
- /**
- * Provides a [StatusBarWindowView].
- *
- * Only [StatusBarWindowController] should inject the view.
- */
- @Module
- companion object {
- @JvmStatic
- @Provides
- @SysUISingleton
- @InternalWindowView
- fun providesStatusBarWindowView(layoutInflater: LayoutInflater): StatusBarWindowView {
- return layoutInflater.inflate(
- R.layout.super_status_bar,
- /* root= */null
- ) as StatusBarWindowView?
- ?: throw IllegalStateException(
- "R.layout.super_status_bar could not be properly inflated"
- )
- }
- }
/**
- * We want [StatusBarWindowView] to be provided to [StatusBarWindowController]'s constructor via
- * dagger so that we can provide a fake window view when testing the controller. However, we wan
- * want *only* the controller to be able to inject the window view.
+ * Binds a [StatusBarWindowViewInflater].
*
- * This protected qualifier annotation achieves this. [StatusBarWindowView] can only be injected
- * if it's annotated with [InternalWindowView], and only classes inside this [statusbar.window]
- * package can access the annotation.
+ * Only [StatusBarWindowControllerImpl] should inject it.
+ */
+ @Binds
+ @SysUISingleton
+ @InternalWindowViewInflater
+ abstract fun providesStatusBarWindowViewInflater(
+ inflaterImpl: StatusBarWindowViewInflaterImpl
+ ): StatusBarWindowViewInflater
+
+ /**
+ * We want [StatusBarWindowViewInflater] to be provided to [StatusBarWindowControllerImpl]'s
+ * constructor via dagger so that we can provide a fake window view when testing the controller.
+ * However, we wan want *only* the controller to be able to inject the window view.
+ *
+ * This protected qualifier annotation achieves this. [StatusBarWindowViewInflater] can only be
+ * injected if it's annotated with [InternalWindowViewInflater], and only classes inside this
+ * [statusbar.window] package can access the annotation.
*/
@Retention(AnnotationRetention.BINARY)
@Qualifier
- protected annotation class InternalWindowView
+ protected annotation class InternalWindowViewInflater
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowViewInflater.kt
new file mode 100644
index 0000000..f030a4a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowViewInflater.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.window
+
+import android.content.Context
+import android.view.LayoutInflater
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/**
+ * Inflates a [StatusBarWindowView]. Exists so that it can be injected into
+ * [StatusBarWindowControllerImpl] and be swapped for a fake implementation in tests.
+ */
+interface StatusBarWindowViewInflater {
+ fun inflate(context: Context): StatusBarWindowView
+}
+
+class StatusBarWindowViewInflaterImpl @Inject constructor() : StatusBarWindowViewInflater {
+
+ override fun inflate(context: Context): StatusBarWindowView {
+ val layoutInflater = LayoutInflater.from(context)
+ return layoutInflater.inflate(R.layout.super_status_bar, /* root= */ null)
+ as StatusBarWindowView?
+ ?: throw IllegalStateException(
+ "R.layout.super_status_bar could not be properly inflated"
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index bbfa32b..32a4f12 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -117,7 +117,14 @@
int displayId) {
Runnable showToastRunnable = () -> {
UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
- Context context = mContext.createContextAsUser(userHandle, 0);
+ Context context;
+ try {
+ context = mContext.createContextAsUser(userHandle, 0);
+ } catch (IllegalStateException e) {
+ // b/366533044 : Own package not found for systemui
+ Log.e(TAG, "Cannot create toast because cannot create context", e);
+ return;
+ }
DisplayManager mDisplayManager = mContext.getSystemService(DisplayManager.class);
Display display = mDisplayManager.getDisplay(displayId);
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt
index 1a41987..80ea925 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt
@@ -30,12 +30,12 @@
private val logger: InputDeviceTutorialLogger,
) {
fun disableGestures() {
- logger.log("Disabling touchpad gestures across the system")
+ logger.d("Disabling touchpad gestures across the system")
setGesturesState(disabled = true)
}
fun enableGestures() {
- logger.log("Enabling touchpad gestures across the system")
+ logger.d("Enabling touchpad gestures across the system")
setGesturesState(disabled = false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
index 5a77c04..6acc891 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
@@ -78,21 +78,21 @@
modifier = modifier
) {
TutorialButton(
- text = stringResource(R.string.touchpad_tutorial_back_gesture_button),
- onClick = onBackTutorialClicked,
+ text = stringResource(R.string.touchpad_tutorial_home_gesture_button),
+ onClick = onHomeTutorialClicked,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.weight(1f)
)
TutorialButton(
- text = stringResource(R.string.touchpad_tutorial_home_gesture_button),
- onClick = onHomeTutorialClicked,
- color = MaterialTheme.colorScheme.secondary,
+ text = stringResource(R.string.touchpad_tutorial_back_gesture_button),
+ onClick = onBackTutorialClicked,
+ color = MaterialTheme.colorScheme.tertiary,
modifier = Modifier.weight(1f)
)
TutorialButton(
text = stringResource(R.string.touchpad_tutorial_recent_apps_gesture_button),
onClick = onRecentAppsTutorialClicked,
- color = MaterialTheme.colorScheme.tertiary,
+ color = MaterialTheme.colorScheme.secondary,
modifier = Modifier.weight(1f)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
index 46ea352..d03b2e7 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
@@ -29,12 +29,10 @@
import com.android.compose.theme.PlatformTheme
import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger
import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger.TutorialContext
-import com.android.systemui.inputdevice.tutorial.ui.composable.ActionKeyTutorialScreen
import com.android.systemui.touchpad.tutorial.ui.composable.BackGestureTutorialScreen
import com.android.systemui.touchpad.tutorial.ui.composable.HomeGestureTutorialScreen
import com.android.systemui.touchpad.tutorial.ui.composable.RecentAppsGestureTutorialScreen
import com.android.systemui.touchpad.tutorial.ui.composable.TutorialSelectionScreen
-import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.ACTION_KEY
import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.BACK_GESTURE
import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.HOME_GESTURE
import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.RECENT_APPS_GESTURE
@@ -59,7 +57,7 @@
}
// required to handle 3+ fingers on touchpad
window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
- window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS)
+ logger.logOpenTutorial(TutorialContext.TOUCHPAD_TUTORIAL)
}
private fun finishTutorial() {
@@ -104,10 +102,5 @@
onDoneButtonClicked = { vm.goTo(TUTORIAL_SELECTION) },
onBack = { vm.goTo(TUTORIAL_SELECTION) },
)
- ACTION_KEY -> // TODO(b/358105049) move action key tutorial to OOBE flow
- ActionKeyTutorialScreen(
- onDoneButtonClicked = { vm.goTo(TUTORIAL_SELECTION) },
- onBack = { vm.goTo(TUTORIAL_SELECTION) },
- )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt
index 599e1b1..c56dcf3 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt
@@ -65,5 +65,4 @@
BACK_GESTURE,
HOME_GESTURE,
RECENT_APPS_GESTURE,
- ACTION_KEY,
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index db4f9ef..7166428 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -35,7 +35,6 @@
import static com.android.internal.jank.InteractionJankMonitor.CUJ_VOLUME_CONTROL;
import static com.android.internal.jank.InteractionJankMonitor.Configuration.Builder;
import static com.android.settingslib.flags.Flags.volumeDialogAudioSharingFix;
-import static com.android.systemui.Flags.hapticVolumeSlider;
import static com.android.systemui.volume.Events.DISMISS_REASON_POSTURE_CHANGED;
import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED;
@@ -928,10 +927,8 @@
}
private void addSliderHapticsToRow(VolumeRow row) {
- if (hapticVolumeSlider()) {
- row.createPlugin(mVibratorHelper, mSystemClock);
- HapticSliderViewBinder.bind(row.slider, row.mHapticPlugin);
- }
+ row.createPlugin(mVibratorHelper, mSystemClock);
+ HapticSliderViewBinder.bind(row.slider, row.mHapticPlugin);
}
@VisibleForTesting void addSliderHapticsToRows() {
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 7385b82..e764015 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -22,6 +22,7 @@
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL;
import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
+import static android.service.notification.NotificationListenerService.REASON_PACKAGE_BANNED;
import static android.service.notification.NotificationStats.DISMISSAL_BUBBLE;
import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
@@ -449,7 +450,8 @@
@Override
public void onEntryRemoved(NotificationEntry entry,
@NotifCollection.CancellationReason int reason) {
- if (reason == REASON_APP_CANCEL || reason == REASON_APP_CANCEL_ALL) {
+ if (reason == REASON_APP_CANCEL || reason == REASON_APP_CANCEL_ALL
+ || reason == REASON_PACKAGE_BANNED) {
BubblesManager.this.onEntryRemoved(entry);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java
index 530ae15..5e9f2a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java
@@ -104,6 +104,7 @@
mContext = spy(mContext);
Display display = mock(Display.class);
when(display.getUniqueId()).thenReturn(UNIQUE_DISPLAY_ID_PRIMARY);
+ when(display.getType()).thenReturn(Display.TYPE_INTERNAL);
when(mContext.getDisplayNoVerify()).thenReturn(display);
// Override the resources to Display Primary
@@ -360,6 +361,7 @@
Display newDisplay = mock(Display.class);
when(newDisplay.getUniqueId()).thenReturn(UNIQUE_DISPLAY_ID_SECONDARY);
+ when(newDisplay.getType()).thenReturn(Display.TYPE_INTERNAL);
when(mContext.getDisplayNoVerify()).thenReturn(newDisplay);
// Override the resources to Display Secondary
mContext.getOrCreateTestableResources()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
index 103449b..ee8ce17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
@@ -26,6 +26,7 @@
import static org.mockito.Mockito.verify;
import android.app.UiModeManager;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
import android.platform.test.annotations.EnableFlags;
@@ -78,6 +79,12 @@
mNightMode = mUiModeManager.getNightMode();
mUiModeManager.setNightMode(MODE_NIGHT_YES);
+ // Programmatically update the resource's configuration to night mode to reduce flakiness
+ Configuration nightConfig = new Configuration(mContext.getResources().getConfiguration());
+ nightConfig.uiMode = Configuration.UI_MODE_NIGHT_YES;
+ mContext.getResources().updateConfiguration(nightConfig,
+ mContext.getResources().getDisplayMetrics(), null);
+
mSpyContext = spy(mContext);
doNothing().when(mSpyContext).startActivity(any());
@@ -101,6 +108,8 @@
@Test
public void insetsOnDarkTheme_menuOnLeft_matchInsets() {
+ // In dark theme, the inset is not 0 to avoid weird spacing issue between the menu and
+ // the edge of the screen.
mMenuView.onConfigurationChanged(/* newConfig= */ null);
final InstantInsetLayerDrawable insetLayerDrawable =
(InstantInsetLayerDrawable) mMenuView.getBackground();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
index 9aaf295..a940bc9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
@@ -8,7 +8,8 @@
import android.graphics.Point
import android.graphics.Rect
import android.os.Looper
-import android.platform.test.flag.junit.SetFlagsRule
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper.RunWithLooper
import android.view.IRemoteAnimationFinishedCallback
import android.view.RemoteAnimationAdapter
@@ -63,7 +64,6 @@
private lateinit var activityTransitionAnimator: ActivityTransitionAnimator
@get:Rule val rule = MockitoJUnit.rule()
- @get:Rule val setFlagsRule = SetFlagsRule()
@Before
fun setup() {
@@ -90,7 +90,7 @@
animator: ActivityTransitionAnimator = this.activityTransitionAnimator,
controller: ActivityTransitionAnimator.Controller? = this.controller,
animate: Boolean = true,
- intentStarter: (RemoteAnimationAdapter?) -> Int
+ intentStarter: (RemoteAnimationAdapter?) -> Int,
) {
// We start in a new thread so that we can ensure that the callbacks are called in the main
// thread.
@@ -98,7 +98,7 @@
animator.startIntentWithAnimation(
controller = controller,
animate = animate,
- intentStarter = intentStarter
+ intentStarter = intentStarter,
)
}
.join()
@@ -175,9 +175,9 @@
assertFalse(willAnimateCaptor.value)
}
+ @EnableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY)
@Test
fun registersReturnIffCookieIsPresent() {
- setFlagsRule.enableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY)
`when`(callback.isOnKeyguard()).thenReturn(false)
startIntentWithAnimation(activityTransitionAnimator, controller) { _ ->
@@ -203,10 +203,12 @@
assertTrue(testShellTransitions.remotesForTakeover.isEmpty())
}
+ @EnableFlags(
+ Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+ )
@Test
fun registersLongLivedTransition() {
- setFlagsRule.enableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY)
-
activityTransitionAnimator.register(
object : DelegateTransitionAnimatorController(controller) {
override val transitionCookie =
@@ -226,10 +228,12 @@
assertEquals(4, testShellTransitions.remotes.size)
}
+ @EnableFlags(
+ Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+ )
@Test
fun registersLongLivedTransitionOverridingPreviousRegistration() {
- setFlagsRule.enableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY)
-
val cookie = ActivityTransitionAnimator.TransitionCookie("test_cookie")
activityTransitionAnimator.register(
object : DelegateTransitionAnimatorController(controller) {
@@ -251,9 +255,9 @@
}
}
+ @DisableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED)
@Test
fun doesNotRegisterLongLivedTransitionIfFlagIsDisabled() {
- setFlagsRule.disableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY)
val controller =
object : DelegateTransitionAnimatorController(controller) {
@@ -266,9 +270,9 @@
}
}
+ @EnableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED)
@Test
fun doesNotRegisterLongLivedTransitionIfMissingRequiredProperties() {
- setFlagsRule.enableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY)
// No TransitionCookie
val controllerWithoutCookie =
@@ -310,9 +314,12 @@
}
}
+ @EnableFlags(
+ Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+ )
@Test
fun unregistersLongLivedTransition() {
- setFlagsRule.enableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY)
val cookies = arrayOfNulls<ActivityTransitionAnimator.TransitionCookie>(3)
@@ -411,7 +418,7 @@
SurfaceControl(),
Rect(),
taskInfo,
- false
+ false,
)
}
}
@@ -430,7 +437,7 @@
override fun registerRemoteForTakeover(
filter: TransitionFilter,
- remoteTransition: RemoteTransition
+ remoteTransition: RemoteTransition,
) {
remotesForTakeover[filter] = remoteTransition
}
@@ -460,7 +467,7 @@
left = 300,
right = 400,
topCornerRadius = 10f,
- bottomCornerRadius = 20f
+ bottomCornerRadius = 20f,
)
private fun assertOnMainThread() {
@@ -480,7 +487,7 @@
override fun onTransitionAnimationProgress(
state: TransitionAnimator.State,
progress: Float,
- linearProgress: Float
+ linearProgress: Float,
) {
assertOnMainThread()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
index 4b61a0d..088bb02 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
@@ -25,6 +25,7 @@
import androidx.test.filters.LargeTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.ui.viewmodel.patternBouncerViewModelFactory
+import com.android.systemui.haptics.msdl.bouncerHapticPlayer
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.motion.createSysUiComposeMotionTestRule
import com.android.systemui.testKosmos
@@ -55,6 +56,7 @@
kosmos.patternBouncerViewModelFactory.create(
isInputEnabled = MutableStateFlow(true).asStateFlow(),
onIntentionalUserInput = {},
+ bouncerHapticPlayer = kosmos.bouncerHapticPlayer,
)
@Before
@@ -75,11 +77,11 @@
content = { play -> if (play) PatternBouncerUnderTest() },
ComposeRecordingSpec.until(
recordBefore = false,
- checkDone = { motionTestValueOfNode(MotionTestKeys.entryCompleted) }
+ checkDone = { motionTestValueOfNode(MotionTestKeys.entryCompleted) },
) {
feature(MotionTestKeys.dotAppearFadeIn, floatArray)
feature(MotionTestKeys.dotAppearMoveUp, floatArray)
- }
+ },
)
assertThat(motion).timeSeriesMatchesGolden()
@@ -100,7 +102,7 @@
viewModel.onDragEnd()
// Failure animation starts when animateFailure flips to true...
viewModel.animateFailure.takeWhile { !it }.collect {}
- }
+ },
) {
// ... and ends when the composable flips it back to false.
viewModel.animateFailure.takeWhile { it }.collect {}
@@ -111,7 +113,7 @@
content = { PatternBouncerUnderTest() },
ComposeRecordingSpec(failureAnimationMotionControl) {
feature(MotionTestKeys.dotScaling, floatArray)
- }
+ },
)
assertThat(motion).timeSeriesMatchesGolden()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
index c65a117..d72b72c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
@@ -32,6 +32,7 @@
import android.content.ClipDescription;
import android.content.ClipboardManager;
import android.os.PersistableBundle;
+import android.os.UserHandle;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.provider.Settings;
@@ -101,8 +102,18 @@
when(mClipboardManager.getPrimaryClip()).thenReturn(mSampleClipData);
when(mClipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource);
- mClipboardListener = new ClipboardListener(getContext(), mOverlayControllerProvider,
- mClipboardToast, mClipboardManager, mKeyguardManager, mUiEventLogger);
+ mClipboardListener = new ClipboardListener(
+ getContext(),
+ mOverlayControllerProvider,
+ mClipboardToast,
+ user -> {
+ if (UserHandle.CURRENT.equals(user)) {
+ return mClipboardManager;
+ }
+ return null;
+ },
+ mKeyguardManager,
+ mUiEventLogger);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
index 823a23d..d32d8cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
@@ -82,6 +82,7 @@
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.After
@@ -200,7 +201,7 @@
Settings.Secure.getInt(
context.contentResolver,
Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- 1
+ 1,
)
private lateinit var staticMockSession: MockitoSession
@@ -221,9 +222,8 @@
Settings.Secure.putInt(
context.contentResolver,
Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- 1
+ 1,
)
-
mediaDataManager =
LegacyMediaDataManagerImpl(
context = context,
@@ -334,7 +334,7 @@
Settings.Secure.putInt(
context.contentResolver,
Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- originalSmartspaceSetting
+ originalSmartspaceSetting,
)
}
@@ -365,7 +365,7 @@
session.sessionToken,
APP_NAME,
pendingIntent,
- PACKAGE_NAME
+ PACKAGE_NAME,
)
runCurrent()
@@ -378,7 +378,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
mediaDataManager.setInactive(PACKAGE_NAME, timedOut = true)
@@ -404,7 +404,7 @@
metadataBuilder
.putLong(
MediaConstants.METADATA_KEY_IS_EXPLICIT,
- MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
+ MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT,
)
.build()
)
@@ -420,7 +420,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value!!.isExplicit).isTrue()
}
@@ -438,7 +438,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value!!.isExplicit).isFalse()
}
@@ -451,7 +451,7 @@
anyInt(),
eq(PACKAGE_NAME),
eq(mediaDataCaptor.value.instanceId),
- eq(MediaData.PLAYBACK_LOCAL)
+ eq(MediaData.PLAYBACK_LOCAL),
)
}
@@ -467,7 +467,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value!!.active).isTrue()
}
@@ -483,7 +483,7 @@
anyInt(),
eq(SYSTEM_PACKAGE_NAME),
eq(mediaDataCaptor.value.instanceId),
- eq(MediaData.PLAYBACK_CAST_REMOTE)
+ eq(MediaData.PLAYBACK_CAST_REMOTE),
)
}
@@ -511,7 +511,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value!!.app).isEqualTo(subName)
@@ -597,7 +597,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
val placeholderTitle = context.getString(R.string.controls_media_empty_title, APP_NAME)
assertThat(mediaDataCaptor.value.song).isEqualTo(placeholderTitle)
@@ -627,7 +627,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
val placeholderTitle = context.getString(R.string.controls_media_empty_title, APP_NAME)
assertThat(mediaDataCaptor.value.song).isEqualTo(placeholderTitle)
@@ -668,7 +668,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.song).isEqualTo(SESSION_TITLE)
}
@@ -683,7 +683,7 @@
mediaDataManager.onMediaDataLoaded(
KEY,
null,
- data.copy(song = SESSION_EMPTY_TITLE, resumeAction = Runnable {})
+ data.copy(song = SESSION_EMPTY_TITLE, resumeAction = Runnable {}),
)
// WHEN the notification is removed
@@ -698,7 +698,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
verify(logger, never())
.logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(instanceId))
@@ -716,7 +716,7 @@
mediaDataManager.onMediaDataLoaded(
KEY,
null,
- data.copy(song = SESSION_BLANK_TITLE, resumeAction = Runnable {})
+ data.copy(song = SESSION_BLANK_TITLE, resumeAction = Runnable {}),
)
// WHEN the notification is removed
@@ -731,7 +731,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
verify(logger, never())
.logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(instanceId))
@@ -756,7 +756,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
assertThat(mediaDataCaptor.value.isPlaying).isFalse()
@@ -777,7 +777,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
val data = mediaDataCaptor.value
assertThat(data.resumption).isFalse()
@@ -789,7 +789,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
val data2 = mediaDataCaptor.value
assertThat(data2.resumption).isFalse()
@@ -807,7 +807,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
verify(listener, never()).onMediaDataRemoved(eq(KEY), eq(false))
@@ -821,7 +821,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
verify(listener).onMediaDataRemoved(eq(KEY_2), eq(false))
@@ -842,7 +842,7 @@
anyInt(),
eq(PACKAGE_NAME),
eq(mediaDataCaptor.value.instanceId),
- eq(MediaData.PLAYBACK_CAST_LOCAL)
+ eq(MediaData.PLAYBACK_CAST_LOCAL),
)
// WHEN the notification is removed
@@ -878,7 +878,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
}
@@ -932,7 +932,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
assertThat(mediaDataCaptor.value.isPlaying).isFalse()
@@ -982,7 +982,7 @@
// WHEN resumption controls are added with explicit indicator
bundle.putLong(
MediaConstants.METADATA_KEY_IS_EXPLICIT,
- MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
+ MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT,
)
val desc =
MediaDescription.Builder().run {
@@ -1015,7 +1015,7 @@
Bundle().apply {
putInt(
MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
- MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED,
)
putDouble(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, progress)
}
@@ -1041,7 +1041,7 @@
Bundle().apply {
putInt(
MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
- MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED,
)
}
val desc =
@@ -1066,7 +1066,7 @@
Bundle().apply {
putInt(
MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
- MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED,
)
}
val desc =
@@ -1118,7 +1118,7 @@
session.sessionToken,
APP_NAME,
pendingIntent,
- PACKAGE_NAME
+ PACKAGE_NAME,
)
// Resumption controls are not added.
@@ -1130,7 +1130,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
}
@@ -1151,7 +1151,7 @@
session.sessionToken,
APP_NAME,
pendingIntent,
- PACKAGE_NAME
+ PACKAGE_NAME,
)
// Resumption controls are not added.
@@ -1163,7 +1163,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
}
@@ -1230,7 +1230,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
}
@@ -1256,7 +1256,7 @@
expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
)
),
- eq(false)
+ eq(false),
)
}
@@ -1280,7 +1280,7 @@
expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
)
),
- eq(false)
+ eq(false),
)
}
@@ -1312,7 +1312,7 @@
expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
)
),
- eq(false)
+ eq(false),
)
}
@@ -1359,7 +1359,7 @@
expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
)
),
- eq(false)
+ eq(false),
)
}
@@ -1393,7 +1393,7 @@
expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
)
),
- eq(false)
+ eq(false),
)
}
@@ -1424,7 +1424,7 @@
expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
)
),
- eq(false)
+ eq(false),
)
verify(listener, never()).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false))
}
@@ -1456,7 +1456,7 @@
expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
)
),
- eq(false)
+ eq(false),
)
}
@@ -1466,7 +1466,7 @@
Settings.Secure.putInt(
context.contentResolver,
Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- 0
+ 0,
)
tunableCaptor.value.onTuningChanged(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, "0")
@@ -1488,7 +1488,7 @@
Settings.Secure.putInt(
context.contentResolver,
Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- 0
+ 0,
)
tunableCaptor.value.onTuningChanged(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, "0")
@@ -1526,7 +1526,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.lastActive).isAtLeast(currentTime)
}
@@ -1553,7 +1553,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
assertThat(mediaDataCaptor.value.lastActive).isAtLeast(currentTime)
@@ -1573,7 +1573,7 @@
mediaDataManager.onMediaDataLoaded(
KEY,
null,
- data.copy(resumeAction = Runnable {}, active = false)
+ data.copy(resumeAction = Runnable {}, active = false),
)
// WHEN the notification is removed
@@ -1589,7 +1589,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
assertThat(mediaDataCaptor.value.lastActive).isLessThan(currentTime)
@@ -1629,7 +1629,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.actionsToShowInCompact.size)
.isEqualTo(LegacyMediaDataManagerImpl.MAX_COMPACT_ACTIONS)
@@ -1664,7 +1664,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.actions.size)
.isEqualTo(LegacyMediaDataManagerImpl.MAX_NOTIFICATION_ACTIONS)
@@ -1695,7 +1695,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value!!.semanticActions).isNull()
@@ -1868,7 +1868,7 @@
anyInt(),
eq(PACKAGE_NAME),
eq(instanceId),
- eq(MediaData.PLAYBACK_CAST_LOCAL)
+ eq(MediaData.PLAYBACK_CAST_LOCAL),
)
// update to remote cast
@@ -1879,7 +1879,7 @@
anyInt(),
eq(SYSTEM_PACKAGE_NAME),
eq(instanceId),
- eq(MediaData.PLAYBACK_CAST_REMOTE)
+ eq(MediaData.PLAYBACK_CAST_REMOTE),
)
}
@@ -1900,7 +1900,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.isPlaying).isTrue()
}
@@ -1948,7 +1948,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.isPlaying).isFalse()
assertThat(mediaDataCaptor.value.semanticActions).isNotNull()
@@ -1977,7 +1977,7 @@
session.sessionToken,
APP_NAME,
pendingIntent,
- PACKAGE_NAME
+ PACKAGE_NAME,
)
runCurrent()
backgroundExecutor.runAllReady()
@@ -1992,7 +1992,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.isPlaying).isFalse()
assertThat(mediaDataCaptor.value.semanticActions).isNotNull()
@@ -2017,7 +2017,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.isPlaying).isFalse()
assertThat(mediaDataCaptor.value.semanticActions).isNull()
@@ -2074,7 +2074,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
assertThat(mediaDataCaptor.value.active).isFalse()
@@ -2082,7 +2082,7 @@
.logActiveConvertedToResume(
anyInt(),
eq(PACKAGE_NAME),
- eq(mediaDataCaptor.value.instanceId)
+ eq(mediaDataCaptor.value.instanceId),
)
}
@@ -2141,7 +2141,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
assertThat(mediaDataCaptor.value.active).isFalse()
@@ -2149,7 +2149,7 @@
.logActiveConvertedToResume(
anyInt(),
eq(PACKAGE_NAME),
- eq(mediaDataCaptor.value.instanceId)
+ eq(mediaDataCaptor.value.instanceId),
)
}
@@ -2193,7 +2193,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
assertThat(mediaDataCaptor.value.active).isFalse()
@@ -2201,7 +2201,7 @@
.logActiveConvertedToResume(
anyInt(),
eq(PACKAGE_NAME),
- eq(mediaDataCaptor.value.instanceId)
+ eq(mediaDataCaptor.value.instanceId),
)
}
@@ -2245,7 +2245,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
assertThat(mediaDataCaptor.value.active).isFalse()
@@ -2253,7 +2253,7 @@
.logActiveConvertedToResume(
anyInt(),
eq(PACKAGE_NAME),
- eq(mediaDataCaptor.value.instanceId)
+ eq(mediaDataCaptor.value.instanceId),
)
}
@@ -2279,7 +2279,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
}
@@ -2321,7 +2321,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
assertThat(mediaDataCaptor.value.active).isFalse()
@@ -2329,7 +2329,7 @@
.logActiveConvertedToResume(
anyInt(),
eq(PACKAGE_NAME),
- eq(mediaDataCaptor.value.instanceId)
+ eq(mediaDataCaptor.value.instanceId),
)
}
@@ -2355,7 +2355,7 @@
any(),
any(),
anyInt(),
- anyInt()
+ anyInt(),
)
)
.thenReturn(1)
@@ -2385,7 +2385,7 @@
any(),
any(),
anyInt(),
- anyInt()
+ anyInt(),
)
)
.thenThrow(SecurityException("Test no permission"))
@@ -2421,7 +2421,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
verify(kosmos.mediaLogger).logDuplicateMediaNotification(eq(KEY))
}
@@ -2440,7 +2440,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
verify(kosmos.mediaLogger, never()).logDuplicateMediaNotification(eq(KEY))
}
@@ -2448,6 +2448,7 @@
private fun TestScope.assertRunAllReady(foreground: Int = 0, background: Int = 0) {
runCurrent()
if (Flags.mediaLoadMetadataViaMediaDataLoader()) {
+ advanceUntilIdle()
// It doesn't make much sense to count tasks when we use coroutines in loader
// so this check is skipped in that scenario.
backgroundExecutor.runAllReady()
@@ -2478,7 +2479,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
}
@@ -2493,7 +2494,7 @@
/** Helper function to add a resumption control and capture the resulting MediaData */
private fun addResumeControlAndLoad(
desc: MediaDescription,
- packageName: String = PACKAGE_NAME
+ packageName: String = PACKAGE_NAME,
) {
mediaDataManager.addResumptionControls(
USER_ID,
@@ -2502,7 +2503,7 @@
session.sessionToken,
APP_NAME,
pendingIntent,
- packageName
+ packageName,
)
testScope.assertRunAllReady(foreground = 1, background = 1)
@@ -2514,7 +2515,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
index 4cf7de3..90af932 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
@@ -90,6 +90,7 @@
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runCurrent
import org.junit.After
import org.junit.Before
@@ -212,7 +213,7 @@
Settings.Secure.getInt(
context.contentResolver,
Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- 1
+ 1,
)
private lateinit var staticMockSession: MockitoSession
@@ -233,7 +234,7 @@
Settings.Secure.putInt(
context.contentResolver,
Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- 1
+ 1,
)
mediaDataProcessor =
MediaDataProcessor(
@@ -274,7 +275,7 @@
mediaDataCombineLatest = mediaDataCombineLatest,
mediaDataFilter = mediaDataFilter,
mediaFilterRepository = mediaFilterRepository,
- mediaFlags = kosmos.mediaFlags
+ mediaFlags = kosmos.mediaFlags,
)
mediaCarouselInteractor.start()
verify(mediaTimeoutListener).stateCallback = capture(stateCallbackCaptor)
@@ -356,7 +357,7 @@
Settings.Secure.putInt(
context.contentResolver,
Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- originalSmartspaceSetting
+ originalSmartspaceSetting,
)
}
@@ -386,7 +387,7 @@
session.sessionToken,
APP_NAME,
pendingIntent,
- PACKAGE_NAME
+ PACKAGE_NAME,
)
testScope.runCurrent()
@@ -399,7 +400,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
mediaDataProcessor.setInactive(PACKAGE_NAME, timedOut = true)
@@ -425,7 +426,7 @@
metadataBuilder
.putLong(
MediaConstants.METADATA_KEY_IS_EXPLICIT,
- MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
+ MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT,
)
.build()
)
@@ -440,7 +441,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value!!.isExplicit).isTrue()
}
@@ -457,7 +458,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value!!.isExplicit).isFalse()
}
@@ -470,7 +471,7 @@
anyInt(),
eq(PACKAGE_NAME),
eq(mediaDataCaptor.value.instanceId),
- eq(MediaData.PLAYBACK_LOCAL)
+ eq(MediaData.PLAYBACK_LOCAL),
)
}
@@ -485,7 +486,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value!!.active).isTrue()
}
@@ -501,7 +502,7 @@
anyInt(),
eq(SYSTEM_PACKAGE_NAME),
eq(mediaDataCaptor.value.instanceId),
- eq(MediaData.PLAYBACK_CAST_REMOTE)
+ eq(MediaData.PLAYBACK_CAST_REMOTE),
)
}
@@ -529,7 +530,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value!!.app).isEqualTo(subName)
@@ -615,7 +616,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
val placeholderTitle = context.getString(R.string.controls_media_empty_title, APP_NAME)
assertThat(mediaDataCaptor.value.song).isEqualTo(placeholderTitle)
@@ -645,7 +646,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
val placeholderTitle = context.getString(R.string.controls_media_empty_title, APP_NAME)
assertThat(mediaDataCaptor.value.song).isEqualTo(placeholderTitle)
@@ -686,7 +687,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.song).isEqualTo(SESSION_TITLE)
}
@@ -701,7 +702,7 @@
mediaDataProcessor.onMediaDataLoaded(
KEY,
null,
- data.copy(song = SESSION_EMPTY_TITLE, resumeAction = Runnable {})
+ data.copy(song = SESSION_EMPTY_TITLE, resumeAction = Runnable {}),
)
// WHEN the notification is removed
@@ -716,7 +717,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
verify(logger, never())
.logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(instanceId))
@@ -734,7 +735,7 @@
mediaDataProcessor.onMediaDataLoaded(
KEY,
null,
- data.copy(song = SESSION_BLANK_TITLE, resumeAction = Runnable {})
+ data.copy(song = SESSION_BLANK_TITLE, resumeAction = Runnable {}),
)
// WHEN the notification is removed
@@ -749,7 +750,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
verify(logger, never())
.logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(instanceId))
@@ -774,7 +775,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
assertThat(mediaDataCaptor.value.isPlaying).isFalse()
@@ -795,7 +796,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
val data = mediaDataCaptor.value
assertThat(data.resumption).isFalse()
@@ -807,7 +808,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
val data2 = mediaDataCaptor.value
assertThat(data2.resumption).isFalse()
@@ -825,7 +826,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean())
@@ -839,7 +840,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
verify(listener).onMediaDataRemoved(eq(KEY_2), eq(false))
@@ -860,7 +861,7 @@
anyInt(),
eq(PACKAGE_NAME),
eq(mediaDataCaptor.value.instanceId),
- eq(MediaData.PLAYBACK_CAST_LOCAL)
+ eq(MediaData.PLAYBACK_CAST_LOCAL),
)
// WHEN the notification is removed
@@ -896,7 +897,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
}
@@ -950,7 +951,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
assertThat(mediaDataCaptor.value.isPlaying).isFalse()
@@ -1000,7 +1001,7 @@
// WHEN resumption controls are added with explicit indicator
bundle.putLong(
MediaConstants.METADATA_KEY_IS_EXPLICIT,
- MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
+ MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT,
)
val desc =
MediaDescription.Builder().run {
@@ -1033,7 +1034,7 @@
Bundle().apply {
putInt(
MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
- MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED,
)
putDouble(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, progress)
}
@@ -1059,7 +1060,7 @@
Bundle().apply {
putInt(
MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
- MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED,
)
}
val desc =
@@ -1084,7 +1085,7 @@
Bundle().apply {
putInt(
MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
- MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED,
)
}
val desc =
@@ -1136,7 +1137,7 @@
session.sessionToken,
APP_NAME,
pendingIntent,
- PACKAGE_NAME
+ PACKAGE_NAME,
)
// Resumption controls are not added.
@@ -1148,7 +1149,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
}
@@ -1169,7 +1170,7 @@
session.sessionToken,
APP_NAME,
pendingIntent,
- PACKAGE_NAME
+ PACKAGE_NAME,
)
// Resumption controls are not added.
@@ -1181,7 +1182,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
}
@@ -1248,7 +1249,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
}
@@ -1274,7 +1275,7 @@
expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
)
),
- eq(false)
+ eq(false),
)
}
@@ -1298,7 +1299,7 @@
expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
)
),
- eq(false)
+ eq(false),
)
}
@@ -1330,7 +1331,7 @@
expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
)
),
- eq(false)
+ eq(false),
)
}
@@ -1377,7 +1378,7 @@
expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
)
),
- eq(false)
+ eq(false),
)
}
@@ -1411,7 +1412,7 @@
expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
)
),
- eq(false)
+ eq(false),
)
}
@@ -1442,7 +1443,7 @@
expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
)
),
- eq(false)
+ eq(false),
)
verify(listener, never()).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false))
}
@@ -1474,7 +1475,7 @@
expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
)
),
- eq(false)
+ eq(false),
)
}
@@ -1536,7 +1537,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.lastActive).isAtLeast(currentTime)
}
@@ -1563,7 +1564,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
assertThat(mediaDataCaptor.value.lastActive).isAtLeast(currentTime)
@@ -1583,7 +1584,7 @@
mediaDataProcessor.onMediaDataLoaded(
KEY,
null,
- data.copy(resumeAction = Runnable {}, active = false)
+ data.copy(resumeAction = Runnable {}, active = false),
)
// WHEN the notification is removed
@@ -1599,7 +1600,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
assertThat(mediaDataCaptor.value.lastActive).isLessThan(currentTime)
@@ -1639,7 +1640,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.actionsToShowInCompact.size)
.isEqualTo(MediaDataProcessor.MAX_COMPACT_ACTIONS)
@@ -1674,7 +1675,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.actions.size)
.isEqualTo(MediaDataProcessor.MAX_NOTIFICATION_ACTIONS)
@@ -1705,7 +1706,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value!!.semanticActions).isNull()
@@ -1944,7 +1945,7 @@
anyInt(),
eq(PACKAGE_NAME),
eq(instanceId),
- eq(MediaData.PLAYBACK_CAST_LOCAL)
+ eq(MediaData.PLAYBACK_CAST_LOCAL),
)
// update to remote cast
@@ -1955,7 +1956,7 @@
anyInt(),
eq(SYSTEM_PACKAGE_NAME),
eq(instanceId),
- eq(MediaData.PLAYBACK_CAST_REMOTE)
+ eq(MediaData.PLAYBACK_CAST_REMOTE),
)
}
@@ -1976,7 +1977,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.isPlaying).isTrue()
}
@@ -2024,7 +2025,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.isPlaying).isFalse()
assertThat(mediaDataCaptor.value.semanticActions).isNotNull()
@@ -2052,7 +2053,7 @@
session.sessionToken,
APP_NAME,
pendingIntent,
- PACKAGE_NAME
+ PACKAGE_NAME,
)
testScope.runCurrent()
backgroundExecutor.runAllReady()
@@ -2067,7 +2068,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.isPlaying).isFalse()
assertThat(mediaDataCaptor.value.semanticActions).isNotNull()
@@ -2092,7 +2093,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.isPlaying).isFalse()
assertThat(mediaDataCaptor.value.semanticActions).isNull()
@@ -2149,7 +2150,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
assertThat(mediaDataCaptor.value.active).isFalse()
@@ -2157,7 +2158,7 @@
.logActiveConvertedToResume(
anyInt(),
eq(PACKAGE_NAME),
- eq(mediaDataCaptor.value.instanceId)
+ eq(mediaDataCaptor.value.instanceId),
)
}
@@ -2216,7 +2217,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
assertThat(mediaDataCaptor.value.active).isFalse()
@@ -2224,7 +2225,7 @@
.logActiveConvertedToResume(
anyInt(),
eq(PACKAGE_NAME),
- eq(mediaDataCaptor.value.instanceId)
+ eq(mediaDataCaptor.value.instanceId),
)
}
@@ -2268,7 +2269,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
assertThat(mediaDataCaptor.value.active).isFalse()
@@ -2276,7 +2277,7 @@
.logActiveConvertedToResume(
anyInt(),
eq(PACKAGE_NAME),
- eq(mediaDataCaptor.value.instanceId)
+ eq(mediaDataCaptor.value.instanceId),
)
}
@@ -2320,7 +2321,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
assertThat(mediaDataCaptor.value.active).isFalse()
@@ -2328,7 +2329,7 @@
.logActiveConvertedToResume(
anyInt(),
eq(PACKAGE_NAME),
- eq(mediaDataCaptor.value.instanceId)
+ eq(mediaDataCaptor.value.instanceId),
)
}
@@ -2354,7 +2355,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
}
@@ -2396,7 +2397,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
assertThat(mediaDataCaptor.value.active).isFalse()
@@ -2404,7 +2405,7 @@
.logActiveConvertedToResume(
anyInt(),
eq(PACKAGE_NAME),
- eq(mediaDataCaptor.value.instanceId)
+ eq(mediaDataCaptor.value.instanceId),
)
}
@@ -2430,7 +2431,7 @@
any(),
any(),
anyInt(),
- anyInt()
+ anyInt(),
)
)
.thenReturn(1)
@@ -2460,7 +2461,7 @@
any(),
any(),
anyInt(),
- anyInt()
+ anyInt(),
)
)
.thenThrow(SecurityException("Test no permission"))
@@ -2501,7 +2502,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
verify(kosmos.mediaLogger).logDuplicateMediaNotification(eq(KEY))
}
@@ -2525,7 +2526,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
verify(kosmos.mediaLogger, never()).logDuplicateMediaNotification(eq(KEY))
}
@@ -2533,6 +2534,7 @@
private fun TestScope.assertRunAllReady(foreground: Int = 0, background: Int = 0) {
runCurrent()
if (Flags.mediaLoadMetadataViaMediaDataLoader()) {
+ advanceUntilIdle()
// It doesn't make much sense to count tasks when we use coroutines in loader
// so this check is skipped in that scenario.
backgroundExecutor.runAllReady()
@@ -2563,7 +2565,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
}
@@ -2578,7 +2580,7 @@
/** Helper function to add a resumption control and capture the resulting MediaData */
private fun addResumeControlAndLoad(
desc: MediaDescription,
- packageName: String = PACKAGE_NAME
+ packageName: String = PACKAGE_NAME,
) {
mediaDataProcessor.addResumptionControls(
USER_ID,
@@ -2587,7 +2589,7 @@
session.sessionToken,
APP_NAME,
pendingIntent,
- packageName
+ packageName,
)
testScope.assertRunAllReady(foreground = 1, background = 1)
@@ -2598,7 +2600,7 @@
capture(mediaDataCaptor),
eq(true),
eq(0),
- eq(false)
+ eq(false),
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 411ff91..8731853 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -77,7 +77,8 @@
private static final int TEST_CURRENT_VOLUME = 10;
// Mock
- private MediaOutputController mMediaOutputController = mock(MediaOutputController.class);
+ private MediaSwitchingController mMediaSwitchingController =
+ mock(MediaSwitchingController.class);
private MediaOutputDialog mMediaOutputDialog = mock(MediaOutputDialog.class);
private MediaDevice mMediaDevice1 = mock(MediaDevice.class);
private MediaDevice mMediaDevice2 = mock(MediaDevice.class);
@@ -95,13 +96,13 @@
@Before
public void setUp() {
- when(mMediaOutputController.getMediaItemList()).thenReturn(mMediaItems);
- when(mMediaOutputController.hasAdjustVolumeUserRestriction()).thenReturn(false);
- when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(false);
- when(mMediaOutputController.getDeviceIconCompat(mMediaDevice1)).thenReturn(mIconCompat);
- when(mMediaOutputController.getDeviceIconCompat(mMediaDevice2)).thenReturn(mIconCompat);
- when(mMediaOutputController.getCurrentConnectedMediaDevice()).thenReturn(mMediaDevice1);
- when(mMediaOutputController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(true);
+ when(mMediaSwitchingController.getMediaItemList()).thenReturn(mMediaItems);
+ when(mMediaSwitchingController.hasAdjustVolumeUserRestriction()).thenReturn(false);
+ when(mMediaSwitchingController.isAnyDeviceTransferring()).thenReturn(false);
+ when(mMediaSwitchingController.getDeviceIconCompat(mMediaDevice1)).thenReturn(mIconCompat);
+ when(mMediaSwitchingController.getDeviceIconCompat(mMediaDevice2)).thenReturn(mIconCompat);
+ when(mMediaSwitchingController.getCurrentConnectedMediaDevice()).thenReturn(mMediaDevice1);
+ when(mMediaSwitchingController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(true);
when(mIconCompat.toIcon(mContext)).thenReturn(mIcon);
when(mMediaDevice1.getName()).thenReturn(TEST_DEVICE_NAME_1);
when(mMediaDevice1.getId()).thenReturn(TEST_DEVICE_ID_1);
@@ -116,7 +117,7 @@
mMediaItems.add(MediaItem.createDeviceMediaItem(mMediaDevice1));
mMediaItems.add(MediaItem.createDeviceMediaItem(mMediaDevice2));
- mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
+ mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController);
mMediaOutputAdapter.updateItems();
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
@@ -142,7 +143,7 @@
@Test
public void onBindViewHolder_bindPairNew_verifyView() {
- mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
+ mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController);
mMediaOutputAdapter.updateItems();
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
@@ -161,11 +162,13 @@
@Test
public void onBindViewHolder_bindGroup_withSessionName_verifyView() {
- when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(
- mMediaItems.stream().map((item) -> item.getMediaDevice().get()).collect(
- Collectors.toList()));
- when(mMediaOutputController.getSessionName()).thenReturn(TEST_SESSION_NAME);
- mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
+ when(mMediaSwitchingController.getSelectedMediaDevice())
+ .thenReturn(
+ mMediaItems.stream()
+ .map((item) -> item.getMediaDevice().get())
+ .collect(Collectors.toList()));
+ when(mMediaSwitchingController.getSessionName()).thenReturn(TEST_SESSION_NAME);
+ mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController);
mMediaOutputAdapter.updateItems();
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
@@ -181,11 +184,13 @@
@Test
public void onBindViewHolder_bindGroup_noSessionName_verifyView() {
- when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(
- mMediaItems.stream().map((item) -> item.getMediaDevice().get()).collect(
- Collectors.toList()));
- when(mMediaOutputController.getSessionName()).thenReturn(null);
- mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
+ when(mMediaSwitchingController.getSelectedMediaDevice())
+ .thenReturn(
+ mMediaItems.stream()
+ .map((item) -> item.getMediaDevice().get())
+ .collect(Collectors.toList()));
+ when(mMediaSwitchingController.getSessionName()).thenReturn(null);
+ mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController);
mMediaOutputAdapter.updateItems();
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
@@ -214,7 +219,7 @@
@Test
public void onBindViewHolder_bindNonRemoteConnectedDevice_verifyView() {
- when(mMediaOutputController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(false);
+ when(mMediaSwitchingController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(false);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -230,9 +235,9 @@
@Test
public void onBindViewHolder_bindConnectedRemoteDevice_verifyView() {
- when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(
- ImmutableList.of(mMediaDevice2));
- when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true);
+ when(mMediaSwitchingController.getSelectableMediaDevice())
+ .thenReturn(ImmutableList.of(mMediaDevice2));
+ when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(true);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -249,9 +254,9 @@
@Test
public void onBindViewHolder_bindConnectedRemoteDevice_verifyContentDescriptionNotNull() {
- when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(
- ImmutableList.of(mMediaDevice2));
- when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true);
+ when(mMediaSwitchingController.getSelectableMediaDevice())
+ .thenReturn(ImmutableList.of(mMediaDevice2));
+ when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(true);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -263,9 +268,8 @@
@Test
public void onBindViewHolder_bindSingleConnectedRemoteDevice_verifyView() {
- when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(
- ImmutableList.of());
- when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true);
+ when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(ImmutableList.of());
+ when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(true);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -283,9 +287,8 @@
@Test
public void onBindViewHolder_bindConnectedRemoteDeviceWithOnGoingSession_verifyView() {
when(mMediaDevice1.hasOngoingSession()).thenReturn(true);
- when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(
- ImmutableList.of());
- when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true);
+ when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(ImmutableList.of());
+ when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(true);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -305,9 +308,8 @@
public void onBindViewHolder_bindConnectedRemoteDeviceWithHostOnGoingSession_verifyView() {
when(mMediaDevice1.hasOngoingSession()).thenReturn(true);
when(mMediaDevice1.isHostForOngoingSession()).thenReturn(true);
- when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(
- ImmutableList.of());
- when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true);
+ when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(ImmutableList.of());
+ when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(true);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -326,8 +328,8 @@
@Test
public void onBindViewHolder_bindConnectedDeviceWithMutingExpectedDeviceExist_verifyView() {
- when(mMediaOutputController.hasMutingExpectedDevice()).thenReturn(true);
- when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(false);
+ when(mMediaSwitchingController.hasMutingExpectedDevice()).thenReturn(true);
+ when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(false);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
@@ -340,8 +342,8 @@
@Test
public void onBindViewHolder_isMutingExpectedDevice_verifyView() {
when(mMediaDevice1.isMutingExpectedDevice()).thenReturn(true);
- when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(false);
- when(mMediaOutputController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(false);
+ when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(false);
+ when(mMediaSwitchingController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(false);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -378,14 +380,14 @@
mOnSeekBarChangeListenerCaptor.getValue().onStopTrackingTouch(mViewHolder.mSeekBar);
assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
- verify(mMediaOutputController).logInteractionAdjustVolume(mMediaDevice1);
+ verify(mMediaSwitchingController).logInteractionAdjustVolume(mMediaDevice1);
}
@Test
public void onBindViewHolder_bindSelectableDevice_verifyView() {
List<MediaDevice> selectableDevices = new ArrayList<>();
selectableDevices.add(mMediaDevice2);
- when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(selectableDevices);
+ when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(selectableDevices);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
@@ -440,7 +442,7 @@
@Test
public void subStatusSupported_onBindViewHolder_bindHostDeviceWithOngoingSession_verifyView() {
- when(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(true);
+ when(mMediaSwitchingController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(true);
when(mMediaDevice1.isHostForOngoingSession()).thenReturn(true);
when(mMediaDevice1.hasSubtext()).thenReturn(true);
when(mMediaDevice1.getSubtext()).thenReturn(SUBTEXT_CUSTOM);
@@ -540,7 +542,7 @@
@Test
public void onBindViewHolder_inTransferring_bindTransferringDevice_verifyView() {
- when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(true);
+ when(mMediaSwitchingController.isAnyDeviceTransferring()).thenReturn(true);
when(mMediaDevice1.getState()).thenReturn(
LocalMediaManager.MediaDeviceState.STATE_CONNECTING);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -556,7 +558,7 @@
@Test
public void onBindViewHolder_bindGroupingDevice_verifyView() {
- when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(false);
+ when(mMediaSwitchingController.isAnyDeviceTransferring()).thenReturn(false);
when(mMediaDevice1.getState()).thenReturn(
LocalMediaManager.MediaDeviceState.STATE_GROUPING);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -572,7 +574,7 @@
@Test
public void onBindViewHolder_inTransferring_bindNonTransferringDevice_verifyView() {
- when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(true);
+ when(mMediaSwitchingController.isAnyDeviceTransferring()).thenReturn(true);
when(mMediaDevice2.getState()).thenReturn(
LocalMediaManager.MediaDeviceState.STATE_CONNECTING);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -586,7 +588,7 @@
@Test
public void onItemClick_clickPairNew_verifyLaunchBluetoothPairing() {
- mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
+ mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController);
mMediaOutputAdapter.updateItems();
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
@@ -595,16 +597,16 @@
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 2);
mViewHolder.mContainerLayout.performClick();
- verify(mMediaOutputController).launchBluetoothPairing(mViewHolder.mContainerLayout);
+ verify(mMediaSwitchingController).launchBluetoothPairing(mViewHolder.mContainerLayout);
}
@Test
public void onItemClick_clickDevice_verifyConnectDevice() {
- when(mMediaOutputController.isCurrentOutputDeviceHasSessionOngoing()).thenReturn(false);
+ when(mMediaSwitchingController.isCurrentOutputDeviceHasSessionOngoing()).thenReturn(false);
assertThat(mMediaDevice2.getState()).isEqualTo(
LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED);
when(mMediaDevice2.getSelectionBehavior()).thenReturn(SELECTION_BEHAVIOR_TRANSFER);
- mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
+ mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController);
mMediaOutputAdapter.updateItems();
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
@@ -613,16 +615,16 @@
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
mViewHolder.mContainerLayout.performClick();
- verify(mMediaOutputController).connectDevice(mMediaDevice2);
+ verify(mMediaSwitchingController).connectDevice(mMediaDevice2);
}
@Test
public void onItemClick_clickDeviceWithSessionOngoing_verifyShowsDialog() {
- when(mMediaOutputController.isCurrentOutputDeviceHasSessionOngoing()).thenReturn(true);
+ when(mMediaSwitchingController.isCurrentOutputDeviceHasSessionOngoing()).thenReturn(true);
assertThat(mMediaDevice2.getState()).isEqualTo(
LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED);
when(mMediaDevice2.getSelectionBehavior()).thenReturn(SELECTION_BEHAVIOR_TRANSFER);
- mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
+ mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController);
mMediaOutputAdapter.updateItems();
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
@@ -633,66 +635,68 @@
mMediaOutputAdapter.onBindViewHolder(spyMediaDeviceViewHolder, 1);
spyMediaDeviceViewHolder.mContainerLayout.performClick();
- verify(mMediaOutputController, never()).connectDevice(mMediaDevice2);
+ verify(mMediaSwitchingController, never()).connectDevice(mMediaDevice2);
verify(spyMediaDeviceViewHolder).showCustomEndSessionDialog(mMediaDevice2);
}
@Test
public void onItemClick_clicksWithMutingExpectedDeviceExist_cancelsMuteAwaitConnection() {
- when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(false);
- when(mMediaOutputController.hasMutingExpectedDevice()).thenReturn(true);
- when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(false);
+ when(mMediaSwitchingController.isAnyDeviceTransferring()).thenReturn(false);
+ when(mMediaSwitchingController.hasMutingExpectedDevice()).thenReturn(true);
+ when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(false);
when(mMediaDevice1.isMutingExpectedDevice()).thenReturn(false);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
mViewHolder.mContainerLayout.performClick();
- verify(mMediaOutputController).cancelMuteAwaitConnection();
+ verify(mMediaSwitchingController).cancelMuteAwaitConnection();
}
@Test
public void onGroupActionTriggered_clicksEndAreaOfSelectableDevice_triggerGrouping() {
List<MediaDevice> selectableDevices = new ArrayList<>();
selectableDevices.add(mMediaDevice2);
- when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(selectableDevices);
+ when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(selectableDevices);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
mViewHolder.mEndTouchArea.performClick();
- verify(mMediaOutputController).addDeviceToPlayMedia(mMediaDevice2);
+ verify(mMediaSwitchingController).addDeviceToPlayMedia(mMediaDevice2);
}
@Test
public void onGroupActionTriggered_clickSelectedRemoteDevice_triggerUngrouping() {
- when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(
- ImmutableList.of(mMediaDevice2));
- when(mMediaOutputController.getDeselectableMediaDevice()).thenReturn(
- ImmutableList.of(mMediaDevice1));
- when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true);
+ when(mMediaSwitchingController.getSelectableMediaDevice())
+ .thenReturn(ImmutableList.of(mMediaDevice2));
+ when(mMediaSwitchingController.getDeselectableMediaDevice())
+ .thenReturn(ImmutableList.of(mMediaDevice1));
+ when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(true);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
mViewHolder.mEndTouchArea.performClick();
- verify(mMediaOutputController).removeDeviceFromPlayMedia(mMediaDevice1);
+ verify(mMediaSwitchingController).removeDeviceFromPlayMedia(mMediaDevice1);
}
@Test
public void onItemClick_onGroupActionTriggered_verifySeekbarDisabled() {
- when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(
- mMediaItems.stream().map((item) -> item.getMediaDevice().get()).collect(
- Collectors.toList()));
- mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
+ when(mMediaSwitchingController.getSelectedMediaDevice())
+ .thenReturn(
+ mMediaItems.stream()
+ .map((item) -> item.getMediaDevice().get())
+ .collect(Collectors.toList()));
+ mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController);
mMediaOutputAdapter.updateItems();
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
List<MediaDevice> selectableDevices = new ArrayList<>();
selectableDevices.add(mMediaDevice1);
- when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(selectableDevices);
- when(mMediaOutputController.hasAdjustVolumeUserRestriction()).thenReturn(true);
+ when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(selectableDevices);
+ when(mMediaSwitchingController.hasAdjustVolumeUserRestriction()).thenReturn(true);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
mViewHolder.mContainerLayout.performClick();
@@ -702,11 +706,11 @@
@Test
public void onBindViewHolder_volumeControlChangeToEnabled_enableSeekbarAgain() {
- when(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(false);
+ when(mMediaSwitchingController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(false);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
assertThat(mViewHolder.mSeekBar.isEnabled()).isFalse();
- when(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(true);
+ when(mMediaSwitchingController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(true);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
assertThat(mViewHolder.mSeekBar.isEnabled()).isTrue();
@@ -719,7 +723,7 @@
mMediaOutputAdapter.updateColorScheme(wallpaperColors, true);
- verify(mMediaOutputController).setCurrentColorScheme(wallpaperColors, true);
+ verify(mMediaSwitchingController).setCurrentColorScheme(wallpaperColors, true);
}
@Test
@@ -727,7 +731,7 @@
mMediaOutputAdapter.updateItems();
List<MediaItem> updatedList = new ArrayList<>();
updatedList.add(MediaItem.createPairNewDeviceMediaItem());
- when(mMediaOutputController.getMediaItemList()).thenReturn(updatedList);
+ when(mMediaSwitchingController.getMediaItemList()).thenReturn(updatedList);
assertThat(mMediaOutputAdapter.getItemCount()).isEqualTo(mMediaItems.size());
mMediaOutputAdapter.updateItems();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index c8cc6b5..47371df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -104,7 +104,7 @@
private List<MediaController> mMediaControllers = new ArrayList<>();
private MediaOutputBaseDialogImpl mMediaOutputBaseDialogImpl;
- private MediaOutputController mMediaOutputController;
+ private MediaSwitchingController mMediaSwitchingController;
private int mHeaderIconRes;
private IconCompat mIconCompat;
private CharSequence mHeaderTitle;
@@ -132,8 +132,8 @@
VolumePanelGlobalStateInteractorKosmosKt.getVolumePanelGlobalStateInteractor(
mKosmos);
- mMediaOutputController =
- new MediaOutputController(
+ mMediaSwitchingController =
+ new MediaSwitchingController(
mContext,
TEST_PACKAGE,
mContext.getUser(),
@@ -153,12 +153,13 @@
// Using a fake package will cause routing operations to fail, so we intercept
// scanning-related operations.
- mMediaOutputController.mLocalMediaManager = mock(LocalMediaManager.class);
- doNothing().when(mMediaOutputController.mLocalMediaManager).startScan();
- doNothing().when(mMediaOutputController.mLocalMediaManager).stopScan();
+ mMediaSwitchingController.mLocalMediaManager = mock(LocalMediaManager.class);
+ doNothing().when(mMediaSwitchingController.mLocalMediaManager).startScan();
+ doNothing().when(mMediaSwitchingController.mLocalMediaManager).stopScan();
- mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender,
- mMediaOutputController);
+ mMediaOutputBaseDialogImpl =
+ new MediaOutputBaseDialogImpl(
+ mContext, mBroadcastSender, mMediaSwitchingController);
mMediaOutputBaseDialogImpl.onCreate(new Bundle());
}
@@ -176,7 +177,7 @@
public void refresh_withIconCompat_iconIsVisible() {
mIconCompat = IconCompat.createWithBitmap(
Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888));
- when(mMediaOutputBaseAdapter.getController()).thenReturn(mMediaOutputController);
+ when(mMediaOutputBaseAdapter.getController()).thenReturn(mMediaSwitchingController);
mMediaOutputBaseDialogImpl.refresh();
final ImageView view = mMediaOutputBaseDialogImpl.mDialogView.requireViewById(
@@ -263,7 +264,7 @@
when(mMediaOutputBaseAdapter.isDragging()).thenReturn(true);
mMediaOutputBaseDialogImpl.refresh();
- assertThat(mMediaOutputController.isRefreshing()).isFalse();
+ assertThat(mMediaSwitchingController.isRefreshing()).isFalse();
}
@Test
@@ -335,12 +336,14 @@
class MediaOutputBaseDialogImpl extends MediaOutputBaseDialog {
- MediaOutputBaseDialogImpl(Context context, BroadcastSender broadcastSender,
- MediaOutputController mediaOutputController) {
+ MediaOutputBaseDialogImpl(
+ Context context,
+ BroadcastSender broadcastSender,
+ MediaSwitchingController mediaSwitchingController) {
super(
context,
broadcastSender,
- mediaOutputController, /* includePlaybackAndAppMetadata */
+ mediaSwitchingController, /* includePlaybackAndAppMetadata */
true);
mAdapter = mMediaOutputBaseAdapter;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
index 189a561..f0902e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
@@ -119,7 +119,7 @@
private UserTracker mUserTracker = mock(UserTracker.class);
private MediaOutputBroadcastDialog mMediaOutputBroadcastDialog;
- private MediaOutputController mMediaOutputController;
+ private MediaSwitchingController mMediaSwitchingController;
@Before
public void setUp() {
@@ -133,8 +133,8 @@
VolumePanelGlobalStateInteractorKosmosKt.getVolumePanelGlobalStateInteractor(
mKosmos);
- mMediaOutputController =
- new MediaOutputController(
+ mMediaSwitchingController =
+ new MediaSwitchingController(
mContext,
TEST_PACKAGE,
mContext.getUser(),
@@ -151,9 +151,10 @@
mFlags,
volumePanelGlobalStateInteractor,
mUserTracker);
- mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
- mMediaOutputBroadcastDialog = new MediaOutputBroadcastDialog(mContext, false,
- mBroadcastSender, mMediaOutputController);
+ mMediaSwitchingController.mLocalMediaManager = mLocalMediaManager;
+ mMediaOutputBroadcastDialog =
+ new MediaOutputBroadcastDialog(
+ mContext, false, mBroadcastSender, mMediaSwitchingController);
mMediaOutputBroadcastDialog.show();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index 90c2930..d3ecb3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -119,7 +119,7 @@
private List<MediaController> mMediaControllers = new ArrayList<>();
private MediaOutputDialog mMediaOutputDialog;
- private MediaOutputController mMediaOutputController;
+ private MediaSwitchingController mMediaSwitchingController;
private final List<String> mFeatures = new ArrayList<>();
@Override
@@ -146,8 +146,8 @@
VolumePanelGlobalStateInteractorKosmosKt.getVolumePanelGlobalStateInteractor(
mKosmos);
- mMediaOutputController =
- new MediaOutputController(
+ mMediaSwitchingController =
+ new MediaSwitchingController(
mContext,
TEST_PACKAGE,
mContext.getUser(),
@@ -164,8 +164,8 @@
mFlags,
volumePanelGlobalStateInteractor,
mUserTracker);
- mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
- mMediaOutputDialog = makeTestDialog(mMediaOutputController);
+ mMediaSwitchingController.mLocalMediaManager = mLocalMediaManager;
+ mMediaOutputDialog = makeTestDialog(mMediaSwitchingController);
mMediaOutputDialog.show();
when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice);
@@ -388,12 +388,15 @@
public void getStopButtonText_notSupportsBroadcast_returnsDefaultText() {
String stopText = mContext.getText(
R.string.media_output_dialog_button_stop_casting).toString();
- MediaOutputController mockMediaOutputController = mock(MediaOutputController.class);
- when(mockMediaOutputController.isBroadcastSupported()).thenReturn(false);
+ MediaSwitchingController mockMediaSwitchingController =
+ mock(MediaSwitchingController.class);
+ when(mockMediaSwitchingController.isBroadcastSupported()).thenReturn(false);
- withTestDialog(mockMediaOutputController, testDialog -> {
- assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText);
- });
+ withTestDialog(
+ mockMediaSwitchingController,
+ testDialog -> {
+ assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText);
+ });
}
@Test
@@ -401,28 +404,35 @@
public void getStopButtonText_supportsBroadcast_returnsBroadcastText() {
String stopText = mContext.getText(R.string.media_output_broadcast).toString();
MediaDevice mMediaDevice = mock(MediaDevice.class);
- MediaOutputController mockMediaOutputController = mock(MediaOutputController.class);
- when(mockMediaOutputController.isBroadcastSupported()).thenReturn(true);
- when(mockMediaOutputController.getCurrentConnectedMediaDevice()).thenReturn(mMediaDevice);
- when(mockMediaOutputController.isBluetoothLeDevice(any())).thenReturn(true);
- when(mockMediaOutputController.isPlaying()).thenReturn(true);
- when(mockMediaOutputController.isBluetoothLeBroadcastEnabled()).thenReturn(false);
- withTestDialog(mockMediaOutputController, testDialog -> {
- assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText);
- });
+ MediaSwitchingController mockMediaSwitchingController =
+ mock(MediaSwitchingController.class);
+ when(mockMediaSwitchingController.isBroadcastSupported()).thenReturn(true);
+ when(mockMediaSwitchingController.getCurrentConnectedMediaDevice())
+ .thenReturn(mMediaDevice);
+ when(mockMediaSwitchingController.isBluetoothLeDevice(any())).thenReturn(true);
+ when(mockMediaSwitchingController.isPlaying()).thenReturn(true);
+ when(mockMediaSwitchingController.isBluetoothLeBroadcastEnabled()).thenReturn(false);
+ withTestDialog(
+ mockMediaSwitchingController,
+ testDialog -> {
+ assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText);
+ });
}
@Test
public void onStopButtonClick_notPlaying_releaseSession() {
- MediaOutputController mockMediaOutputController = mock(MediaOutputController.class);
- when(mockMediaOutputController.isBroadcastSupported()).thenReturn(false);
- when(mockMediaOutputController.getCurrentConnectedMediaDevice()).thenReturn(null);
- when(mockMediaOutputController.isPlaying()).thenReturn(false);
- withTestDialog(mockMediaOutputController, testDialog -> {
- testDialog.onStopButtonClick();
- });
+ MediaSwitchingController mockMediaSwitchingController =
+ mock(MediaSwitchingController.class);
+ when(mockMediaSwitchingController.isBroadcastSupported()).thenReturn(false);
+ when(mockMediaSwitchingController.getCurrentConnectedMediaDevice()).thenReturn(null);
+ when(mockMediaSwitchingController.isPlaying()).thenReturn(false);
+ withTestDialog(
+ mockMediaSwitchingController,
+ testDialog -> {
+ testDialog.onStopButtonClick();
+ });
- verify(mockMediaOutputController).releaseSession();
+ verify(mockMediaSwitchingController).releaseSession();
verify(mDialogTransitionAnimator).disableAllCurrentDialogsExitAnimations();
}
@@ -430,14 +440,14 @@
// Check the visibility metric logging by creating a new MediaOutput dialog,
// and verify if the calling times increases.
public void onCreate_ShouldLogVisibility() {
- withTestDialog(mMediaOutputController, testDialog -> {});
+ withTestDialog(mMediaSwitchingController, testDialog -> {});
verify(mUiEventLogger, times(2))
.log(MediaOutputDialog.MediaOutputEvent.MEDIA_OUTPUT_DIALOG_SHOW);
}
@NonNull
- private MediaOutputDialog makeTestDialog(MediaOutputController controller) {
+ private MediaOutputDialog makeTestDialog(MediaSwitchingController controller) {
return new MediaOutputDialog(
mContext,
false,
@@ -448,7 +458,8 @@
true);
}
- private void withTestDialog(MediaOutputController controller, Consumer<MediaOutputDialog> c) {
+ private void withTestDialog(
+ MediaSwitchingController controller, Consumer<MediaOutputDialog> c) {
MediaOutputDialog testDialog = makeTestDialog(controller);
testDialog.show();
c.accept(testDialog);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
similarity index 73%
rename from packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
index 714fad9..53f0800 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
@@ -43,6 +43,7 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.MediaDescription;
import android.media.MediaMetadata;
@@ -58,6 +59,7 @@
import android.os.PowerExemptionManager;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
import android.service.notification.StatusBarNotification;
import android.testing.TestableLooper;
import android.text.TextUtils;
@@ -67,8 +69,11 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.media.flags.Flags;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.media.InputMediaDevice;
+import com.android.settingslib.media.InputRouteManager;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
import com.android.systemui.SysuiTestCase;
@@ -96,12 +101,13 @@
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
@SmallTest
@RunWith(AndroidJUnit4.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class MediaOutputControllerTest extends SysuiTestCase {
+public class MediaSwitchingControllerTest extends SysuiTestCase {
private static final String TEST_DEVICE_1_ID = "test_device_1_id";
private static final String TEST_DEVICE_2_ID = "test_device_2_id";
private static final String TEST_DEVICE_3_ID = "test_device_3_id";
@@ -111,6 +117,10 @@
private static final String TEST_SONG = "test_song";
private static final String TEST_SESSION_ID = "test_session_id";
private static final String TEST_SESSION_NAME = "test_session_name";
+ private static final int MAX_VOLUME = 1;
+ private static final int CURRENT_VOLUME = 0;
+ private static final boolean VOLUME_FIXED_TRUE = true;
+
@Mock
private DialogTransitionAnimator mDialogTransitionAnimator;
@Mock
@@ -126,8 +136,7 @@
private CachedBluetoothDeviceManager mCachedBluetoothDeviceManager;
@Mock
private LocalBluetoothManager mLocalBluetoothManager;
- @Mock
- private MediaOutputController.Callback mCb;
+ @Mock private MediaSwitchingController.Callback mCb;
@Mock
private MediaDevice mMediaDevice1;
@Mock
@@ -166,7 +175,8 @@
private FeatureFlags mFlags = mock(FeatureFlags.class);
private View mDialogLaunchView = mock(View.class);
- private MediaOutputController.Callback mCallback = mock(MediaOutputController.Callback.class);
+ private MediaSwitchingController.Callback mCallback =
+ mock(MediaSwitchingController.Callback.class);
final Notification mNotification = mock(Notification.class);
private final VolumePanelGlobalStateInteractor mVolumePanelGlobalStateInteractor =
@@ -175,8 +185,9 @@
private Context mSpyContext;
private String mPackageName = null;
- private MediaOutputController mMediaOutputController;
+ private MediaSwitchingController mMediaSwitchingController;
private LocalMediaManager mLocalMediaManager;
+ private InputRouteManager mInputRouteManager;
private List<MediaController> mMediaControllers = new ArrayList<>();
private List<MediaDevice> mMediaDevices = new ArrayList<>();
private List<NearbyDevice> mNearbyDevices = new ArrayList<>();
@@ -203,9 +214,8 @@
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(
mCachedBluetoothDeviceManager);
-
- mMediaOutputController =
- new MediaOutputController(
+ mMediaSwitchingController =
+ new MediaSwitchingController(
mSpyContext,
mPackageName,
mContext.getUser(),
@@ -222,9 +232,13 @@
mFlags,
mVolumePanelGlobalStateInteractor,
mUserTracker);
- mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager);
+ mLocalMediaManager = spy(mMediaSwitchingController.mLocalMediaManager);
when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(false);
- mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
+ mMediaSwitchingController.mLocalMediaManager = mLocalMediaManager;
+ mMediaSwitchingController.mInputRouteManager =
+ new InputRouteManager(mContext, mAudioManager);
+ mInputRouteManager = spy(mMediaSwitchingController.mInputRouteManager);
+ mMediaSwitchingController.mInputRouteManager = mInputRouteManager;
MediaDescription.Builder builder = new MediaDescription.Builder();
builder.setTitle(TEST_SONG);
builder.setSubtitle(TEST_ARTIST);
@@ -264,26 +278,26 @@
@Test
public void start_verifyLocalMediaManagerInit() {
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
- verify(mLocalMediaManager).registerCallback(mMediaOutputController);
+ verify(mLocalMediaManager).registerCallback(mMediaSwitchingController);
verify(mLocalMediaManager).startScan();
}
@Test
public void stop_verifyLocalMediaManagerDeinit() {
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mLocalMediaManager);
- mMediaOutputController.stop();
+ mMediaSwitchingController.stop();
- verify(mLocalMediaManager).unregisterCallback(mMediaOutputController);
+ verify(mLocalMediaManager).unregisterCallback(mMediaSwitchingController);
verify(mLocalMediaManager).stopScan();
}
@Test
public void start_notificationNotFound_mediaControllerInitFromSession() {
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
verify(mSessionMediaController).registerCallback(any());
}
@@ -291,7 +305,7 @@
@Test
public void start_MediaNotificationFound_mediaControllerNotInitFromSession() {
when(mNotification.isMediaNotification()).thenReturn(true);
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
verify(mSessionMediaController, never()).registerCallback(any());
verifyZeroInteractions(mMediaSessionManager);
@@ -299,8 +313,8 @@
@Test
public void start_withoutPackageName_verifyMediaControllerInit() {
- mMediaOutputController =
- new MediaOutputController(
+ mMediaSwitchingController =
+ new MediaSwitchingController(
mSpyContext,
null,
mContext.getUser(),
@@ -318,32 +332,32 @@
mVolumePanelGlobalStateInteractor,
mUserTracker);
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
verify(mSessionMediaController, never()).registerCallback(any());
}
@Test
public void start_nearbyMediaDevicesManagerNotNull_registersNearbyDevicesCallback() {
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
verify(mNearbyMediaDevicesManager).registerNearbyDevicesCallback(any());
}
@Test
public void stop_withPackageName_verifyMediaControllerDeinit() {
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mSessionMediaController);
- mMediaOutputController.stop();
+ mMediaSwitchingController.stop();
verify(mSessionMediaController).unregisterCallback(any());
}
@Test
public void stop_withoutPackageName_verifyMediaControllerDeinit() {
- mMediaOutputController =
- new MediaOutputController(
+ mMediaSwitchingController =
+ new MediaSwitchingController(
mSpyContext,
null,
mSpyContext.getUser(),
@@ -361,26 +375,26 @@
mVolumePanelGlobalStateInteractor,
mUserTracker);
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
- mMediaOutputController.stop();
+ mMediaSwitchingController.stop();
verify(mSessionMediaController, never()).unregisterCallback(any());
}
@Test
public void stop_nearbyMediaDevicesManagerNotNull_unregistersNearbyDevicesCallback() {
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mSessionMediaController);
- mMediaOutputController.stop();
+ mMediaSwitchingController.stop();
verify(mNearbyMediaDevicesManager).unregisterNearbyDevicesCallback(any());
}
@Test
public void tryToLaunchMediaApplication_nullIntent_skip() {
- mMediaOutputController.tryToLaunchMediaApplication(mDialogLaunchView);
+ mMediaSwitchingController.tryToLaunchMediaApplication(mDialogLaunchView);
verify(mCb, never()).dismissDialog();
}
@@ -391,9 +405,9 @@
.thenReturn(mController);
Intent intent = new Intent(mPackageName);
doReturn(intent).when(mPackageManager).getLaunchIntentForPackage(mPackageName);
- mMediaOutputController.start(mCallback);
+ mMediaSwitchingController.start(mCallback);
- mMediaOutputController.tryToLaunchMediaApplication(mDialogLaunchView);
+ mMediaSwitchingController.tryToLaunchMediaApplication(mDialogLaunchView);
verify(mStarter).startActivity(any(Intent.class), anyBoolean(),
Mockito.eq(mController));
@@ -403,11 +417,12 @@
public void tryToLaunchInAppRoutingIntent_componentNameNotNull_startActivity() {
when(mDialogTransitionAnimator.createActivityTransitionController(any(View.class)))
.thenReturn(mController);
- mMediaOutputController.start(mCallback);
+ mMediaSwitchingController.start(mCallback);
when(mLocalMediaManager.getLinkedItemComponentName()).thenReturn(
new ComponentName(mPackageName, ""));
- mMediaOutputController.tryToLaunchInAppRoutingIntent(TEST_DEVICE_1_ID, mDialogLaunchView);
+ mMediaSwitchingController.tryToLaunchInAppRoutingIntent(
+ TEST_DEVICE_1_ID, mDialogLaunchView);
verify(mStarter).startActivity(any(Intent.class), anyBoolean(),
Mockito.eq(mController));
@@ -415,9 +430,9 @@
@Test
public void onDevicesUpdated_unregistersNearbyDevicesCallback() throws RemoteException {
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
- mMediaOutputController.onDevicesUpdated(ImmutableList.of());
+ mMediaSwitchingController.onDevicesUpdated(ImmutableList.of());
verify(mNearbyMediaDevicesManager).unregisterNearbyDevicesCallback(any());
}
@@ -425,11 +440,11 @@
@Test
public void onDeviceListUpdate_withNearbyDevices_updatesRangeInformation()
throws RemoteException {
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaOutputController.onDevicesUpdated(mNearbyDevices);
- mMediaOutputController.onDeviceListUpdate(mMediaDevices);
+ mMediaSwitchingController.onDevicesUpdated(mNearbyDevices);
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
verify(mMediaDevice1).setRangeZone(NearbyDevice.RANGE_FAR);
verify(mMediaDevice2).setRangeZone(NearbyDevice.RANGE_CLOSE);
@@ -438,11 +453,11 @@
@Test
public void onDeviceListUpdate_withNearbyDevices_rankByRangeInformation()
throws RemoteException {
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaOutputController.onDevicesUpdated(mNearbyDevices);
- mMediaOutputController.onDeviceListUpdate(mMediaDevices);
+ mMediaSwitchingController.onDevicesUpdated(mNearbyDevices);
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
assertThat(mMediaDevices.get(0).getId()).isEqualTo(TEST_DEVICE_1_ID);
}
@@ -451,11 +466,11 @@
public void routeProcessSupport_onDeviceListUpdate_preferenceExist_NotUpdatesRangeInformation()
throws RemoteException {
when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(true);
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaOutputController.onDevicesUpdated(mNearbyDevices);
- mMediaOutputController.onDeviceListUpdate(mMediaDevices);
+ mMediaSwitchingController.onDevicesUpdated(mNearbyDevices);
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
verify(mMediaDevice1, never()).setRangeZone(anyInt());
verify(mMediaDevice2, never()).setRangeZone(anyInt());
@@ -463,7 +478,8 @@
@Test
public void onDeviceListUpdate_verifyDeviceListCallback() {
- // This test relies on mMediaOutputController.start being called while the selected device
+ // This test relies on mMediaSwitchingController.start being called while the selected
+ // device
// list has exactly one item, and that item's id is:
// - Different from both ids in mMediaDevices.
// - Different from the id of the route published by the device under test (usually the
@@ -475,12 +491,12 @@
.when(mLocalMediaManager)
.getSelectedMediaDevice();
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaOutputController.onDeviceListUpdate(mMediaDevices);
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
final List<MediaDevice> devices = new ArrayList<>();
- for (MediaItem item : mMediaOutputController.getMediaItemList()) {
+ for (MediaItem item : mMediaSwitchingController.getMediaItemList()) {
if (item.getMediaDevice().isPresent()) {
devices.add(item.getMediaDevice().get());
}
@@ -488,14 +504,15 @@
assertThat(devices.containsAll(mMediaDevices)).isTrue();
assertThat(devices.size()).isEqualTo(mMediaDevices.size());
- assertThat(mMediaOutputController.getMediaItemList().size()).isEqualTo(
- mMediaDevices.size() + 2);
+ assertThat(mMediaSwitchingController.getMediaItemList().size())
+ .isEqualTo(mMediaDevices.size() + 2);
verify(mCb).onDeviceListChanged();
}
@Test
public void advanced_onDeviceListUpdateWithConnectedDeviceRemote_verifyItemSize() {
- // This test relies on mMediaOutputController.start being called while the selected device
+ // This test relies on mMediaSwitchingController.start being called while the selected
+ // device
// list has exactly one item, and that item's id is:
// - Different from both ids in mMediaDevices.
// - Different from the id of the route published by the device under test (usually the
@@ -510,12 +527,12 @@
when(mMediaDevice1.getFeatures()).thenReturn(
ImmutableList.of(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK));
when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1);
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaOutputController.onDeviceListUpdate(mMediaDevices);
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
final List<MediaDevice> devices = new ArrayList<>();
- for (MediaItem item : mMediaOutputController.getMediaItemList()) {
+ for (MediaItem item : mMediaSwitchingController.getMediaItemList()) {
if (item.getMediaDevice().isPresent()) {
devices.add(item.getMediaDevice().get());
}
@@ -523,23 +540,69 @@
assertThat(devices.containsAll(mMediaDevices)).isTrue();
assertThat(devices.size()).isEqualTo(mMediaDevices.size());
- assertThat(mMediaOutputController.getMediaItemList().size()).isEqualTo(
- mMediaDevices.size() + 1);
+ assertThat(mMediaSwitchingController.getMediaItemList().size())
+ .isEqualTo(mMediaDevices.size() + 1);
verify(mCb).onDeviceListChanged();
}
+ @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
+ @Test
+ public void onInputDeviceListUpdate_verifyDeviceListCallback() {
+ AudioDeviceInfo[] audioDeviceInfos = {};
+ when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS))
+ .thenReturn(audioDeviceInfos);
+ mMediaSwitchingController.start(mCb);
+
+ // Output devices have changed.
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
+
+ final MediaDevice mediaDevice3 =
+ InputMediaDevice.create(
+ mContext,
+ TEST_DEVICE_3_ID,
+ AudioDeviceInfo.TYPE_BUILTIN_MIC,
+ MAX_VOLUME,
+ CURRENT_VOLUME,
+ VOLUME_FIXED_TRUE);
+ final MediaDevice mediaDevice4 =
+ InputMediaDevice.create(
+ mContext,
+ TEST_DEVICE_4_ID,
+ AudioDeviceInfo.TYPE_WIRED_HEADSET,
+ MAX_VOLUME,
+ CURRENT_VOLUME,
+ VOLUME_FIXED_TRUE);
+ final List<MediaDevice> inputDevices = new ArrayList<>();
+ inputDevices.add(mediaDevice3);
+ inputDevices.add(mediaDevice4);
+
+ // Input devices have changed.
+ mMediaSwitchingController.mInputDeviceCallback.onInputDeviceListUpdated(inputDevices);
+
+ final List<MediaDevice> devices = new ArrayList<>();
+ for (MediaItem item : mMediaSwitchingController.getMediaItemList()) {
+ if (item.getMediaDevice().isPresent()) {
+ devices.add(item.getMediaDevice().get());
+ }
+ }
+
+ assertThat(devices).containsAtLeastElementsIn(mMediaDevices);
+ assertThat(devices).hasSize(mMediaDevices.size() + inputDevices.size());
+ verify(mCb, atLeastOnce()).onDeviceListChanged();
+ }
+
@Test
public void advanced_categorizeMediaItems_withSuggestedDevice_verifyDeviceListSize() {
when(mMediaDevice1.isSuggestedDevice()).thenReturn(true);
when(mMediaDevice2.isSuggestedDevice()).thenReturn(false);
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaOutputController.getMediaItemList().clear();
- mMediaOutputController.onDeviceListUpdate(mMediaDevices);
+ mMediaSwitchingController.getMediaItemList().clear();
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
final List<MediaDevice> devices = new ArrayList<>();
int dividerSize = 0;
- for (MediaItem item : mMediaOutputController.getMediaItemList()) {
+ for (MediaItem item : mMediaSwitchingController.getMediaItemList()) {
if (item.getMediaDevice().isPresent()) {
devices.add(item.getMediaDevice().get());
}
@@ -556,33 +619,33 @@
@Test
public void onDeviceListUpdate_isRefreshing_updatesNeedRefreshToTrue() {
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaOutputController.mIsRefreshing = true;
+ mMediaSwitchingController.mIsRefreshing = true;
- mMediaOutputController.onDeviceListUpdate(mMediaDevices);
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
- assertThat(mMediaOutputController.mNeedRefresh).isTrue();
+ assertThat(mMediaSwitchingController.mNeedRefresh).isTrue();
}
@Test
public void advanced_onDeviceListUpdate_isRefreshing_updatesNeedRefreshToTrue() {
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaOutputController.mIsRefreshing = true;
+ mMediaSwitchingController.mIsRefreshing = true;
- mMediaOutputController.onDeviceListUpdate(mMediaDevices);
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
- assertThat(mMediaOutputController.mNeedRefresh).isTrue();
+ assertThat(mMediaSwitchingController.mNeedRefresh).isTrue();
}
@Test
public void cancelMuteAwaitConnection_cancelsWithMediaManager() {
when(mAudioManager.getMutingExpectedDevice()).thenReturn(mock(AudioDeviceAttributes.class));
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaOutputController.cancelMuteAwaitConnection();
+ mMediaSwitchingController.cancelMuteAwaitConnection();
verify(mAudioManager).cancelMuteAwaitConnection(any());
}
@@ -590,17 +653,17 @@
@Test
public void cancelMuteAwaitConnection_audioManagerIsNull_noAction() {
when(mAudioManager.getMutingExpectedDevice()).thenReturn(null);
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaOutputController.cancelMuteAwaitConnection();
+ mMediaSwitchingController.cancelMuteAwaitConnection();
verify(mAudioManager, never()).cancelMuteAwaitConnection(any());
}
@Test
public void getAppSourceName_packageNameIsNull_returnsNull() {
- MediaOutputController testMediaOutputController =
- new MediaOutputController(
+ MediaSwitchingController testMediaSwitchingController =
+ new MediaSwitchingController(
mSpyContext,
"",
mSpyContext.getUser(),
@@ -617,25 +680,25 @@
mFlags,
mVolumePanelGlobalStateInteractor,
mUserTracker);
- testMediaOutputController.start(mCb);
+ testMediaSwitchingController.start(mCb);
reset(mCb);
- testMediaOutputController.getAppSourceName();
+ testMediaSwitchingController.getAppSourceName();
- assertThat(testMediaOutputController.getAppSourceName()).isNull();
+ assertThat(testMediaSwitchingController.getAppSourceName()).isNull();
}
@Test
public void isActiveItem_deviceNotConnected_returnsFalse() {
when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice2);
- assertThat(mMediaOutputController.isActiveItem(mMediaDevice1)).isFalse();
+ assertThat(mMediaSwitchingController.isActiveItem(mMediaDevice1)).isFalse();
}
@Test
public void getNotificationSmallIcon_packageNameIsNull_returnsNull() {
- MediaOutputController testMediaOutputController =
- new MediaOutputController(
+ MediaSwitchingController testMediaSwitchingController =
+ new MediaSwitchingController(
mSpyContext,
"",
mSpyContext.getUser(),
@@ -652,23 +715,23 @@
mFlags,
mVolumePanelGlobalStateInteractor,
mUserTracker);
- testMediaOutputController.start(mCb);
+ testMediaSwitchingController.start(mCb);
reset(mCb);
- testMediaOutputController.getAppSourceName();
+ testMediaSwitchingController.getAppSourceName();
- assertThat(testMediaOutputController.getNotificationSmallIcon()).isNull();
+ assertThat(testMediaSwitchingController.getNotificationSmallIcon()).isNull();
}
@Test
public void refreshDataSetIfNeeded_needRefreshIsTrue_setsToFalse() {
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaOutputController.mNeedRefresh = true;
+ mMediaSwitchingController.mNeedRefresh = true;
- mMediaOutputController.refreshDataSetIfNeeded();
+ mMediaSwitchingController.refreshDataSetIfNeeded();
- assertThat(mMediaOutputController.mNeedRefresh).isFalse();
+ assertThat(mMediaSwitchingController.mNeedRefresh).isFalse();
}
@Test
@@ -677,13 +740,13 @@
ImmutableList.of(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK));
when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1);
- assertThat(mMediaOutputController.isCurrentConnectedDeviceRemote()).isTrue();
+ assertThat(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).isTrue();
}
@Test
public void addDeviceToPlayMedia_callsLocalMediaManager() {
- MediaOutputController testMediaOutputController =
- new MediaOutputController(
+ MediaSwitchingController testMediaSwitchingController =
+ new MediaSwitchingController(
mSpyContext,
null,
mSpyContext.getUser(),
@@ -702,16 +765,16 @@
mUserTracker);
LocalMediaManager mockLocalMediaManager = mock(LocalMediaManager.class);
- testMediaOutputController.mLocalMediaManager = mockLocalMediaManager;
+ testMediaSwitchingController.mLocalMediaManager = mockLocalMediaManager;
- testMediaOutputController.addDeviceToPlayMedia(mMediaDevice2);
+ testMediaSwitchingController.addDeviceToPlayMedia(mMediaDevice2);
verify(mockLocalMediaManager).addDeviceToPlayMedia(mMediaDevice2);
}
@Test
public void removeDeviceFromPlayMedia_callsLocalMediaManager() {
- MediaOutputController testMediaOutputController =
- new MediaOutputController(
+ MediaSwitchingController testMediaSwitchingController =
+ new MediaSwitchingController(
mSpyContext,
null,
mSpyContext.getUser(),
@@ -730,15 +793,15 @@
mUserTracker);
LocalMediaManager mockLocalMediaManager = mock(LocalMediaManager.class);
- testMediaOutputController.mLocalMediaManager = mockLocalMediaManager;
+ testMediaSwitchingController.mLocalMediaManager = mockLocalMediaManager;
- testMediaOutputController.removeDeviceFromPlayMedia(mMediaDevice2);
+ testMediaSwitchingController.removeDeviceFromPlayMedia(mMediaDevice2);
verify(mockLocalMediaManager).removeDeviceFromPlayMedia(mMediaDevice2);
}
@Test
public void getDeselectableMediaDevice_triggersFromLocalMediaManager() {
- mMediaOutputController.getDeselectableMediaDevice();
+ mMediaSwitchingController.getDeselectableMediaDevice();
verify(mLocalMediaManager).getDeselectableMediaDevice();
}
@@ -746,108 +809,108 @@
@Test
public void adjustSessionVolume_adjustWithoutId_triggersFromLocalMediaManager() {
int testVolume = 10;
- mMediaOutputController.adjustSessionVolume(testVolume);
+ mMediaSwitchingController.adjustSessionVolume(testVolume);
verify(mLocalMediaManager).adjustSessionVolume(testVolume);
}
@Test
public void logInteractionAdjustVolume_triggersFromMetricLogger() {
- MediaOutputMetricLogger spyMediaOutputMetricLogger = spy(
- mMediaOutputController.mMetricLogger);
- mMediaOutputController.mMetricLogger = spyMediaOutputMetricLogger;
+ MediaOutputMetricLogger spyMediaOutputMetricLogger =
+ spy(mMediaSwitchingController.mMetricLogger);
+ mMediaSwitchingController.mMetricLogger = spyMediaOutputMetricLogger;
- mMediaOutputController.logInteractionAdjustVolume(mMediaDevice1);
+ mMediaSwitchingController.logInteractionAdjustVolume(mMediaDevice1);
verify(spyMediaOutputMetricLogger).logInteractionAdjustVolume(mMediaDevice1);
}
@Test
public void getSessionVolumeMax_triggersFromLocalMediaManager() {
- mMediaOutputController.getSessionVolumeMax();
+ mMediaSwitchingController.getSessionVolumeMax();
verify(mLocalMediaManager).getSessionVolumeMax();
}
@Test
public void getSessionVolume_triggersFromLocalMediaManager() {
- mMediaOutputController.getSessionVolume();
+ mMediaSwitchingController.getSessionVolume();
verify(mLocalMediaManager).getSessionVolume();
}
@Test
public void getSessionName_triggersFromLocalMediaManager() {
- mMediaOutputController.getSessionName();
+ mMediaSwitchingController.getSessionName();
verify(mLocalMediaManager).getSessionName();
}
@Test
public void releaseSession_triggersFromLocalMediaManager() {
- mMediaOutputController.releaseSession();
+ mMediaSwitchingController.releaseSession();
verify(mLocalMediaManager).releaseSession();
}
@Test
public void isAnyDeviceTransferring_noDevicesStateIsConnecting_returnsFalse() {
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaOutputController.onDeviceListUpdate(mMediaDevices);
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
- assertThat(mMediaOutputController.isAnyDeviceTransferring()).isFalse();
+ assertThat(mMediaSwitchingController.isAnyDeviceTransferring()).isFalse();
}
@Test
public void isAnyDeviceTransferring_deviceStateIsConnecting_returnsTrue() {
when(mMediaDevice1.getState()).thenReturn(
LocalMediaManager.MediaDeviceState.STATE_CONNECTING);
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaOutputController.onDeviceListUpdate(mMediaDevices);
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
- assertThat(mMediaOutputController.isAnyDeviceTransferring()).isTrue();
+ assertThat(mMediaSwitchingController.isAnyDeviceTransferring()).isTrue();
}
@Test
public void isAnyDeviceTransferring_advancedLayoutSupport() {
when(mMediaDevice1.getState()).thenReturn(
LocalMediaManager.MediaDeviceState.STATE_CONNECTING);
- mMediaOutputController.start(mCb);
- mMediaOutputController.onDeviceListUpdate(mMediaDevices);
+ mMediaSwitchingController.start(mCb);
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
- assertThat(mMediaOutputController.isAnyDeviceTransferring()).isTrue();
+ assertThat(mMediaSwitchingController.isAnyDeviceTransferring()).isTrue();
}
@Test
public void isPlaying_stateIsNull() {
when(mSessionMediaController.getPlaybackState()).thenReturn(null);
- assertThat(mMediaOutputController.isPlaying()).isFalse();
+ assertThat(mMediaSwitchingController.isPlaying()).isFalse();
}
@Test
public void onSelectedDeviceStateChanged_verifyCallback() {
when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice2);
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaOutputController.connectDevice(mMediaDevice1);
+ mMediaSwitchingController.connectDevice(mMediaDevice1);
- mMediaOutputController.onSelectedDeviceStateChanged(mMediaDevice1,
- LocalMediaManager.MediaDeviceState.STATE_CONNECTED);
+ mMediaSwitchingController.onSelectedDeviceStateChanged(
+ mMediaDevice1, LocalMediaManager.MediaDeviceState.STATE_CONNECTED);
verify(mCb).onRouteChanged();
}
@Test
public void onDeviceAttributesChanged_verifyCallback() {
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaOutputController.onDeviceAttributesChanged();
+ mMediaSwitchingController.onDeviceAttributesChanged();
verify(mCb).onRouteChanged();
}
@@ -855,11 +918,11 @@
@Test
public void onRequestFailed_verifyCallback() {
when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1);
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaOutputController.connectDevice(mMediaDevice2);
+ mMediaSwitchingController.connectDevice(mMediaDevice2);
- mMediaOutputController.onRequestFailed(0 /* reason */);
+ mMediaSwitchingController.onRequestFailed(0 /* reason */);
verify(mCb, atLeastOnce()).onRouteChanged();
}
@@ -868,37 +931,40 @@
public void getHeaderTitle_withoutMetadata_returnDefaultString() {
when(mSessionMediaController.getMetadata()).thenReturn(null);
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
- assertThat(mMediaOutputController.getHeaderTitle()).isEqualTo(
- mContext.getText(R.string.controls_media_title));
+ assertThat(
+ mMediaSwitchingController
+ .getHeaderTitle()
+ .equals(mContext.getText(R.string.controls_media_title)))
+ .isTrue();
}
@Test
public void getHeaderTitle_withMetadata_returnSongName() {
when(mSessionMediaController.getMetadata()).thenReturn(mMediaMetadata);
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
- assertThat(mMediaOutputController.getHeaderTitle()).isEqualTo(TEST_SONG);
+ assertThat(mMediaSwitchingController.getHeaderTitle().equals(TEST_SONG)).isTrue();
}
@Test
public void getHeaderSubTitle_withoutMetadata_returnNull() {
when(mSessionMediaController.getMetadata()).thenReturn(null);
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
- assertThat(mMediaOutputController.getHeaderSubTitle()).isNull();
+ assertThat(mMediaSwitchingController.getHeaderSubTitle()).isNull();
}
@Test
public void getHeaderSubTitle_withMetadata_returnArtistName() {
when(mSessionMediaController.getMetadata()).thenReturn(mMediaMetadata);
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
- assertThat(mMediaOutputController.getHeaderSubTitle()).isEqualTo(TEST_ARTIST);
+ assertThat(mMediaSwitchingController.getHeaderSubTitle().equals(TEST_ARTIST)).isTrue();
}
@Test
@@ -911,8 +977,8 @@
mRoutingSessionInfos.add(mRemoteSessionInfo);
when(mLocalMediaManager.getRemoteRoutingSessions()).thenReturn(mRoutingSessionInfos);
- assertThat(mMediaOutputController.getActiveRemoteMediaDevices()).containsExactly(
- mRemoteSessionInfo);
+ assertThat(mMediaSwitchingController.getActiveRemoteMediaDevices())
+ .containsExactly(mRemoteSessionInfo);
}
@Test
@@ -933,7 +999,8 @@
selectableMediaDevices.add(selectableMediaDevice2);
doReturn(selectedMediaDevices).when(mLocalMediaManager).getSelectedMediaDevice();
doReturn(selectableMediaDevices).when(mLocalMediaManager).getSelectableMediaDevice();
- final List<MediaDevice> groupMediaDevices = mMediaOutputController.getGroupMediaDevices();
+ final List<MediaDevice> groupMediaDevices =
+ mMediaSwitchingController.getGroupMediaDevices();
// Reset order
selectedMediaDevices.clear();
selectedMediaDevices.add(selectedMediaDevice2);
@@ -941,7 +1008,7 @@
selectableMediaDevices.clear();
selectableMediaDevices.add(selectableMediaDevice2);
selectableMediaDevices.add(selectableMediaDevice1);
- final List<MediaDevice> newDevices = mMediaOutputController.getGroupMediaDevices();
+ final List<MediaDevice> newDevices = mMediaSwitchingController.getGroupMediaDevices();
assertThat(newDevices.size()).isEqualTo(groupMediaDevices.size());
for (int i = 0; i < groupMediaDevices.size(); i++) {
@@ -970,7 +1037,8 @@
selectableMediaDevices.add(selectableMediaDevice2);
doReturn(selectedMediaDevices).when(mLocalMediaManager).getSelectedMediaDevice();
doReturn(selectableMediaDevices).when(mLocalMediaManager).getSelectableMediaDevice();
- final List<MediaDevice> groupMediaDevices = mMediaOutputController.getGroupMediaDevices();
+ final List<MediaDevice> groupMediaDevices =
+ mMediaSwitchingController.getGroupMediaDevices();
// Reset order
selectedMediaDevices.clear();
selectedMediaDevices.add(selectedMediaDevice2);
@@ -979,7 +1047,7 @@
selectableMediaDevices.add(selectableMediaDevice3);
selectableMediaDevices.add(selectableMediaDevice2);
selectableMediaDevices.add(selectableMediaDevice1);
- final List<MediaDevice> newDevices = mMediaOutputController.getGroupMediaDevices();
+ final List<MediaDevice> newDevices = mMediaSwitchingController.getGroupMediaDevices();
assertThat(newDevices.size()).isEqualTo(5);
for (int i = 0; i < groupMediaDevices.size(); i++) {
@@ -991,8 +1059,8 @@
@Test
public void getNotificationLargeIcon_withoutPackageName_returnsNull() {
- mMediaOutputController =
- new MediaOutputController(
+ mMediaSwitchingController =
+ new MediaSwitchingController(
mSpyContext,
null,
mSpyContext.getUser(),
@@ -1010,7 +1078,7 @@
mVolumePanelGlobalStateInteractor,
mUserTracker);
- assertThat(mMediaOutputController.getNotificationIcon()).isNull();
+ assertThat(mMediaSwitchingController.getNotificationIcon()).isNull();
}
@Test
@@ -1028,7 +1096,7 @@
when(notification.isMediaNotification()).thenReturn(true);
when(notification.getLargeIcon()).thenReturn(null);
- assertThat(mMediaOutputController.getNotificationIcon()).isNull();
+ assertThat(mMediaSwitchingController.getNotificationIcon()).isNull();
}
@Test
@@ -1047,7 +1115,7 @@
when(notification.isMediaNotification()).thenReturn(true);
when(notification.getLargeIcon()).thenReturn(icon);
- assertThat(mMediaOutputController.getNotificationIcon()).isInstanceOf(IconCompat.class);
+ assertThat(mMediaSwitchingController.getNotificationIcon()).isInstanceOf(IconCompat.class);
}
@Test
@@ -1066,7 +1134,7 @@
when(notification.isMediaNotification()).thenReturn(false);
when(notification.getLargeIcon()).thenReturn(icon);
- assertThat(mMediaOutputController.getNotificationIcon()).isNull();
+ assertThat(mMediaSwitchingController.getNotificationIcon()).isNull();
}
@Test
@@ -1084,7 +1152,7 @@
when(notification.isMediaNotification()).thenReturn(true);
when(notification.getSmallIcon()).thenReturn(null);
- assertThat(mMediaOutputController.getNotificationSmallIcon()).isNull();
+ assertThat(mMediaSwitchingController.getNotificationSmallIcon()).isNull();
}
@Test
@@ -1103,8 +1171,8 @@
when(notification.isMediaNotification()).thenReturn(true);
when(notification.getSmallIcon()).thenReturn(icon);
- assertThat(mMediaOutputController.getNotificationSmallIcon()).isInstanceOf(
- IconCompat.class);
+ assertThat(mMediaSwitchingController.getNotificationSmallIcon())
+ .isInstanceOf(IconCompat.class);
}
@Test
@@ -1112,8 +1180,8 @@
when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice2);
when(mMediaDevice1.getIcon()).thenReturn(mDrawable);
- assertThat(mMediaOutputController.getDeviceIconCompat(mMediaDevice1)).isInstanceOf(
- IconCompat.class);
+ assertThat(mMediaSwitchingController.getDeviceIconCompat(mMediaDevice1))
+ .isInstanceOf(IconCompat.class);
}
@Test
@@ -1121,13 +1189,13 @@
when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice2);
when(mMediaDevice1.getIcon()).thenReturn(null);
- assertThat(mMediaOutputController.getDeviceIconCompat(mMediaDevice1)).isInstanceOf(
- IconCompat.class);
+ assertThat(mMediaSwitchingController.getDeviceIconCompat(mMediaDevice1))
+ .isInstanceOf(IconCompat.class);
}
@Test
public void setColorFilter_setColorFilterToDrawable() {
- mMediaOutputController.setColorFilter(mDrawable, true);
+ mMediaSwitchingController.setColorFilter(mDrawable, true);
verify(mDrawable).setColorFilter(any(PorterDuffColorFilter.class));
}
@@ -1150,11 +1218,11 @@
selectableMediaDevices.add(selectableMediaDevice2);
doReturn(selectedMediaDevices).when(mLocalMediaManager).getSelectedMediaDevice();
doReturn(selectableMediaDevices).when(mLocalMediaManager).getSelectableMediaDevice();
- assertThat(mMediaOutputController.getGroupMediaDevices().isEmpty()).isFalse();
+ assertThat(mMediaSwitchingController.getGroupMediaDevices().isEmpty()).isFalse();
- mMediaOutputController.resetGroupMediaDevices();
+ mMediaSwitchingController.resetGroupMediaDevices();
- assertThat(mMediaOutputController.mGroupMediaDevices.isEmpty()).isTrue();
+ assertThat(mMediaSwitchingController.mGroupMediaDevices.isEmpty()).isTrue();
}
@Test
@@ -1164,7 +1232,7 @@
when(mMediaDevice1.isVolumeFixed()).thenReturn(true);
- assertThat(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).isFalse();
+ assertThat(mMediaSwitchingController.isVolumeControlEnabled(mMediaDevice1)).isFalse();
}
@Test
@@ -1174,7 +1242,7 @@
when(mMediaDevice1.isVolumeFixed()).thenReturn(false);
- assertThat(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).isTrue();
+ assertThat(mMediaSwitchingController.isVolumeControlEnabled(mMediaDevice1)).isTrue();
}
@Test
@@ -1187,7 +1255,7 @@
when(mMediaDevice2.getDeviceType()).thenReturn(
MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE);
- mMediaOutputController.setTemporaryAllowListExceptionIfNeeded(mMediaDevice2);
+ mMediaSwitchingController.setTemporaryAllowListExceptionIfNeeded(mMediaDevice2);
verify(mPowerExemptionManager).addToTemporaryAllowList(anyString(), anyInt(), anyString(),
anyLong());
@@ -1195,8 +1263,8 @@
@Test
public void setTemporaryAllowListExceptionIfNeeded_packageNameIsNull_NoAction() {
- MediaOutputController testMediaOutputController =
- new MediaOutputController(
+ MediaSwitchingController testMediaSwitchingController =
+ new MediaSwitchingController(
mSpyContext,
null,
mSpyContext.getUser(),
@@ -1214,7 +1282,7 @@
mVolumePanelGlobalStateInteractor,
mUserTracker);
- testMediaOutputController.setTemporaryAllowListExceptionIfNeeded(mMediaDevice2);
+ testMediaSwitchingController.setTemporaryAllowListExceptionIfNeeded(mMediaDevice2);
verify(mPowerExemptionManager, never()).addToTemporaryAllowList(anyString(), anyInt(),
anyString(),
@@ -1223,22 +1291,22 @@
@Test
public void onMetadataChanged_triggersOnMetadataChanged() {
- mMediaOutputController.mCallback = this.mCallback;
+ mMediaSwitchingController.mCallback = this.mCallback;
- mMediaOutputController.mCb.onMetadataChanged(mMediaMetadata);
+ mMediaSwitchingController.mCb.onMetadataChanged(mMediaMetadata);
- verify(mMediaOutputController.mCallback).onMediaChanged();
+ verify(mMediaSwitchingController.mCallback).onMediaChanged();
}
@Test
public void onPlaybackStateChanged_updateWithNullState_onMediaStoppedOrPaused() {
when(mPlaybackState.getState()).thenReturn(PlaybackState.STATE_PLAYING);
- mMediaOutputController.mCallback = this.mCallback;
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.mCallback = this.mCallback;
+ mMediaSwitchingController.start(mCb);
- mMediaOutputController.mCb.onPlaybackStateChanged(null);
+ mMediaSwitchingController.mCb.onPlaybackStateChanged(null);
- verify(mMediaOutputController.mCallback).onMediaStoppedOrPaused();
+ verify(mMediaSwitchingController.mCallback).onMediaStoppedOrPaused();
}
@Test
@@ -1246,10 +1314,29 @@
when(mDialogTransitionAnimator.createActivityTransitionController(mDialogLaunchView))
.thenReturn(mActivityTransitionAnimatorController);
when(mKeyguardManager.isKeyguardLocked()).thenReturn(true);
- mMediaOutputController.mCallback = this.mCallback;
+ mMediaSwitchingController.mCallback = this.mCallback;
- mMediaOutputController.launchBluetoothPairing(mDialogLaunchView);
+ mMediaSwitchingController.launchBluetoothPairing(mDialogLaunchView);
verify(mCallback).dismissDialog();
}
+
+ @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
+ @Test
+ public void getSelectedMediaDevice() {
+ // Mock MediaDevice since none of the output media device constructor is publicly available
+ // outside of SettingsLib package.
+ final MediaDevice selectedOutputMediaDevice = mock(MediaDevice.class);
+ doReturn(Collections.singletonList(selectedOutputMediaDevice))
+ .when(mLocalMediaManager)
+ .getSelectedMediaDevice();
+
+ // Mock selected input media device.
+ final MediaDevice selectedInputMediaDevice = mock(MediaDevice.class);
+ doReturn(selectedInputMediaDevice).when(mInputRouteManager).getSelectedInputDevice();
+
+ List<MediaDevice> selectedMediaDevices = mMediaSwitchingController.getSelectedMediaDevice();
+ assertThat(selectedMediaDevices)
+ .containsExactly(selectedOutputMediaDevice, selectedInputMediaDevice);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
index 70af5e7..755adc6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
@@ -26,7 +26,6 @@
import androidx.compose.ui.test.hasContentDescription
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onChildAt
import androidx.compose.ui.test.onChildren
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithTag
@@ -40,6 +39,7 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.qs.panels.shared.model.SizedTile
import com.android.systemui.qs.panels.shared.model.SizedTileImpl
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.DefaultEditTileGrid
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.shared.model.TileCategory
@@ -57,7 +57,7 @@
@Composable
private fun EditTileGridUnderTest(
listState: EditTileListState,
- onSetTiles: (List<TileSpec>) -> Unit
+ onSetTiles: (List<TileSpec>) -> Unit,
) {
DefaultEditTileGrid(
currentListState = listState,
@@ -182,7 +182,7 @@
private fun ComposeContentTestRule.assertTileGridContainsExactly(specs: List<String>) {
onNodeWithTag(CURRENT_TILES_GRID_TEST_TAG).onChildren().apply {
fetchSemanticsNodes().forEachIndexed { index, _ ->
- get(index).onChildAt(0).assert(hasContentDescription(specs[index]))
+ get(index).assert(hasContentDescription(specs[index]))
}
}
}
@@ -198,7 +198,7 @@
icon =
Icon.Resource(
android.R.drawable.star_on,
- ContentDescription.Loaded(tileSpec)
+ ContentDescription.Loaded(tileSpec),
),
label = AnnotatedString(tileSpec),
appName = null,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index af04309..c710c56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -168,7 +168,7 @@
import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
-import com.android.systemui.statusbar.core.StatusBarInitializer;
+import com.android.systemui.statusbar.core.StatusBarInitializerImpl;
import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
@@ -504,7 +504,7 @@
mock(FragmentService.class),
mLightBarController,
mAutoHideController,
- new StatusBarInitializer(
+ new StatusBarInitializerImpl(
mStatusBarWindowController,
mCollapsedStatusBarFragmentProvider,
emptySet()),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 1e2648b22..ecc7909 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -20,7 +20,6 @@
import static android.media.AudioManager.RINGER_MODE_SILENT;
import static android.media.AudioManager.RINGER_MODE_VIBRATE;
-import static com.android.systemui.Flags.FLAG_HAPTIC_VOLUME_SLIDER;
import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN;
import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN;
import static com.android.systemui.volume.VolumeDialogControllerImpl.DYNAMIC_STREAM_BROADCAST;
@@ -51,7 +50,6 @@
import android.media.AudioManager;
import android.media.AudioSystem;
import android.os.SystemClock;
-import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.provider.Settings;
import android.testing.TestableLooper;
@@ -285,23 +283,8 @@
}
@Test
- @DisableFlags(FLAG_HAPTIC_VOLUME_SLIDER)
- public void addSliderHaptics_withHapticsDisabled_doesNotDeliverOnProgressChangedHaptics() {
- // GIVEN that the slider haptics flag is disabled and we try to add haptics to volume rows
- mDialog.addSliderHapticsToRows();
-
- // WHEN haptics try to be delivered to a volume stream
- boolean canDeliverHaptics =
- mDialog.canDeliverProgressHapticsToStream(AudioSystem.STREAM_MUSIC, true, 50);
-
- // THEN the result is that haptics are not successfully delivered
- assertFalse(canDeliverHaptics);
- }
-
- @Test
- @EnableFlags(FLAG_HAPTIC_VOLUME_SLIDER)
- public void addSliderHaptics_withHapticsEnabled_canDeliverOnProgressChangedHaptics() {
- // GIVEN that the slider haptics flag is enabled and we try to add haptics to volume rows
+ public void addSliderHaptics_canDeliverOnProgressChangedHaptics() {
+ // GIVEN that the slider haptics are added to rows
mDialog.addSliderHapticsToRows();
// WHEN haptics try to be delivered to a volume stream
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 3e7980d..0d39834 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -24,6 +24,7 @@
import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
+import static android.service.notification.NotificationListenerService.REASON_PACKAGE_BANNED;
import static androidx.test.ext.truth.content.IntentSubject.assertThat;
@@ -1100,6 +1101,18 @@
}
@Test
+ public void testNotifsBanned_entryListenerRemove() {
+ mEntryListener.onEntryAdded(mRow);
+ mBubbleController.updateBubble(mBubbleEntry);
+
+ assertTrue(mBubbleController.hasBubbles());
+
+ // Removes the notification
+ mEntryListener.onEntryRemoved(mRow, REASON_PACKAGE_BANNED);
+ assertFalse(mBubbleController.hasBubbles());
+ }
+
+ @Test
public void removeBubble_intercepted() {
mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
diff --git a/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt b/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt
index ee36cad..de4bbec 100644
--- a/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt
+++ b/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt
@@ -84,7 +84,7 @@
if (devices.containsKey(deviceId)) {
return
}
- addPhysicalKeyboard(deviceId, enabled)
+ addPhysicalKeyboard(deviceId, enabled = enabled)
}
fun registerInputDeviceListener(listener: InputDeviceListener) {
@@ -92,9 +92,15 @@
inputDeviceListener = listener
}
- fun addPhysicalKeyboard(id: Int, enabled: Boolean = true) {
+ fun addPhysicalKeyboard(
+ id: Int,
+ vendorId: Int = 0,
+ productId: Int = 0,
+ isFullKeyboard: Boolean = true,
+ enabled: Boolean = true
+ ) {
check(id > 0) { "Physical keyboard ids have to be > 0" }
- addKeyboard(id, enabled)
+ addKeyboard(id, vendorId, productId, isFullKeyboard, enabled)
}
fun removeKeysFromKeyboard(deviceId: Int, vararg keyCodes: Int) {
@@ -102,20 +108,38 @@
supportedKeyCodesByDeviceId[deviceId]!!.removeAll(keyCodes.asList())
}
- private fun addKeyboard(id: Int, enabled: Boolean = true) {
- devices[id] =
+ private fun addKeyboard(
+ id: Int,
+ vendorId: Int = 0,
+ productId: Int = 0,
+ isFullKeyboard: Boolean = true,
+ enabled: Boolean = true
+ ) {
+ val keyboardType =
+ if (isFullKeyboard) InputDevice.KEYBOARD_TYPE_ALPHABETIC
+ else InputDevice.KEYBOARD_TYPE_NON_ALPHABETIC
+ // VendorId and productId are set to 0 if not specified, which is the same as the default
+ // values used in InputDevice.Builder
+ val builder =
InputDevice.Builder()
.setId(id)
- .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC)
+ .setVendorId(vendorId)
+ .setProductId(productId)
+ .setKeyboardType(keyboardType)
.setSources(InputDevice.SOURCE_KEYBOARD)
.setEnabled(enabled)
.setKeyCharacterMap(keyCharacterMap)
- .build()
+ devices[id] = builder.build()
+ inputDeviceListener?.onInputDeviceAdded(id)
supportedKeyCodesByDeviceId[id] = allKeyCodes.toMutableSet()
}
- fun addDevice(id: Int, sources: Int) {
- devices[id] = InputDevice.Builder().setId(id).setSources(sources).build()
+ fun addDevice(id: Int, sources: Int, isNotFound: Boolean = false) {
+ // there's not way of differentiate device connection vs registry in current implementation.
+ // If the device isNotFound, it means that we connect an unregistered device.
+ if (!isNotFound) {
+ devices[id] = InputDevice.Builder().setId(id).setSources(sources).build()
+ }
inputDeviceListener?.onInputDeviceAdded(id)
}
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 649e4e8..1b1d8c5 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
@@ -25,6 +25,8 @@
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.bouncer.ui.helper.BouncerHapticPlayer
+import com.android.systemui.haptics.msdl.bouncerHapticPlayer
import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -34,9 +36,7 @@
import kotlinx.coroutines.flow.StateFlow
val Kosmos.bouncerUserActionsViewModel by Fixture {
- BouncerUserActionsViewModel(
- bouncerInteractor = bouncerInteractor,
- )
+ BouncerUserActionsViewModel(bouncerInteractor = bouncerInteractor)
}
val Kosmos.bouncerUserActionsViewModelFactory by Fixture {
@@ -59,6 +59,7 @@
pinViewModelFactory = pinBouncerViewModelFactory,
patternViewModelFactory = patternBouncerViewModelFactory,
passwordViewModelFactory = passwordBouncerViewModelFactory,
+ bouncerHapticPlayer = bouncerHapticPlayer,
)
}
@@ -76,6 +77,7 @@
isInputEnabled: StateFlow<Boolean>,
onIntentionalUserInput: () -> Unit,
authenticationMethod: AuthenticationMethodModel,
+ bouncerHapticPlayer: BouncerHapticPlayer,
): PinBouncerViewModel {
return PinBouncerViewModel(
applicationContext = applicationContext,
@@ -84,6 +86,7 @@
isInputEnabled = isInputEnabled,
onIntentionalUserInput = onIntentionalUserInput,
authenticationMethod = authenticationMethod,
+ bouncerHapticPlayer = bouncerHapticPlayer,
)
}
}
@@ -92,6 +95,7 @@
val Kosmos.patternBouncerViewModelFactory by Fixture {
object : PatternBouncerViewModel.Factory {
override fun create(
+ bouncerHapticPlayer: BouncerHapticPlayer,
isInputEnabled: StateFlow<Boolean>,
onIntentionalUserInput: () -> Unit,
): PatternBouncerViewModel {
@@ -100,6 +104,7 @@
interactor = bouncerInteractor,
isInputEnabled = isInputEnabled,
onIntentionalUserInput = onIntentionalUserInput,
+ bouncerHapticPlayer = bouncerHapticPlayer,
)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
index 1ed10fbe..8922b2f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
@@ -20,6 +20,7 @@
import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
import com.android.systemui.flags.fakeSystemPropertiesHelper
import com.android.systemui.flags.systemPropertiesHelper
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.trustInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -37,5 +38,6 @@
powerInteractor = powerInteractor,
biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
systemPropertiesHelper = fakeSystemPropertiesHelper,
+ keyguardTransitionInteractor = keyguardTransitionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
index 1d2bce2..1df3ef4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
@@ -19,7 +19,7 @@
import com.android.systemui.kosmos.Kosmos
import java.time.Instant
-var Kosmos.contextualEducationRepository: ContextualEducationRepository by
+var Kosmos.contextualEducationRepository: FakeContextualEducationRepository by
Kosmos.Fixture { FakeContextualEducationRepository() }
var Kosmos.fakeEduClock: FakeEduClock by Kosmos.Fixture { FakeEduClock(Instant.MIN) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
index fb4e2fb..4667bf5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
@@ -17,39 +17,77 @@
package com.android.systemui.education.data.repository
import com.android.systemui.contextualeducation.GestureType
+import com.android.systemui.contextualeducation.GestureType.ALL_APPS
+import com.android.systemui.contextualeducation.GestureType.BACK
+import com.android.systemui.contextualeducation.GestureType.HOME
+import com.android.systemui.contextualeducation.GestureType.OVERVIEW
import com.android.systemui.education.data.model.EduDeviceConnectionTime
import com.android.systemui.education.data.model.GestureEduModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
class FakeContextualEducationRepository : ContextualEducationRepository {
- private val userGestureMap = mutableMapOf<Int, GestureEduModel>()
- private val _gestureEduModels = MutableStateFlow(GestureEduModel(userId = 0))
- private val gestureEduModelsFlow = _gestureEduModels.asStateFlow()
+ private val userGestureMap = mutableMapOf<Int, MutableMap<GestureType, GestureEduModel>>()
+
+ private val _backGestureEduModels = MutableStateFlow(GestureEduModel(BACK, userId = 0))
+ private val backGestureEduModelsFlow = _backGestureEduModels.asStateFlow()
+
+ private val _homeGestureEduModels = MutableStateFlow(GestureEduModel(HOME, userId = 0))
+ private val homeEduModelsFlow = _homeGestureEduModels.asStateFlow()
+
+ private val _allAppsGestureEduModels = MutableStateFlow(GestureEduModel(ALL_APPS, userId = 0))
+ private val allAppsGestureEduModels = _allAppsGestureEduModels.asStateFlow()
+
+ private val _overviewsGestureEduModels = MutableStateFlow(GestureEduModel(OVERVIEW, userId = 0))
+ private val overviewsGestureEduModels = _overviewsGestureEduModels.asStateFlow()
private val userEduDeviceConnectionTimeMap = mutableMapOf<Int, EduDeviceConnectionTime>()
private val _eduDeviceConnectionTime = MutableStateFlow(EduDeviceConnectionTime())
private val eduDeviceConnectionTime = _eduDeviceConnectionTime.asStateFlow()
+ private val _keyboardShortcutTriggered = MutableStateFlow<GestureType?>(null)
+
private var currentUser: Int = 0
override fun setUser(userId: Int) {
if (!userGestureMap.contains(userId)) {
- userGestureMap[userId] = GestureEduModel(userId = userId)
+ userGestureMap[userId] = createGestureEduModelMap(userId = userId)
userEduDeviceConnectionTimeMap[userId] = EduDeviceConnectionTime()
}
// save data of current user to the map
- userGestureMap[currentUser] = _gestureEduModels.value
- userEduDeviceConnectionTimeMap[currentUser] = _eduDeviceConnectionTime.value
+ val currentUserMap = userGestureMap[currentUser]!!
+ currentUserMap[BACK] = _backGestureEduModels.value
+ currentUserMap[HOME] = _homeGestureEduModels.value
+ currentUserMap[ALL_APPS] = _allAppsGestureEduModels.value
+ currentUserMap[OVERVIEW] = _overviewsGestureEduModels.value
+
// switch to data of new user
- _gestureEduModels.value = userGestureMap[userId]!!
+ val newUserGestureMap = userGestureMap[userId]!!
+ newUserGestureMap[BACK]?.let { _backGestureEduModels.value = it }
+ newUserGestureMap[HOME]?.let { _homeGestureEduModels.value = it }
+ newUserGestureMap[ALL_APPS]?.let { _allAppsGestureEduModels.value = it }
+ newUserGestureMap[OVERVIEW]?.let { _overviewsGestureEduModels.value = it }
+
+ userEduDeviceConnectionTimeMap[currentUser] = _eduDeviceConnectionTime.value
_eduDeviceConnectionTime.value = userEduDeviceConnectionTimeMap[userId]!!
}
+ private fun createGestureEduModelMap(userId: Int): MutableMap<GestureType, GestureEduModel> {
+ val gestureModelMap = mutableMapOf<GestureType, GestureEduModel>()
+ GestureType.values().forEach { gestureModelMap[it] = GestureEduModel(it, userId = userId) }
+ return gestureModelMap
+ }
+
override fun readGestureEduModelFlow(gestureType: GestureType): Flow<GestureEduModel> {
- return gestureEduModelsFlow
+ return when (gestureType) {
+ BACK -> backGestureEduModelsFlow
+ HOME -> homeEduModelsFlow
+ ALL_APPS -> allAppsGestureEduModels
+ OVERVIEW -> overviewsGestureEduModels
+ }
}
override fun readEduDeviceConnectionTime(): Flow<EduDeviceConnectionTime> {
@@ -60,8 +98,16 @@
gestureType: GestureType,
transform: (GestureEduModel) -> GestureEduModel
) {
- val currentModel = _gestureEduModels.value
- _gestureEduModels.value = transform(currentModel)
+ val gestureModels =
+ when (gestureType) {
+ BACK -> _backGestureEduModels
+ HOME -> _homeGestureEduModels
+ ALL_APPS -> _allAppsGestureEduModels
+ OVERVIEW -> _overviewsGestureEduModels
+ }
+
+ val currentModel = gestureModels.value
+ gestureModels.value = transform(currentModel)
}
override suspend fun updateEduDeviceConnectionTime(
@@ -70,4 +116,11 @@
val currentModel = _eduDeviceConnectionTime.value
_eduDeviceConnectionTime.value = transform(currentModel)
}
+
+ override val keyboardShortcutTriggered: Flow<GestureType>
+ get() = _keyboardShortcutTriggered.filterNotNull()
+
+ fun setKeyboardShortcutTriggered(gestureType: GestureType) {
+ _keyboardShortcutTriggered.value = gestureType
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
index 80f6fc2..2d275f9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
@@ -40,8 +40,7 @@
touchpadRepository,
userRepository
),
- clock = fakeEduClock,
- inputManager = mockEduInputManager
+ clock = fakeEduClock
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
index c252924..c0152b26 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
@@ -17,7 +17,6 @@
package com.android.systemui.flags
import android.platform.test.annotations.EnableFlags
-import com.android.systemui.Flags.FLAG_COMPOSE_LOCKSCREEN
import com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR
import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
@@ -31,7 +30,6 @@
* that feature. It is also picked up by [SceneContainerRule] to set non-aconfig prerequisites.
*/
@EnableFlags(
- FLAG_COMPOSE_LOCKSCREEN,
FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
FLAG_KEYGUARD_WM_STATE_REFACTOR,
FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt
index 5ad973a..2b81da3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt
@@ -20,8 +20,10 @@
import com.google.android.msdl.data.model.MSDLToken
import com.google.android.msdl.domain.InteractionProperties
import com.google.android.msdl.domain.MSDLPlayer
+import com.google.android.msdl.logging.MSDLEvent
class FakeMSDLPlayer : MSDLPlayer {
+ private val history = arrayListOf<MSDLEvent>()
var currentFeedbackLevel = FeedbackLevel.DEFAULT
var latestTokenPlayed: MSDLToken? = null
private set
@@ -34,5 +36,8 @@
override fun playToken(token: MSDLToken, properties: InteractionProperties?) {
latestTokenPlayed = token
latestPropertiesPlayed = properties
+ history.add(MSDLEvent(token, properties))
}
+
+ override fun getHistory(): List<MSDLEvent> = history
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
index ca748b66..80db1e9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.haptics.qs
+import com.android.systemui.classifier.fakeFalsingManager
import com.android.systemui.haptics.vibratorHelper
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.log.core.FakeLogBuffer
@@ -26,6 +27,7 @@
QSLongPressEffect(
vibratorHelper,
keyguardStateController,
+ fakeFalsingManager,
FakeLogBuffer.Factory.create(),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractorKosmos.kt
index 6e650a3..77afa79 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractorKosmos.kt
@@ -21,6 +21,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
import com.android.systemui.media.controls.domain.pipeline.mediaDataProcessor
+import com.android.systemui.media.controls.shared.mediaLogger
import com.android.systemui.media.controls.util.mediaInstanceId
import com.android.systemui.media.mediaOutputDialogManager
import com.android.systemui.plugins.activityStarter
@@ -39,5 +40,6 @@
lockscreenUserManager = notificationLockscreenUserManager,
mediaOutputDialogManager = mediaOutputDialogManager,
broadcastDialogController = mockBroadcastDialogController,
+ mediaLogger = mediaLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/factory/MediaControlInteractorFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/factory/MediaControlInteractorFactoryKosmos.kt
index e490b75..9ea660f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/factory/MediaControlInteractorFactoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/factory/MediaControlInteractorFactoryKosmos.kt
@@ -23,6 +23,7 @@
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaControlInteractor
import com.android.systemui.media.controls.domain.pipeline.mediaDataProcessor
+import com.android.systemui.media.controls.shared.mediaLogger
import com.android.systemui.media.mediaOutputDialogManager
import com.android.systemui.plugins.activityStarter
import com.android.systemui.statusbar.notificationLockscreenUserManager
@@ -42,6 +43,7 @@
lockscreenUserManager = notificationLockscreenUserManager,
mediaOutputDialogManager = mediaOutputDialogManager,
broadcastDialogController = mockBroadcastDialogController,
+ mediaLogger = mediaLogger,
)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorKosmos.kt
deleted file mode 100644
index edbc4c1..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorKosmos.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.panels.domain.interactor
-
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.log.core.FakeLogBuffer
-import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
-
-val Kosmos.gridConsistencyInteractor by
- Kosmos.Fixture {
- GridConsistencyInteractor(
- gridLayoutTypeInteractor,
- currentTilesInteractor,
- gridConsistencyInteractorsMap,
- noopGridConsistencyInteractor,
- FakeLogBuffer.Factory.create(),
- applicationCoroutineScope,
- )
- }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
index 34e99d3..c951642 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
@@ -27,6 +27,3 @@
val Kosmos.gridLayoutMap: Map<GridLayoutType, GridLayout> by
Kosmos.Fixture { mapOf(Pair(InfiniteGridLayoutType, infiniteGridLayout)) }
-
-var Kosmos.gridConsistencyInteractorsMap: Map<GridLayoutType, GridTypeConsistencyInteractor> by
- Kosmos.Fixture { mapOf(Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor)) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt
deleted file mode 100644
index 320c2ec..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.panels.domain.interactor
-
-import com.android.systemui.kosmos.Kosmos
-
-val Kosmos.infiniteGridConsistencyInteractor by
- Kosmos.Fixture {
- InfiniteGridConsistencyInteractor(iconTilesInteractor, fixedColumnsSizeInteractor)
- }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
index be00152..3f62b4d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
@@ -17,7 +17,7 @@
package com.android.systemui.qs.panels.domain.interactor
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.InfiniteGridLayout
import com.android.systemui.qs.panels.ui.viewmodel.fixedColumnsSizeViewModel
import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractorKosmos.kt
deleted file mode 100644
index e3beff7..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractorKosmos.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.panels.domain.interactor
-
-import com.android.systemui.kosmos.Kosmos
-
-val Kosmos.noopGridConsistencyInteractor by Kosmos.Fixture { NoopGridConsistencyInteractor() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt
index c416ea1..91602c2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt
@@ -16,8 +16,13 @@
package com.android.systemui.statusbar.data.repository
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
class FakeRemoteInputRepository : RemoteInputRepository {
override val isRemoteInputActive = MutableStateFlow(false)
+ override val remoteInputRowBottomBound: Flow<Float?> = flowOf(null)
+
+ override fun setRemoteInputRowBottomBound(bottom: Float?) {}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt
index 6370a5d..7244d46 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt
@@ -22,6 +22,7 @@
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.domain.interactor.remoteInputInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
val Kosmos.notificationScrollViewModel by Fixture {
@@ -29,6 +30,7 @@
dumpManager = dumpManager,
stackAppearanceInteractor = notificationStackAppearanceInteractor,
shadeInteractor = shadeInteractor,
+ remoteInputInteractor = remoteInputInteractor,
sceneInteractor = sceneInteractor,
keyguardInteractor = { keyguardInteractor },
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
index 8bfc390..e5cf0a9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
@@ -22,6 +22,7 @@
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.domain.interactor.remoteInputInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
@@ -31,6 +32,7 @@
sceneInteractor = sceneInteractor,
shadeInteractor = shadeInteractor,
headsUpNotificationInteractor = headsUpNotificationInteractor,
+ remoteInputInteractor = remoteInputInteractor,
featureFlags = featureFlagsClassic,
dumpManager = dumpManager,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
index 61b53c9..99cd830 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
@@ -22,6 +22,8 @@
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.shared.notifications.data.repository.notificationSettingsRepository
+import com.android.systemui.statusbar.policy.data.repository.deviceProvisioningRepository
+import com.android.systemui.statusbar.policy.data.repository.userSetupRepository
import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
val Kosmos.zenModeInteractor by Fixture {
@@ -31,5 +33,7 @@
notificationSettingsRepository = notificationSettingsRepository,
bgDispatcher = testDispatcher,
iconLoader = zenIconLoader,
+ deviceProvisioningRepository = deviceProvisioningRepository,
+ userSetupRepository = userSetupRepository,
)
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index a100974..57b58d8 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -128,7 +128,11 @@
val currentDirection =
if (angle < lastHingeAngle) FOLD_UPDATE_START_CLOSING else FOLD_UPDATE_START_OPENING
- if (isTransitionInProgress && currentDirection != lastFoldUpdate) {
+ val changedDirectionWhileInTransition =
+ isTransitionInProgress && currentDirection != lastFoldUpdate
+ val unfoldedPastThresholdSinceLastTransition =
+ angle - lastHingeAngleBeforeTransition > HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES
+ if (changedDirectionWhileInTransition || unfoldedPastThresholdSinceLastTransition) {
lastHingeAngleBeforeTransition = lastHingeAngle
}
@@ -153,7 +157,7 @@
isOnLargeScreen // Avoids sending closing event when on small screen.
// Start event is sent regardless due to hall sensor.
) {
- notifyFoldUpdate(transitionUpdate, lastHingeAngle)
+ notifyFoldUpdate(transitionUpdate, angle)
}
if (isTransitionInProgress) {
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index c3d68cf..93cdde0 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -529,167 +529,6 @@
*/
public static Pair<PreviewExtenderImpl, ImageCaptureExtenderImpl> initializeExtension(
int extensionType) {
- if (Flags.concertModeApi()) {
- if (extensionType == CameraExtensionCharacteristics.EXTENSION_EYES_FREE_VIDEOGRAPHY) {
- // Basic extensions are deprecated starting with extension version 1.5
- return new Pair<>(new PreviewExtenderImpl() {
- @Override
- public boolean isExtensionAvailable(String cameraId,
- CameraCharacteristics cameraCharacteristics) {
- return false;
- }
-
- @Override
- public void init(String cameraId, CameraCharacteristics cameraCharacteristics) {
-
- }
-
- @Override
- public androidx.camera.extensions.impl.CaptureStageImpl getCaptureStage() {
- return null;
- }
-
- @Override
- public ProcessorType getProcessorType() {
- return null;
- }
-
- @Override
- public ProcessorImpl getProcessor() {
- return null;
- }
-
- @Nullable
- @Override
- public List<Pair<Integer, Size[]>> getSupportedResolutions() {
- return null;
- }
-
- @Override
- public void onInit(String cameraId, CameraCharacteristics cameraCharacteristics,
- Context context) { }
-
- @Override
- public void onDeInit() { }
-
- @Override
- public androidx.camera.extensions.impl.CaptureStageImpl onPresetSession() {
- return null;
- }
-
- @Override
- public androidx.camera.extensions.impl.CaptureStageImpl onEnableSession() {
- return null;
- }
-
- @Override
- public androidx.camera.extensions.impl.CaptureStageImpl onDisableSession() {
- return null;
- }
-
- @Override
- public int onSessionType() {
- return 0;
- }
- }, new ImageCaptureExtenderImpl() {
- @Override
- public boolean isExtensionAvailable(String cameraId,
- CameraCharacteristics cameraCharacteristics) {
- return false;
- }
-
- @Override
- public void init(String cameraId,
- CameraCharacteristics cameraCharacteristics) { }
-
- @Override
- public CaptureProcessorImpl getCaptureProcessor() {
- return null;
- }
-
- @Override
- public
- List<androidx.camera.extensions.impl.CaptureStageImpl> getCaptureStages() {
- return null;
- }
-
- @Override
- public int getMaxCaptureStage() {
- return 0;
- }
-
- @Override
- public List<Pair<Integer, Size[]>> getSupportedResolutions() {
- return null;
- }
-
- @Override
- public List<Pair<Integer, Size[]>> getSupportedPostviewResolutions(
- Size captureSize) {
- return null;
- }
-
- @Override
- public Range<Long> getEstimatedCaptureLatencyRange(
- Size captureOutputSize) {
- return null;
- }
-
- @Override
- public List<CaptureRequest.Key> getAvailableCaptureRequestKeys() {
- return null;
- }
-
- @Override
- public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
- return null;
- }
-
- @Override
- public boolean isCaptureProcessProgressAvailable() {
- return false;
- }
-
- @Override
- public Pair<Long, Long> getRealtimeCaptureLatency() {
- return null;
- }
-
- @Override
- public boolean isPostviewAvailable() {
- return false;
- }
-
- @Override
- public void onInit(String cameraId,
- CameraCharacteristics cameraCharacteristics, Context context) { }
-
- @Override
- public void onDeInit() { }
-
- @Override
- public androidx.camera.extensions.impl.CaptureStageImpl onPresetSession() {
- return null;
- }
-
- @Override
- public androidx.camera.extensions.impl.CaptureStageImpl onEnableSession() {
- return null;
- }
-
- @Override
- public androidx.camera.extensions.impl.CaptureStageImpl onDisableSession() {
- return null;
- }
-
- @Override
- public int onSessionType() {
- return 0;
- }
- });
- }
- }
-
switch (extensionType) {
case CameraExtensionCharacteristics.EXTENSION_AUTOMATIC:
return new Pair<>(new AutoPreviewExtenderImpl(),
@@ -714,88 +553,6 @@
* @hide
*/
public static AdvancedExtenderImpl initializeAdvancedExtensionImpl(int extensionType) {
- if (Flags.concertModeApi()) {
- if (extensionType == CameraExtensionCharacteristics.EXTENSION_EYES_FREE_VIDEOGRAPHY) {
- if (EFV_SUPPORTED) {
- return new EyesFreeVideographyAdvancedExtenderImpl();
- } else {
- return new AdvancedExtenderImpl() {
- @Override
- public boolean isExtensionAvailable(String cameraId,
- Map<String, CameraCharacteristics> characteristicsMap) {
- return false;
- }
-
- @Override
- public void init(String cameraId,
- Map<String, CameraCharacteristics> characteristicsMap) {
-
- }
-
- @Override
- public Range<Long> getEstimatedCaptureLatencyRange(String cameraId,
- Size captureOutputSize, int imageFormat) {
- return null;
- }
-
- @Override
- public Map<Integer, List<Size>> getSupportedPreviewOutputResolutions(
- String cameraId) {
- return null;
- }
-
- @Override
- public Map<Integer, List<Size>> getSupportedCaptureOutputResolutions(
- String cameraId) {
- return null;
- }
-
- @Override
- public Map<Integer, List<Size>> getSupportedPostviewResolutions(
- Size captureSize) {
- return null;
- }
-
- @Override
- public List<Size> getSupportedYuvAnalysisResolutions(String cameraId) {
- return null;
- }
-
- @Override
- public SessionProcessorImpl createSessionProcessor() {
- return null;
- }
-
- @Override
- public List<CaptureRequest.Key> getAvailableCaptureRequestKeys() {
- return null;
- }
-
- @Override
- public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
- return null;
- }
-
- @Override
- public boolean isCaptureProcessProgressAvailable() {
- return false;
- }
-
- @Override
- public boolean isPostviewAvailable() {
- return false;
- }
-
- @Override
- public List<Pair<CameraCharacteristics.Key, Object>>
- getAvailableCharacteristicsKeyValues() {
- return Collections.emptyList();
- }
- };
- }
- }
- }
-
switch (extensionType) {
case CameraExtensionCharacteristics.EXTENSION_AUTOMATIC:
return new AutoAdvancedExtenderImpl();
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index d1a3bf9..10e4f38 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -343,6 +343,8 @@
data: [
":framework-res",
":ravenwood-empty-res",
+ ":framework-platform-compat-config",
+ ":services-platform-compat-config",
],
libs: [
"100-framework-minus-apex.ravenwood",
diff --git a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodRemove.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodRemove.java
index 6727327..b69c637 100644
--- a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodRemove.java
+++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodRemove.java
@@ -35,4 +35,18 @@
@Target({TYPE, FIELD, METHOD, CONSTRUCTOR})
@Retention(RetentionPolicy.CLASS)
public @interface RavenwoodRemove {
+ /**
+ * One or more classes that aren't yet supported by Ravenwood, which is why this method throws.
+ */
+ Class<?>[] blockedBy() default {};
+
+ /**
+ * General free-form description of why this method throws.
+ */
+ String reason() default "";
+
+ /**
+ * Tracking bug number, if any.
+ */
+ long bug() default 0;
}
diff --git a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java
index 83a7b6e..57cdfd2 100644
--- a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java
+++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java
@@ -42,4 +42,9 @@
* General free-form description of why this method is being replaced.
*/
String reason() default "";
+
+ /**
+ * Tracking bug number, if any.
+ */
+ long bug() default 0;
}
diff --git a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodThrow.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodThrow.java
index 0bb1f39..19e6af1 100644
--- a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodThrow.java
+++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodThrow.java
@@ -43,4 +43,9 @@
* General free-form description of why this method throws.
*/
String reason() default "";
+
+ /**
+ * Tracking bug number, if any.
+ */
+ long bug() default 0;
}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNoConfigNoRuleTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNoConfigNoRuleTest.java
new file mode 100644
index 0000000..d473305
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNoConfigNoRuleTest.java
@@ -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.ravenwoodtest.bivalenttest;
+
+import static org.junit.Assert.assertNotNull;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test to make sure the environment is still initialized when no config and no rules are set.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodNoConfigNoRuleTest {
+
+ @Test
+ public void testInitialization() {
+ assertNotNull(InstrumentationRegistry.getInstrumentation());
+ }
+}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
index 7a6f9e3..478bead 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
@@ -15,8 +15,6 @@
*/
package android.platform.test.ravenwood;
-import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERSION_JAVA_SYSPROP;
-
import static org.junit.Assert.fail;
import android.os.Bundle;
@@ -27,10 +25,6 @@
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.internal.os.RuntimeInit;
-import com.android.platform.test.ravenwood.runtimehelper.ClassLoadHook;
-import com.android.ravenwood.common.RavenwoodCommonUtils;
-
import org.junit.runner.Description;
import org.junit.runners.model.TestClass;
@@ -50,14 +44,16 @@
}
/**
+ * Called before any code starts. Internally it will only initialize the environment once.
+ */
+ public static void performGlobalInitialization() {
+ RavenwoodRuntimeEnvironmentController.globalInitOnce();
+ }
+
+ /**
* Called when a runner starts, before the inner runner gets a chance to run.
*/
public static void onRunnerInitializing(RavenwoodAwareTestRunner runner, TestClass testClass) {
- // TODO: Move the initialization code to a better place.
-
- initOnce();
-
- // This log call also ensures the framework JNI is loaded.
Log.i(TAG, "onRunnerInitializing: testClass=" + testClass.getJavaClass()
+ " runner=" + runner);
@@ -65,33 +61,6 @@
InstrumentationRegistry.registerInstance(null, Bundle.EMPTY);
}
- private static boolean sInitialized = false;
-
- private static void initOnce() {
- if (sInitialized) {
- return;
- }
- sInitialized = true;
-
- // We haven't initialized liblog yet, so directly write to System.out here.
- RavenwoodCommonUtils.log(TAG, "initOnce()");
-
- // Make sure libandroid_runtime is loaded.
- ClassLoadHook.onClassLoaded(Log.class);
-
- // Redirect stdout/stdin to liblog.
- RuntimeInit.redirectLogStreams();
-
- // This will let AndroidJUnit4 use the original runner.
- System.setProperty("android.junit.runner",
- "androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner");
- System.setProperty(RAVENWOOD_VERSION_JAVA_SYSPROP, "1");
-
- // Do the basic set up for the android sysprops.
- RavenwoodRuntimeEnvironmentController.setSystemProperties(
- RavenwoodSystemProperties.DEFAULT_VALUES);
- }
-
/**
* Called when a whole test class is skipped.
*/
@@ -227,4 +196,4 @@
runner.mState.exitRavenwoodRule(rule);
}
-}
\ No newline at end of file
+}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java
new file mode 100644
index 0000000..e548611
--- /dev/null
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java
@@ -0,0 +1,134 @@
+/*
+ * 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.platform.test.ravenwood;
+
+import com.android.ravenwood.common.RavenwoodCommonUtils;
+
+import java.util.Arrays;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * We use this class to load libandroid_runtime.
+ * In the future, we may load other native libraries.
+ */
+public final class RavenwoodNativeLoader {
+ public static final String CORE_NATIVE_CLASSES = "core_native_classes";
+ public static final String ICU_DATA_PATH = "icu.data.path";
+ public static final String KEYBOARD_PATHS = "keyboard_paths";
+ public static final String GRAPHICS_NATIVE_CLASSES = "graphics_native_classes";
+
+ public static final String LIBANDROID_RUNTIME_NAME = "android_runtime";
+
+ /**
+ * Classes with native methods that are backed by libandroid_runtime.
+ *
+ * See frameworks/base/core/jni/platform/host/HostRuntime.cpp
+ */
+ private static final Class<?>[] sLibandroidClasses = {
+ android.util.Log.class,
+ android.os.Parcel.class,
+ android.os.Binder.class,
+ android.content.res.ApkAssets.class,
+ android.content.res.AssetManager.class,
+ android.content.res.StringBlock.class,
+ android.content.res.XmlBlock.class,
+ };
+
+ /**
+ * Classes with native methods that are backed by libhwui.
+ *
+ * See frameworks/base/libs/hwui/apex/LayoutlibLoader.cpp
+ */
+ private static final Class<?>[] sLibhwuiClasses = {
+ android.graphics.Interpolator.class,
+ android.graphics.Matrix.class,
+ android.graphics.Path.class,
+ android.graphics.Color.class,
+ android.graphics.ColorSpace.class,
+ };
+
+ /**
+ * Extra strings needed to pass to register_android_graphics_classes().
+ *
+ * `android.graphics.Graphics` is not actually a class, so we just hardcode it here.
+ */
+ public final static String[] GRAPHICS_EXTRA_INIT_PARAMS = new String[] {
+ "android.graphics.Graphics"
+ };
+
+ private RavenwoodNativeLoader() {
+ }
+
+ private static void log(String message) {
+ System.out.println("RavenwoodNativeLoader: " + message);
+ }
+
+ private static void log(String fmt, Object... args) {
+ log(String.format(fmt, args));
+ }
+
+ private static void ensurePropertyNotSet(String key) {
+ if (System.getProperty(key) != null) {
+ throw new RuntimeException("System property \"" + key + "\" is set unexpectedly");
+ }
+ }
+
+ private static void setProperty(String key, String value) {
+ System.setProperty(key, value);
+ log("Property set: %s=\"%s\"", key, value);
+ }
+
+ private static void dumpSystemProperties() {
+ for (var prop : System.getProperties().entrySet()) {
+ log(" %s=\"%s\"", prop.getKey(), prop.getValue());
+ }
+ }
+
+ /**
+ * libandroid_runtime uses Java's system properties to decide what JNI methods to set up.
+ * Set up these properties and load the native library
+ */
+ public static void loadFrameworkNativeCode() {
+ if ("1".equals(System.getenv("RAVENWOOD_DUMP_PROPERTIES"))) {
+ log("Java system properties:");
+ dumpSystemProperties();
+ }
+
+ // Make sure these properties are not set.
+ ensurePropertyNotSet(CORE_NATIVE_CLASSES);
+ ensurePropertyNotSet(ICU_DATA_PATH);
+ ensurePropertyNotSet(KEYBOARD_PATHS);
+ ensurePropertyNotSet(GRAPHICS_NATIVE_CLASSES);
+
+ // Build the property values
+ final var joiner = Collectors.joining(",");
+ final var libandroidClasses =
+ Arrays.stream(sLibandroidClasses).map(Class::getName).collect(joiner);
+ final var libhwuiClasses = Stream.concat(
+ Arrays.stream(sLibhwuiClasses).map(Class::getName),
+ Arrays.stream(GRAPHICS_EXTRA_INIT_PARAMS)
+ ).collect(joiner);
+
+ // Load the libraries
+ setProperty(CORE_NATIVE_CLASSES, libandroidClasses);
+ setProperty(GRAPHICS_NATIVE_CLASSES, libhwuiClasses);
+ log("Loading " + LIBANDROID_RUNTIME_NAME + " for '" + libandroidClasses + "' and '"
+ + libhwuiClasses + "'");
+ RavenwoodCommonUtils.loadJniLibrary(LIBANDROID_RUNTIME_NAME);
+ }
+}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
index 04b67c4..03513ab 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
@@ -63,6 +63,7 @@
private RavenwoodConfig mCurrentConfig;
private RavenwoodRule mCurrentRule;
+ private boolean mHasRavenwoodRule;
public Description getClassDescription() {
return mClassDescription;
@@ -71,6 +72,7 @@
public void enterTestClass(Description classDescription) throws IOException {
mClassDescription = classDescription;
+ mHasRavenwoodRule = hasRavenwoodRule(mRunner.getTestClass().getJavaClass());
mCurrentConfig = extractConfiguration(mRunner.getTestClass().getJavaClass());
if (mCurrentConfig != null) {
@@ -97,9 +99,13 @@
}
public void enterRavenwoodRule(RavenwoodRule rule) throws IOException {
+ if (!mHasRavenwoodRule) {
+ fail("If you have a RavenwoodRule in your test, make sure the field type is"
+ + " RavenwoodRule so Ravenwood can detect it.");
+ }
if (mCurrentConfig != null) {
- fail("RavenwoodConfiguration and RavenwoodRule cannot be used in the same class."
- + " Suggest migrating to RavenwoodConfiguration.");
+ fail("RavenwoodConfig and RavenwoodRule cannot be used in the same class."
+ + " Suggest migrating to RavenwoodConfig.");
}
if (mCurrentRule != null) {
fail("Multiple nesting RavenwoodRule's are detected in the same class,"
@@ -125,16 +131,20 @@
* @return a configuration from a test class, if any.
*/
@Nullable
- private static RavenwoodConfig extractConfiguration(Class<?> testClass) {
- final boolean hasRavenwoodRule = hasRavenwoodRule(testClass);
-
+ private RavenwoodConfig extractConfiguration(Class<?> testClass) {
var field = findConfigurationField(testClass);
if (field == null) {
- return null;
+ if (mHasRavenwoodRule) {
+ // Should be handled by RavenwoodRule
+ return null;
+ }
+
+ // If no RavenwoodConfig and no RavenwoodRule, return a default config
+ return new RavenwoodConfig.Builder().build();
}
- if (hasRavenwoodRule) {
- fail("RavenwoodConfiguration and RavenwoodRule cannot be used in the same class."
- + " Suggest migrating to RavenwoodConfiguration.");
+ if (mHasRavenwoodRule) {
+ fail("RavenwoodConfig and RavenwoodRule cannot be used in the same class."
+ + " Suggest migrating to RavenwoodConfig.");
}
try {
@@ -155,7 +165,7 @@
private static boolean hasRavenwoodRule(Class<?> testClass) {
for (var field : testClass.getDeclaredFields()) {
if (!field.isAnnotationPresent(Rule.class)
- && field.isAnnotationPresent(ClassRule.class)) {
+ && !field.isAnnotationPresent(ClassRule.class)) {
continue;
}
if (field.getType().equals(RavenwoodRule.class)) {
@@ -165,7 +175,7 @@
// JUnit supports rules as methods, so we need to check them too.
for (var method : testClass.getDeclaredMethods()) {
if (!method.isAnnotationPresent(Rule.class)
- && method.isAnnotationPresent(ClassRule.class)) {
+ && !method.isAnnotationPresent(ClassRule.class)) {
continue;
}
if (method.getReturnType().equals(RavenwoodRule.class)) {
@@ -180,8 +190,8 @@
}
/**
- * Find and return a field with @RavenwoodConfiguration.Config, which must be of type
- * RavenwoodConfiguration.
+ * Find and return a field with @RavenwoodConfig.Config, which must be of type
+ * RavenwoodConfig.
*/
@Nullable
private static Field findConfigurationField(Class<?> testClass) {
@@ -198,7 +208,7 @@
fail(String.format(
"Class %s has multiple fields with %s",
testClass.getCanonicalName(),
- "@RavenwoodConfiguration.Config"));
+ "@RavenwoodConfig.Config"));
}
// Make sure it's static public
ensureIsPublicMember(field, true);
@@ -209,8 +219,8 @@
"Field %s.%s has %s but type is not %s",
testClass.getCanonicalName(),
field.getName(),
- "@RavenwoodConfiguration.Config",
- "RavenwoodConfiguration"));
+ "@RavenwoodConfig.Config",
+ "RavenwoodConfig"));
return null; // unreachable
}
} else {
@@ -219,8 +229,8 @@
"Field %s.%s does not have %s but type is %s",
testClass.getCanonicalName(),
field.getName(),
- "@RavenwoodConfiguration.Config",
- "RavenwoodConfiguration"));
+ "@RavenwoodConfig.Config",
+ "RavenwoodConfig"));
return null; // unreachable
} else {
// Unrelated field, ignore.
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
index f4756c5..2417262 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -18,6 +18,7 @@
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RESOURCE_APK;
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING;
+import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERSION_JAVA_SYSPROP;
import static org.junit.Assert.fail;
@@ -31,10 +32,14 @@
import android.os.HandlerThread;
import android.os.Looper;
import android.os.ServiceManager;
+import android.system.ErrnoException;
+import android.system.Os;
import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.internal.os.RuntimeInit;
+import com.android.ravenwood.common.RavenwoodCommonUtils;
import com.android.ravenwood.common.RavenwoodRuntimeException;
import com.android.ravenwood.common.SneakyThrow;
import com.android.server.LocalServices;
@@ -114,6 +119,43 @@
}
private static RavenwoodConfig sConfig;
+ private static boolean sInitialized = false;
+
+ /**
+ * Initialize the global environment.
+ */
+ public static void globalInitOnce() {
+ if (sInitialized) {
+ return;
+ }
+ sInitialized = true;
+
+ // We haven't initialized liblog yet, so directly write to System.out here.
+ RavenwoodCommonUtils.log(TAG, "globalInit()");
+
+ // Do the basic set up for the android sysprops.
+ setSystemProperties(RavenwoodSystemProperties.DEFAULT_VALUES);
+
+ // Make sure libandroid_runtime is loaded.
+ RavenwoodNativeLoader.loadFrameworkNativeCode();
+
+ // Redirect stdout/stdin to liblog.
+ RuntimeInit.redirectLogStreams();
+
+ if (RAVENWOOD_VERBOSE_LOGGING) {
+ RavenwoodCommonUtils.log(TAG, "Force enabling verbose logging");
+ try {
+ Os.setenv("ANDROID_LOG_TAGS", "*:v", true);
+ } catch (ErrnoException e) {
+ // Shouldn't happen.
+ }
+ }
+
+ System.setProperty(RAVENWOOD_VERSION_JAVA_SYSPROP, "1");
+ // This will let AndroidJUnit4 use the original runner.
+ System.setProperty("android.junit.runner",
+ "androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner");
+ }
/**
* Initialize the environment.
diff --git a/ravenwood/junit-src/android/platform/test/annotations/DisabledOnRavenwood.java b/ravenwood/junit-src/android/platform/test/annotations/DisabledOnRavenwood.java
index 1adb0f3..470165c 100644
--- a/ravenwood/junit-src/android/platform/test/annotations/DisabledOnRavenwood.java
+++ b/ravenwood/junit-src/android/platform/test/annotations/DisabledOnRavenwood.java
@@ -51,4 +51,9 @@
* General free-form description of why this test is being ignored.
*/
String reason() default "";
+
+ /**
+ * Tracking bug number, if any.
+ */
+ long bug() default 0;
}
diff --git a/ravenwood/junit-src/android/platform/test/annotations/IgnoreUnderRavenwood.java b/ravenwood/junit-src/android/platform/test/annotations/IgnoreUnderRavenwood.java
index 7faa654..1c06829 100644
--- a/ravenwood/junit-src/android/platform/test/annotations/IgnoreUnderRavenwood.java
+++ b/ravenwood/junit-src/android/platform/test/annotations/IgnoreUnderRavenwood.java
@@ -51,4 +51,9 @@
* General free-form description of why this test is being ignored.
*/
String reason() default "";
+
+ /**
+ * Tracking bug number, if any.
+ */
+ long bug() default 0;
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
index cb8af0c..5d251bd 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
@@ -46,6 +46,7 @@
import org.junit.runner.notification.RunNotifier;
import org.junit.runner.notification.StoppedByUserException;
import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.Suite;
import org.junit.runners.model.MultipleFailureException;
import org.junit.runners.model.RunnerBuilder;
import org.junit.runners.model.Statement;
@@ -170,6 +171,7 @@
private Runner mRealRunner = null;
private Description mDescription = null;
private Throwable mExceptionInConstructor = null;
+ private boolean mRealRunnerTakesRunnerBuilder = false;
/**
* Stores internal states / methods associated with this runner that's only needed in
@@ -191,23 +193,27 @@
*/
public RavenwoodAwareTestRunner(Class<?> testClass) {
try {
+ performGlobalInitialization();
+
+ /*
+ * If the class has @DisabledOnRavenwood, then we'll delegate to
+ * ClassSkippingTestRunner, which simply skips it.
+ *
+ * We need to do it before instantiating TestClass for b/367694651.
+ */
+ if (isOnRavenwood() && !RavenwoodAwareTestRunnerHook.shouldRunClassOnRavenwood(
+ testClass)) {
+ mRealRunner = new ClassSkippingTestRunner(testClass);
+ mDescription = mRealRunner.getDescription();
+ return;
+ }
+
mTestClass = new TestClass(testClass);
Log.v(TAG, "RavenwoodAwareTestRunner starting for " + testClass.getCanonicalName());
onRunnerInitializing();
- /*
- * If the class has @DisabledOnRavenwood, then we'll delegate to
- * ClassSkippingTestRunner, which simply skips it.
- */
- if (isOnRavenwood() && !RavenwoodAwareTestRunnerHook.shouldRunClassOnRavenwood(
- mTestClass.getJavaClass())) {
- mRealRunner = new ClassSkippingTestRunner(mTestClass);
- mDescription = mRealRunner.getDescription();
- return;
- }
-
// Find the real runner.
final Class<? extends Runner> realRunnerClass;
final InnerRunner innerRunnerAnnotation = mTestClass.getAnnotation(InnerRunner.class);
@@ -243,7 +249,7 @@
}
}
- private static Runner instantiateRealRunner(
+ private Runner instantiateRealRunner(
Class<? extends Runner> realRunnerClass,
Class<?> testClass)
throws NoSuchMethodException, InvocationTargetException, InstantiationException,
@@ -251,12 +257,19 @@
try {
return realRunnerClass.getConstructor(Class.class).newInstance(testClass);
} catch (NoSuchMethodException e) {
- var runnerBuilder = new AllDefaultPossibilitiesBuilder();
- return realRunnerClass.getConstructor(Class.class,
- RunnerBuilder.class).newInstance(testClass, runnerBuilder);
+ var constructor = realRunnerClass.getConstructor(Class.class, RunnerBuilder.class);
+ mRealRunnerTakesRunnerBuilder = true;
+ return constructor.newInstance(testClass, new AllDefaultPossibilitiesBuilder());
}
}
+ private void performGlobalInitialization() {
+ if (!isOnRavenwood()) {
+ return;
+ }
+ RavenwoodAwareTestRunnerHook.performGlobalInitialization();
+ }
+
/**
* Run the bare minimum setup to initialize the wrapped runner.
*/
@@ -265,7 +278,6 @@
if (!isOnRavenwood()) {
return;
}
- // DO NOT USE android.util.Log before calling onRunnerInitializing().
RavenwoodAwareTestRunnerHook.onRunnerInitializing(this, mTestClass);
@@ -317,14 +329,20 @@
return;
}
+ // TODO(b/365976974): handle nested classes better
+ final boolean skipRunnerHook =
+ mRealRunnerTakesRunnerBuilder && mRealRunner instanceof Suite;
+
sCurrentRunner.set(this);
try {
- try {
- RavenwoodAwareTestRunnerHook.onBeforeInnerRunnerStart(
- this, getDescription());
- } catch (Throwable th) {
- notifier.reportBeforeTestFailure(getDescription(), th);
- return;
+ if (!skipRunnerHook) {
+ try {
+ RavenwoodAwareTestRunnerHook.onBeforeInnerRunnerStart(
+ this, getDescription());
+ } catch (Throwable th) {
+ notifier.reportBeforeTestFailure(getDescription(), th);
+ return;
+ }
}
// Delegate to the inner runner.
@@ -332,12 +350,13 @@
} finally {
sCurrentRunner.remove();
- try {
- RavenwoodAwareTestRunnerHook.onAfterInnerRunnerFinished(
- this, getDescription());
- } catch (Throwable th) {
- notifier.reportAfterTestFailure(th);
- return;
+ if (!skipRunnerHook) {
+ try {
+ RavenwoodAwareTestRunnerHook.onAfterInnerRunnerFinished(
+ this, getDescription());
+ } catch (Throwable th) {
+ notifier.reportAfterTestFailure(th);
+ }
}
}
}
@@ -427,14 +446,11 @@
* filter.
*/
private static class ClassSkippingTestRunner extends Runner implements Filterable {
- private final TestClass mTestClass;
private final Description mDescription;
private boolean mFilteredOut;
- ClassSkippingTestRunner(TestClass testClass) {
- mTestClass = testClass;
- mDescription = Description.createTestDescription(
- testClass.getJavaClass(), testClass.getJavaClass().getSimpleName());
+ ClassSkippingTestRunner(Class<?> testClass) {
+ mDescription = Description.createTestDescription(testClass, testClass.getSimpleName());
mFilteredOut = false;
}
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
index 0178b93..aa8c299 100644
--- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
+++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
@@ -35,6 +35,12 @@
}
/**
+ * Called before any code starts. Internally it will only initialize the environment once.
+ */
+ public static void performGlobalInitialization() {
+ }
+
+ /**
* Called when a runner starts, before the inner runner gets a chance to run.
*/
public static void onRunnerInitializing(RavenwoodAwareTestRunner runner, TestClass testClass) {
diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
index 790bb1c..be8c443 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
@@ -15,54 +15,10 @@
*/
package com.android.platform.test.ravenwood.runtimehelper;
-import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING;
-
-import android.system.ErrnoException;
-import android.system.Os;
-
-import com.android.ravenwood.common.RavenwoodCommonUtils;
-
-import java.io.File;
-import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-
/**
* Standard class loader hook.
- *
- * Currently, we use this class to load libandroid_runtime (if needed). In the future, we may
- * load other JNI or do other set up here.
*/
public class ClassLoadHook {
- /**
- * If true, we won't load `libandroid_runtime`
- *
- * <p>Looks like there's some complexity in running a host test with JNI with `atest`,
- * so we need a way to remove the dependency.
- */
- private static final boolean SKIP_LOADING_LIBANDROID = "1".equals(System.getenv(
- "RAVENWOOD_SKIP_LOADING_LIBANDROID"));
-
- public static final String CORE_NATIVE_CLASSES = "core_native_classes";
- public static final String ICU_DATA_PATH = "icu.data.path";
- public static final String KEYBOARD_PATHS = "keyboard_paths";
- public static final String GRAPHICS_NATIVE_CLASSES = "graphics_native_classes";
-
- public static final String LIBANDROID_RUNTIME_NAME = "android_runtime";
-
- /**
- * Extra strings needed to pass to register_android_graphics_classes().
- *
- * `android.graphics.Graphics` is not actually a class, so we can't use the same initialization
- * strategy than the "normal" classes. So we just hardcode it here.
- */
- public static final String GRAPHICS_EXTRA_INIT_PARAMS = ",android.graphics.Graphics";
-
- private static String sInitialDir = new File("").getAbsolutePath();
-
- static {
- log("Initialized. Current dir=" + sInitialDir);
- }
-
private ClassLoadHook() {
}
@@ -75,144 +31,12 @@
public static void onClassLoaded(Class<?> clazz) {
System.out.println("Framework class loaded: " + clazz.getCanonicalName());
- loadFrameworkNativeCode();
- }
-
- private static void log(String message) {
- System.out.println("ClassLoadHook: " + message);
- }
-
- private static void log(String fmt, Object... args) {
- log(String.format(fmt, args));
- }
-
- private static void ensurePropertyNotSet(String key) {
- if (System.getProperty(key) != null) {
- throw new RuntimeException("System property \"" + key + "\" is set unexpectedly");
+ // Always try to initialize the environment in case classes are loaded before
+ // RavenwoodAwareTestRunner is initialized
+ try {
+ Class.forName("android.platform.test.ravenwood.RavenwoodRuntimeEnvironmentController")
+ .getMethod("globalInitOnce").invoke(null);
+ } catch (ReflectiveOperationException ignored) {
}
}
-
- private static void setProperty(String key, String value) {
- System.setProperty(key, value);
- log("Property set: %s=\"%s\"", key, value);
- }
-
- private static void dumpSystemProperties() {
- for (var prop : System.getProperties().entrySet()) {
- log(" %s=\"%s\"", prop.getKey(), prop.getValue());
- }
- }
-
- private static boolean sLoadFrameworkNativeCodeCalled = false;
-
- /**
- * Load `libandroid_runtime` if needed.
- */
- private static void loadFrameworkNativeCode() {
- // This is called from class-initializers, so no synchronization is needed.
- if (sLoadFrameworkNativeCodeCalled) {
- return;
- }
- sLoadFrameworkNativeCodeCalled = true;
-
- // libandroid_runtime uses Java's system properties to decide what JNI methods to set up.
- // Set up these properties for host-side tests.
-
- if ("1".equals(System.getenv("RAVENWOOD_DUMP_PROPERTIES"))) {
- log("Java system properties:");
- dumpSystemProperties();
- }
-
- if (SKIP_LOADING_LIBANDROID) {
- log("Skip loading native runtime.");
- return;
- }
-
- if (RAVENWOOD_VERBOSE_LOGGING) {
- log("Force enabling verbose logging");
- try {
- Os.setenv("ANDROID_LOG_TAGS", "*:v", true);
- } catch (ErrnoException e) {
- // Shouldn't happen.
- }
- }
-
- // Make sure these properties are not set.
- ensurePropertyNotSet(CORE_NATIVE_CLASSES);
- ensurePropertyNotSet(ICU_DATA_PATH);
- ensurePropertyNotSet(KEYBOARD_PATHS);
- ensurePropertyNotSet(GRAPHICS_NATIVE_CLASSES);
-
- // Load the libraries, if needed.
- final var libanrdoidClasses = getClassesWithNativeMethods(sLibandroidClasses);
- final var libhwuiClasses = getClassesWithNativeMethods(sLibhwuiClasses);
- if (libanrdoidClasses.isEmpty() && libhwuiClasses.isEmpty()) {
- log("No classes require JNI methods, skip loading native runtime.");
- return;
- }
- setProperty(CORE_NATIVE_CLASSES, libanrdoidClasses);
- setProperty(GRAPHICS_NATIVE_CLASSES, libhwuiClasses + GRAPHICS_EXTRA_INIT_PARAMS);
-
- log("Loading " + LIBANDROID_RUNTIME_NAME + " for '" + libanrdoidClasses + "' and '"
- + libhwuiClasses + "'");
- RavenwoodCommonUtils.loadJniLibrary(LIBANDROID_RUNTIME_NAME);
- }
-
- /**
- * Classes with native methods that are backed by libandroid_runtime.
- *
- * See frameworks/base/core/jni/platform/host/HostRuntime.cpp
- */
- private static final Class<?>[] sLibandroidClasses = {
- android.util.Log.class,
- android.os.Parcel.class,
- android.os.Binder.class,
- android.content.res.ApkAssets.class,
- android.content.res.AssetManager.class,
- android.content.res.StringBlock.class,
- android.content.res.XmlBlock.class,
- };
-
- /**
- * Classes with native methods that are backed by libhwui.
- *
- * See frameworks/base/libs/hwui/apex/LayoutlibLoader.cpp
- */
- private static final Class<?>[] sLibhwuiClasses = {
- android.graphics.Interpolator.class,
- android.graphics.Matrix.class,
- android.graphics.Path.class,
- android.graphics.Color.class,
- android.graphics.ColorSpace.class,
- };
-
- /**
- * @return if a given class and its nested classes, if any, have any native method or not.
- */
- private static boolean hasNativeMethod(Class<?> clazz) {
- for (var nestedClass : clazz.getNestMembers()) {
- for (var method : nestedClass.getDeclaredMethods()) {
- if (Modifier.isNative(method.getModifiers())) {
- return true;
- }
- }
- }
- return false;
- }
- /**
- * Create a list of classes as comma-separated that require JNI methods to be set up from
- * a given class list, ignoring classes with no native methods.
- */
- private static String getClassesWithNativeMethods(Class<?>[] classes) {
- final var coreNativeClassesToLoad = new ArrayList<String>();
-
- for (var clazz : classes) {
- if (hasNativeMethod(clazz)) {
- log("Class %s has native methods", clazz.getCanonicalName());
- coreNativeClassesToLoad.add(clazz.getName());
- }
- }
-
- return String.join(",", coreNativeClassesToLoad);
- }
}
diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerCallbackTest.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerCallbackTest.java
index 6d8fb98..09ed12d 100644
--- a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerCallbackTest.java
+++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerCallbackTest.java
@@ -17,12 +17,14 @@
import static org.junit.Assume.assumeTrue;
+import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.annotations.NoRavenizer;
import android.platform.test.ravenwood.RavenwoodAwareTestRunner;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.AfterClass;
+import org.junit.Assert;
import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.ClassRule;
@@ -353,4 +355,34 @@
public void test2() {
}
}
+
+ /**
+ * The test class is unloadable, but has a @DisabledOnRavenwood.
+ */
+ @RunWith(AndroidJUnit4.class)
+ @DisabledOnRavenwood
+ // CHECKSTYLE:OFF
+ @Expected("""
+ testRunStarted: classes
+ testSuiteStarted: classes
+ testSuiteStarted: ClassUnloadbleTest(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleTest)
+ testIgnored: ClassUnloadbleTest(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleTest)
+ testSuiteFinished: ClassUnloadbleTest(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleTest)
+ testSuiteFinished: classes
+ testRunFinished: 0,0,0,1
+ """)
+ // CHECKSTYLE:ON
+ public static class ClassUnloadbleTest {
+ static {
+ Assert.fail("Class unloadable!");
+ }
+
+ @Test
+ public void test1() {
+ }
+
+ @Test
+ public void test2() {
+ }
+ }
}
diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java
index 6ee443f..73ea64f 100644
--- a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java
+++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java
@@ -146,7 +146,7 @@
testRunStarted: classes
testSuiteStarted: classes
testStarted: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateConfigTest)
- testFailure: Class com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest.DuplicateConfigTest has multiple fields with @RavenwoodConfiguration.Config
+ testFailure: Class com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest.DuplicateConfigTest has multiple fields with @RavenwoodConfig.Config
testFinished: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateConfigTest)
testSuiteFinished: classes
testRunFinished: 1,1,0,0
@@ -220,7 +220,7 @@
}
/**
- * @Config's must be of type RavenwoodConfiguration.
+ * @Config's must be of type RavenwoodConfig.
*/
@RunWith(AndroidJUnit4.class)
// CHECKSTYLE:OFF
@@ -228,7 +228,7 @@
testRunStarted: classes
testSuiteStarted: classes
testStarted: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeConfigTest)
- testFailure: Field com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest.WrongTypeConfigTest.sConfig has @RavenwoodConfiguration.Config but type is not RavenwoodConfiguration
+ testFailure: Field com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest.WrongTypeConfigTest.sConfig has @RavenwoodConfig.Config but type is not RavenwoodConfig
testFinished: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeConfigTest)
testSuiteFinished: classes
testRunFinished: 1,1,0,0
@@ -237,7 +237,7 @@
public static class WrongTypeConfigTest {
@RavenwoodConfig.Config
- public Object sConfig =
+ public static Object sConfig =
new RavenwoodConfig.Builder().build();
@Test
@@ -247,6 +247,34 @@
}
/**
+ * @Rule must be of type RavenwoodRule.
+ */
+ @RunWith(AndroidJUnit4.class)
+ // CHECKSTYLE:OFF
+ @Expected("""
+ testRunStarted: classes
+ testSuiteStarted: classes
+ testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeRuleTest
+ testStarted: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeRuleTest)
+ testFailure: If you have a RavenwoodRule in your test, make sure the field type is RavenwoodRule so Ravenwood can detect it.
+ testFinished: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeRuleTest)
+ testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeRuleTest
+ testSuiteFinished: classes
+ testRunFinished: 1,1,0,0
+ """)
+ // CHECKSTYLE:ON
+ public static class WrongTypeRuleTest {
+
+ @Rule
+ public TestRule mRule = new RavenwoodRule.Builder().build();
+
+ @Test
+ public void testConfig() {
+ }
+
+ }
+
+ /**
* Config can't be used with a (instance) Rule.
*/
@RunWith(AndroidJUnit4.class)
@@ -255,7 +283,7 @@
testRunStarted: classes
testSuiteStarted: classes
testStarted: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WithInstanceRuleTest)
- testFailure: RavenwoodConfiguration and RavenwoodRule cannot be used in the same class. Suggest migrating to RavenwoodConfiguration.
+ testFailure: RavenwoodConfig and RavenwoodRule cannot be used in the same class. Suggest migrating to RavenwoodConfig.
testFinished: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WithInstanceRuleTest)
testSuiteFinished: classes
testRunFinished: 1,1,0,0
@@ -373,7 +401,7 @@
testRunStarted: classes
testSuiteStarted: classes
testStarted: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleInBaseClassTest)
- testFailure: RavenwoodConfiguration and RavenwoodRule cannot be used in the same class. Suggest migrating to RavenwoodConfiguration.
+ testFailure: RavenwoodConfig and RavenwoodRule cannot be used in the same class. Suggest migrating to RavenwoodConfig.
testFinished: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleInBaseClassTest)
testSuiteFinished: classes
testRunFinished: 1,1,0,0
@@ -408,10 +436,11 @@
testSuiteStarted: classes
testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest
testStarted: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest)
+ testFailure: If you have a RavenwoodRule in your test, make sure the field type is RavenwoodRule so Ravenwood can detect it.
testFinished: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest)
testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest
testSuiteFinished: classes
- testRunFinished: 1,0,0,0
+ testRunFinished: 1,1,0,0
""")
// CHECKSTYLE:ON
public static class RuleWithDifferentTypeInBaseClassSuccessTest extends RuleWithDifferentTypeInBaseClass {
@@ -434,7 +463,7 @@
testSuiteStarted: classes
testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleWithDifferentTypeInBaseClassTest
testStarted: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleWithDifferentTypeInBaseClassTest)
- testFailure: RavenwoodConfiguration and RavenwoodRule cannot be used in the same class. Suggest migrating to RavenwoodConfiguration.
+ testFailure: If you have a RavenwoodRule in your test, make sure the field type is RavenwoodRule so Ravenwood can detect it.
testFinished: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleWithDifferentTypeInBaseClassTest)
testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleWithDifferentTypeInBaseClassTest
testSuiteFinished: classes
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
index c3b7087..1f98334 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
@@ -16,7 +16,15 @@
package com.android.server.appfunctions;
+import android.annotation.NonNull;
+import android.os.UserHandle;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@@ -33,5 +41,50 @@
/* unit= */ TimeUnit.SECONDS,
/* workQueue= */ new LinkedBlockingQueue<>());
+ /** A map of per-user executors for queued work. */
+ @GuardedBy("sLock")
+ private static final SparseArray<ExecutorService> mPerUserExecutorsLocked = new SparseArray<>();
+
+ private static final Object sLock = new Object();
+
+ /**
+ * Returns a per-user executor for queued metadata sync request.
+ *
+ * <p>The work submitted to these executor (Sync request) needs to be synchronous per user hence
+ * the use of a single thread.
+ *
+ * <p>Note: Use a different executor if not calling {@code submitSyncRequest} on a {@code
+ * MetadataSyncAdapter}.
+ */
+ // TODO(b/357551503): Restrict the scope of this executor to the MetadataSyncAdapter itself.
+ public static ExecutorService getPerUserSyncExecutor(@NonNull UserHandle user) {
+ synchronized (sLock) {
+ ExecutorService executor = mPerUserExecutorsLocked.get(user.getIdentifier(), null);
+ if (executor == null) {
+ executor = Executors.newSingleThreadExecutor();
+ mPerUserExecutorsLocked.put(user.getIdentifier(), executor);
+ }
+ return executor;
+ }
+ }
+
+ /**
+ * Shuts down and removes the per-user executor for queued work.
+ *
+ * <p>This should be called when the user is removed.
+ */
+ public static void shutDownAndRemoveUserExecutor(@NonNull UserHandle user)
+ throws InterruptedException {
+ ExecutorService executor;
+ synchronized (sLock) {
+ executor = mPerUserExecutorsLocked.get(user.getIdentifier());
+ mPerUserExecutorsLocked.remove(user.getIdentifier());
+ }
+ if (executor != null) {
+ executor.shutdown();
+ var unused = executor.awaitTermination(30, TimeUnit.SECONDS);
+ }
+ }
+
private AppFunctionExecutors() {}
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java
index 02800cb..c293087 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.appfunctions;
+import android.annotation.NonNull;
import android.app.appfunctions.AppFunctionManagerConfiguration;
import android.content.Context;
@@ -36,4 +37,14 @@
publishBinderService(Context.APP_FUNCTION_SERVICE, mServiceImpl);
}
}
+
+ @Override
+ public void onUserUnlocked(@NonNull TargetUser user) {
+ mServiceImpl.onUserUnlocked(user);
+ }
+
+ @Override
+ public void onUserStopping(@NonNull TargetUser user) {
+ mServiceImpl.onUserStopping(user);
+ }
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index 2362b91..cf039df 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -19,29 +19,35 @@
import static com.android.server.appfunctions.AppFunctionExecutors.THREAD_POOL_EXECUTOR;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.appfunctions.AppFunctionStaticMetadataHelper;
import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
import android.app.appfunctions.ExecuteAppFunctionResponse;
import android.app.appfunctions.IAppFunctionManager;
import android.app.appfunctions.IAppFunctionService;
import android.app.appfunctions.IExecuteAppFunctionCallback;
import android.app.appfunctions.SafeOneTimeExecuteAppFunctionCallback;
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.observer.DocumentChangeInfo;
+import android.app.appsearch.observer.ObserverCallback;
+import android.app.appsearch.observer.ObserverSpec;
+import android.app.appsearch.observer.SchemaChangeInfo;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Slog;
-import android.app.appsearch.AppSearchResult;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.SystemService.TargetUser;
import com.android.server.appfunctions.RemoteServiceCaller.RunServiceCallCallback;
import com.android.server.appfunctions.RemoteServiceCaller.ServiceUsageCompleteListener;
+import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.CompletionException;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
/** Implementation of the AppFunctionManagerService. */
public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
@@ -51,9 +57,11 @@
private final CallerValidator mCallerValidator;
private final ServiceHelper mInternalServiceHelper;
private final ServiceConfig mServiceConfig;
+ private final Context mContext;
public AppFunctionManagerServiceImpl(@NonNull Context context) {
this(
+ context,
new RemoteServiceCallerImpl<>(
context, IAppFunctionService.Stub::asInterface, THREAD_POOL_EXECUTOR),
new CallerValidatorImpl(context),
@@ -63,10 +71,12 @@
@VisibleForTesting
AppFunctionManagerServiceImpl(
+ Context context,
RemoteServiceCaller<IAppFunctionService> remoteServiceCaller,
CallerValidator callerValidator,
ServiceHelper appFunctionInternalServiceHelper,
ServiceConfig serviceConfig) {
+ mContext = Objects.requireNonNull(context);
mRemoteServiceCaller = Objects.requireNonNull(remoteServiceCaller);
mCallerValidator = Objects.requireNonNull(callerValidator);
mInternalServiceHelper = Objects.requireNonNull(appFunctionInternalServiceHelper);
@@ -90,6 +100,26 @@
}
}
+ /** Called when the user is unlocked. */
+ public void onUserUnlocked(TargetUser user) {
+ Objects.requireNonNull(user);
+
+ registerAppSearchObserver(user);
+ trySyncRuntimeMetadata(user);
+ }
+
+ /** Called when the user is stopping. */
+ public void onUserStopping(@NonNull TargetUser user) {
+ Objects.requireNonNull(user);
+
+ try {
+ AppFunctionExecutors.shutDownAndRemoveUserExecutor(user.getUserHandle());
+ MetadataSyncPerUser.removeUserSyncAdapter(user.getUserHandle());
+ } catch (InterruptedException e) {
+ Slog.e(TAG, "Unable to remove data for: " + user.getUserHandle(), e);
+ }
+ }
+
private void executeAppFunctionInternal(
ExecuteAppFunctionAidlRequest requestInternal,
SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback) {
@@ -132,53 +162,55 @@
return;
}
- var unused = mCallerValidator
- .verifyCallerCanExecuteAppFunction(
- validatedCallingPackage,
- targetPackageName,
- requestInternal.getClientRequest().getFunctionIdentifier())
- .thenAccept(
- canExecute -> {
- if (!canExecute) {
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_DENIED,
- "Caller does not have permission to execute the"
- + " appfunction",
- /* extras= */ null));
- return;
- }
- Intent serviceIntent =
- mInternalServiceHelper.resolveAppFunctionService(
- targetPackageName, targetUser);
- if (serviceIntent == null) {
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
- "Cannot find the target service.",
- /* extras= */ null));
- return;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- bindAppFunctionServiceUnchecked(
- requestInternal,
- serviceIntent,
- targetUser,
- safeExecuteAppFunctionCallback,
- /* bindFlags= */ Context.BIND_AUTO_CREATE,
- /* timeoutInMillis= */ mServiceConfig
- .getExecuteAppFunctionTimeoutMillis());
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- })
- .exceptionally(
- ex -> {
- safeExecuteAppFunctionCallback.onResult(
- mapExceptionToExecuteAppFunctionResponse(ex));
- return null;
- });
+ var unused =
+ mCallerValidator
+ .verifyCallerCanExecuteAppFunction(
+ validatedCallingPackage,
+ targetPackageName,
+ requestInternal.getClientRequest().getFunctionIdentifier())
+ .thenAccept(
+ canExecute -> {
+ if (!canExecute) {
+ safeExecuteAppFunctionCallback.onResult(
+ ExecuteAppFunctionResponse.newFailure(
+ ExecuteAppFunctionResponse.RESULT_DENIED,
+ "Caller does not have permission to execute"
+ + " the appfunction",
+ /* extras= */ null));
+ return;
+ }
+ Intent serviceIntent =
+ mInternalServiceHelper.resolveAppFunctionService(
+ targetPackageName, targetUser);
+ if (serviceIntent == null) {
+ safeExecuteAppFunctionCallback.onResult(
+ ExecuteAppFunctionResponse.newFailure(
+ ExecuteAppFunctionResponse
+ .RESULT_INTERNAL_ERROR,
+ "Cannot find the target service.",
+ /* extras= */ null));
+ return;
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ bindAppFunctionServiceUnchecked(
+ requestInternal,
+ serviceIntent,
+ targetUser,
+ safeExecuteAppFunctionCallback,
+ /* bindFlags= */ Context.BIND_AUTO_CREATE,
+ /* timeoutInMillis= */ mServiceConfig
+ .getExecuteAppFunctionTimeoutMillis());
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ })
+ .exceptionally(
+ ex -> {
+ safeExecuteAppFunctionCallback.onResult(
+ mapExceptionToExecuteAppFunctionResponse(ex));
+ return null;
+ });
}
private void bindAppFunctionServiceUnchecked(
@@ -256,7 +288,7 @@
}
private ExecuteAppFunctionResponse mapExceptionToExecuteAppFunctionResponse(Throwable e) {
- if(e instanceof CompletionException) {
+ if (e instanceof CompletionException) {
e = e.getCause();
}
@@ -291,4 +323,103 @@
}
return ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR;
}
+
+ private void registerAppSearchObserver(@NonNull TargetUser user) {
+ AppSearchManager perUserAppSearchManager =
+ mContext.createContextAsUser(user.getUserHandle(), /* flags= */ 0)
+ .getSystemService(AppSearchManager.class);
+ if (perUserAppSearchManager == null) {
+ Slog.d(TAG, "AppSearch Manager not found for user: " + user.getUserIdentifier());
+ return;
+ }
+ try (FutureGlobalSearchSession futureGlobalSearchSession =
+ new FutureGlobalSearchSession(
+ perUserAppSearchManager, AppFunctionExecutors.THREAD_POOL_EXECUTOR)) {
+ AppFunctionMetadataObserver appFunctionMetadataObserver =
+ new AppFunctionMetadataObserver(
+ user.getUserHandle(),
+ mContext.createContextAsUser(user.getUserHandle(), /* flags= */ 0));
+ var unused =
+ futureGlobalSearchSession
+ .registerObserverCallbackAsync(
+ "android",
+ new ObserverSpec.Builder().build(),
+ THREAD_POOL_EXECUTOR,
+ appFunctionMetadataObserver)
+ .whenComplete(
+ (voidResult, ex) -> {
+ if (ex != null) {
+ Slog.e(TAG, "Failed to register observer: ", ex);
+ }
+ });
+
+ } catch (IOException ex) {
+ Slog.e(TAG, "Failed to close observer session: ", ex);
+ }
+ }
+
+ private void trySyncRuntimeMetadata(@NonNull TargetUser user) {
+ MetadataSyncAdapter metadataSyncAdapter =
+ MetadataSyncPerUser.getPerUserMetadataSyncAdapter(
+ user.getUserHandle(),
+ mContext.createContextAsUser(user.getUserHandle(), /* flags= */ 0));
+ if (metadataSyncAdapter != null) {
+ var unused =
+ metadataSyncAdapter
+ .submitSyncRequest()
+ .whenComplete(
+ (isSuccess, ex) -> {
+ if (ex != null || !isSuccess) {
+ Slog.e(TAG, "Sync was not successful");
+ }
+ });
+ }
+ }
+
+ private static class AppFunctionMetadataObserver implements ObserverCallback {
+ @Nullable private final MetadataSyncAdapter mPerUserMetadataSyncAdapter;
+
+ AppFunctionMetadataObserver(@NonNull UserHandle userHandle, @NonNull Context userContext) {
+ mPerUserMetadataSyncAdapter =
+ MetadataSyncPerUser.getPerUserMetadataSyncAdapter(userHandle, userContext);
+ }
+
+ @Override
+ public void onDocumentChanged(@NonNull DocumentChangeInfo documentChangeInfo) {
+ if (mPerUserMetadataSyncAdapter == null) {
+ return;
+ }
+ if (documentChangeInfo
+ .getDatabaseName()
+ .equals(AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_METADATA_DB)
+ && documentChangeInfo
+ .getNamespace()
+ .equals(
+ AppFunctionStaticMetadataHelper
+ .APP_FUNCTION_STATIC_NAMESPACE)) {
+ var unused = mPerUserMetadataSyncAdapter.submitSyncRequest();
+ }
+ }
+
+ @Override
+ public void onSchemaChanged(@NonNull SchemaChangeInfo schemaChangeInfo) {
+ if (mPerUserMetadataSyncAdapter == null) {
+ return;
+ }
+ if (schemaChangeInfo
+ .getDatabaseName()
+ .equals(AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_METADATA_DB)) {
+ boolean shouldInitiateSync = false;
+ for (String schemaName : schemaChangeInfo.getChangedSchemaNames()) {
+ if (schemaName.startsWith(AppFunctionStaticMetadataHelper.STATIC_SCHEMA_TYPE)) {
+ shouldInitiateSync = true;
+ break;
+ }
+ }
+ if (shouldInitiateSync) {
+ var unused = mPerUserMetadataSyncAdapter.submitSyncRequest();
+ }
+ }
+ }
+ }
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
index e2573590..e29b6e4 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
@@ -24,6 +24,8 @@
import android.app.appfunctions.AppFunctionRuntimeMetadata;
import android.app.appfunctions.AppFunctionStaticMetadataHelper;
import android.app.appsearch.AppSearchBatchResult;
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchManager.SearchContext;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.PackageIdentifier;
@@ -61,9 +63,8 @@
*/
public class MetadataSyncAdapter {
private static final String TAG = MetadataSyncAdapter.class.getSimpleName();
- private final FutureAppSearchSession mRuntimeMetadataSearchSession;
- private final FutureAppSearchSession mStaticMetadataSearchSession;
private final Executor mSyncExecutor;
+ private final AppSearchManager mAppSearchManager;
private final PackageManager mPackageManager;
// Hidden constants in {@link SetSchemaRequest} that restricts runtime metadata visibility
@@ -73,13 +74,11 @@
public MetadataSyncAdapter(
@NonNull Executor syncExecutor,
- @NonNull FutureAppSearchSession runtimeMetadataSearchSession,
- @NonNull FutureAppSearchSession staticMetadataSearchSession,
- @NonNull PackageManager packageManager) {
+ @NonNull PackageManager packageManager,
+ @NonNull AppSearchManager appSearchManager) {
mSyncExecutor = Objects.requireNonNull(syncExecutor);
- mRuntimeMetadataSearchSession = Objects.requireNonNull(runtimeMetadataSearchSession);
- mStaticMetadataSearchSession = Objects.requireNonNull(staticMetadataSearchSession);
mPackageManager = Objects.requireNonNull(packageManager);
+ mAppSearchManager = Objects.requireNonNull(appSearchManager);
}
/**
@@ -89,31 +88,54 @@
* synchronization was successful.
*/
public AndroidFuture<Boolean> submitSyncRequest() {
+ SearchContext staticMetadataSearchContext =
+ new SearchContext.Builder(
+ AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_METADATA_DB)
+ .build();
+ SearchContext runtimeMetadataSearchContext =
+ new SearchContext.Builder(
+ AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_METADATA_DB)
+ .build();
AndroidFuture<Boolean> settableSyncStatus = new AndroidFuture<>();
mSyncExecutor.execute(
() -> {
- try {
- trySyncAppFunctionMetadataBlocking();
+ try (FutureAppSearchSession staticMetadataSearchSession =
+ new FutureAppSearchSessionImpl(
+ mAppSearchManager,
+ AppFunctionExecutors.THREAD_POOL_EXECUTOR,
+ staticMetadataSearchContext);
+ FutureAppSearchSession runtimeMetadataSearchSession =
+ new FutureAppSearchSessionImpl(
+ mAppSearchManager,
+ AppFunctionExecutors.THREAD_POOL_EXECUTOR,
+ runtimeMetadataSearchContext)) {
+
+ trySyncAppFunctionMetadataBlocking(
+ staticMetadataSearchSession, runtimeMetadataSearchSession);
settableSyncStatus.complete(true);
- } catch (Exception e) {
- settableSyncStatus.completeExceptionally(e);
+
+ } catch (Exception ex) {
+ settableSyncStatus.completeExceptionally(ex);
}
});
return settableSyncStatus;
}
@WorkerThread
- private void trySyncAppFunctionMetadataBlocking()
+ @VisibleForTesting
+ void trySyncAppFunctionMetadataBlocking(
+ @NonNull FutureAppSearchSession staticMetadataSearchSession,
+ @NonNull FutureAppSearchSession runtimeMetadataSearchSession)
throws ExecutionException, InterruptedException {
ArrayMap<String, ArraySet<String>> staticPackageToFunctionMap =
getPackageToFunctionIdMap(
- mStaticMetadataSearchSession,
+ staticMetadataSearchSession,
AppFunctionStaticMetadataHelper.STATIC_SCHEMA_TYPE,
AppFunctionStaticMetadataHelper.PROPERTY_FUNCTION_ID,
AppFunctionStaticMetadataHelper.PROPERTY_PACKAGE_NAME);
ArrayMap<String, ArraySet<String>> runtimePackageToFunctionMap =
getPackageToFunctionIdMap(
- mRuntimeMetadataSearchSession,
+ runtimeMetadataSearchSession,
RUNTIME_SCHEMA_TYPE,
AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID,
AppFunctionRuntimeMetadata.PROPERTY_PACKAGE_NAME);
@@ -123,18 +145,30 @@
ArrayMap<String, ArraySet<String>> removedFunctionsDiffMap =
getRemovedFunctionsDiffMap(staticPackageToFunctionMap, runtimePackageToFunctionMap);
- Set<AppSearchSchema> appRuntimeMetadataSchemas =
- getAllRuntimeMetadataSchemas(staticPackageToFunctionMap.keySet());
- appRuntimeMetadataSchemas.add(
- AppFunctionRuntimeMetadata.createParentAppFunctionRuntimeSchema());
+ if (!staticPackageToFunctionMap.keySet().equals(runtimePackageToFunctionMap.keySet())) {
+ // Drop removed packages from removedFunctionsDiffMap, as setSchema() deletes them
+ ArraySet<String> removedPackages =
+ getRemovedPackages(
+ staticPackageToFunctionMap.keySet(), removedFunctionsDiffMap.keySet());
+ for (String packageName : removedPackages) {
+ removedFunctionsDiffMap.remove(packageName);
+ }
+ Set<AppSearchSchema> appRuntimeMetadataSchemas =
+ getAllRuntimeMetadataSchemas(staticPackageToFunctionMap.keySet());
+ appRuntimeMetadataSchemas.add(
+ AppFunctionRuntimeMetadata.createParentAppFunctionRuntimeSchema());
+ SetSchemaRequest addSetSchemaRequest =
+ buildSetSchemaRequestForRuntimeMetadataSchemas(
+ mPackageManager, appRuntimeMetadataSchemas);
+ Objects.requireNonNull(
+ runtimeMetadataSearchSession.setSchema(addSetSchemaRequest).get());
+ }
- // Operation order matters here. i.e. remove -> setSchema -> add. Otherwise we would
- // encounter an error trying to delete a document with no existing schema.
if (!removedFunctionsDiffMap.isEmpty()) {
RemoveByDocumentIdRequest removeByDocumentIdRequest =
buildRemoveRuntimeMetadataRequest(removedFunctionsDiffMap);
AppSearchBatchResult<String, Void> removeDocumentBatchResult =
- mRuntimeMetadataSearchSession.remove(removeByDocumentIdRequest).get();
+ runtimeMetadataSearchSession.remove(removeByDocumentIdRequest).get();
if (!removeDocumentBatchResult.isSuccess()) {
throw convertFailedAppSearchResultToException(
removeDocumentBatchResult.getFailures().values());
@@ -142,15 +176,10 @@
}
if (!addedFunctionsDiffMap.isEmpty()) {
- // TODO(b/357551503): only set schema on package diff
- SetSchemaRequest addSetSchemaRequest =
- buildSetSchemaRequestForRuntimeMetadataSchemas(appRuntimeMetadataSchemas);
- Objects.requireNonNull(
- mRuntimeMetadataSearchSession.setSchema(addSetSchemaRequest).get());
PutDocumentsRequest putDocumentsRequest =
buildPutRuntimeMetadataRequest(addedFunctionsDiffMap);
AppSearchBatchResult<String, Void> putDocumentBatchResult =
- mRuntimeMetadataSearchSession.put(putDocumentsRequest).get();
+ runtimeMetadataSearchSession.put(putDocumentsRequest).get();
if (!putDocumentBatchResult.isSuccess()) {
throw convertFailedAppSearchResultToException(
putDocumentBatchResult.getFailures().values());
@@ -211,6 +240,7 @@
@NonNull
private SetSchemaRequest buildSetSchemaRequestForRuntimeMetadataSchemas(
+ @NonNull PackageManager packageManager,
@NonNull Set<AppSearchSchema> metadataSchemaSet) {
Objects.requireNonNull(metadataSchemaSet);
SetSchemaRequest.Builder setSchemaRequestBuilder =
@@ -220,7 +250,7 @@
String packageName =
AppFunctionRuntimeMetadata.getPackageNameFromSchema(
runtimeMetadataSchema.getSchemaType());
- byte[] packageCert = getCertificate(packageName);
+ byte[] packageCert = getCertificate(packageManager, packageName);
if (packageCert == null) {
continue;
}
@@ -252,6 +282,30 @@
}
/**
+ * This method returns a set of packages that are in the removed function packages but not in
+ * the all existing static packages.
+ *
+ * @param allExistingStaticPackages A set of all existing static metadata packages.
+ * @param removedFunctionPackages A set of all removed function packages.
+ * @return A set of packages that are in the removed function packages but not in the all
+ * existing static packages.
+ */
+ @NonNull
+ private static ArraySet<String> getRemovedPackages(
+ @NonNull Set<String> allExistingStaticPackages,
+ @NonNull Set<String> removedFunctionPackages) {
+ ArraySet<String> removedPackages = new ArraySet<>();
+
+ for (String packageName : removedFunctionPackages) {
+ if (!allExistingStaticPackages.contains(packageName)) {
+ removedPackages.add(packageName);
+ }
+ }
+
+ return removedPackages;
+ }
+
+ /**
* This method returns a map of package names to a set of function ids that are in the static
* metadata but not in the runtime metadata.
*
@@ -399,13 +453,15 @@
/** Gets the SHA-256 certificate from a {@link PackageManager}, or null if it is not found. */
@Nullable
- private byte[] getCertificate(@NonNull String packageName) {
+ private byte[] getCertificate(
+ @NonNull PackageManager packageManager, @NonNull String packageName) {
+ Objects.requireNonNull(packageManager);
Objects.requireNonNull(packageName);
PackageInfo packageInfo;
try {
packageInfo =
Objects.requireNonNull(
- mPackageManager.getPackageInfo(
+ packageManager.getPackageInfo(
packageName,
PackageManager.GET_META_DATA
| PackageManager.GET_SIGNING_CERTIFICATES));
diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java
new file mode 100644
index 0000000..f421527
--- /dev/null
+++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appfunctions;
+
+import android.annotation.Nullable;
+import android.app.appsearch.AppSearchManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+/** A Singleton class that manages per-user metadata sync adapters. */
+public final class MetadataSyncPerUser {
+ private static final String TAG = MetadataSyncPerUser.class.getSimpleName();
+
+ /** A map of per-user adapter for synchronizing appFunction metadata. */
+ @GuardedBy("sLock")
+ private static final SparseArray<MetadataSyncAdapter> sPerUserMetadataSyncAdapter =
+ new SparseArray<>();
+
+ private static final Object sLock = new Object();
+
+ /**
+ * Returns the per-user metadata sync adapter for the given user.
+ *
+ * @param user The user for which to get the metadata sync adapter.
+ * @param userContext The user context for the given user.
+ * @return The metadata sync adapter for the given user.
+ */
+ @Nullable
+ public static MetadataSyncAdapter getPerUserMetadataSyncAdapter(
+ UserHandle user, Context userContext) {
+ synchronized (sLock) {
+ MetadataSyncAdapter metadataSyncAdapter =
+ sPerUserMetadataSyncAdapter.get(user.getIdentifier(), null);
+ if (metadataSyncAdapter == null) {
+ AppSearchManager perUserAppSearchManager =
+ userContext.getSystemService(AppSearchManager.class);
+ PackageManager perUserPackageManager = userContext.getPackageManager();
+ if (perUserAppSearchManager != null) {
+ metadataSyncAdapter =
+ new MetadataSyncAdapter(
+ AppFunctionExecutors.getPerUserSyncExecutor(user),
+ perUserPackageManager,
+ perUserAppSearchManager);
+ sPerUserMetadataSyncAdapter.put(user.getIdentifier(), metadataSyncAdapter);
+ return metadataSyncAdapter;
+ }
+ }
+ return metadataSyncAdapter;
+ }
+ }
+
+ /**
+ * Removes the per-user metadata sync adapter for the given user.
+ *
+ * @param user The user for which to remove the metadata sync adapter.
+ */
+ public static void removeUserSyncAdapter(UserHandle user) {
+ synchronized (sLock) {
+ sPerUserMetadataSyncAdapter.remove(user.getIdentifier());
+ }
+ }
+}
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index 2c261fe..bd1b0ea 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -732,6 +732,7 @@
// These autofill id's are being refilled, so they had failed previously.
// Note that these autofillIds correspond to the new autofill ids after relayout.
event.mFailedAutofillIds = new ArraySet<>(autofillIds);
+ setHasRelayoutLog();
});
}
@@ -753,6 +754,7 @@
int failureCount = ids.size();
if (isRefill) {
event.mViewFailedOnRefillCount = failureCount;
+ setHasRelayoutLog();
} else {
event.mViewFillFailureCount = failureCount;
event.mViewFailedPriorToRefillCount = failureCount;
@@ -834,6 +836,19 @@
}
/**
+ * Set the log contains relayout metrics.
+ * This is being added as a temporary measure to add logging.
+ * In future, when we map Session's old view states to the new autofill id's as part of fixing
+ * save for relayout cases, we no longer would need this. But till then, this is needed to set
+ * autofill logs for relayout cases.
+ */
+ private void setHasRelayoutLog() {
+ mEventInternal.ifPresent(event -> {
+ event.mHasRelayoutLog = true;
+ });
+ }
+
+ /**
* Finish and log the event.
*/
public void logAndEndEvent(String caller) {
@@ -844,8 +859,10 @@
}
PresentationStatsEventInternal event = mEventInternal.get();
- boolean ignoreLogging = !event.mIsDatasetAvailable;
-
+ boolean ignoreLogging = !event.mIsDatasetAvailable
+ && !event.mHasRelayoutLog
+ && !(event.mFixExpireResponseDuringAuthCount > 0)
+ && !(event.mNotifyViewEnteredIgnoredDuringAuthCount > 0);
if (sVerbose) {
Slog.v(TAG, "(" + caller + ") "
+ (ignoreLogging ? "IGNORING - following event won't be logged: " : "")
@@ -1032,8 +1049,9 @@
ArraySet<AutofillId> mFailedAutofillIds = new ArraySet<>();
ArraySet<AutofillId> mAlreadyFilledAutofillIds = new ArraySet<>();
- // Not logged - used for internal logic
+ // Following are not logged and used only for internal logic
boolean shouldResetShownCount = false;
+ boolean mHasRelayoutLog = false;
PresentationStatsEventInternal() {}
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index b109472..2fa0e0d 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -720,6 +720,9 @@
void handleInlineSuggestionRequest(InlineSuggestionsRequest inlineSuggestionsRequest,
ViewState viewState) {
+ if (sVerbose) {
+ Slog.v(TAG, "handleInlineSuggestionRequest(): inline suggestion request received");
+ }
synchronized (mLock) {
if (!mWaitForInlineRequest || mPendingInlineSuggestionsRequest != null) {
return;
@@ -734,15 +737,27 @@
@GuardedBy("mLock")
void maybeRequestFillLocked() {
if (mPendingFillRequest == null) {
+ if (sVerbose) {
+ Slog.v(TAG, "maybeRequestFillLocked(): cancelling calling fill request "
+ + "due to empty pending fill request");
+ }
return;
}
mFieldClassificationIdSnapshot = sIdCounterForPcc.get();
if (mWaitForInlineRequest) {
if (mPendingInlineSuggestionsRequest == null) {
+ if (sVerbose) {
+ Slog.v(TAG, "maybeRequestFillLocked(): cancelling calling fill request "
+ + "due to waiting for inline request and pending inline request is "
+ + "currently empty");
+ }
return;
}
-
+ if (sVerbose) {
+ Slog.v(TAG, "maybeRequestFillLocked(): adding inline request to pending "
+ + "fill request");
+ }
mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(),
mPendingFillRequest.getFillContexts(),
mPendingFillRequest.getHints(),
@@ -750,8 +765,17 @@
mPendingFillRequest.getFlags(),
mPendingInlineSuggestionsRequest,
mPendingFillRequest.getDelayedFillIntentSender());
+ } else {
+ if (sVerbose) {
+ Slog.v(TAG, "maybeRequestFillLocked(): not adding inline request to pending "
+ + "fill request");
+ }
}
+
mLastFillRequest = mPendingFillRequest;
+ if (sVerbose) {
+ Slog.v(TAG, "maybeRequestFillLocked(): sending fill request");
+ }
if (shouldRequestSecondaryProvider(mPendingFillRequest.getFlags())
&& mSecondaryProviderHandler != null) {
Slog.v(TAG, "Requesting fill response to secondary provider.");
diff --git a/services/companion/java/com/android/server/companion/virtual/OWNERS b/services/companion/java/com/android/server/companion/virtual/OWNERS
index 4fe0592..4b732ac 100644
--- a/services/companion/java/com/android/server/companion/virtual/OWNERS
+++ b/services/companion/java/com/android/server/companion/virtual/OWNERS
@@ -2,7 +2,9 @@
set noparent
-marvinramin@google.com
vladokom@google.com
+marvinramin@google.com
+caen@google.com
+biswarupp@google.com
ogunwale@google.com
michaelwr@google.com
\ No newline at end of file
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 6657c1c..59dea09 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -160,6 +160,7 @@
private int mLastChargeCounter;
private int mLastBatteryCycleCount;
private int mLastChargingState;
+ private int mLastBatteryCapacityLevel;
/**
* The last seen charging policy. This requires the
* {@link android.Manifest.permission#BATTERY_STATS} permission and should therefore not be
@@ -609,7 +610,8 @@
|| mHealthInfo.batteryChargeCounterUah != mLastChargeCounter
|| mInvalidCharger != mLastInvalidCharger
|| mHealthInfo.batteryCycleCount != mLastBatteryCycleCount
- || mHealthInfo.chargingState != mLastChargingState)) {
+ || mHealthInfo.chargingState != mLastChargingState
+ || mHealthInfo.batteryCapacityLevel != mLastBatteryCapacityLevel)) {
if (mPlugType != mLastPlugType) {
if (mLastPlugType == BATTERY_PLUGGED_NONE) {
@@ -829,6 +831,7 @@
mLastInvalidCharger = mInvalidCharger;
mLastBatteryCycleCount = mHealthInfo.batteryCycleCount;
mLastChargingState = mHealthInfo.chargingState;
+ mLastBatteryCapacityLevel = mHealthInfo.batteryCapacityLevel;
}
}
@@ -862,6 +865,7 @@
intent.putExtra(BatteryManager.EXTRA_CHARGE_COUNTER, mHealthInfo.batteryChargeCounterUah);
intent.putExtra(BatteryManager.EXTRA_CYCLE_COUNT, mHealthInfo.batteryCycleCount);
intent.putExtra(BatteryManager.EXTRA_CHARGING_STATUS, mHealthInfo.chargingState);
+ intent.putExtra(BatteryManager.EXTRA_CAPACITY_LEVEL, mHealthInfo.batteryCapacityLevel);
if (DEBUG) {
Slog.d(TAG, "Sending ACTION_BATTERY_CHANGED. scale:" + BATTERY_SCALE
+ ", info:" + mHealthInfo.toString());
@@ -964,6 +968,7 @@
event.putLong(BatteryManager.EXTRA_EVENT_TIMESTAMP, now);
event.putInt(BatteryManager.EXTRA_CYCLE_COUNT, mHealthInfo.batteryCycleCount);
event.putInt(BatteryManager.EXTRA_CHARGING_STATUS, mHealthInfo.chargingState);
+ event.putInt(BatteryManager.EXTRA_CAPACITY_LEVEL, mHealthInfo.batteryCapacityLevel);
boolean queueWasEmpty = mBatteryLevelsEventQueue.isEmpty();
mBatteryLevelsEventQueue.add(event);
@@ -1401,6 +1406,7 @@
pw.println(" technology: " + mHealthInfo.batteryTechnology);
pw.println(" Charging state: " + mHealthInfo.chargingState);
pw.println(" Charging policy: " + mHealthInfo.chargingPolicy);
+ pw.println(" Capacity level: " + mHealthInfo.batteryCapacityLevel);
} else {
Shell shell = new Shell();
shell.exec(mBinderService, null, fd, null, args, null, new ResultReceiver(null));
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 68d0ad2..a459ea9 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -173,6 +173,15 @@
"include-filter": "com.android.server.wm.BackgroundActivityStart*"
}
]
+ },
+ {
+ "name": "CtsOsTestCases",
+ "file_patterns": ["StorageManagerService\\.java"],
+ "options": [
+ {
+ "include-filter": "android.os.storage.cts.StorageStatsManagerTest"
+ }
+ ]
}
]
}
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 1c13ad5..f32031de 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -32,11 +32,11 @@
import static android.app.UiModeManager.PROJECTION_TYPE_NONE;
import static android.os.UserHandle.USER_SYSTEM;
import static android.os.UserHandle.getCallingUserId;
-import static android.os.UserManager.isVisibleBackgroundUsersEnabled;
import static android.provider.Settings.Secure.CONTRAST_LEVEL;
import static android.util.TimeUtils.isTimeBetween;
import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
+import static com.android.server.pm.UserManagerService.enforceCurrentUserIfVisibleBackgroundEnabled;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -100,7 +100,6 @@
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.util.DumpUtils;
-import com.android.server.pm.UserManagerService;
import com.android.server.twilight.TwilightListener;
import com.android.server.twilight.TwilightManager;
import com.android.server.twilight.TwilightState;
@@ -850,7 +849,7 @@
}
final int user = UserHandle.getCallingUserId();
- enforceValidCallingUser(user);
+ enforceCurrentUserIfVisibleBackgroundEnabled(user);
final long ident = Binder.clearCallingIdentity();
try {
@@ -914,7 +913,7 @@
@AttentionModeThemeOverlayType int attentionModeThemeOverlayType) {
setAttentionModeThemeOverlay_enforcePermission();
- enforceValidCallingUser(UserHandle.getCallingUserId());
+ enforceCurrentUserIfVisibleBackgroundEnabled(UserHandle.getCallingUserId());
synchronized (mLock) {
if (mAttentionModeThemeOverlay != attentionModeThemeOverlayType) {
@@ -1005,7 +1004,7 @@
return false;
}
final int user = Binder.getCallingUserHandle().getIdentifier();
- enforceValidCallingUser(user);
+ enforceCurrentUserIfVisibleBackgroundEnabled(user);
if (user != mCurrentUser && getContext().checkCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS)
@@ -1064,7 +1063,7 @@
return;
}
final int user = UserHandle.getCallingUserId();
- enforceValidCallingUser(user);
+ enforceCurrentUserIfVisibleBackgroundEnabled(user);
final long ident = Binder.clearCallingIdentity();
try {
@@ -1094,7 +1093,7 @@
return;
}
final int user = UserHandle.getCallingUserId();
- enforceValidCallingUser(user);
+ enforceCurrentUserIfVisibleBackgroundEnabled(user);
final long ident = Binder.clearCallingIdentity();
try {
@@ -1116,7 +1115,7 @@
assertLegit(callingPackage);
assertSingleProjectionType(projectionType);
enforceProjectionTypePermissions(projectionType);
- enforceValidCallingUser(getCallingUserId());
+ enforceCurrentUserIfVisibleBackgroundEnabled(getCallingUserId());
synchronized (mLock) {
if (mProjectionHolders == null) {
@@ -1162,7 +1161,7 @@
assertLegit(callingPackage);
assertSingleProjectionType(projectionType);
enforceProjectionTypePermissions(projectionType);
- enforceValidCallingUser(getCallingUserId());
+ enforceCurrentUserIfVisibleBackgroundEnabled(getCallingUserId());
return releaseProjectionUnchecked(projectionType, callingPackage);
}
@@ -1204,7 +1203,7 @@
return;
}
- enforceValidCallingUser(getCallingUserId());
+ enforceCurrentUserIfVisibleBackgroundEnabled(getCallingUserId());
synchronized (mLock) {
if (mProjectionListeners == null) {
@@ -1253,32 +1252,6 @@
}
};
- // This method validates whether calling user is valid in visible background users
- // feature. Valid user is the current user or the system or in the same profile group as
- // the current user.
- private void enforceValidCallingUser(int userId) {
- if (!isVisibleBackgroundUsersEnabled()) {
- return;
- }
- if (LOG) {
- Slog.d(TAG, "enforceValidCallingUser: userId=" + userId
- + " isSystemUser=" + (userId == USER_SYSTEM) + " current user=" + mCurrentUser
- + " callingPid=" + Binder.getCallingPid()
- + " callingUid=" + mInjector.getCallingUid());
- }
- long ident = Binder.clearCallingIdentity();
- try {
- if (userId != USER_SYSTEM && userId != mCurrentUser
- && !UserManagerService.getInstance().isSameProfileGroup(userId, mCurrentUser)) {
- throw new SecurityException(
- "Calling user is not valid for level-1 compatibility in MUMD. "
- + "callingUserId=" + userId + " currentUserId=" + mCurrentUser);
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
private void enforceProjectionTypePermissions(@UiModeManager.ProjectionType int p) {
if ((p & PROJECTION_TYPE_AUTOMOTIVE) != 0) {
getContext().enforceCallingPermission(
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index e2b6bd6..d19899f 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -489,7 +489,10 @@
// Check subscription is active first; much cheaper/faster check, and an app (currently)
// cannot be carrier privileged for inactive subscriptions.
- if (subMgr.isValidSlotIndex(info.getSimSlotIndex())
+ final int simSlotIndex = info.getSimSlotIndex();
+ final boolean isValidSlotIndex =
+ simSlotIndex >= 0 && simSlotIndex < telMgr.getActiveModemCount();
+ if (isValidSlotIndex
&& telMgr.checkCarrierPrivilegesForPackage(pkgName)
== TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
// TODO (b/173717728): Allow configuration for inactive, but manageable
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 54a7410..871c320 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -714,12 +714,14 @@
/**
* Map userId to its companion app uids.
*/
+ @GuardedBy("mCompanionAppUidsMap")
private final Map<Integer, Set<Integer>> mCompanionAppUidsMap = new ArrayMap<>();
/**
* The profile owner UIDs.
*/
- private ArraySet<Integer> mProfileOwnerUids = null;
+ @GuardedBy("mProfileOwnerUids")
+ private final ArraySet<Integer> mProfileOwnerUids = new ArraySet<>();
final UserController mUserController;
@VisibleForTesting
@@ -17535,32 +17537,35 @@
@Override
public void setProfileOwnerUid(ArraySet<Integer> profileOwnerUids) {
- synchronized (ActivityManagerService.this) {
- mProfileOwnerUids = profileOwnerUids;
+ synchronized (mProfileOwnerUids) {
+ mProfileOwnerUids.clear();
+ mProfileOwnerUids.addAll(profileOwnerUids);
}
}
@Override
public boolean isProfileOwner(int uid) {
- synchronized (ActivityManagerService.this) {
- return mProfileOwnerUids != null && mProfileOwnerUids.indexOf(uid) >= 0;
+ synchronized (mProfileOwnerUids) {
+ return mProfileOwnerUids.indexOf(uid) >= 0;
}
}
@Override
public void setCompanionAppUids(int userId, Set<Integer> companionAppUids) {
- synchronized (ActivityManagerService.this) {
+ synchronized (mCompanionAppUidsMap) {
mCompanionAppUidsMap.put(userId, companionAppUids);
}
}
@Override
public boolean isAssociatedCompanionApp(int userId, int uid) {
- final Set<Integer> allUids = mCompanionAppUidsMap.get(userId);
- if (allUids == null) {
- return false;
+ synchronized (mCompanionAppUidsMap) {
+ final Set<Integer> allUids = mCompanionAppUidsMap.get(userId);
+ if (allUids == null) {
+ return false;
+ }
+ return allUids.contains(uid);
}
- return allUids.contains(uid);
}
@Override
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 6bb56c9..e885c14 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -79,6 +79,7 @@
sSecureSettingToTypeMap.put(Settings.Secure.MULTI_PRESS_TIMEOUT, int.class);
sSecureSettingToTypeMap.put(Settings.Secure.KEY_REPEAT_TIMEOUT_MS, int.class);
sSecureSettingToTypeMap.put(Settings.Secure.KEY_REPEAT_DELAY_MS, int.class);
+ sSecureSettingToTypeMap.put(Settings.Secure.KEY_REPEAT_ENABLED, int.class);
sSecureSettingToTypeMap.put(Settings.Secure.STYLUS_POINTER_ICON_ENABLED, int.class);
// add other secure settings here...
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index f0cc09f..22ec790 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -73,6 +73,7 @@
import static android.media.audio.Flags.roForegroundAudioControl;
import static android.os.Process.THREAD_GROUP_BACKGROUND;
import static android.os.Process.THREAD_GROUP_DEFAULT;
+import static android.os.Process.THREAD_GROUP_FOREGROUND_WINDOW;
import static android.os.Process.THREAD_GROUP_RESTRICTED;
import static android.os.Process.THREAD_GROUP_TOP_APP;
import static android.os.Process.THREAD_PRIORITY_DISPLAY;
@@ -116,6 +117,7 @@
import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
import static com.android.server.am.ProcessList.SCHED_GROUP_DEFAULT;
+import static com.android.server.am.ProcessList.SCHED_GROUP_FOREGROUND_WINDOW;
import static com.android.server.am.ProcessList.SCHED_GROUP_RESTRICTED;
import static com.android.server.am.ProcessList.SCHED_GROUP_TOP_APP;
import static com.android.server.am.ProcessList.SCHED_GROUP_TOP_APP_BOUND;
@@ -1731,6 +1733,11 @@
// The recently used non-top visible freeform app.
schedGroup = SCHED_GROUP_TOP_APP;
mAdjType = "perceptible-freeform-activity";
+ } else if ((flags
+ & WindowProcessController.ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE) != 0) {
+ // Currently the only case is from freeform apps which are not close to top.
+ schedGroup = SCHED_GROUP_FOREGROUND_WINDOW;
+ mAdjType = "vis-multi-window-activity";
}
foregroundActivities = true;
mHasVisibleActivities = true;
@@ -3438,6 +3445,9 @@
case SCHED_GROUP_RESTRICTED:
processGroup = THREAD_GROUP_RESTRICTED;
break;
+ case SCHED_GROUP_FOREGROUND_WINDOW:
+ processGroup = THREAD_GROUP_FOREGROUND_WINDOW;
+ break;
default:
processGroup = THREAD_GROUP_DEFAULT;
break;
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index 21842db..fb1c2e9 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -331,7 +331,7 @@
void forEachNewNode(int slot, @NonNull Consumer<OomAdjusterArgs> callback) {
ProcessRecordNode node = mLastNode[slot].mNext;
final ProcessRecordNode tail = mProcessRecordNodes[slot].TAIL;
- while (node != tail) {
+ while (node != null && node != tail) {
mTmpOomAdjusterArgs.mApp = node.mApp;
if (node.mApp == null) {
// TODO(b/336178916) - Temporary logging for root causing b/336178916.
@@ -365,7 +365,9 @@
}
// Save the next before calling callback, since that may change the node.mNext.
final ProcessRecordNode next = node.mNext;
- callback.accept(mTmpOomAdjusterArgs);
+ if (mTmpOomAdjusterArgs.mApp != null) {
+ callback.accept(mTmpOomAdjusterArgs);
+ }
// There are couple of cases:
// a) The current node is moved to another slot
// - for this case, we'd need to keep using the "next" node.
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index cb918a0..00250b4 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -303,6 +303,9 @@
// Activity manager's version of Process.THREAD_GROUP_TOP_APP
// Disambiguate between actual top app and processes bound to the top app
static final int SCHED_GROUP_TOP_APP_BOUND = 4;
+ // Activity manager's version of Process.THREAD_GROUP_FOREGROUND_WINDOW
+ // The priority is like between default and top-app.
+ static final int SCHED_GROUP_FOREGROUND_WINDOW = 5;
// The minimum number of cached apps we want to be able to keep around,
// without empty apps being able to push them out of memory.
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index b186eaa..262c76e 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -156,6 +156,9 @@
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
@@ -223,18 +226,9 @@
private static final int USER_SWITCH_CALLBACKS_TIMEOUT_MS = 5 * 1000;
/**
- * Amount of time waited for
- * {@link ActivityTaskManagerInternal.ScreenObserver#onKeyguardStateChanged} callbacks to be
- * called after calling {@link WindowManagerService#lockDeviceNow}.
- * Otherwise, we should throw a {@link RuntimeException} and never dismiss the
- * {@link UserSwitchingDialog}.
- */
- static final int SHOW_KEYGUARD_TIMEOUT_MS = 20 * 1000;
-
- /**
* Amount of time waited for {@link WindowManagerService#dismissKeyguard} callbacks to be
* called after dismissing the keyguard.
- * Otherwise, we should move on to dismiss the dialog {@link #dismissUserSwitchDialog}}
+ * Otherwise, we should move on to dismiss the dialog {@link #dismissUserSwitchDialog()}
* and report user switch is complete {@link #REPORT_USER_SWITCH_COMPLETE_MSG}.
*/
private static final int DISMISS_KEYGUARD_TIMEOUT_MS = 2 * 1000;
@@ -1986,10 +1980,18 @@
// it should be moved outside, but for now it's not as there are many calls to
// external components here afterwards
updateProfileRelatedCaches();
+ dispatchOnBeforeUserSwitching(userId);
mInjector.getWindowManager().setCurrentUser(userId);
mInjector.reportCurWakefulnessUsageEvent();
+ // Once the internal notion of the active user has switched, we lock the device
+ // with the option to show the user switcher on the keyguard.
if (userSwitchUiEnabled) {
mInjector.getWindowManager().setSwitchingUser(true);
+ // Only lock if the user has a secure keyguard PIN/Pattern/Pwd
+ if (mInjector.getKeyguardManager().isDeviceSecure(userId)) {
+ // Make sure the device is locked before moving on with the user switch
+ mInjector.lockDeviceNowAndWaitForKeyguardShown();
+ }
}
} else {
@@ -2284,6 +2286,25 @@
mUserSwitchObservers.finishBroadcast();
}
+ private void dispatchOnBeforeUserSwitching(@UserIdInt int newUserId) {
+ final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
+ t.traceBegin("dispatchOnBeforeUserSwitching-" + newUserId);
+ final int observerCount = mUserSwitchObservers.beginBroadcast();
+ for (int i = 0; i < observerCount; i++) {
+ final String name = "#" + i + " " + mUserSwitchObservers.getBroadcastCookie(i);
+ t.traceBegin("onBeforeUserSwitching-" + name);
+ try {
+ mUserSwitchObservers.getBroadcastItem(i).onBeforeUserSwitching(newUserId);
+ } catch (RemoteException e) {
+ // Ignore
+ } finally {
+ t.traceEnd();
+ }
+ }
+ mUserSwitchObservers.finishBroadcast();
+ t.traceEnd();
+ }
+
/** Called on handler thread */
@VisibleForTesting
void dispatchUserSwitchComplete(@UserIdInt int oldUserId, @UserIdInt int newUserId) {
@@ -2499,17 +2520,6 @@
final int observerCount = mUserSwitchObservers.beginBroadcast();
if (observerCount > 0) {
- for (int i = 0; i < observerCount; i++) {
- final String name = "#" + i + " " + mUserSwitchObservers.getBroadcastCookie(i);
- t.traceBegin("onBeforeUserSwitching-" + name);
- try {
- mUserSwitchObservers.getBroadcastItem(i).onBeforeUserSwitching(newUserId);
- } catch (RemoteException e) {
- // Ignore
- } finally {
- t.traceEnd();
- }
- }
final ArraySet<String> curWaitingUserSwitchCallbacks = new ArraySet<>();
synchronized (mLock) {
uss.switching = true;
@@ -2606,56 +2616,34 @@
@VisibleForTesting
void completeUserSwitch(int oldUserId, int newUserId) {
- final Runnable sendUserSwitchCompleteMessage = () -> {
- mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
- mHandler.sendMessage(mHandler.obtainMessage(
- REPORT_USER_SWITCH_COMPLETE_MSG, oldUserId, newUserId));
- };
- if (isUserSwitchUiEnabled()) {
- if (mInjector.getKeyguardManager().isDeviceSecure(newUserId)) {
- this.showKeyguard(() -> dismissUserSwitchDialog(sendUserSwitchCompleteMessage));
- } else {
- this.dismissKeyguard(() -> dismissUserSwitchDialog(sendUserSwitchCompleteMessage));
- }
+ final boolean isUserSwitchUiEnabled = isUserSwitchUiEnabled();
+ // serialize each conditional step
+ await(
+ // STEP 1 - If there is no challenge set, dismiss the keyguard right away
+ isUserSwitchUiEnabled && !mInjector.getKeyguardManager().isDeviceSecure(newUserId),
+ mInjector::dismissKeyguard,
+ () -> await(
+ // STEP 2 - If user switch ui was enabled, dismiss user switch dialog
+ isUserSwitchUiEnabled,
+ this::dismissUserSwitchDialog,
+ () -> {
+ // STEP 3 - Send REPORT_USER_SWITCH_COMPLETE_MSG to broadcast
+ // ACTION_USER_SWITCHED & call UserSwitchObservers.onUserSwitchComplete
+ mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
+ mHandler.sendMessage(mHandler.obtainMessage(
+ REPORT_USER_SWITCH_COMPLETE_MSG, oldUserId, newUserId));
+ }
+ ));
+ }
+
+ private void await(boolean condition, Consumer<Runnable> conditionalStep, Runnable nextStep) {
+ if (condition) {
+ conditionalStep.accept(nextStep);
} else {
- sendUserSwitchCompleteMessage.run();
+ nextStep.run();
}
}
- protected void showKeyguard(Runnable runnable) {
- runWithTimeout(mInjector::showKeyguard, SHOW_KEYGUARD_TIMEOUT_MS, runnable, () -> {
- throw new RuntimeException(
- "Keyguard is not shown in " + SHOW_KEYGUARD_TIMEOUT_MS + " ms.");
- }, "showKeyguard");
- }
-
- protected void dismissKeyguard(Runnable runnable) {
- runWithTimeout(mInjector::dismissKeyguard, DISMISS_KEYGUARD_TIMEOUT_MS, runnable, runnable,
- "dismissKeyguard");
- }
-
- private void runWithTimeout(Consumer<Runnable> task, int timeoutMs, Runnable onSuccess,
- Runnable onTimeout, String traceMsg) {
- final AtomicInteger state = new AtomicInteger(0); // state = 0 (RUNNING)
-
- asyncTraceBegin(traceMsg, 0);
-
- mHandler.postDelayed(() -> {
- if (state.compareAndSet(0, 1)) { // state = 1 (TIMEOUT)
- asyncTraceEnd(traceMsg, 0);
- Slogf.w(TAG, "Timeout: %s did not finish in %d ms", traceMsg, timeoutMs);
- onTimeout.run();
- }
- }, timeoutMs);
-
- task.accept(() -> {
- if (state.compareAndSet(0, 2)) { // state = 2 (SUCCESS)
- asyncTraceEnd(traceMsg, 0);
- onSuccess.run();
- }
- });
- }
-
private void moveUserToForeground(UserState uss, int newUserId) {
boolean homeInFront = mInjector.taskSupervisorSwitchUser(newUserId, uss);
if (homeInFront) {
@@ -4100,45 +4088,29 @@
return IStorageManager.Stub.asInterface(ServiceManager.getService("mount"));
}
- protected void showKeyguard(Runnable runnable) {
- if (getWindowManager().isKeyguardLocked()) {
- runnable.run();
- return;
- }
- getActivityTaskManagerInternal().registerScreenObserver(
- new ActivityTaskManagerInternal.ScreenObserver() {
- @Override
- public void onAwakeStateChanged(boolean isAwake) {
-
- }
-
- @Override
- public void onKeyguardStateChanged(boolean isShowing) {
- if (isShowing) {
- getActivityTaskManagerInternal().unregisterScreenObserver(this);
- runnable.run();
- }
- }
- }
- );
- getWindowManager().lockDeviceNow();
- }
-
protected void dismissKeyguard(Runnable runnable) {
+ final AtomicBoolean isFirst = new AtomicBoolean(true);
+ final Runnable runOnce = () -> {
+ if (isFirst.getAndSet(false)) {
+ runnable.run();
+ }
+ };
+
+ mHandler.postDelayed(runOnce, DISMISS_KEYGUARD_TIMEOUT_MS);
getWindowManager().dismissKeyguard(new IKeyguardDismissCallback.Stub() {
@Override
public void onDismissError() throws RemoteException {
- runnable.run();
+ mHandler.post(runOnce);
}
@Override
public void onDismissSucceeded() throws RemoteException {
- runnable.run();
+ mHandler.post(runOnce);
}
@Override
public void onDismissCancelled() throws RemoteException {
- runnable.run();
+ mHandler.post(runOnce);
}
}, /* message= */ null);
}
@@ -4164,5 +4136,43 @@
void onSystemUserVisibilityChanged(boolean visible) {
getUserManagerInternal().onSystemUserVisibilityChanged(visible);
}
+
+ void lockDeviceNowAndWaitForKeyguardShown() {
+ if (getWindowManager().isKeyguardLocked()) {
+ return;
+ }
+
+ final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
+ t.traceBegin("lockDeviceNowAndWaitForKeyguardShown");
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ ActivityTaskManagerInternal.ScreenObserver screenObserver =
+ new ActivityTaskManagerInternal.ScreenObserver() {
+ @Override
+ public void onAwakeStateChanged(boolean isAwake) {
+
+ }
+
+ @Override
+ public void onKeyguardStateChanged(boolean isShowing) {
+ if (isShowing) {
+ latch.countDown();
+ }
+ }
+ };
+
+ getActivityTaskManagerInternal().registerScreenObserver(screenObserver);
+ getWindowManager().lockDeviceNow();
+ try {
+ if (!latch.await(20, TimeUnit.SECONDS)) {
+ throw new RuntimeException("Keyguard is not shown in 20 seconds");
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ } finally {
+ getActivityTaskManagerInternal().unregisterScreenObserver(screenObserver);
+ t.traceEnd();
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index e145c90..55d9c6e 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -533,7 +533,8 @@
AudioDeviceInfo.TYPE_BLE_SPEAKER,
AudioDeviceInfo.TYPE_LINE_ANALOG,
AudioDeviceInfo.TYPE_HDMI,
- AudioDeviceInfo.TYPE_AUX_LINE
+ AudioDeviceInfo.TYPE_AUX_LINE,
+ AudioDeviceInfo.TYPE_BUS
};
/*package */ static boolean isValidCommunicationDevice(@NonNull AudioDeviceInfo device) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index c3d09bb..99404428 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -4006,6 +4006,7 @@
&& isFullVolumeDevice(device);
boolean tvConditions = mHdmiTvClient != null
&& mHdmiSystemAudioSupported
+ && isFullVolumeDevice(device)
&& !isAbsoluteVolumeDevice(device)
&& !isA2dpAbsoluteVolumeDevice(device);
diff --git a/services/core/java/com/android/server/cpu/CpuMonitorService.java b/services/core/java/com/android/server/cpu/CpuMonitorService.java
index 88ff7e4..2cadbc58 100644
--- a/services/core/java/com/android/server/cpu/CpuMonitorService.java
+++ b/services/core/java/com/android/server/cpu/CpuMonitorService.java
@@ -216,7 +216,7 @@
@Override
public void onBootPhase(int phase) {
- if (phase != PHASE_BOOT_COMPLETED) {
+ if (phase != PHASE_BOOT_COMPLETED || mHandler == null) {
return;
}
Slogf.i(TAG, "Stopping periodic cpuset reading on boot complete");
diff --git a/services/core/java/com/android/server/display/DisplayControl.java b/services/core/java/com/android/server/display/DisplayControl.java
index 38eb416..ddea285 100644
--- a/services/core/java/com/android/server/display/DisplayControl.java
+++ b/services/core/java/com/android/server/display/DisplayControl.java
@@ -109,7 +109,7 @@
/**
* Sets the HDR conversion mode for the device.
*
- * Returns the system preferred Hdr output type nn case when HDR conversion mode is
+ * Returns the system preferred HDR output type in case when HDR conversion mode is
* {@link android.hardware.display.HdrConversionMode#HDR_CONVERSION_SYSTEM}.
* Returns Hdr::INVALID in other cases.
* @hide
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 93bd926..acf4db3 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -318,13 +318,16 @@
*/
public Display.HdrCapabilities hdrCapabilities;
+ /** When true, all HDR capabilities are hidden from public APIs */
+ public boolean isForceSdr;
+
/**
* Indicates whether this display supports Auto Low Latency Mode.
*/
public boolean allmSupported;
/**
- * Indicates whether this display suppors Game content type.
+ * Indicates whether this display supports Game content type.
*/
public boolean gameContentTypeSupported;
@@ -516,6 +519,7 @@
|| !Arrays.equals(supportedModes, other.supportedModes)
|| !Arrays.equals(supportedColorModes, other.supportedColorModes)
|| !Objects.equals(hdrCapabilities, other.hdrCapabilities)
+ || isForceSdr != other.isForceSdr
|| allmSupported != other.allmSupported
|| gameContentTypeSupported != other.gameContentTypeSupported
|| densityDpi != other.densityDpi
@@ -560,6 +564,7 @@
colorMode = other.colorMode;
supportedColorModes = other.supportedColorModes;
hdrCapabilities = other.hdrCapabilities;
+ isForceSdr = other.isForceSdr;
allmSupported = other.allmSupported;
gameContentTypeSupported = other.gameContentTypeSupported;
densityDpi = other.densityDpi;
@@ -603,6 +608,7 @@
sb.append(", colorMode ").append(colorMode);
sb.append(", supportedColorModes ").append(Arrays.toString(supportedColorModes));
sb.append(", hdrCapabilities ").append(hdrCapabilities);
+ sb.append(", isForceSdr ").append(isForceSdr);
sb.append(", allmSupported ").append(allmSupported);
sb.append(", gameContentTypeSupported ").append(gameContentTypeSupported);
sb.append(", density ").append(densityDpi);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 3c2167e..e7fd8f7 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -48,6 +48,7 @@
import static android.provider.Settings.Secure.RESOLUTION_MODE_FULL;
import static android.provider.Settings.Secure.RESOLUTION_MODE_HIGH;
import static android.provider.Settings.Secure.RESOLUTION_MODE_UNKNOWN;
+import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID;
import static com.android.server.display.layout.Layout.Display.POSITION_REAR;
@@ -284,7 +285,7 @@
@GuardedBy("mSyncRoot")
private int[] mUserDisabledHdrTypes = {};
@Display.HdrCapabilities.HdrType
- private int[] mSupportedHdrOutputType;
+ private int[] mSupportedHdrOutputTypes;
@GuardedBy("mSyncRoot")
private boolean mAreUserDisabledHdrTypesAllowed = true;
@@ -299,10 +300,10 @@
// HDR conversion mode chosen by user
@GuardedBy("mSyncRoot")
private HdrConversionMode mHdrConversionMode = null;
- // Actual HDR conversion mode, which takes app overrides into account.
- private HdrConversionMode mOverrideHdrConversionMode = null;
+ // Whether app has disabled HDR conversion
+ private boolean mShouldDisableHdrConversion = false;
@GuardedBy("mSyncRoot")
- private int mSystemPreferredHdrOutputType = Display.HdrCapabilities.HDR_TYPE_INVALID;
+ private int mSystemPreferredHdrOutputType = HDR_TYPE_INVALID;
// The synchronization root for the display manager.
@@ -1419,7 +1420,8 @@
}
}
- private void setUserDisabledHdrTypesInternal(int[] userDisabledHdrTypes) {
+ @VisibleForTesting
+ void setUserDisabledHdrTypesInternal(int[] userDisabledHdrTypes) {
synchronized (mSyncRoot) {
if (userDisabledHdrTypes == null) {
Slog.e(TAG, "Null is not an expected argument to "
@@ -1437,6 +1439,7 @@
if (Arrays.equals(mUserDisabledHdrTypes, userDisabledHdrTypes)) {
return;
}
+
String userDisabledFormatsString = "";
if (userDisabledHdrTypes.length != 0) {
userDisabledFormatsString = TextUtils.join(",",
@@ -1452,6 +1455,15 @@
handleLogicalDisplayChangedLocked(display);
});
}
+ /* Note: it may be expected to reset the Conversion Mode when an HDR type is enabled
+ and the Conversion Mode is set to System Preferred. This is handled in the Settings
+ code because in the special case where HDR is indirectly disabled by Force SDR
+ Conversion, manually enabling HDR is not recognized as an action that reduces the
+ disabled HDR count. Thus, this case needs to be checked in the Settings code when we
+ know we're enabling an HDR mode. If we split checking for SystemConversion and
+ isForceSdr in two places, we may have duplicate calls to resetting to System Conversion
+ and get two black screens.
+ */
}
}
@@ -1464,19 +1476,20 @@
return true;
}
- private void setAreUserDisabledHdrTypesAllowedInternal(
+ @VisibleForTesting
+ void setAreUserDisabledHdrTypesAllowedInternal(
boolean areUserDisabledHdrTypesAllowed) {
synchronized (mSyncRoot) {
if (mAreUserDisabledHdrTypesAllowed == areUserDisabledHdrTypesAllowed) {
return;
}
mAreUserDisabledHdrTypesAllowed = areUserDisabledHdrTypesAllowed;
- if (mUserDisabledHdrTypes.length == 0) {
- return;
- }
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.ARE_USER_DISABLED_HDR_FORMATS_ALLOWED,
areUserDisabledHdrTypesAllowed ? 1 : 0);
+ if (mUserDisabledHdrTypes.length == 0) {
+ return;
+ }
int userDisabledHdrTypes[] = {};
if (!mAreUserDisabledHdrTypesAllowed) {
userDisabledHdrTypes = mUserDisabledHdrTypes;
@@ -1487,6 +1500,14 @@
display.setUserDisabledHdrTypes(finalUserDisabledHdrTypes);
handleLogicalDisplayChangedLocked(display);
});
+ // When HDR conversion mode is set to SYSTEM, modification to
+ // areUserDisabledHdrTypesAllowed requires refreshing the HDR conversion mode to tell
+ // the system which HDR types it is not allowed to use.
+ if (getHdrConversionModeInternal().getConversionMode()
+ == HdrConversionMode.HDR_CONVERSION_SYSTEM) {
+ setHdrConversionModeInternal(
+ new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM));
+ }
}
}
@@ -2357,7 +2378,7 @@
final int preferredHdrOutputType =
hdrConversionMode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_FORCE
? hdrConversionMode.getPreferredHdrOutputType()
- : Display.HdrCapabilities.HDR_TYPE_INVALID;
+ : HDR_TYPE_INVALID;
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.HDR_FORCE_CONVERSION_TYPE, preferredHdrOutputType);
}
@@ -2370,7 +2391,7 @@
? Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.HDR_FORCE_CONVERSION_TYPE,
Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION)
- : Display.HdrCapabilities.HDR_TYPE_INVALID;
+ : HDR_TYPE_INVALID;
mHdrConversionMode = new HdrConversionMode(conversionMode, preferredHdrOutputType);
setHdrConversionModeInternal(mHdrConversionMode);
}
@@ -2507,22 +2528,38 @@
});
}
+ /**
+ * Returns the HDR output types that are supported by the device's HDR conversion capabilities,
+ * stripping out any user-disabled HDR types if mAreUserDisabledHdrTypesAllowed is false.
+ */
@GuardedBy("mSyncRoot")
- private int[] getEnabledAutoHdrTypesLocked() {
- IntArray autoHdrOutputTypesArray = new IntArray();
+ @VisibleForTesting
+ int[] getEnabledHdrOutputTypesLocked() {
+ if (mAreUserDisabledHdrTypesAllowed) {
+ return getSupportedHdrOutputTypesInternal();
+ }
+ // Strip out all HDR formats that are currently user-disabled
+ IntArray enabledHdrOutputTypesArray = new IntArray();
for (int type : getSupportedHdrOutputTypesInternal()) {
- boolean isDisabled = false;
+ boolean isEnabled = true;
for (int disabledType : mUserDisabledHdrTypes) {
if (type == disabledType) {
- isDisabled = true;
+ isEnabled = false;
break;
}
}
- if (!isDisabled) {
- autoHdrOutputTypesArray.add(type);
+ if (isEnabled) {
+ enabledHdrOutputTypesArray.add(type);
}
}
- return autoHdrOutputTypesArray.toArray();
+ return enabledHdrOutputTypesArray.toArray();
+ }
+
+ @VisibleForTesting
+ int[] getEnabledHdrOutputTypes() {
+ synchronized (mSyncRoot) {
+ return getEnabledHdrOutputTypesLocked();
+ }
}
@GuardedBy("mSyncRoot")
@@ -2531,7 +2568,7 @@
final int preferredHdrOutputType =
mode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM
? mSystemPreferredHdrOutputType : mode.getPreferredHdrOutputType();
- if (preferredHdrOutputType != Display.HdrCapabilities.HDR_TYPE_INVALID) {
+ if (preferredHdrOutputType != HDR_TYPE_INVALID) {
int[] hdrTypesWithLatency = mInjector.getHdrOutputTypesWithLatency();
return ArrayUtils.contains(hdrTypesWithLatency, preferredHdrOutputType);
}
@@ -2565,41 +2602,57 @@
if (!mInjector.getHdrOutputConversionSupport()) {
return;
}
- int[] autoHdrOutputTypes = null;
+
synchronized (mSyncRoot) {
if (hdrConversionMode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM
&& hdrConversionMode.getPreferredHdrOutputType()
- != Display.HdrCapabilities.HDR_TYPE_INVALID) {
+ != HDR_TYPE_INVALID) {
throw new IllegalArgumentException("preferredHdrOutputType must not be set if"
+ " the conversion mode is HDR_CONVERSION_SYSTEM");
}
mHdrConversionMode = hdrConversionMode;
storeHdrConversionModeLocked(mHdrConversionMode);
- // For auto mode, all supported HDR types are allowed except the ones specifically
- // disabled by the user.
+ // If the HDR conversion is HDR_CONVERSION_SYSTEM, all supported HDR types are allowed
+ // except the ones specifically disabled by the user.
+ int[] enabledHdrOutputTypes = null;
if (hdrConversionMode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM) {
- autoHdrOutputTypes = getEnabledAutoHdrTypesLocked();
+ enabledHdrOutputTypes = getEnabledHdrOutputTypesLocked();
}
int conversionMode = hdrConversionMode.getConversionMode();
int preferredHdrType = hdrConversionMode.getPreferredHdrOutputType();
+
// If the HDR conversion is disabled by an app through WindowManager.LayoutParams, then
// set HDR conversion mode to HDR_CONVERSION_PASSTHROUGH.
- if (mOverrideHdrConversionMode == null) {
- // HDR_CONVERSION_FORCE with HDR_TYPE_INVALID is used to represent forcing SDR type.
- // But, internally SDR is selected by using passthrough mode.
- if (conversionMode == HdrConversionMode.HDR_CONVERSION_FORCE
- && preferredHdrType == Display.HdrCapabilities.HDR_TYPE_INVALID) {
- conversionMode = HdrConversionMode.HDR_CONVERSION_PASSTHROUGH;
- }
+ if (mShouldDisableHdrConversion) {
+ conversionMode = HdrConversionMode.HDR_CONVERSION_PASSTHROUGH;
+ preferredHdrType = -1;
+ enabledHdrOutputTypes = null;
} else {
- conversionMode = mOverrideHdrConversionMode.getConversionMode();
- preferredHdrType = mOverrideHdrConversionMode.getPreferredHdrOutputType();
- autoHdrOutputTypes = null;
+ // HDR_CONVERSION_FORCE with HDR_TYPE_INVALID is used to represent forcing SDR type.
+ // But, internally SDR is forced by using passthrough mode and not reporting any
+ // HDR capabilities to apps.
+ if (conversionMode == HdrConversionMode.HDR_CONVERSION_FORCE
+ && preferredHdrType == HDR_TYPE_INVALID) {
+ conversionMode = HdrConversionMode.HDR_CONVERSION_PASSTHROUGH;
+ mLogicalDisplayMapper.forEachLocked(
+ logicalDisplay -> {
+ if (logicalDisplay.setIsForceSdr(true)) {
+ handleLogicalDisplayChangedLocked(logicalDisplay);
+ }
+ });
+ } else {
+ mLogicalDisplayMapper.forEachLocked(
+ logicalDisplay -> {
+ if (logicalDisplay.setIsForceSdr(false)) {
+ handleLogicalDisplayChangedLocked(logicalDisplay);
+ }
+ });
+ }
}
mSystemPreferredHdrOutputType = mInjector.setHdrConversionMode(
- conversionMode, preferredHdrType, autoHdrOutputTypes);
+ conversionMode, preferredHdrType, enabledHdrOutputTypes);
}
}
@@ -2621,8 +2674,8 @@
}
HdrConversionMode mode;
synchronized (mSyncRoot) {
- mode = mOverrideHdrConversionMode != null
- ? mOverrideHdrConversionMode
+ mode = mShouldDisableHdrConversion
+ ? new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH)
: mHdrConversionMode;
// Handle default: PASSTHROUGH. Don't include the system-preferred type.
if (mode == null
@@ -2630,8 +2683,6 @@
return new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH);
}
// Handle default or current mode: SYSTEM. Include the system preferred type.
- // mOverrideHdrConversionMode and mHdrConversionMode do not include the system
- // preferred type, it is kept separately in mSystemPreferredHdrOutputType.
if (mode == null
|| mode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM) {
return new HdrConversionMode(
@@ -2642,10 +2693,10 @@
}
private @Display.HdrCapabilities.HdrType int[] getSupportedHdrOutputTypesInternal() {
- if (mSupportedHdrOutputType == null) {
- mSupportedHdrOutputType = mInjector.getSupportedHdrOutputTypes();
+ if (mSupportedHdrOutputTypes == null) {
+ mSupportedHdrOutputTypes = mInjector.getSupportedHdrOutputTypes();
}
- return mSupportedHdrOutputType;
+ return mSupportedHdrOutputTypes;
}
void setShouldAlwaysRespectAppRequestedModeInternal(boolean enabled) {
@@ -2831,15 +2882,9 @@
// HDR conversion is disabled in two cases:
// - HDR conversion introduces latency and minimal post-processing is requested
// - app requests to disable HDR conversion
- if (mOverrideHdrConversionMode == null && (disableHdrConversion
- || disableHdrConversionForLatency)) {
- mOverrideHdrConversionMode =
- new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH);
- setHdrConversionModeInternal(mHdrConversionMode);
- handleLogicalDisplayChangedLocked(display);
- } else if (mOverrideHdrConversionMode != null && !disableHdrConversion
- && !disableHdrConversionForLatency) {
- mOverrideHdrConversionMode = null;
+ boolean previousShouldDisableHdrConversion = mShouldDisableHdrConversion;
+ mShouldDisableHdrConversion = disableHdrConversion || disableHdrConversionForLatency;
+ if (previousShouldDisableHdrConversion != mShouldDisableHdrConversion) {
setHdrConversionModeInternal(mHdrConversionMode);
handleLogicalDisplayChangedLocked(display);
}
@@ -3530,9 +3575,9 @@
}
int setHdrConversionMode(int conversionMode, int preferredHdrOutputType,
- int[] autoHdrTypes) {
+ int[] allowedHdrOutputTypes) {
return DisplayControl.setHdrConversionMode(conversionMode, preferredHdrOutputType,
- autoHdrTypes);
+ allowedHdrOutputTypes);
}
@Display.HdrCapabilities.HdrType
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index e8be8a4..007e3a8 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -518,6 +518,7 @@
deviceInfo.supportedColorModes,
deviceInfo.supportedColorModes.length);
mBaseDisplayInfo.hdrCapabilities = deviceInfo.hdrCapabilities;
+ mBaseDisplayInfo.isForceSdr = deviceInfo.isForceSdr;
mBaseDisplayInfo.userDisabledHdrTypes = mUserDisabledHdrTypes;
mBaseDisplayInfo.minimalPostProcessingSupported =
deviceInfo.allmSupported || deviceInfo.gameContentTypeSupported;
@@ -899,6 +900,29 @@
}
/**
+ * Checks whether display is of the type where HDR settings are relevant, and then sets
+ * whether Force SDR conversion mode is active. isForceSdr is checked by the Display when
+ * returning HDR capabilities.
+ *
+ * @param isForceSdr Whether Force SDR conversion mode is active
+ * @return Whether Display Manager should call handleLogicalDisplayChangedLocked()
+ */
+ public boolean setIsForceSdr(boolean isForceSdr) {
+ int displayType = getDisplayInfoLocked().type;
+ boolean isTargetDisplayType = displayType == Display.TYPE_INTERNAL
+ || displayType == Display.TYPE_EXTERNAL
+ || displayType == Display.TYPE_OVERLAY;
+
+ boolean handleLogicalDisplayChangedLocked = false;
+ if (isTargetDisplayType && mBaseDisplayInfo.isForceSdr != isForceSdr) {
+ mBaseDisplayInfo.isForceSdr = isForceSdr;
+ mInfo.set(null);
+ handleLogicalDisplayChangedLocked = true;
+ }
+ return handleLogicalDisplayChangedLocked;
+ }
+
+ /**
* Swap the underlying {@link DisplayDevice} with the specified LogicalDisplay.
*
* @param targetDisplay The display with which to swap display-devices.
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 ea240c7..9d04682 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -196,12 +196,7 @@
File signatureFile = new File(dir, FONT_SIGNATURE_FILE);
if (!signatureFile.exists()) {
Slog.i(TAG, "The signature file is missing.");
- if (com.android.text.flags.Flags.fixFontUpdateFailure()) {
- return;
- } else {
- FileUtils.deleteContentsAndDir(dir);
- continue;
- }
+ return;
}
byte[] signature;
try {
@@ -226,39 +221,33 @@
FontFileInfo fontFileInfo = validateFontFile(fontFile, signature);
if (fontConfig == null) {
- if (com.android.text.flags.Flags.fixFontUpdateFailure()) {
- // Use preinstalled font config for checking revision number.
- fontConfig = mConfigSupplier.apply(Collections.emptyMap());
- } else {
- fontConfig = getSystemFontConfig();
- }
+ // Use preinstalled font config for checking revision number.
+ fontConfig = mConfigSupplier.apply(Collections.emptyMap());
}
addFileToMapIfSameOrNewer(fontFileInfo, fontConfig, true /* deleteOldFile */);
}
- if (com.android.text.flags.Flags.fixFontUpdateFailure()) {
- // Treat as error if post script name of font family was not installed.
- for (int i = 0; i < config.fontFamilies.size(); ++i) {
- FontUpdateRequest.Family family = config.fontFamilies.get(i);
- for (int j = 0; j < family.getFonts().size(); ++j) {
- FontUpdateRequest.Font font = family.getFonts().get(j);
- if (mFontFileInfoMap.containsKey(font.getPostScriptName())) {
- continue;
- }
-
- if (fontConfig == null) {
- fontConfig = mConfigSupplier.apply(Collections.emptyMap());
- }
-
- if (getFontByPostScriptName(font.getPostScriptName(), fontConfig) != null) {
- continue;
- }
-
- Slog.e(TAG, "Unknown font that has PostScript name "
- + font.getPostScriptName() + " is requested in FontFamily "
- + family.getName());
- return;
+ // Treat as error if post script name of font family was not installed.
+ for (int i = 0; i < config.fontFamilies.size(); ++i) {
+ FontUpdateRequest.Family family = config.fontFamilies.get(i);
+ for (int j = 0; j < family.getFonts().size(); ++j) {
+ FontUpdateRequest.Font font = family.getFonts().get(j);
+ if (mFontFileInfoMap.containsKey(font.getPostScriptName())) {
+ continue;
}
+
+ if (fontConfig == null) {
+ fontConfig = mConfigSupplier.apply(Collections.emptyMap());
+ }
+
+ if (getFontByPostScriptName(font.getPostScriptName(), fontConfig) != null) {
+ continue;
+ }
+
+ Slog.e(TAG, "Unknown font that has PostScript name "
+ + font.getPostScriptName() + " is requested in FontFamily "
+ + family.getName());
+ return;
}
}
@@ -273,9 +262,7 @@
mFontFileInfoMap.clear();
mLastModifiedMillis = 0;
FileUtils.deleteContents(mFilesDir);
- if (com.android.text.flags.Flags.fixFontUpdateFailure()) {
- mConfigFile.delete();
- }
+ mConfigFile.delete();
}
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 5db17bb..d0ad6fc 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -171,6 +171,17 @@
addAndStartAction(
new HotplugDetectionAction(HdmiCecLocalDevicePlayback.this));
}
+
+ if (mService.isHdmiControlEnhancedBehaviorFlagEnabled()) {
+ List<PowerStatusMonitorActionFromPlayback>
+ powerStatusMonitorActionsFromPlayback =
+ getActions(PowerStatusMonitorActionFromPlayback.class);
+ if (powerStatusMonitorActionsFromPlayback.isEmpty()) {
+ addAndStartAction(
+ new PowerStatusMonitorActionFromPlayback(
+ HdmiCecLocalDevicePlayback.this));
+ }
+ }
}
});
addAndStartAction(action);
@@ -686,6 +697,7 @@
removeAction(DeviceDiscoveryAction.class);
removeAction(HotplugDetectionAction.class);
removeAction(NewDeviceAction.class);
+ removeAction(PowerStatusMonitorActionFromPlayback.class);
super.disableDevice(initiatedByCec, callback);
clearDeviceInfoList();
checkIfPendingActionsCleared();
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 271836a..8e41d18 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -16,6 +16,8 @@
package com.android.server.hdmi;
+import static android.media.tv.flags.Flags.hdmiControlEnhancedBehavior;
+
import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_ADD_DEVICE;
import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE;
import static android.hardware.hdmi.HdmiControlManager.EARC_FEATURE_DISABLED;
@@ -1378,7 +1380,8 @@
@ServiceThreadOnly
private List<Integer> getCecLocalDeviceTypes() {
ArrayList<Integer> allLocalDeviceTypes = new ArrayList<>(mCecLocalDevices);
- if (isDsmEnabled() && !allLocalDeviceTypes.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)
+ if (!isTvDevice() && isDsmEnabled()
+ && !allLocalDeviceTypes.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)
&& isArcSupported() && mSoundbarModeFeatureFlagEnabled) {
allLocalDeviceTypes.add(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
}
@@ -1685,7 +1688,11 @@
private void sendCecCommandWithRetries(HdmiCecMessage command,
@Nullable SendMessageCallback callback) {
assertRunOnServiceThread();
- HdmiCecLocalDevice localDevice = getAllCecLocalDevices().get(0);
+ List<HdmiCecLocalDevice> devices = getAllCecLocalDevices();
+ if (devices.isEmpty()) {
+ return;
+ }
+ HdmiCecLocalDevice localDevice = devices.get(0);
if (localDevice != null) {
sendCecCommandWithoutRetries(command, new SendMessageCallback() {
@Override
@@ -5137,4 +5144,8 @@
tv().startArcAction(enabled, callback);
}
}
+
+ protected boolean isHdmiControlEnhancedBehaviorFlagEnabled() {
+ return hdmiControlEnhancedBehavior();
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/PowerStatusMonitorActionFromPlayback.java b/services/core/java/com/android/server/hdmi/PowerStatusMonitorActionFromPlayback.java
new file mode 100644
index 0000000..9a3cde1
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/PowerStatusMonitorActionFromPlayback.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_STANDBY;
+
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * This action is used by playback devices to query TV's power status such that they can go to
+ * standby when the TV reports power off.
+ */
+public class PowerStatusMonitorActionFromPlayback extends HdmiCecFeatureAction {
+ private static final String TAG = "PowerStatusMonitorActionFromPlayback";
+
+ // State that waits for <Report Power Status> once sending <Give Device Power Status>
+ // to all external devices.
+ private static final int STATE_WAIT_FOR_REPORT_POWER_STATUS = 1;
+ // State that waits for next monitoring.
+ private static final int STATE_WAIT_FOR_NEXT_MONITORING = 2;
+ // Monitoring interval (60s)
+ @VisibleForTesting
+ protected static final int MONITORING_INTERVAL_MS = 60000;
+ // Timeout once sending <Give Device Power Status>
+ private static final int REPORT_POWER_STATUS_TIMEOUT_MS = 5000;
+ // Maximum number of retries in case the <Give Device Power Status> failed being sent or times
+ // out.
+ private static final int GIVE_POWER_STATUS_FOR_SOURCE_RETRIES = 5;
+ private int mPowerStatusRetries = 0;
+
+ PowerStatusMonitorActionFromPlayback(HdmiCecLocalDevice source) {
+ super(source);
+ }
+
+ @Override
+ boolean start() {
+ // Start after timeout since the device just finished allocation.
+ mState = STATE_WAIT_FOR_NEXT_MONITORING;
+ addTimer(mState, MONITORING_INTERVAL_MS);
+ return true;
+ }
+
+ @Override
+ boolean processCommand(HdmiCecMessage cmd) {
+ if (mState == STATE_WAIT_FOR_REPORT_POWER_STATUS
+ && cmd.getOpcode() == Constants.MESSAGE_REPORT_POWER_STATUS
+ && cmd.getSource() == Constants.ADDR_TV) {
+ return handleReportPowerStatusFromTv(cmd);
+ }
+ return false;
+ }
+
+ private boolean handleReportPowerStatusFromTv(HdmiCecMessage cmd) {
+ int powerStatus = cmd.getParams()[0] & 0xFF;
+ if (powerStatus == POWER_STATUS_STANDBY) {
+ Slog.d(TAG, "TV reported it turned off, going to sleep.");
+ source().getService().standby();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ void handleTimerEvent(int state) {
+ switch (mState) {
+ case STATE_WAIT_FOR_NEXT_MONITORING:
+ mPowerStatusRetries = 0;
+ queryPowerStatus();
+ break;
+ case STATE_WAIT_FOR_REPORT_POWER_STATUS:
+ handleTimeout();
+ break;
+ }
+ }
+
+ private void queryPowerStatus() {
+ sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(),
+ Constants.ADDR_TV));
+
+ mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
+ addTimer(mState, REPORT_POWER_STATUS_TIMEOUT_MS);
+ }
+
+ private void handleTimeout() {
+ if (mState == STATE_WAIT_FOR_REPORT_POWER_STATUS) {
+ if (mPowerStatusRetries++ < GIVE_POWER_STATUS_FOR_SOURCE_RETRIES) {
+ queryPowerStatus();
+ } else {
+ mPowerStatusRetries = 0;
+ mState = STATE_WAIT_FOR_NEXT_MONITORING;
+ addTimer(mState, MONITORING_INTERVAL_MS);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 44d6753..65adaba 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -16,6 +16,9 @@
package com.android.server.input;
+import static android.Manifest.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW;
+import static android.content.PermissionChecker.PERMISSION_GRANTED;
+import static android.content.PermissionChecker.PID_UNKNOWN;
import static android.provider.DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT;
import static android.view.KeyEvent.KEYCODE_UNKNOWN;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
@@ -37,6 +40,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.PermissionChecker;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.graphics.PixelFormat;
@@ -121,6 +125,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.inputmethod.InputMethodSubtypeHandle;
import com.android.internal.os.SomeArgs;
+import com.android.internal.policy.KeyInterceptionInfo;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.server.DisplayThread;
@@ -130,6 +135,7 @@
import com.android.server.input.debug.FocusEventDebugView;
import com.android.server.input.debug.TouchpadDebugViewController;
import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.wm.WindowManagerInternal;
import libcore.io.IoUtils;
@@ -177,6 +183,7 @@
private final InputManagerHandler mHandler;
private DisplayManagerInternal mDisplayManagerInternal;
+ private WindowManagerInternal mWindowManagerInternal;
private PackageManagerInternal mPackageManagerInternal;
private final File mDoubleTouchGestureEnableFile;
@@ -449,6 +456,14 @@
void registerLocalService(InputManagerInternal localService) {
LocalServices.addService(InputManagerInternal.class, localService);
}
+
+ KeyboardBacklightControllerInterface getKeyboardBacklightController(
+ NativeInputManagerService nativeService, PersistentDataStore dataStore) {
+ return InputFeatureFlagProvider.isKeyboardBacklightControlEnabled()
+ ? new KeyboardBacklightController(mContext, nativeService, dataStore,
+ mLooper, mUEventManager)
+ : new KeyboardBacklightControllerInterface() {};
+ }
}
public InputManagerService(Context context) {
@@ -472,10 +487,7 @@
injector.getLooper(), this) : null;
mBatteryController = new BatteryController(mContext, mNative, injector.getLooper(),
injector.getUEventManager());
- mKeyboardBacklightController = InputFeatureFlagProvider.isKeyboardBacklightControlEnabled()
- ? new KeyboardBacklightController(mContext, mNative, mDataStore,
- injector.getLooper(), injector.getUEventManager())
- : new KeyboardBacklightControllerInterface() {};
+ mKeyboardBacklightController = injector.getKeyboardBacklightController(mNative, mDataStore);
mStickyModifierStateController = new StickyModifierStateController();
mKeyGestureController = new KeyGestureController(mContext, injector.getLooper());
mKeyboardLedController = new KeyboardLedController(mContext, injector.getLooper(),
@@ -548,6 +560,7 @@
}
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
+ mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mSettingsObserver.registerAndUpdate();
@@ -598,6 +611,8 @@
mKeyRemapper.systemRunning();
mPointerIconCache.systemRunning();
mKeyboardGlyphManager.systemRunning();
+
+ initKeyGestures();
}
private void reloadDeviceAliases() {
@@ -2471,6 +2486,11 @@
long interceptKeyBeforeDispatching(IBinder focus, KeyEvent event, int policyFlags) {
final long keyNotConsumed = 0;
long value = keyNotConsumed;
+ // TODO(b/358569822) Remove below once we have nicer API for listening to shortcuts
+ if ((event.isMetaPressed() || KeyEvent.isMetaKey(event.getKeyCode()))
+ && shouldInterceptShortcuts(focus)) {
+ return keyNotConsumed;
+ }
if (useKeyGestureEventHandler()) {
value = mKeyGestureController.interceptKeyBeforeDispatching(focus, event, policyFlags);
}
@@ -2481,6 +2501,69 @@
return value;
}
+ private boolean shouldInterceptShortcuts(IBinder focusedToken) {
+ KeyInterceptionInfo info =
+ mWindowManagerInternal.getKeyInterceptionInfoFromToken(focusedToken);
+ boolean hasInterceptWindowFlag = info != null && (info.layoutParamsPrivateFlags
+ & WindowManager.LayoutParams.PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS) != 0;
+ return hasInterceptWindowFlag && PermissionChecker.checkPermissionForDataDelivery(mContext,
+ OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW, PID_UNKNOWN, info.windowOwnerUid,
+ null, null, null) == PERMISSION_GRANTED;
+ }
+
+ private void initKeyGestures() {
+ if (!useKeyGestureEventHandler()) {
+ return;
+ }
+ InputManager im = Objects.requireNonNull(mContext.getSystemService(InputManager.class));
+ im.registerKeyGestureEventHandler(new InputManager.KeyGestureEventHandler() {
+ @Override
+ public boolean handleKeyGestureEvent(@NonNull KeyGestureEvent event,
+ @Nullable IBinder focussedToken) {
+ int deviceId = event.getDeviceId();
+ boolean complete = event.getAction() == KeyGestureEvent.ACTION_GESTURE_COMPLETE
+ && !event.isCancelled();
+ switch (event.getKeyGestureType()) {
+ case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP:
+ if (complete) {
+ mKeyboardBacklightController.incrementKeyboardBacklight(deviceId);
+ }
+ return true;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN:
+ if (complete) {
+ mKeyboardBacklightController.decrementKeyboardBacklight(deviceId);
+ }
+ return true;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE:
+ // TODO(b/367748270): Add functionality to turn keyboard backlight on/off.
+ return true;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK:
+ if (complete) {
+ mNative.toggleCapsLock(deviceId);
+ }
+ return true;
+ default:
+ return false;
+
+ }
+ }
+
+ @Override
+ public boolean isKeyGestureSupported(int gestureType) {
+ switch (gestureType) {
+ case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP:
+ case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN:
+ case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE:
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK:
+ return true;
+ default:
+ return false;
+
+ }
+ }
+ });
+ }
+
// Native callback.
@SuppressWarnings("unused")
private KeyEvent dispatchUnhandledKey(IBinder focus, KeyEvent event, int policyFlags) {
@@ -3184,6 +3267,8 @@
mKeyboardBacklightController.onInteractiveChanged(interactive);
}
+ // TODO(b/358569822): Remove this method from InputManagerInternal after key gesture
+ // handler refactoring complete
@Override
public void toggleCapsLock(int deviceId) {
mNative.toggleCapsLock(deviceId);
@@ -3271,11 +3356,15 @@
mKeyboardBacklightController.notifyUserActivity();
}
+ // TODO(b/358569822): Remove this method from InputManagerInternal after key gesture
+ // handler refactoring complete
@Override
public void incrementKeyboardBacklight(int deviceId) {
mKeyboardBacklightController.incrementKeyboardBacklight(deviceId);
}
+ // TODO(b/358569822): Remove this method from InputManagerInternal after key gesture
+ // handler refactoring complete
@Override
public void decrementKeyboardBacklight(int deviceId) {
mKeyboardBacklightController.decrementKeyboardBacklight(deviceId);
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index 835fb72..d70bd8b 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -94,6 +94,8 @@
(reason) -> updateKeyRepeatInfo()),
Map.entry(Settings.Secure.getUriFor(Settings.Secure.KEY_REPEAT_DELAY_MS),
(reason) -> updateKeyRepeatInfo()),
+ Map.entry(Settings.Secure.getUriFor(Settings.Secure.KEY_REPEAT_ENABLED),
+ (reason) -> updateKeyRepeatInfo()),
Map.entry(Settings.System.getUriFor(Settings.System.SHOW_ROTARY_INPUT),
(reason) -> updateShowRotaryInput()),
Map.entry(Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BOUNCE_KEYS),
@@ -230,6 +232,11 @@
}
private void updateKeyRepeatInfo() {
+ // Key repeat is enabled by default
+ final boolean keyRepeatEnabled = Settings.Secure.getIntForUser(
+ mContext.getContentResolver(), Settings.Secure.KEY_REPEAT_ENABLED, 1,
+ UserHandle.USER_CURRENT) != 0;
+
// Use ViewConfiguration getters only as fallbacks because they may return stale values.
final int timeoutMs = Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.KEY_REPEAT_TIMEOUT_MS, ViewConfiguration.getKeyRepeatTimeout(),
@@ -237,7 +244,7 @@
final int delayMs = Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.KEY_REPEAT_DELAY_MS, ViewConfiguration.getKeyRepeatDelay(),
UserHandle.USER_CURRENT);
- mNative.setKeyRepeatConfiguration(timeoutMs, delayMs);
+ mNative.setKeyRepeatConfiguration(timeoutMs, delayMs, keyRepeatEnabled);
}
private void updateMaximumObscuringOpacityForTouch() {
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 1e7c97f9..5dd461d 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -212,7 +212,7 @@
void setMotionClassifierEnabled(boolean enabled);
- void setKeyRepeatConfiguration(int timeoutMs, int delayMs);
+ void setKeyRepeatConfiguration(int timeoutMs, int delayMs, boolean keyRepeatEnabled);
InputSensorInfo[] getSensorList(int deviceId);
@@ -509,7 +509,8 @@
public native void setMotionClassifierEnabled(boolean enabled);
@Override
- public native void setKeyRepeatConfiguration(int timeoutMs, int delayMs);
+ public native void setKeyRepeatConfiguration(int timeoutMs, int delayMs,
+ boolean keyRepeatEnabled);
@Override
public native InputSensorInfo[] getSensorList(int deviceId);
diff --git a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
index 3f11e78..0e940d2 100644
--- a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
+++ b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
@@ -29,7 +29,9 @@
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
+import android.view.SurfaceControl;
import android.view.ViewConfiguration;
+import android.view.ViewRootImpl;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.TextView;
@@ -49,6 +51,7 @@
private static final float DEFAULT_RES_X = 47f;
private static final float DEFAULT_RES_Y = 45f;
private static final int TEXT_PADDING_DP = 12;
+ private static final int ROUNDED_CORNER_RADIUS_DP = 24;
/**
* Input device ID for the touchpad that this debug view is displaying.
@@ -152,7 +155,35 @@
}
@Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ postDelayed(() -> {
+ final ViewRootImpl viewRootImpl = getRootView().getViewRootImpl();
+ if (viewRootImpl == null) {
+ Slog.d("TouchpadDebugView", "ViewRootImpl is null.");
+ return;
+ }
+
+ SurfaceControl surfaceControl = viewRootImpl.getSurfaceControl();
+ if (surfaceControl != null && surfaceControl.isValid()) {
+ try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
+ transaction.setCornerRadius(surfaceControl,
+ TypedValue.applyDimension(COMPLEX_UNIT_DIP,
+ ROUNDED_CORNER_RADIUS_DP,
+ getResources().getDisplayMetrics())).apply();
+ }
+ } else {
+ Slog.d("TouchpadDebugView", "SurfaceControl is invalid or has been released.");
+ }
+ }, 100);
+ }
+
+ @Override
public boolean onTouchEvent(MotionEvent event) {
+ if (event.getClassification() == MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE) {
+ return false;
+ }
+
float deltaX;
float deltaY;
switch (event.getAction()) {
diff --git a/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java b/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java
index 67c3621..2eed9ba 100644
--- a/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java
+++ b/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java
@@ -27,20 +27,28 @@
import com.android.server.input.TouchpadHardwareProperties;
import com.android.server.input.TouchpadHardwareState;
+import java.util.ArrayDeque;
+import java.util.HashMap;
+import java.util.Map;
+
public class TouchpadVisualizationView extends View {
private static final String TAG = "TouchpadVizMain";
private static final boolean DEBUG = true;
private static final float DEFAULT_RES_X = 47f;
private static final float DEFAULT_RES_Y = 45f;
+ private static final float MAX_TRACE_HISTORY_DURATION_SECONDS = 1f;
private final TouchpadHardwareProperties mTouchpadHardwareProperties;
private float mScaleFactor;
- TouchpadHardwareState mLatestHardwareState = new TouchpadHardwareState(0, 0, 0, 0,
- new TouchpadFingerState[]{});
+ private final ArrayDeque<TouchpadHardwareState> mHardwareStateHistory =
+ new ArrayDeque<TouchpadHardwareState>();
+ private final Map<Integer, TouchpadFingerState> mTempFingerStatesByTrackingId = new HashMap<>();
private final Paint mOvalStrokePaint;
private final Paint mOvalFillPaint;
+ private final Paint mTracePaint;
+ private final Paint mCenterPointPaint;
private final RectF mTempOvalRect = new RectF();
public TouchpadVisualizationView(Context context,
@@ -55,6 +63,29 @@
mOvalFillPaint = new Paint();
mOvalFillPaint.setAntiAlias(true);
mOvalFillPaint.setARGB(255, 0, 0, 0);
+ mTracePaint = new Paint();
+ mTracePaint.setAntiAlias(false);
+ mTracePaint.setARGB(255, 0, 0, 255);
+ mTracePaint.setStyle(Paint.Style.STROKE);
+ mTracePaint.setStrokeWidth(2);
+ mCenterPointPaint = new Paint();
+ mCenterPointPaint.setAntiAlias(true);
+ mCenterPointPaint.setARGB(255, 255, 0, 0);
+ mCenterPointPaint.setStrokeWidth(2);
+ }
+
+ private void removeOldPoints() {
+ float latestTimestamp = mHardwareStateHistory.getLast().getTimestamp();
+
+ while (!mHardwareStateHistory.isEmpty()) {
+ TouchpadHardwareState oldestPoint = mHardwareStateHistory.getFirst();
+ float onScreenTime = latestTimestamp - oldestPoint.getTimestamp();
+ if (onScreenTime >= MAX_TRACE_HISTORY_DURATION_SECONDS) {
+ mHardwareStateHistory.removeFirst();
+ } else {
+ break;
+ }
+ }
}
private void drawOval(Canvas canvas, float x, float y, float major, float minor, float angle) {
@@ -71,19 +102,22 @@
@Override
protected void onDraw(Canvas canvas) {
+ if (mHardwareStateHistory.isEmpty()) {
+ return;
+ }
+
+ TouchpadHardwareState latestHardwareState = mHardwareStateHistory.getLast();
+
float maximumPressure = 0;
- for (TouchpadFingerState touchpadFingerState : mLatestHardwareState.getFingerStates()) {
+ for (TouchpadFingerState touchpadFingerState : latestHardwareState.getFingerStates()) {
maximumPressure = Math.max(maximumPressure, touchpadFingerState.getPressure());
}
- for (TouchpadFingerState touchpadFingerState : mLatestHardwareState.getFingerStates()) {
- float newX = translateRange(mTouchpadHardwareProperties.getLeft(),
- mTouchpadHardwareProperties.getRight(), 0, getWidth(),
- touchpadFingerState.getPositionX());
+ // Visualizing fingers as ovals
+ for (TouchpadFingerState touchpadFingerState : latestHardwareState.getFingerStates()) {
+ float newX = translateX(touchpadFingerState.getPositionX());
- float newY = translateRange(mTouchpadHardwareProperties.getTop(),
- mTouchpadHardwareProperties.getBottom(), 0, getHeight(),
- touchpadFingerState.getPositionY());
+ float newY = translateY(touchpadFingerState.getPositionY());
float newAngle = translateRange(0, mTouchpadHardwareProperties.getOrientationMaximum(),
0, 90, touchpadFingerState.getOrientation());
@@ -102,6 +136,28 @@
drawOval(canvas, newX, newY, newTouchMajor, newTouchMinor, newAngle);
}
+
+ mTempFingerStatesByTrackingId.clear();
+
+ // Drawing the trace
+ for (TouchpadHardwareState currentHardwareState : mHardwareStateHistory) {
+ for (TouchpadFingerState currentFingerState : currentHardwareState.getFingerStates()) {
+ TouchpadFingerState prevFingerState = mTempFingerStatesByTrackingId.put(
+ currentFingerState.getTrackingId(), currentFingerState);
+
+ if (prevFingerState == null) {
+ continue;
+ }
+
+ float currentX = translateX(currentFingerState.getPositionX());
+ float currentY = translateY(currentFingerState.getPositionY());
+ float prevX = translateX(prevFingerState.getPositionX());
+ float prevY = translateY(prevFingerState.getPositionY());
+
+ canvas.drawLine(prevX, prevY, currentX, currentY, mTracePaint);
+ canvas.drawPoint(currentX, currentY, mCenterPointPaint);
+ }
+ }
}
/**
@@ -114,7 +170,18 @@
logHardwareState(schs);
}
- mLatestHardwareState = schs;
+ if (!mHardwareStateHistory.isEmpty()
+ && mHardwareStateHistory.getLast().getFingerCount() == 0
+ && schs.getFingerCount() > 0) {
+ mHardwareStateHistory.clear();
+ }
+
+ mHardwareStateHistory.addLast(schs);
+ removeOldPoints();
+
+ if (DEBUG) {
+ logFingerTrace();
+ }
invalidate();
}
@@ -128,6 +195,16 @@
mScaleFactor = scaleFactor;
}
+ private float translateX(float x) {
+ return translateRange(mTouchpadHardwareProperties.getLeft(),
+ mTouchpadHardwareProperties.getRight(), 0, getWidth(), x);
+ }
+
+ private float translateY(float y) {
+ return translateRange(mTouchpadHardwareProperties.getTop(),
+ mTouchpadHardwareProperties.getBottom(), 0, getHeight(), y);
+ }
+
private float translateRange(float rangeBeforeMin, float rangeBeforeMax,
float rangeAfterMin, float rangeAfterMax, float value) {
return rangeAfterMin + (value - rangeBeforeMin) / (rangeBeforeMax - rangeBeforeMin) * (
@@ -154,4 +231,10 @@
}
}
-}
+ private void logFingerTrace() {
+ Slog.d(TAG, "Trace size= " + mHardwareStateHistory.size());
+ for (TouchpadFingerState tfs : mHardwareStateHistory.getLast().getFingerStates()) {
+ Slog.d(TAG, "ID= " + tfs.getTrackingId());
+ }
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index f747879..2bb2b7b 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -6107,7 +6107,22 @@
final int argUserId = parseUserIdFromDumpArgs(args);
final Printer p = new PrintWriterPrinter(pw);
p.println("Current Input Method Manager state:");
- p.println(" concurrentMultiUserModeEnabled=" + mConcurrentMultiUserModeEnabled);
+ p.println(" mSystemReady=" + mSystemReady);
+ p.println(" mInteractive=" + mIsInteractive);
+ p.println(" mConcurrentMultiUserModeEnabled=" + mConcurrentMultiUserModeEnabled);
+ p.println(" ENABLE_HIDE_IME_CAPTION_BAR="
+ + InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR);
+ synchronized (ImfLock.class) {
+ p.println(" mStylusIds=" + (mStylusIds != null
+ ? Arrays.toString(mStylusIds.toArray()) : ""));
+ }
+ if (Flags.imeSwitcherRevamp()) {
+ p.println(" mMenuControllerNew:");
+ mMenuControllerNew.dump(p, " ");
+ } else {
+ p.println(" mMenuController:");
+ mMenuController.dump(p, " ");
+ }
if (mConcurrentMultiUserModeEnabled && argUserId == UserHandle.USER_NULL) {
mUserDataRepository.forAllUserData(
u -> dumpAsStringNoCheckForUser(u, fd, pw, args, isCritical));
@@ -6116,6 +6131,22 @@
final var userData = getUserData(userId);
dumpAsStringNoCheckForUser(userData, fd, pw, args, isCritical);
}
+
+ // TODO(b/365868861): Make StartInputHistory, SoftInputShowHideHistory and ImeTracker per
+ // user.
+ synchronized (ImfLock.class) {
+ p.println(" mStartInputHistory:");
+ mStartInputHistory.dump(pw, " ");
+
+ p.println(" mSoftInputShowHideHistory:");
+ mSoftInputShowHideHistory.dump(pw, " ");
+ }
+
+ p.println(" mImeTrackerService#History:");
+ mImeTrackerService.dump(pw, " ");
+
+ dumpUserRepository(p);
+ dumpClientStates(p);
}
@UserIdInt
@@ -6140,104 +6171,33 @@
userData.mUserId);
final List<InputMethodInfo> methodList = settings.getMethodList();
int numImes = methodList.size();
- p.println(" Input Methods:");
+ p.println(" Input Methods:");
for (int i = 0; i < numImes; i++) {
InputMethodInfo info = methodList.get(i);
- p.println(" InputMethod #" + i + ":");
- info.dump(p, " ");
+ p.println(" InputMethod #" + i + ":");
+ info.dump(p, " ");
}
- // Dump ClientController#mClients
- p.println(" ClientStates:");
- // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed.
- @SuppressWarnings("GuardedBy") Consumer<ClientState> clientControllerDump = c -> {
- p.println(" " + c + ":");
- p.println(" client=" + c.mClient);
- p.println(" fallbackInputConnection="
- + c.mFallbackInputConnection);
- p.println(" sessionRequested="
- + c.mSessionRequested);
- p.println(" sessionRequestedForAccessibility="
- + c.mSessionRequestedForAccessibility);
- p.println(" curSession=" + c.mCurSession);
- p.println(" selfReportedDisplayId=" + c.mSelfReportedDisplayId);
- p.println(" uid=" + c.mUid);
- p.println(" pid=" + c.mPid);
- };
- mClientController.forAllClients(clientControllerDump);
final var bindingController = userData.mBindingController;
- p.println(" mCurMethodId=" + bindingController.getSelectedMethodId());
+ p.println(" mCurMethodId=" + bindingController.getSelectedMethodId());
client = userData.mCurClient;
- p.println(" mCurClient=" + client + " mCurSeq="
+ p.println(" mCurClient=" + client + " mCurSeq="
+ bindingController.getSequenceNumber());
- p.println(" mFocusedWindowPerceptible=" + mFocusedWindowPerceptible);
+ p.println(" mFocusedWindowPerceptible=" + mFocusedWindowPerceptible);
userData.mImeBindingState.dump(/* prefix= */ " ", p);
-
- p.println(" mCurId=" + bindingController.getCurId()
- + " mHaveConnection=" + bindingController.hasMainConnection()
- + " mBoundToMethod=" + userData.mBoundToMethod + " mVisibleBound="
- + bindingController.isVisibleBound());
-
- p.println(" mUserDataRepository=");
- // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed.
- @SuppressWarnings("GuardedBy") Consumer<UserData> userDataDump =
- u -> {
- p.println(" mUserId=" + u.mUserId);
- p.println(" unlocked=" + u.mIsUnlockingOrUnlocked.get());
- p.println(" hasMainConnection="
- + u.mBindingController.hasMainConnection());
- p.println(" isVisibleBound=" + u.mBindingController.isVisibleBound());
- p.println(" boundToMethod=" + u.mBoundToMethod);
- p.println(" curClient=" + u.mCurClient);
- if (u.mCurEditorInfo != null) {
- p.println(" curEditorInfo:");
- u.mCurEditorInfo.dump(p, " ", false /* dumpExtras */);
- } else {
- p.println(" curEditorInfo: null");
- }
- p.println(" imeBindingState:");
- u.mImeBindingState.dump(" ", p);
- p.println(" enabledSession=" + u.mEnabledSession);
- p.println(" inFullscreenMode=" + u.mInFullscreenMode);
- p.println(" imeDrawsNavBar=" + u.mImeDrawsNavBar.get());
- p.println(" switchingController:");
- u.mSwitchingController.dump(p, " ");
- p.println(" mLastEnabledInputMethodsStr="
- + u.mLastEnabledInputMethodsStr);
- };
- mUserDataRepository.forAllUserData(userDataDump);
-
- if (Flags.imeSwitcherRevamp()) {
- p.println(" menuControllerNew:");
- mMenuControllerNew.dump(p, " ");
- } else {
- p.println(" menuController:");
- mMenuController.dump(p, " ");
- }
- p.println(" mCurToken=" + bindingController.getCurToken());
- p.println(" mCurTokenDisplayId=" + bindingController.getCurTokenDisplayId());
- p.println(" mCurHostInputToken=" + bindingController.getCurHostInputToken());
- p.println(" mCurIntent=" + bindingController.getCurIntent());
+ p.println(" mCurId=" + bindingController.getCurId());
+ p.println(" mHaveConnection=" + bindingController.hasMainConnection());
+ p.println(" mBoundToMethod=" + userData.mBoundToMethod);
+ p.println(" mVisibleBound=" + bindingController.isVisibleBound());
+ p.println(" mCurToken=" + bindingController.getCurToken());
+ p.println(" mCurTokenDisplayId=" + bindingController.getCurTokenDisplayId());
+ p.println(" mCurHostInputToken=" + bindingController.getCurHostInputToken());
+ p.println(" mCurIntent=" + bindingController.getCurIntent());
method = bindingController.getCurMethod();
- p.println(" mCurMethod=" + method);
- p.println(" mEnabledSession=" + userData.mEnabledSession);
+ p.println(" mCurMethod=" + method);
+ p.println(" mEnabledSession=" + userData.mEnabledSession);
final var visibilityStateComputer = userData.mVisibilityStateComputer;
visibilityStateComputer.dump(pw, " ");
- p.println(" mInFullscreenMode=" + userData.mInFullscreenMode);
- p.println(" mSystemReady=" + mSystemReady + " mInteractive=" + mIsInteractive);
- p.println(" mConcurrentMultiUserModeEnabled=" + mConcurrentMultiUserModeEnabled);
- p.println(" ENABLE_HIDE_IME_CAPTION_BAR="
- + InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR);
- p.println(" mStylusIds=" + (mStylusIds != null
- ? Arrays.toString(mStylusIds.toArray()) : ""));
-
- p.println(" mStartInputHistory:");
- mStartInputHistory.dump(pw, " ");
-
- p.println(" mSoftInputShowHideHistory:");
- mSoftInputShowHideHistory.dump(pw, " ");
-
- p.println(" mImeTrackerService#History:");
- mImeTrackerService.dump(pw, " ");
+ p.println(" mInFullscreenMode=" + userData.mInFullscreenMode);
}
// Exit here for critical dump, as remaining sections require IPCs to other processes.
@@ -6288,6 +6248,61 @@
}
}
+ private void dumpClientStates(Printer p) {
+ p.println(" ClientStates:");
+ // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed.
+ @SuppressWarnings("GuardedBy") Consumer<ClientState> clientControllerDump = c -> {
+ p.println(" " + c + ":");
+ p.println(" client=" + c.mClient);
+ p.println(" fallbackInputConnection="
+ + c.mFallbackInputConnection);
+ p.println(" sessionRequested="
+ + c.mSessionRequested);
+ p.println(" sessionRequestedForAccessibility="
+ + c.mSessionRequestedForAccessibility);
+ p.println(" curSession=" + c.mCurSession);
+ p.println(" selfReportedDisplayId=" + c.mSelfReportedDisplayId);
+ p.println(" uid=" + c.mUid);
+ p.println(" pid=" + c.mPid);
+ };
+ synchronized (ImfLock.class) {
+ mClientController.forAllClients(clientControllerDump);
+ }
+ }
+
+ private void dumpUserRepository(Printer p) {
+ p.println(" mUserDataRepository=");
+ // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed.
+ @SuppressWarnings("GuardedBy") Consumer<UserData> userDataDump =
+ u -> {
+ p.println(" mUserId=" + u.mUserId);
+ p.println(" unlocked=" + u.mIsUnlockingOrUnlocked.get());
+ p.println(" hasMainConnection="
+ + u.mBindingController.hasMainConnection());
+ p.println(" isVisibleBound=" + u.mBindingController.isVisibleBound());
+ p.println(" boundToMethod=" + u.mBoundToMethod);
+ p.println(" curClient=" + u.mCurClient);
+ if (u.mCurEditorInfo != null) {
+ p.println(" curEditorInfo:");
+ u.mCurEditorInfo.dump(p, " ", false /* dumpExtras */);
+ } else {
+ p.println(" curEditorInfo: null");
+ }
+ p.println(" imeBindingState:");
+ u.mImeBindingState.dump(" ", p);
+ p.println(" enabledSession=" + u.mEnabledSession);
+ p.println(" inFullscreenMode=" + u.mInFullscreenMode);
+ p.println(" imeDrawsNavBar=" + u.mImeDrawsNavBar.get());
+ p.println(" switchingController:");
+ u.mSwitchingController.dump(p, " ");
+ p.println(" mLastEnabledInputMethodsStr="
+ + u.mLastEnabledInputMethodsStr);
+ };
+ synchronized (ImfLock.class) {
+ mUserDataRepository.forAllUserData(userDataDump);
+ }
+ }
+
@BinderThread
@Override
public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index 4b2c12a..63bd9ab 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -389,7 +389,7 @@
// Reload gnss config for no SIM case
mGnssConfiguration.reloadGpsProperties();
}
- if (Flags.enableNiSuplMessageInjectionByCarrierConfig()) {
+ if (Flags.enableNiSuplMessageInjectionByCarrierConfigBugfix()) {
updateNiSuplMessageListenerRegistration(
mGnssConfiguration.isNiSuplMessageInjectionEnabled());
}
@@ -538,7 +538,7 @@
intentFilter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
mContext.registerReceiver(mIntentReceiver, intentFilter, null, mHandler);
- if (!Flags.enableNiSuplMessageInjectionByCarrierConfig()) {
+ if (!Flags.enableNiSuplMessageInjectionByCarrierConfigBugfix()) {
updateNiSuplMessageListenerRegistration(
mGnssConfiguration.isNiSuplMessageInjectionEnabled());
}
@@ -1672,7 +1672,7 @@
if (dumpAll) {
mNetworkTimeHelper.dump(pw);
pw.println("mSupportsPsds=" + mSupportsPsds);
- if (Flags.enableNiSuplMessageInjectionByCarrierConfig()) {
+ if (Flags.enableNiSuplMessageInjectionByCarrierConfigBugfix()) {
pw.println("mNiSuplMessageListenerRegistered="
+ mNiSuplMessageListenerRegistered);
}
diff --git a/services/core/java/com/android/server/location/gnss/NetworkTimeHelper.java b/services/core/java/com/android/server/location/gnss/NetworkTimeHelper.java
index 01c108b..494ea77 100644
--- a/services/core/java/com/android/server/location/gnss/NetworkTimeHelper.java
+++ b/services/core/java/com/android/server/location/gnss/NetworkTimeHelper.java
@@ -19,6 +19,7 @@
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.content.Context;
+import android.location.flags.Flags;
import android.os.Looper;
import java.io.PrintWriter;
@@ -55,7 +56,7 @@
static NetworkTimeHelper create(
@NonNull Context context, @NonNull Looper looper,
@NonNull InjectTimeCallback injectTimeCallback) {
- if (USE_TIME_DETECTOR_IMPL) {
+ if (!Flags.useLegacyNtpTime()) {
TimeDetectorNetworkTimeHelper.Environment environment =
new TimeDetectorNetworkTimeHelper.EnvironmentImpl(looper);
return new TimeDetectorNetworkTimeHelper(environment, injectTimeCallback);
diff --git a/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java b/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java
index df45a6e..177eefb 100644
--- a/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java
@@ -76,11 +76,12 @@
try {
mIsInEmergencyCall = mTelephonyManager.isEmergencyNumber(
intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER));
- dispatchEmergencyStateChanged();
} catch (IllegalStateException | UnsupportedOperationException e) {
Log.w(TAG, "Failed to call TelephonyManager.isEmergencyNumber().", e);
}
}
+
+ dispatchEmergencyStateChanged();
}
}, new IntentFilter(Intent.ACTION_NEW_OUTGOING_CALL));
@@ -140,9 +141,10 @@
if (mIsInEmergencyCall) {
mEmergencyCallEndRealtimeMs = SystemClock.elapsedRealtime();
mIsInEmergencyCall = false;
- dispatchEmergencyStateChanged();
}
}
+
+ dispatchEmergencyStateChanged();
}
}
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 158d444..1e25f1c 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -140,7 +140,7 @@
try {
db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?",
new String[] {key, Integer.toString(userId)});
- db.insert(TABLE, null, cv);
+ db.insertOrThrow(TABLE, null, cv);
db.setTransactionSuccessful();
mCache.putKeyValue(key, value, userId);
} finally {
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 621c090..48d24f2 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -179,7 +179,8 @@
/**
* In order to record the keyguard, the MediaProjection package must be either:
* - a holder of RECORD_SENSITIVE_CONTENT permission, or
- * - be one of the bugreport whitelisted packages
+ * - be one of the bugreport allowlisted packages, or
+ * - hold the OP_PROJECT_MEDIA AppOp.
*/
private boolean canCaptureKeyguard() {
if (!android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()) {
@@ -194,6 +195,14 @@
== PackageManager.PERMISSION_GRANTED) {
return true;
}
+ boolean operationActive = mAppOps.isOperationActive(AppOpsManager.OP_PROJECT_MEDIA,
+ mProjectionGrant.uid,
+ mProjectionGrant.packageName);
+ if (operationActive) {
+ // Some tools use media projection by granting the OP_PROJECT_MEDIA app
+ // op via a shell command. Those tools can be granted keyguard capture
+ return true;
+ }
return SystemConfig.getInstance().getBugreportWhitelistedPackages()
.contains(mProjectionGrant.packageName);
}
diff --git a/services/core/java/com/android/server/notification/VibratorHelper.java b/services/core/java/com/android/server/notification/VibratorHelper.java
index fbe7772..f54c1f7 100644
--- a/services/core/java/com/android/server/notification/VibratorHelper.java
+++ b/services/core/java/com/android/server/notification/VibratorHelper.java
@@ -218,10 +218,16 @@
* @param uri {@code Uri} an uri including query parameter "vibraiton_uri"
*/
public @Nullable VibrationEffect createVibrationEffectFromSoundUri(Uri uri) {
- if (uri == null) {
+ if (uri == null || uri.isOpaque()) {
return null;
}
- return Utils.parseVibrationEffect(mVibrator, Utils.getVibrationUri(uri));
+
+ try {
+ return Utils.parseVibrationEffect(mVibrator, Utils.getVibrationUri(uri));
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to get vibration effect: ", e);
+ }
+ return null;
}
/** Returns if a given vibration can be played by the vibrator that does notification buzz. */
diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java
index d495ef5..50bfbc3 100644
--- a/services/core/java/com/android/server/notification/ZenModeConditions.java
+++ b/services/core/java/com/android/server/notification/ZenModeConditions.java
@@ -157,7 +157,7 @@
}
// empty rule? disable and bail early
if (rule.component == null && rule.enabler == null) {
- if (!android.app.Flags.modesUi() || (android.app.Flags.modesUi() && !isManual)) {
+ if (!isManual) {
Log.w(TAG, "No component found for automatic rule: " + rule.conditionId);
rule.enabled = false;
}
diff --git a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
index 4135161..5aea356 100644
--- a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
+++ b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
@@ -18,6 +18,7 @@
import static android.media.AudioAttributes.USAGE_ALARM;
+import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.Notification;
@@ -42,6 +43,8 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import java.util.List;
+
public class BackgroundUserSoundNotifier {
private static final boolean DEBUG = false;
@@ -49,11 +52,21 @@
private static final String BUSN_CHANNEL_ID = "bg_user_sound_channel";
private static final String BUSN_CHANNEL_NAME = "BackgroundUserSound";
public static final String ACTION_MUTE_SOUND = "com.android.server.ACTION_MUTE_BG_USER";
- private static final String EXTRA_NOTIFICATION_ID = "com.android.server.EXTRA_CLIENT_UID";
- private static final String EXTRA_CURRENT_USER_ID = "com.android.server.EXTRA_CURRENT_USER_ID";
private static final String ACTION_SWITCH_USER = "com.android.server.ACTION_SWITCH_TO_USER";
- /** ID of user with notification displayed, -1 if notification is not showing*/
- private int mUserWithNotification = -1;
+ private static final String ACTION_DISMISS_NOTIFICATION =
+ "com.android.server.ACTION_DISMISS_NOTIFICATION";
+ /**
+ * The clientUid from the AudioFocusInfo of the background user,
+ * for which an active notification is currently displayed.
+ * Set to -1 if no notification is being shown.
+ * TODO: b/367615180 - add support for multiple simultaneous alarms
+ */
+ @VisibleForTesting
+ int mNotificationClientUid = -1;
+ @VisibleForTesting
+ AudioPolicy mFocusControlAudioPolicy;
+ @VisibleForTesting
+ BackgroundUserListener mBgUserListener;
private final Context mSystemUserContext;
@VisibleForTesting
final NotificationManager mNotificationManager;
@@ -67,11 +80,18 @@
mSystemUserContext = context;
mNotificationManager = mSystemUserContext.getSystemService(NotificationManager.class);
mUserManager = mSystemUserContext.getSystemService(UserManager.class);
+ createNotificationChannel();
+ setupFocusControlAudioPolicy();
+ }
+
+ /**
+ * Creates a dedicated channel for background user related notifications.
+ */
+ private void createNotificationChannel() {
NotificationChannel channel = new NotificationChannel(BUSN_CHANNEL_ID, BUSN_CHANNEL_NAME,
NotificationManager.IMPORTANCE_HIGH);
channel.setSound(null, null);
mNotificationManager.createNotificationChannel(channel);
- setupFocusControlAudioPolicy();
}
private void setupFocusControlAudioPolicy() {
@@ -81,15 +101,16 @@
ActivityManager am = mSystemUserContext.getSystemService(ActivityManager.class);
registerReceiver(am);
- BackgroundUserListener bgUserListener = new BackgroundUserListener(mSystemUserContext);
+ mBgUserListener = new BackgroundUserListener(mSystemUserContext);
AudioPolicy.Builder focusControlPolicyBuilder = new AudioPolicy.Builder(mSystemUserContext);
focusControlPolicyBuilder.setLooper(Looper.getMainLooper());
- focusControlPolicyBuilder.setAudioPolicyFocusListener(bgUserListener);
+ focusControlPolicyBuilder.setAudioPolicyFocusListener(mBgUserListener);
- AudioPolicy mFocusControlAudioPolicy = focusControlPolicyBuilder.build();
+ mFocusControlAudioPolicy = focusControlPolicyBuilder.build();
int status = mSystemUserContext.getSystemService(AudioManager.class)
.registerAudioPolicy(mFocusControlAudioPolicy);
+
if (status != AudioManager.SUCCESS) {
Log.w(LOG_TAG , "Could not register the service's focus"
+ " control audio policy, error: " + status);
@@ -117,123 +138,170 @@
@SuppressLint("MissingPermission")
public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {
- BackgroundUserSoundNotifier.this.dismissNotificationIfNecessary(afi);
+ BackgroundUserSoundNotifier.this.dismissNotificationIfNecessary();
}
}
+ @VisibleForTesting
+ BackgroundUserListener getAudioPolicyFocusListener() {
+ return mBgUserListener;
+ }
+
/**
* Registers a BroadcastReceiver for actions related to background user sound notifications.
* When ACTION_MUTE_SOUND is received, it mutes a background user's alarm sound.
* When ACTION_SWITCH_USER is received, a switch to the background user with alarm is started.
*/
- private void registerReceiver(ActivityManager service) {
+ private void registerReceiver(ActivityManager activityManager) {
BroadcastReceiver backgroundUserNotificationBroadcastReceiver = new BroadcastReceiver() {
@SuppressLint("MissingPermission")
@Override
public void onReceive(Context context, Intent intent) {
- if (!(intent.hasExtra(EXTRA_NOTIFICATION_ID)
- && intent.hasExtra(EXTRA_CURRENT_USER_ID)
- && intent.hasExtra(Intent.EXTRA_USER_ID))) {
+ if (mNotificationClientUid == -1) {
return;
}
- final int notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1);
+ dismissNotification();
if (DEBUG) {
- Log.d(LOG_TAG,
- "User with alarm id " + intent.getIntExtra(Intent.EXTRA_USER_ID,
- -1) + " current user id " + intent.getIntExtra(
- EXTRA_CURRENT_USER_ID, -1));
+ final int actionIndex = intent.getAction().lastIndexOf(".") + 1;
+ final String action = intent.getAction().substring(actionIndex);
+ Log.d(LOG_TAG, "Action requested: " + action + ", by userId "
+ + ActivityManager.getCurrentUser() + " for alarm on user "
+ + UserHandle.getUserHandleForUid(mNotificationClientUid));
}
- mUserWithNotification = -1;
- mNotificationManager.cancelAsUser(LOG_TAG, notificationId,
- UserHandle.of(intent.getIntExtra(EXTRA_CURRENT_USER_ID, -1)));
+
if (ACTION_MUTE_SOUND.equals(intent.getAction())) {
- final AudioManager audioManager =
- mSystemUserContext.getSystemService(AudioManager.class);
- if (audioManager != null) {
- for (AudioPlaybackConfiguration apc :
- audioManager.getActivePlaybackConfigurations()) {
- if (apc.getAudioAttributes().getUsage() == USAGE_ALARM) {
- if (apc.getPlayerProxy() != null) {
- apc.getPlayerProxy().stop();
- }
- }
- }
- }
+ muteAlarmSounds(mSystemUserContext);
} else if (ACTION_SWITCH_USER.equals(intent.getAction())) {
- service.switchUser(intent.getIntExtra(Intent.EXTRA_USER_ID, -1));
+ activityManager.switchUser(UserHandle.getUserId(mNotificationClientUid));
}
+
+ mNotificationClientUid = -1;
}
};
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_MUTE_SOUND);
filter.addAction(ACTION_SWITCH_USER);
+ filter.addAction(ACTION_DISMISS_NOTIFICATION);
mSystemUserContext.registerReceiver(backgroundUserNotificationBroadcastReceiver, filter,
Context.RECEIVER_NOT_EXPORTED);
}
/**
+ * Stop player proxy for the ongoing alarm and drop focus for its AudioFocusInfo.
+ */
+ @VisibleForTesting
+ void muteAlarmSounds(Context context) {
+ AudioManager audioManager = context.getSystemService(AudioManager.class);
+ if (audioManager != null) {
+ for (AudioPlaybackConfiguration apc : audioManager.getActivePlaybackConfigurations()) {
+ if (apc.getClientUid() == mNotificationClientUid && apc.getPlayerProxy() != null) {
+ apc.getPlayerProxy().stop();
+ }
+ }
+ }
+ }
+
+ /**
* Check if sound is coming from background user and show notification is required.
*/
@VisibleForTesting
- void notifyForegroundUserAboutSoundIfNecessary(AudioFocusInfo afi, Context
- foregroundContext) throws RemoteException {
+ void notifyForegroundUserAboutSoundIfNecessary(AudioFocusInfo afi, Context foregroundContext)
+ throws RemoteException {
final int userId = UserHandle.getUserId(afi.getClientUid());
final int usage = afi.getAttributes().getUsage();
UserInfo userInfo = mUserManager.getUserInfo(userId);
- if (userInfo != null && userId != foregroundContext.getUserId()) {
+ // Only show notification if the sound is coming from background user and the notification
+ // is not already shown.
+ if (userInfo != null && userId != foregroundContext.getUserId()
+ && mNotificationClientUid == -1) {
//TODO: b/349138482 - Add handling of cases when usage == USAGE_NOTIFICATION_RINGTONE
if (usage == USAGE_ALARM) {
- Intent muteIntent = createIntent(ACTION_MUTE_SOUND, afi, foregroundContext, userId);
- PendingIntent mutePI = PendingIntent.getBroadcast(mSystemUserContext, 0,
- muteIntent, PendingIntent.FLAG_UPDATE_CURRENT
- | PendingIntent.FLAG_IMMUTABLE);
- Intent switchIntent = createIntent(ACTION_SWITCH_USER, afi, foregroundContext,
- userId);
- PendingIntent switchPI = PendingIntent.getBroadcast(mSystemUserContext, 0,
- switchIntent, PendingIntent.FLAG_UPDATE_CURRENT
- | PendingIntent.FLAG_IMMUTABLE);
+ if (DEBUG) {
+ Log.d(LOG_TAG, "Alarm ringing on background user " + userId
+ + ", displaying notification for current user "
+ + foregroundContext.getUserId());
+ }
- mUserWithNotification = foregroundContext.getUserId();
- mNotificationManager.notifyAsUser(LOG_TAG, afi.getClientUid(),
- createNotification(userInfo.name, mutePI, switchPI, foregroundContext),
+ mNotificationClientUid = afi.getClientUid();
+
+ mNotificationManager.notifyAsUser(LOG_TAG, mNotificationClientUid,
+ createNotification(userInfo.name, foregroundContext),
foregroundContext.getUser());
}
}
}
/**
- * If notification is present, dismisses it. To be called when the relevant sound loses focus.
+ * Dismisses notification if the associated focus has been removed from the focus stack.
+ * Notification remains if the focus is temporarily lost due to another client taking over the
+ * focus ownership.
*/
- private void dismissNotificationIfNecessary(AudioFocusInfo afi) {
- if (mUserWithNotification >= 0) {
- mNotificationManager.cancelAsUser(LOG_TAG, afi.getClientUid(),
- UserHandle.of(mUserWithNotification));
+ @VisibleForTesting
+ void dismissNotificationIfNecessary() {
+ if (getAudioFocusInfoForNotification() == null && mNotificationClientUid >= 0) {
+ if (DEBUG) {
+ Log.d(LOG_TAG, "Alarm ringing on background user "
+ + UserHandle.getUserHandleForUid(mNotificationClientUid).getIdentifier()
+ + " left focus stack, dismissing notification");
+ }
+ dismissNotification();
+ mNotificationClientUid = -1;
}
- mUserWithNotification = -1;
}
- private Intent createIntent(String intentAction, AudioFocusInfo afi, Context fgUserContext,
- int userId) {
+ /**
+ * Dismisses notification for all users in case user switch occurred after notification was
+ * shown.
+ */
+ @SuppressLint("MissingPermission")
+ private void dismissNotification() {
+ mNotificationManager.cancelAsUser(LOG_TAG, mNotificationClientUid, UserHandle.ALL);
+ }
+
+ /**
+ * Returns AudioFocusInfo associated with the current notification.
+ */
+ @SuppressLint("MissingPermission")
+ @VisibleForTesting
+ @Nullable
+ AudioFocusInfo getAudioFocusInfoForNotification() {
+ if (mNotificationClientUid >= 0) {
+ List<AudioFocusInfo> stack = mFocusControlAudioPolicy.getFocusStack();
+ for (int i = stack.size() - 1; i >= 0; i--) {
+ if (stack.get(i).getClientUid() == mNotificationClientUid) {
+ return stack.get(i);
+ }
+ }
+ }
+ return null;
+ }
+
+ private PendingIntent createPendingIntent(String intentAction) {
final Intent intent = new Intent(intentAction);
- intent.putExtra(EXTRA_CURRENT_USER_ID, fgUserContext.getUserId());
- intent.putExtra(EXTRA_NOTIFICATION_ID, afi.getClientUid());
- intent.putExtra(Intent.EXTRA_USER_ID, userId);
- return intent;
+ PendingIntent resultPI = PendingIntent.getBroadcast(mSystemUserContext, 0, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ return resultPI;
}
- private Notification createNotification(String userName, PendingIntent muteIntent,
- PendingIntent switchIntent, Context fgContext) {
+ @VisibleForTesting
+ Notification createNotification(String userName, Context fgContext) {
final String title = fgContext.getString(R.string.bg_user_sound_notification_title_alarm,
userName);
final int icon = R.drawable.ic_audio_alarm;
+
+ PendingIntent mutePI = createPendingIntent(ACTION_MUTE_SOUND);
+ PendingIntent switchPI = createPendingIntent(ACTION_SWITCH_USER);
+ PendingIntent dismissNotificationPI = createPendingIntent(ACTION_DISMISS_NOTIFICATION);
+
final Notification.Action mute = new Notification.Action.Builder(null,
fgContext.getString(R.string.bg_user_sound_notification_button_mute),
- muteIntent).build();
+ mutePI).build();
final Notification.Action switchUser = new Notification.Action.Builder(null,
fgContext.getString(R.string.bg_user_sound_notification_button_switch_user),
- switchIntent).build();
+ switchPI).build();
+
Notification.Builder notificationBuilder = new Notification.Builder(mSystemUserContext,
BUSN_CHANNEL_ID)
.setSmallIcon(icon)
@@ -243,16 +311,18 @@
.setOngoing(true)
.setColor(fgContext.getColor(R.color.system_notification_accent_color))
.setContentTitle(title)
- .setContentIntent(muteIntent)
+ .setContentIntent(mutePI)
.setAutoCancel(true)
+ .setDeleteIntent(dismissNotificationPI)
.setVisibility(Notification.VISIBILITY_PUBLIC);
+
if (mUserManager.isUserSwitcherEnabled() && (mUserManager.getUserSwitchability(
- UserHandle.of(fgContext.getUserId())) == UserManager.SWITCHABILITY_STATUS_OK)) {
+ fgContext.getUser()) == UserManager.SWITCHABILITY_STATUS_OK)) {
notificationBuilder.setActions(mute, switchUser);
} else {
notificationBuilder.setActions(mute);
}
+
return notificationBuilder.build();
}
}
-
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index ee15bec..efd58ed 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -92,7 +92,6 @@
import android.multiuser.Flags;
import android.net.Uri;
import android.os.Binder;
-import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IInterface;
@@ -215,7 +214,7 @@
@VisibleForTesting
static class LauncherAppsImpl extends ILauncherApps.Stub {
- private static final boolean DEBUG = Build.IS_DEBUGGABLE;
+ private static final boolean DEBUG = false;
private static final String TAG = "LauncherAppsService";
private static final String NAMESPACE_MULTIUSER = "multiuser";
private static final String FLAG_NON_SYSTEM_ACCESS_TO_HIDDEN_PROFILES =
@@ -496,28 +495,8 @@
private boolean canAccessProfile(int callingUid, int callingUserId, int callingPid,
int targetUserId, String message) {
- if (DEBUG) {
- final AndroidPackage callingPackage =
- mPackageManagerInternal.getPackage(callingUid);
- final String callingPackageName = callingPackage == null
- ? null : callingPackage.getPackageName();
- Slog.v(TAG, "canAccessProfile called by " + callingPackageName
- + " for user " + callingUserId
- + " requesting to access user "
- + targetUserId + " when invoking " + message);
- }
- if (targetUserId == callingUserId) {
- if (DEBUG) {
- Slog.v(TAG, message + " passed canAccessProfile for targetuser"
- + targetUserId + " because it is the same as the calling user");
- }
- return true;
- }
+ if (targetUserId == callingUserId) return true;
if (injectHasInteractAcrossUsersFullPermission(callingPid, callingUid)) {
- if (DEBUG) {
- Slog.v(TAG, message + " passed because calling process"
- + "has permission to interact across users");
- }
return true;
}
@@ -535,25 +514,11 @@
if (isHiddenProfile(UserHandle.of(targetUserId))
&& !canAccessHiddenProfile(callingUid, callingPid)) {
- Slog.w(TAG, message + " for hidden profile user " + targetUserId
- + " from " + callingUserId + " not allowed");
-
return false;
}
- final boolean ret = mUserManagerInternal.isProfileAccessible(
- callingUserId, targetUserId, message, true);
- if (DEBUG) {
- final AndroidPackage callingPackage =
- mPackageManagerInternal.getPackage(callingUid);
- final String callingPackageName = callingPackage == null
- ? null : callingPackage.getPackageName();
- Slog.v(TAG, "canAccessProfile returned " + ret + " for " + callingPackageName
- + " for user " + callingUserId
- + " requesting to access user "
- + targetUserId + " when invoking " + message);
- }
- return ret;
+ return mUserManagerInternal.isProfileAccessible(callingUserId, targetUserId,
+ message, true);
}
private boolean isHiddenProfile(UserHandle targetUser) {
@@ -1376,10 +1341,6 @@
@Override
public void pinShortcuts(String callingPackage, String packageName, List<String> ids,
UserHandle targetUser) {
- if (DEBUG) {
- Slog.v(TAG, "pinShortcuts: " + callingPackage + " is pinning shortcuts from "
- + packageName + " for user " + targetUser);
- }
if (!mShortcutServiceInternal
.areShortcutsSupportedOnHomeScreen(targetUser.getIdentifier())) {
// Requires strict ACCESS_SHORTCUTS permission for user-profiles with items
@@ -1390,11 +1351,6 @@
}
ensureShortcutPermission(callingPackage);
if (!canAccessProfile(targetUser.getIdentifier(), "Cannot pin shortcuts")) {
- if (DEBUG) {
- Slog.v(TAG, "pinShortcuts: " + callingPackage
- + " is pinning shortcuts from " + packageName
- + " for user " + targetUser + " but cannot access profile");
- }
return;
}
@@ -2451,7 +2407,7 @@
final int callbackUserId = callbackUser.getIdentifier();
final int shortcutUserId = shortcutUser.getIdentifier();
- if (shortcutUser == callbackUser) return true;
+ if ((shortcutUser.equals(callbackUser))) return true;
return mUserManagerInternal.isProfileAccessible(callbackUserId, shortcutUserId,
null, false);
}
@@ -2485,16 +2441,28 @@
final BroadcastCookie cookie =
(BroadcastCookie) mListeners.getBroadcastCookie(i);
if (!isEnabledProfileOf(cookie, user, "onPackageRemoved")) {
+ // b/350144057
+ Slog.d(TAG, "onPackageRemoved: Skipping - profile not enabled"
+ + " or not accessible for user=" + user
+ + ", packageName=" + packageName);
continue;
}
if (!isCallingAppIdAllowed(appIdAllowList, UserHandle.getAppId(
cookie.callingUid))) {
+ // b/350144057
+ Slog.d(TAG, "onPackageRemoved: Skipping - appId not allowed"
+ + " for user=" + user
+ + ", packageName=" + packageName);
continue;
}
try {
+ // b/350144057
+ Slog.d(TAG, "onPackageRemoved: triggering onPackageRemoved"
+ + " for user=" + user
+ + ", packageName=" + packageName);
listener.onPackageRemoved(user, packageName);
} catch (RemoteException re) {
- Slog.d(TAG, "Callback failed ", re);
+ Slog.d(TAG, "onPackageRemoved: Callback failed ", re);
}
}
} finally {
@@ -2524,15 +2492,27 @@
IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
if (!isEnabledProfileOf(cookie, user, "onPackageAdded")) {
+ // b/350144057
+ Slog.d(TAG, "onPackageAdded: Skipping - profile not enabled"
+ + " or not accessible for user=" + user
+ + ", packageName=" + packageName);
continue;
}
if (!isPackageVisibleToListener(packageName, cookie, user)) {
+ // b/350144057
+ Slog.d(TAG, "onPackageAdded: Skipping - package filtered"
+ + " for user=" + user
+ + ", packageName=" + packageName);
continue;
}
try {
+ // b/350144057
+ Slog.d(TAG, "onPackageAdded: triggering onPackageAdded"
+ + " for user=" + user
+ + ", packageName=" + packageName);
listener.onPackageAdded(user, packageName);
} catch (RemoteException re) {
- Slog.d(TAG, "Callback failed ", re);
+ Slog.d(TAG, "onPackageAdded: Callback failed ", re);
}
}
} finally {
@@ -2566,7 +2546,7 @@
try {
listener.onPackageChanged(user, packageName);
} catch (RemoteException re) {
- Slog.d(TAG, "Callback failed ", re);
+ Slog.d(TAG, "onPackageChanged: Callback failed ", re);
}
}
} finally {
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index d65e30b..045d4db 100644
--- a/services/core/java/com/android/server/pm/ShortcutLauncher.java
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -42,7 +42,6 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
-import java.util.stream.Collectors;
/**
* Launcher information used by {@link ShortcutService}.
@@ -129,15 +128,9 @@
*/
public void pinShortcuts(@UserIdInt int packageUserId,
@NonNull String packageName, @NonNull List<String> ids, boolean forPinRequest) {
- if (ShortcutService.DEBUG) {
- Slog.v(TAG, "ShortcutLauncher#pinShortcuts: pin shortcuts from " + packageName
- + " with userId=" + packageUserId + " shortcutIds="
- + ids.stream().collect(Collectors.joining(", ", "[", "]")));
- }
final ShortcutPackage packageShortcuts =
mShortcutUser.getPackageShortcutsIfExists(packageName);
if (packageShortcuts == null) {
- Slog.w(TAG, "ShortcutLauncher#pinShortcuts packageShortcuts is null");
return; // No need to instantiate.
}
@@ -162,10 +155,6 @@
final String id = ids.get(i);
final ShortcutInfo si = packageShortcuts.findShortcutById(id);
if (si == null) {
- if (ShortcutService.DEBUG) {
- Slog.w(TAG, "ShortcutLauncher#pinShortcuts: cannot pin "
- + id + " because it does not exist");
- }
continue;
}
if (si.isDynamic() || si.isLongLived()
@@ -185,13 +174,6 @@
}
}
}
- if (ShortcutService.DEBUG) {
- Slog.v(TAG, "ShortcutLauncher#pinShortcuts: "
- + " newSet: " + newSet.stream().collect(
- Collectors.joining(", ", "[", "]"))
- + " floatingSet: " + floatingSet.stream().collect(
- Collectors.joining(", ", "[", "]")));
- }
mPinnedShortcuts.put(up, newSet);
}
}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index c9ad498..60056eb 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -729,11 +729,6 @@
}
pinnedShortcuts.addAll(pinned);
});
- if (ShortcutService.DEBUG) {
- Slog.v(TAG, "ShortcutPackage#refreshPinnedFlags: "
- + " pinnedShortcuts: " + pinnedShortcuts.stream().collect(
- Collectors.joining(", ", "[", "]")));
- }
// Secondly, update the pinned state if necessary.
final List<ShortcutInfo> pinned = findAll(pinnedShortcuts);
if (pinned != null) {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index ea495c9..a3ff195 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -169,7 +169,7 @@
public class ShortcutService extends IShortcutService.Stub {
static final String TAG = "ShortcutService";
- static final boolean DEBUG = Build.IS_DEBUGGABLE; // STOPSHIP if true
+ static final boolean DEBUG = false; // STOPSHIP if true
static final boolean DEBUG_LOAD = false; // STOPSHIP if true
static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true
static final boolean DEBUG_REBOOT = Build.IS_DEBUGGABLE;
@@ -3206,11 +3206,6 @@
public void pinShortcuts(int launcherUserId,
@NonNull String callingPackage, @NonNull String packageName,
@NonNull List<String> shortcutIds, int userId) {
- if (DEBUG) {
- Slog.v(TAG, "pinShortcuts: " + callingPackage + ", with userId=" + launcherUserId
- + ", is trying to pin shortcuts from " + packageName
- + " with userId=" + userId);
- }
// Calling permission must be checked by LauncherAppsImpl.
Preconditions.checkStringNotEmpty(packageName, "packageName");
Objects.requireNonNull(shortcutIds, "shortcutIds");
@@ -3235,11 +3230,6 @@
&& !si.isDeclaredInManifest(),
ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO,
callingPackage, launcherUserId, false);
- } else {
- if (DEBUG) {
- Slog.w(TAG, "specified package " + packageName + ", with userId=" + userId
- + ", doesn't exist.");
- }
}
// Get list of shortcuts that will get unpinned.
ArraySet<String> oldPinnedIds = launcher.getPinnedShortcutIds(packageName, userId);
@@ -5458,17 +5448,6 @@
*/
private List<ShortcutInfo> prepareChangedShortcuts(ArraySet<String> changedIds,
ArraySet<String> newIds, List<ShortcutInfo> deletedList, final ShortcutPackage ps) {
- if (DEBUG) {
- Slog.v(TAG, "prepareChangedShortcuts: "
- + " changedIds=" + (changedIds == null
- ? "n/a" : changedIds.stream().collect(Collectors.joining(", ", "[", "]")))
- + " newIds=" + (newIds == null
- ? "n/a" : newIds.stream().collect(Collectors.joining(", ", "[", "]")))
- + " deletedList=" + (deletedList == null
- ? "n/a" : deletedList.stream().map(ShortcutInfo::getId).collect(
- Collectors.joining(", ", "[", "]")))
- + " ps=" + (ps == null ? "n/a" : ps.getPackageName()));
- }
if (ps == null) {
// This can happen when package restore is not finished yet.
return null;
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index c75622c..21a6df2 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -208,6 +208,22 @@
"name": "CtsUpdateOwnershipEnforcementTestCases"
},
{
+ "name": "CtsPackageInstallerCUJDeviceAdminTestCases",
+ "file_patterns": [
+ "core/java/.*Install.*",
+ "services/core/.*Install.*",
+ "services/core/java/com/android/server/pm/.*"
+ ],
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ }
+ ]
+ },
+ {
"name": "CtsPackageInstallerCUJInstallationTestCases",
"file_patterns": [
"core/java/.*Install.*",
@@ -224,6 +240,22 @@
]
},
{
+ "name": "CtsPackageInstallerCUJMultiUsersTestCases",
+ "file_patterns": [
+ "core/java/.*Install.*",
+ "services/core/.*Install.*",
+ "services/core/java/com/android/server/pm/.*"
+ ],
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ }
+ ]
+ },
+ {
"name": "CtsPackageInstallerCUJUninstallationTestCases",
"file_patterns": [
"core/java/.*Install.*",
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index a683a8c..89417f3 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1090,6 +1090,21 @@
mUser0Allocations = DBG_ALLOCATION ? new AtomicInteger() : null;
mPrivateSpaceAutoLockSettingsObserver = new SettingsObserver(mHandler);
emulateSystemUserModeIfNeeded();
+ initPropertyInvalidatedCaches();
+ }
+
+ /**
+ * This method is used to invalidate the caches at server statup,
+ * so that caches can start working.
+ */
+ private static final void initPropertyInvalidatedCaches() {
+ if (android.multiuser.Flags.cachesNotInvalidatedAtStartReadOnly()) {
+ UserManager.invalidateIsUserUnlockedCache();
+ UserManager.invalidateQuietModeEnabledCache();
+ UserManager.invalidateStaticUserProperties();
+ UserManager.invalidateUserPropertiesCache();
+ UserManager.invalidateUserSerialNumberCache();
+ }
}
private boolean doesDeviceHardwareSupportPrivateSpace() {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 98091b1..02c02b0 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3350,12 +3350,6 @@
}
}
- // TODO(b/358569822) Remove below once we have nicer API for listening to shortcuts
- if ((event.isMetaPressed() || KeyEvent.isMetaKey(keyCode))
- && shouldInterceptShortcuts(focusedToken)) {
- return keyNotConsumed;
- }
-
Set<Integer> consumedKeys = mConsumedKeysForDevice.get(deviceId);
if (consumedKeys == null) {
consumedKeys = new HashSet<>();
@@ -3855,10 +3849,6 @@
case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS:
case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH:
case KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH:
- case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP:
- case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN:
- case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE:
- case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK:
return true;
default:
return false;
@@ -4016,25 +4006,6 @@
sendSwitchKeyboardLayout(displayId, focusedToken, direction);
}
return true;
- // TODO (b/358569822): Move these input specific gesture handling to IMS since we
- // are calling into InputManager through internal API anyways
- case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP:
- if (complete) {
- mInputManagerInternal.incrementKeyboardBacklight(deviceId);
- }
- return true;
- case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN:
- if (complete) {
- mInputManagerInternal.decrementKeyboardBacklight(deviceId);
- }
- return true;
- case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE:
- return true;
- case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK:
- if (complete) {
- mInputManagerInternal.toggleCapsLock(deviceId);
- }
- return true;
}
return false;
}
@@ -4064,15 +4035,6 @@
startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);
}
- private boolean shouldInterceptShortcuts(IBinder focusedToken) {
- KeyInterceptionInfo info =
- mWindowManagerInternal.getKeyInterceptionInfoFromToken(focusedToken);
- boolean hasInterceptWindowFlag = (info.layoutParamsPrivateFlags
- & WindowManager.LayoutParams.PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS) != 0;
- return hasInterceptWindowFlag && mButtonOverridePermissionChecker.canAppOverrideSystemKey(
- mContext, info.windowOwnerUid);
- }
-
/**
* In this function, we check whether a system key should be sent to the application. We also
* detect the key gesture on this key, even if the key will be sent to the app. The gesture
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 68026ea..e3d71e4 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -24,8 +24,11 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.WorkerThread;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -78,6 +81,8 @@
public final class RollbackPackageHealthObserver implements PackageHealthObserver {
private static final String TAG = "RollbackPackageHealthObserver";
private static final String NAME = "rollback-observer";
+ private static final String ACTION_NAME = RollbackPackageHealthObserver.class.getName();
+
private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
| ApplicationInfo.FLAG_SYSTEM;
@@ -596,12 +601,40 @@
}
};
- final LocalIntentReceiver rollbackReceiver = new LocalIntentReceiver(result -> {
- mHandler.post(() -> onResult.accept(result));
- });
+ if (Flags.refactorCrashrecovery()) {
+ // Define a BroadcastReceiver to handle the result
+ BroadcastReceiver rollbackReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent result) {
+ mHandler.post(() -> onResult.accept(result));
+ }
+ };
- rollbackManager.commitRollback(rollback.getRollbackId(),
- Collections.singletonList(failedPackage), rollbackReceiver.getIntentSender());
+ // Register the BroadcastReceiver
+ mContext.registerReceiver(rollbackReceiver,
+ new IntentFilter(ACTION_NAME),
+ Context.RECEIVER_NOT_EXPORTED);
+
+ Intent intentReceiver = new Intent(ACTION_NAME);
+ intentReceiver.putExtra("rollbackId", rollback.getRollbackId());
+ intentReceiver.setPackage(mContext.getPackageName());
+
+ PendingIntent rollbackPendingIntent = PendingIntent.getBroadcast(mContext,
+ rollback.getRollbackId(),
+ intentReceiver,
+ PendingIntent.FLAG_MUTABLE);
+
+ rollbackManager.commitRollback(rollback.getRollbackId(),
+ Collections.singletonList(failedPackage),
+ rollbackPendingIntent.getIntentSender());
+ } else {
+ final LocalIntentReceiver rollbackReceiver = new LocalIntentReceiver(result -> {
+ mHandler.post(() -> onResult.accept(result));
+ });
+
+ rollbackManager.commitRollback(rollback.getRollbackId(),
+ Collections.singletonList(failedPackage), rollbackReceiver.getIntentSender());
+ }
}
/**
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 953aae9..457196b 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -89,6 +89,7 @@
import com.android.internal.widget.LockSettingsStateListener;
import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.servicewatcher.CurrentUserServiceSupplier;
import com.android.server.servicewatcher.ServiceWatcher;
import com.android.server.utils.Slogf;
@@ -170,6 +171,7 @@
private final ActivityManager mActivityManager;
private FingerprintManager mFingerprintManager;
private FaceManager mFaceManager;
+ private UserManagerInternal mUserManagerInternal;
private enum TrustState {
// UNTRUSTED means that TrustManagerService is currently *not* giving permission for the
@@ -1064,6 +1066,8 @@
Log.w(TAG, "Unable to check keyguard lock state", e);
}
currentUserIsUnlocked = unlockedUser == id;
+ } else if (isVisibleBackgroundUser(id)) {
+ showingKeyguard = !mUserManager.isUserUnlocked(id);
}
final boolean deviceLocked = secure && showingKeyguard && !trusted
&& !biometricAuthenticated;
@@ -1095,6 +1099,16 @@
}
}
+ private boolean isVisibleBackgroundUser(int userId) {
+ if (!mUserManager.isVisibleBackgroundUsersSupported()) {
+ return false;
+ }
+ if (mUserManagerInternal == null) {
+ mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
+ }
+ return mUserManagerInternal.isVisibleBackgroundFullUser(userId);
+ }
+
private void notifyTrustAgentsOfDeviceLockState(int userId, boolean isLocked) {
for (int i = 0; i < mActiveAgents.size(); i++) {
AgentInfo agent = mActiveAgents.valueAt(i);
diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
index 96a25da..1e82b89 100644
--- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
+++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
@@ -322,9 +322,16 @@
if (SubscriptionManager.isValidSubscriptionId(subId)) {
// Get only configs as needed to save memory.
- final PersistableBundle carrierConfig =
- CarrierConfigManager.getCarrierConfigSubset(mContext, subId,
- VcnManager.VCN_RELATED_CARRIER_CONFIG_KEYS);
+ PersistableBundle carrierConfig = new PersistableBundle();
+ try {
+ carrierConfig =
+ mCarrierConfigManager.getConfigForSubId(
+ subId, VcnManager.VCN_RELATED_CARRIER_CONFIG_KEYS);
+
+ } catch (RuntimeException exception) {
+ Slog.w(TAG, "CarrierConfigLoader is not available.");
+ }
+
if (mDeps.isConfigForIdentifiedCarrier(carrierConfig)) {
mReadySubIdsBySlotId.put(slotId, subId);
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 8d378a0..b574782 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -177,6 +177,10 @@
/** Default number of parallel SAs requested */
static final int TUNNEL_AGGREGATION_SA_COUNT_MAX_DEFAULT = 1;
+ // The returned string of
+ // TelephonyManager#getNetworkTypeName(TelephonyManager.NETWORK_TYPE_UNKNOWN)
+ private static final String NETWORK_TYPE_STRING_UNKNOWN = "UNKNOWN";
+
// Matches DataConnection.NETWORK_TYPE private constant, and magic string from
// ConnectivityManager#getNetworkTypeName()
@VisibleForTesting(visibility = Visibility.PRIVATE)
@@ -1815,9 +1819,7 @@
.setLegacyType(ConnectivityManager.TYPE_MOBILE)
.setLegacyTypeName(NETWORK_INFO_NETWORK_TYPE_STRING)
.setLegacySubType(TelephonyManager.NETWORK_TYPE_UNKNOWN)
- .setLegacySubTypeName(
- TelephonyManager.getNetworkTypeName(
- TelephonyManager.NETWORK_TYPE_UNKNOWN))
+ .setLegacySubTypeName(NETWORK_TYPE_STRING_UNKNOWN)
.setLegacyExtraInfo(NETWORK_INFO_EXTRA_INFO)
.build();
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
index 3f8d39e..2b0ca08 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
@@ -360,7 +360,10 @@
final NetworkRequest.Builder nrBuilder =
getBaseNetworkRequestBuilder()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
- .setNetworkSpecifier(new TelephonyNetworkSpecifier(subId));
+ .setNetworkSpecifier(
+ new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(subId)
+ .build());
for (CapabilityMatchCriteria capMatchCriteria : capsMatchCriteria) {
final int cap = capMatchCriteria.capability;
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
index cae6b34..006a5bb 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
@@ -16,6 +16,8 @@
package com.android.server.vibrator;
+import static android.os.vibrator.Flags.hapticFeedbackInputSourceCustomizationEnabled;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.Resources;
@@ -121,7 +123,6 @@
return getVibrationForHapticFeedback(effectId);
}
- // TODO(b/354049335): handle input source customized VibrationAttributes.
/**
* Provides the {@link VibrationAttributes} that should be used for a haptic feedback.
*
@@ -131,7 +132,7 @@
* @param privFlags Additional private flags as per {@link HapticFeedbackConstants}.
* @return the {@link VibrationAttributes} that should be used for the provided haptic feedback.
*/
- public VibrationAttributes getVibrationAttributesForHapticFeedback(int effectId,
+ public VibrationAttributes getVibrationAttributes(int effectId,
@HapticFeedbackConstants.Flags int flags,
@HapticFeedbackConstants.PrivateFlags int privFlags) {
VibrationAttributes attrs;
@@ -142,10 +143,13 @@
break;
case HapticFeedbackConstants.ASSISTANT_BUTTON:
case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON:
+ attrs = HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES;
+ break;
case HapticFeedbackConstants.SCROLL_TICK:
case HapticFeedbackConstants.SCROLL_ITEM_FOCUS:
case HapticFeedbackConstants.SCROLL_LIMIT:
- attrs = HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES;
+ attrs = hapticFeedbackInputSourceCustomizationEnabled() ? TOUCH_VIBRATION_ATTRIBUTES
+ : HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES;
break;
case HapticFeedbackConstants.KEYBOARD_TAP:
case HapticFeedbackConstants.KEYBOARD_RELEASE:
@@ -158,19 +162,32 @@
default:
attrs = TOUCH_VIBRATION_ATTRIBUTES;
}
+ return getVibrationAttributesWithFlags(attrs, effectId, flags);
+ }
- int vibFlags = 0;
- boolean bypassVibrationIntensitySetting =
- (flags & HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0;
- if (bypassVibrationIntensitySetting) {
- vibFlags |= VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF;
+ /**
+ * Similar to {@link #getVibrationAttributes(int, int, int)} but also handles
+ * input source customization.
+ *
+ * @param inputSource the {@link InputDevice.Source} that customizes the
+ * {@link VibrationAttributes}.
+ */
+ public VibrationAttributes getVibrationAttributes(int effectId,
+ int inputSource,
+ @HapticFeedbackConstants.Flags int flags,
+ @HapticFeedbackConstants.PrivateFlags int privFlags) {
+ if (hapticFeedbackInputSourceCustomizationEnabled()
+ && inputSource == InputDevice.SOURCE_ROTARY_ENCODER) {
+ switch (effectId) {
+ case HapticFeedbackConstants.SCROLL_TICK,
+ HapticFeedbackConstants.SCROLL_ITEM_FOCUS,
+ HapticFeedbackConstants.SCROLL_LIMIT -> {
+ return getVibrationAttributesWithFlags(HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES,
+ effectId, flags);
+ }
+ }
}
- if (shouldBypassInterruptionPolicy(effectId)) {
- vibFlags |= VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
- }
-
- return vibFlags == 0 ? attrs : new VibrationAttributes.Builder(attrs)
- .setFlags(vibFlags).build();
+ return getVibrationAttributes(effectId, flags, privFlags);
}
/**
@@ -344,6 +361,20 @@
return IME_FEEDBACK_VIBRATION_ATTRIBUTES;
}
+ private VibrationAttributes getVibrationAttributesWithFlags(VibrationAttributes attrs,
+ int effectId, int flags) {
+ int vibFlags = 0;
+ if ((flags & HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0) {
+ vibFlags |= VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF;
+ }
+ if (shouldBypassInterruptionPolicy(effectId)) {
+ vibFlags |= VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
+ }
+
+ return vibFlags == 0 ? attrs : new VibrationAttributes.Builder(attrs)
+ .setFlags(vibFlags).build();
+ }
+
private static boolean shouldBypassInterruptionPolicy(int effectId) {
switch (effectId) {
case HapticFeedbackConstants.SCROLL_TICK:
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 899f0b1..a76d8d6 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -509,7 +509,7 @@
}
return performHapticFeedbackWithEffect(uid, deviceId, opPkg, constant, reason, token,
hapticVibrationProvider.getVibration(constant),
- hapticVibrationProvider.getVibrationAttributesForHapticFeedback(
+ hapticVibrationProvider.getVibrationAttributes(
constant, flags, privFlags));
}
@@ -534,8 +534,8 @@
}
return performHapticFeedbackWithEffect(uid, deviceId, opPkg, constant, reason, token,
hapticVibrationProvider.getVibration(constant, inputSource),
- hapticVibrationProvider.getVibrationAttributesForHapticFeedback(
- constant, flags, privFlags));
+ hapticVibrationProvider.getVibrationAttributes(constant, inputSource, flags,
+ privFlags));
}
private HalVibration performHapticFeedbackWithEffect(int uid, int deviceId, String opPkg,
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperData.java b/services/core/java/com/android/server/wallpaper/WallpaperData.java
index a698429..15f86e9 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperData.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperData.java
@@ -78,7 +78,7 @@
/**
* The component name of the currently set live wallpaper.
*/
- ComponentName wallpaperComponent;
+ private ComponentName mWallpaperComponent;
// TODO(b/347235611) Remove this field
/**
@@ -195,7 +195,7 @@
*/
WallpaperData(WallpaperData source) {
this.userId = source.userId;
- this.wallpaperComponent = source.wallpaperComponent;
+ this.mWallpaperComponent = source.mWallpaperComponent;
this.mWhich = source.mWhich;
this.wallpaperId = source.wallpaperId;
this.cropHint.set(source.cropHint);
@@ -230,6 +230,14 @@
return result;
}
+ ComponentName getComponent() {
+ return mWallpaperComponent;
+ }
+
+ void setComponent(ComponentName componentName) {
+ this.mWallpaperComponent = componentName;
+ }
+
@Override
public String toString() {
StringBuilder out = new StringBuilder(defaultString(this));
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index b15facb..e3e83b3 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -189,13 +189,13 @@
String comp = parser.getAttributeValue(null, "component");
if (removeNextWallpaperComponent()) {
- wallpaperToParse.wallpaperComponent = comp != null
+ wallpaperToParse.setComponent(comp != null
? ComponentName.unflattenFromString(comp)
- : null;
- if (wallpaperToParse.wallpaperComponent == null
- || "android".equals(wallpaperToParse.wallpaperComponent
+ : null);
+ if (wallpaperToParse.getComponent() == null
+ || "android".equals(wallpaperToParse.getComponent()
.getPackageName())) {
- wallpaperToParse.wallpaperComponent = mImageWallpaper;
+ wallpaperToParse.setComponent(mImageWallpaper);
}
} else {
wallpaperToParse.nextWallpaperComponent = comp != null
@@ -219,7 +219,7 @@
Slog.v(TAG, "primaryColors:" + wallpaper.primaryColors);
Slog.v(TAG, "mName:" + wallpaper.name);
if (removeNextWallpaperComponent()) {
- Slog.v(TAG, "mWallpaperComponent:" + wallpaper.wallpaperComponent);
+ Slog.v(TAG, "mWallpaperComponent:" + wallpaper.getComponent());
} else {
Slog.v(TAG, "mNextWallpaperComponent:"
+ wallpaper.nextWallpaperComponent);
@@ -340,7 +340,7 @@
getAttributeInt(parser, "totalCropTop", 0),
getAttributeInt(parser, "totalCropRight", 0),
getAttributeInt(parser, "totalCropBottom", 0));
- ComponentName componentName = removeNextWallpaperComponent() ? wallpaper.wallpaperComponent
+ ComponentName componentName = removeNextWallpaperComponent() ? wallpaper.getComponent()
: wallpaper.nextWallpaperComponent;
if (multiCrop() && mImageWallpaper.equals(componentName)) {
wallpaper.mCropHints = new SparseArray<>();
@@ -480,7 +480,7 @@
out.startTag(null, tag);
out.attributeInt(null, "id", wallpaper.wallpaperId);
- if (multiCrop() && mImageWallpaper.equals(wallpaper.wallpaperComponent)) {
+ if (multiCrop() && mImageWallpaper.equals(wallpaper.getComponent())) {
if (wallpaper.mCropHints == null) {
Slog.e(TAG, "cropHints should not be null when saved");
wallpaper.mCropHints = new SparseArray<>();
@@ -580,10 +580,10 @@
}
out.attribute(null, "name", wallpaper.name);
- if (wallpaper.wallpaperComponent != null
- && !wallpaper.wallpaperComponent.equals(mImageWallpaper)) {
+ if (wallpaper.getComponent() != null
+ && !wallpaper.getComponent().equals(mImageWallpaper)) {
out.attribute(null, "component",
- wallpaper.wallpaperComponent.flattenToShortString());
+ wallpaper.getComponent().flattenToShortString());
}
if (wallpaper.allowBackup) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 6cc37dd..4754ffb 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -276,7 +276,7 @@
final boolean isMigration = moved && lockWallpaperChanged;
final boolean isRestore = moved && !isMigration;
final boolean isAppliedToLock = (wallpaper.mWhich & FLAG_LOCK) != 0;
- final boolean needsUpdate = wallpaper.wallpaperComponent == null
+ final boolean needsUpdate = wallpaper.getComponent() == null
|| event != CLOSE_WRITE // includes the MOVED_TO case
|| wallpaper.imageWallpaperPending;
@@ -527,7 +527,7 @@
* @return true unless the wallpaper changed during the color computation
*/
private boolean extractColors(WallpaperData wallpaper) {
- if (offloadColorExtraction()) return !mImageWallpaper.equals(wallpaper.wallpaperComponent);
+ if (offloadColorExtraction()) return !mImageWallpaper.equals(wallpaper.getComponent());
String cropFile = null;
boolean defaultImageWallpaper = false;
int wallpaperId;
@@ -550,8 +550,8 @@
synchronized (mLock) {
// Not having a wallpaperComponent means it's a lock screen wallpaper.
- final boolean imageWallpaper = mImageWallpaper.equals(wallpaper.wallpaperComponent)
- || wallpaper.wallpaperComponent == null;
+ final boolean imageWallpaper = mImageWallpaper.equals(wallpaper.getComponent())
+ || wallpaper.getComponent() == null;
if (imageWallpaper && wallpaper.getCropFile().exists()) {
cropFile = wallpaper.getCropFile().getAbsolutePath();
} else if (imageWallpaper && !wallpaper.cropExists() && !wallpaper.sourceExists()) {
@@ -824,13 +824,13 @@
return;
}
TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
- t.traceBegin("WPMS.connectLocked-" + wallpaper.wallpaperComponent);
+ t.traceBegin("WPMS.connectLocked-" + wallpaper.getComponent());
if (DEBUG) Slog.v(TAG, "Adding window token: " + mToken);
mWindowManagerInternal.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId,
null /* options */);
mWindowManagerInternal.setWallpaperShowWhenLocked(
mToken, (wallpaper.mWhich & FLAG_LOCK) != 0);
- if (multiCrop() && mImageWallpaper.equals(wallpaper.wallpaperComponent)) {
+ if (multiCrop() && mImageWallpaper.equals(wallpaper.getComponent())) {
mWindowManagerInternal.setWallpaperCropHints(mToken,
mWallpaperCropper.getRelativeCropHints(wallpaper));
} else {
@@ -906,7 +906,7 @@
}
if (!mWallpaper.wallpaperUpdating && mWallpaper.userId == mCurrentUserId) {
- Slog.w(TAG, "Wallpaper reconnect timed out for " + mWallpaper.wallpaperComponent
+ Slog.w(TAG, "Wallpaper reconnect timed out for " + mWallpaper.getComponent()
+ ", reverting to built-in wallpaper!");
clearWallpaperLocked(mWallpaper.mWhich, mWallpaper.userId, false, null);
}
@@ -1035,9 +1035,9 @@
public void onServiceDisconnected(ComponentName name) {
synchronized (mLock) {
Slog.w(TAG, "Wallpaper service gone: " + name);
- if (!Objects.equals(name, mWallpaper.wallpaperComponent)) {
+ if (!Objects.equals(name, mWallpaper.getComponent())) {
Slog.e(TAG, "Does not match expected wallpaper component "
- + mWallpaper.wallpaperComponent);
+ + mWallpaper.getComponent());
}
mService = null;
forEachDisplayConnector(connector -> connector.mEngine = null);
@@ -1065,7 +1065,7 @@
fgHandler.postDelayed(mResetRunnable, WALLPAPER_RECONNECT_TIMEOUT_MS);
if (DEBUG_LIVE) {
Slog.i(TAG,
- "Started wallpaper reconnect timeout for " + mWallpaper.wallpaperComponent);
+ "Started wallpaper reconnect timeout for " + mWallpaper.getComponent());
}
}
@@ -1081,7 +1081,7 @@
return;
}
- final ComponentName wpService = mWallpaper.wallpaperComponent;
+ final ComponentName wpService = mWallpaper.getComponent();
// The broadcast of package update could be delayed after service disconnected. Try
// to re-bind the service for 10 seconds.
mWallpaper.mBindSource = BindSource.CONNECTION_TRY_TO_REBIND;
@@ -1110,7 +1110,7 @@
// The wallpaper disappeared. If this isn't a system-default one, track
// crashes and fall back to default if it continues to misbehave.
if (this == mWallpaper.connection) {
- final ComponentName wpService = mWallpaper.wallpaperComponent;
+ final ComponentName wpService = mWallpaper.getComponent();
if (!mWallpaper.wallpaperUpdating
&& mWallpaper.userId == mCurrentUserId
&& !Objects.equals(mDefaultWallpaperComponent, wpService)
@@ -1188,7 +1188,7 @@
synchronized (mLock) {
// Do not broadcast changes on ImageWallpaper since it's handled
// internally by this class.
- boolean isImageWallpaper = mImageWallpaper.equals(mWallpaper.wallpaperComponent);
+ boolean isImageWallpaper = mImageWallpaper.equals(mWallpaper.getComponent());
if (isImageWallpaper && (!offloadColorExtraction() || primaryColors == null)) {
return;
}
@@ -1303,7 +1303,7 @@
if (mNewWallpaper.mWhich == FLAG_SYSTEM) {
// New wp is system only, so old system+lock is now lock only
final boolean originalIsStatic = mImageWallpaper.equals(
- mOriginalSystem.wallpaperComponent);
+ mOriginalSystem.getComponent());
if (originalIsStatic) {
// Static wp: image file rename has already been tried via
// migrateStaticSystemToLockWallpaperLocked() and added to the lock wp map
@@ -1314,8 +1314,7 @@
if (DEBUG) {
Slog.v(TAG, "static system+lock to system success");
}
- lockWp.wallpaperComponent =
- mOriginalSystem.wallpaperComponent;
+ lockWp.setComponent(mOriginalSystem.getComponent());
lockWp.connection = mOriginalSystem.connection;
lockWp.connection.mWallpaper = lockWp;
mOriginalSystem.mWhich = FLAG_LOCK;
@@ -1376,7 +1375,7 @@
return;
}
for (WallpaperData wallpaper: getWallpapers()) {
- final ComponentName wpService = wallpaper.wallpaperComponent;
+ final ComponentName wpService = wallpaper.getComponent();
if (wpService != null && wpService.getPackageName().equals(packageName)) {
if (DEBUG_LIVE) {
Slog.i(TAG, "Wallpaper " + wpService + " update has finished");
@@ -1402,8 +1401,8 @@
return;
}
for (WallpaperData wallpaper: getWallpapers()) {
- if (wallpaper.wallpaperComponent != null
- && wallpaper.wallpaperComponent.getPackageName().equals(packageName)) {
+ if (wallpaper.getComponent() != null
+ && wallpaper.getComponent().getPackageName().equals(packageName)) {
doPackagesChangedLocked(true, wallpaper);
}
}
@@ -1417,10 +1416,10 @@
return;
}
for (WallpaperData wallpaper: getWallpapers()) {
- if (wallpaper.wallpaperComponent != null
- && wallpaper.wallpaperComponent.getPackageName().equals(packageName)) {
+ if (wallpaper.getComponent() != null
+ && wallpaper.getComponent().getPackageName().equals(packageName)) {
if (DEBUG_LIVE) {
- Slog.i(TAG, "Wallpaper service " + wallpaper.wallpaperComponent
+ Slog.i(TAG, "Wallpaper service " + wallpaper.getComponent()
+ " is updating");
}
wallpaper.wallpaperUpdating = true;
@@ -1462,15 +1461,15 @@
boolean doPackagesChangedLocked(boolean doit, WallpaperData wallpaper) {
boolean changed = false;
- if (wallpaper.wallpaperComponent != null) {
- int change = isPackageDisappearing(wallpaper.wallpaperComponent
+ if (wallpaper.getComponent() != null) {
+ int change = isPackageDisappearing(wallpaper.getComponent()
.getPackageName());
if (change == PACKAGE_PERMANENT_CHANGE
|| change == PACKAGE_TEMPORARY_CHANGE) {
changed = true;
if (doit) {
Slog.w(TAG, "Wallpaper uninstalled, removing: "
- + wallpaper.wallpaperComponent);
+ + wallpaper.getComponent());
clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null);
}
}
@@ -1485,15 +1484,15 @@
}
}
}
- if (wallpaper.wallpaperComponent != null
- && isPackageModified(wallpaper.wallpaperComponent.getPackageName())) {
+ if (wallpaper.getComponent() != null
+ && isPackageModified(wallpaper.getComponent().getPackageName())) {
try {
- mContext.getPackageManager().getServiceInfo(wallpaper.wallpaperComponent,
+ mContext.getPackageManager().getServiceInfo(wallpaper.getComponent(),
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
} catch (NameNotFoundException e) {
Slog.w(TAG, "Wallpaper component gone, removing: "
- + wallpaper.wallpaperComponent);
+ + wallpaper.getComponent());
clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null);
}
}
@@ -1636,8 +1635,8 @@
// sure we have something to render
boolean isImageComponent;
if (removeNextWallpaperComponent()) {
- isImageComponent = wallpaper.wallpaperComponent == null
- || mImageWallpaper.equals(wallpaper.wallpaperComponent);
+ isImageComponent = wallpaper.getComponent() == null
+ || mImageWallpaper.equals(wallpaper.getComponent());
} else {
isImageComponent = mImageWallpaper.equals(wallpaper.nextWallpaperComponent);
}
@@ -1892,10 +1891,10 @@
final ComponentName cname;
if (removeNextWallpaperComponent()) {
- cname = wallpaper.wallpaperComponent;
+ cname = wallpaper.getComponent();
} else {
- cname = (wallpaper.wallpaperComponent != null)
- ? wallpaper.wallpaperComponent : wallpaper.nextWallpaperComponent;
+ cname = (wallpaper.getComponent() != null)
+ ? wallpaper.getComponent() : wallpaper.nextWallpaperComponent;
}
if (!bindWallpaperComponentLocked(cname, true, false, wallpaper, reply)) {
// We failed to bind the desired wallpaper, but that might
@@ -1927,7 +1926,7 @@
// We might end up persisting the current wallpaper data
// while locked, so pretend like the component was actually
// bound into place
- wallpaper.wallpaperComponent = wallpaper.nextWallpaperComponent;
+ wallpaper.setComponent(wallpaper.nextWallpaperComponent);
}
final WallpaperData fallback = new WallpaperData(wallpaper.userId, wallpaper.mWhich);
@@ -2004,7 +2003,7 @@
// lock only case: set the system wallpaper component to both screens
if (which == FLAG_LOCK) {
- component = wallpaper.wallpaperComponent;
+ component = wallpaper.getComponent();
finalWhich = FLAG_LOCK | FLAG_SYSTEM;
} else {
component = null;
@@ -2310,7 +2309,7 @@
checkPermission(READ_WALLPAPER_INTERNAL);
WallpaperData wallpaper = (which == FLAG_LOCK) ? mLockWallpaperMap.get(userId)
: mWallpaperMap.get(userId);
- if (wallpaper == null || !mImageWallpaper.equals(wallpaper.wallpaperComponent)) {
+ if (wallpaper == null || !mImageWallpaper.equals(wallpaper.getComponent())) {
return null;
}
SparseArray<Rect> relativeSuggestedCrops =
@@ -2760,7 +2759,7 @@
WallpaperData wallpaperData = (which == FLAG_LOCK ? mLockWallpaperMap : mWallpaperMap)
.get(mCurrentUserId);
if (wallpaperData == null) return false;
- return mImageWallpaper.equals(wallpaperData.wallpaperComponent);
+ return mImageWallpaper.equals(wallpaperData.getComponent());
}
}
@@ -2996,7 +2995,7 @@
final WallpaperData originalSystemWallpaper = mWallpaperMap.get(userId);
final boolean systemIsStatic =
originalSystemWallpaper != null && mImageWallpaper.equals(
- originalSystemWallpaper.wallpaperComponent);
+ originalSystemWallpaper.getComponent());
final boolean systemIsBoth = mLockWallpaperMap.get(userId) == null;
/* If we're setting system but not lock, and lock is currently sharing the system
@@ -3190,7 +3189,7 @@
throw new IllegalStateException("Wallpaper not yet initialized for user " + userId);
}
final boolean systemIsStatic = mImageWallpaper.equals(
- originalSystemWallpaper.wallpaperComponent);
+ originalSystemWallpaper.getComponent());
final boolean systemIsBoth = mLockWallpaperMap.get(userId) == null;
if (which == FLAG_SYSTEM && systemIsBoth && systemIsStatic) {
@@ -3212,7 +3211,7 @@
liveSync = new WallpaperDestinationChangeHandler(
newWallpaper);
boolean same = changingToSame(name, newWallpaper.connection,
- newWallpaper.wallpaperComponent);
+ newWallpaper.getComponent());
/*
* If we have a shared system+lock wallpaper, and we reapply the same wallpaper
@@ -3243,7 +3242,7 @@
}
}
boolean lockBitmapCleared = false;
- if (!mImageWallpaper.equals(newWallpaper.wallpaperComponent)) {
+ if (!mImageWallpaper.equals(newWallpaper.getComponent())) {
clearWallpaperBitmaps(newWallpaper);
lockBitmapCleared = newWallpaper.mWhich == FLAG_LOCK;
}
@@ -3324,7 +3323,7 @@
}
// Has the component changed?
if (!force && changingToSame(componentName, wallpaper.connection,
- wallpaper.wallpaperComponent)) {
+ wallpaper.getComponent())) {
try {
if (DEBUG_LIVE) {
Slog.v(TAG, "Changing to the same component, ignoring");
@@ -3461,7 +3460,7 @@
return false;
}
maybeDetachLastWallpapers(wallpaper);
- wallpaper.wallpaperComponent = componentName;
+ wallpaper.setComponent(componentName);
wallpaper.connection = newConn;
newConn.mReply = reply;
updateCurrentWallpapers(wallpaper);
@@ -3586,7 +3585,7 @@
}
private void clearWallpaperComponentLocked(WallpaperData wallpaper) {
- wallpaper.wallpaperComponent = null;
+ wallpaper.setComponent(null);
detachWallpaperLocked(wallpaper);
}
@@ -3831,7 +3830,7 @@
wallpaper.wallpaperId = makeWallpaperIdLocked(); // always bump id at restore
wallpaper.allowBackup = true; // by definition if it was restored
ComponentName componentName =
- removeNextWallpaperComponent() ? wallpaper.wallpaperComponent
+ removeNextWallpaperComponent() ? wallpaper.getComponent()
: wallpaper.nextWallpaperComponent;
if (componentName != null && !componentName.equals(mImageWallpaper)) {
wallpaper.mBindSource = BindSource.RESTORE_SETTINGS_LIVE_SUCCESS;
@@ -3907,7 +3906,7 @@
if (multiCrop()) pw.print(" mCropHints="); pw.println(wallpaper.mCropHints);
pw.print(" mName="); pw.println(wallpaper.name);
pw.print(" mAllowBackup="); pw.println(wallpaper.allowBackup);
- pw.print(" mWallpaperComponent="); pw.println(wallpaper.wallpaperComponent);
+ pw.print(" mWallpaperComponent="); pw.println(wallpaper.getComponent());
pw.print(" mWallpaperDimAmount="); pw.println(wallpaper.mWallpaperDimAmount);
pw.print(" isColorExtracted="); pw.println(wallpaper.mIsColorExtractedFromDim);
pw.println(" mUidToDimAmount:");
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 3d5b273..6009b4a 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -6218,7 +6218,8 @@
public void onProcessRemoved(String name, int uid) {
synchronized (mGlobalLockWithoutBoost) {
final WindowProcessController proc = mProcessNames.remove(name, uid);
- if (proc != null && !mStartingProcessActivities.isEmpty()) {
+ if (proc != null && !proc.mHasEverAttached
+ && !mStartingProcessActivities.isEmpty()) {
// Use a copy in case finishIfPossible changes the list indirectly.
final ArrayList<ActivityRecord> activities =
new ArrayList<>(mStartingProcessActivities);
diff --git a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
index 3be266e..f069dcd 100644
--- a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
@@ -145,11 +145,13 @@
}
}
- void updateSizeCompatScale(@NonNull Rect resolvedAppBounds, @NonNull Rect containerAppBounds) {
+ void updateSizeCompatScale(@NonNull Rect resolvedAppBounds, @NonNull Rect containerAppBounds,
+ @NonNull Configuration newParentConfig) {
mSizeCompatScale = mActivityRecord.mAppCompatController.getTransparentPolicy()
.findOpaqueNotFinishingActivityBelow()
.map(activityRecord -> mSizeCompatScale)
- .orElseGet(() -> calculateSizeCompatScale(resolvedAppBounds, containerAppBounds));
+ .orElseGet(() -> calculateSizeCompatScale(
+ resolvedAppBounds, containerAppBounds, newParentConfig));
}
void clearSizeCompatModeAttributes() {
@@ -290,7 +292,7 @@
// Calculates the scale the size compatibility bounds into the region which is available
// to application.
final float lastSizeCompatScale = mSizeCompatScale;
- updateSizeCompatScale(resolvedAppBounds, containerAppBounds);
+ updateSizeCompatScale(resolvedAppBounds, containerAppBounds, newParentConfiguration);
final int containerTopInset = containerAppBounds.top - containerBounds.top;
final boolean topNotAligned =
@@ -423,7 +425,7 @@
}
private float calculateSizeCompatScale(@NonNull Rect resolvedAppBounds,
- @NonNull Rect containerAppBounds) {
+ @NonNull Rect containerAppBounds, @NonNull Configuration newParentConfig) {
final int contentW = resolvedAppBounds.width();
final int contentH = resolvedAppBounds.height();
final int viewportW = containerAppBounds.width();
@@ -432,7 +434,8 @@
// original container or if it's a freeform window in desktop mode.
boolean shouldAllowUpscaling = !(contentW <= viewportW && contentH <= viewportH)
|| (canEnterDesktopMode(mActivityRecord.mAtmService.mContext)
- && mActivityRecord.getWindowingMode() == WINDOWING_MODE_FREEFORM);
+ && newParentConfig.windowConfiguration.getWindowingMode()
+ == WINDOWING_MODE_FREEFORM);
return shouldAllowUpscaling ? Math.min(
(float) viewportW / contentW, (float) viewportH / contentH) : 1f;
}
diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
index 1994174..3a2cffb 100644
--- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
+++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
@@ -422,6 +422,7 @@
|| first.brightnessMaximum != second.brightnessMaximum
|| first.brightnessDefault != second.brightnessDefault
|| first.installOrientation != second.installOrientation
+ || first.isForceSdr != second.isForceSdr
|| !Objects.equals(first.layoutLimitedRefreshRate, second.layoutLimitedRefreshRate)
|| !BrightnessSynchronizer.floatEquals(first.hdrSdrRatio, second.hdrSdrRatio)
|| !first.thermalRefreshRateThrottling.contentEquals(
diff --git a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
index cc6904f..156d8a0 100644
--- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
+++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
@@ -103,7 +103,7 @@
final TaskDisplayArea displayArea = task.getDisplayArea();
final Rect screenBounds = displayArea.getBounds();
final Size idealSize = calculateIdealSize(screenBounds, DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
- if (!DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(activity.mWmService.mContext)) {
+ if (!DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()) {
return centerInScreen(idealSize, screenBounds);
}
if (activity.mAppCompatController.getAppCompatAspectRatioOverrides()
diff --git a/services/core/java/com/android/server/wm/DesktopModeHelper.java b/services/core/java/com/android/server/wm/DesktopModeHelper.java
index 61fbb96..da76317 100644
--- a/services/core/java/com/android/server/wm/DesktopModeHelper.java
+++ b/services/core/java/com/android/server/wm/DesktopModeHelper.java
@@ -35,8 +35,8 @@
"persist.wm.debug.desktop_mode_enforce_device_restrictions", true);
/** Whether desktop mode is enabled. */
- static boolean isDesktopModeEnabled(@NonNull Context context) {
- return DesktopModeFlags.DESKTOP_WINDOWING_MODE.isEnabled(context);
+ static boolean isDesktopModeEnabled() {
+ return DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isEnabled();
}
/**
@@ -60,7 +60,7 @@
* Return {@code true} if desktop mode can be entered on the current device.
*/
static boolean canEnterDesktopMode(@NonNull Context context) {
- return isDesktopModeEnabled(context)
+ return isDesktopModeEnabled()
&& (!shouldEnforceDeviceRestrictions() || isDesktopModeSupported(context));
}
}
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index 5514294e..e007b1d 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -181,22 +181,30 @@
return true;
}
- boolean transferToHost(@NonNull InputTransferToken embeddedWindowToken,
+ boolean transferToHost(int callingUid, @NonNull InputTransferToken embeddedWindowToken,
@NonNull WindowState transferToHostWindowState) {
EmbeddedWindow ew = getByInputTransferToken(embeddedWindowToken);
if (!isValidTouchGestureParams(transferToHostWindowState, ew)) {
return false;
}
+ if (callingUid != ew.mOwnerUid) {
+ throw new SecurityException(
+ "Transfer request must originate from owner of transferFromToken");
+ }
return mInputManagerService.transferTouchGesture(ew.getInputChannelToken(),
transferToHostWindowState.mInputChannelToken);
}
- boolean transferToEmbedded(WindowState hostWindowState,
+ boolean transferToEmbedded(int callingUid, WindowState hostWindowState,
@NonNull InputTransferToken transferToToken) {
final EmbeddedWindowController.EmbeddedWindow ew = getByInputTransferToken(transferToToken);
if (!isValidTouchGestureParams(hostWindowState, ew)) {
return false;
}
+ if (callingUid != hostWindowState.mOwnerUid) {
+ throw new SecurityException(
+ "Transfer request must originate from owner of transferFromToken");
+ }
return mInputManagerService.transferTouchGesture(hostWindowState.mInputChannelToken,
ew.getInputChannelToken());
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 8f5612c..84072e2 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1841,6 +1841,7 @@
}
boolean attachApplication(WindowProcessController app) throws RemoteException {
+ app.mHasEverAttached = true;
final ArrayList<ActivityRecord> activities = mService.mStartingProcessActivities;
RemoteException remoteException = null;
boolean hasActivityStarted = false;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 3490b3e..a2fda0a 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2789,11 +2789,15 @@
@Override
void onDisplayChanged(DisplayContent dc) {
+ final int lastDisplayId = getDisplayId();
super.onDisplayChanged(dc);
if (isLeafTask()) {
final int displayId = (dc != null) ? dc.getDisplayId() : INVALID_DISPLAY;
- mWmService.mAtmService.getTaskChangeNotificationController().notifyTaskDisplayChanged(
- mTaskId, displayId);
+ //Send the callback when the task reparented to another display.
+ if (lastDisplayId != displayId) {
+ mWmService.mAtmService.getTaskChangeNotificationController()
+ .notifyTaskDisplayChanged(mTaskId, displayId);
+ }
}
if (isRootTask()) {
updateSurfaceBounds();
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 638e92f..42ea5a8 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -28,6 +28,7 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.view.Display.INVALID_DISPLAY;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
@@ -57,6 +58,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.internal.util.function.pooled.PooledPredicate;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.wm.LaunchParamsController.LaunchParams;
import java.io.PrintWriter;
@@ -1761,10 +1763,10 @@
* @return last reparented root task, or {@code null} if the root tasks had to be destroyed.
*/
Task remove() {
+ final TaskDisplayArea toDisplayArea = getReparentToTaskDisplayArea(getFocusedRootTask());
mPreferredTopFocusableRootTask = null;
// TODO(b/153090332): Allow setting content removal mode per task display area
final boolean destroyContentOnRemoval = mDisplayContent.shouldDestroyContentOnRemove();
- final TaskDisplayArea toDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
Task lastReparentedRootTask = null;
// Root tasks could be reparented from the removed display area to other display area. After
@@ -1830,6 +1832,41 @@
return lastReparentedRootTask;
}
+ /**
+ * Returns the {@link TaskDisplayArea} to which root tasks should be reparented.
+ *
+ * <p>In the automotive multi-user multi-display environment where background users have
+ * UI access on their assigned displays (a.k.a. visible background users), it's not allowed to
+ * launch an activity on an unassigned display. If an activity is attempted to launch on an
+ * unassigned display, it throws an exception.
+ * <p>This method determines the appropriate {@link TaskDisplayArea} for reparenting root tasks
+ * when a display is removed, in order to avoid the exception. If the root task is null,
+ * the visible background user is not supported or the user associated with the root task is
+ * not a visible background user, it returns the default {@link TaskDisplayArea} of the default
+ * display. Otherwise, it returns the default {@link TaskDisplayArea} of the main display
+ * assigned to the user.
+ *
+ * @param rootTask The root task whose {@link TaskDisplayArea} needs to be determined.
+ * @return The {@link TaskDisplayArea} where the root tasks should be reparented to.
+ */
+ private TaskDisplayArea getReparentToTaskDisplayArea(Task rootTask) {
+ final TaskDisplayArea defaultTaskDisplayArea =
+ mRootWindowContainer.getDefaultTaskDisplayArea();
+ if (rootTask == null) {
+ return defaultTaskDisplayArea;
+ }
+ UserManagerInternal userManagerInternal = mAtmService.mWindowManager.mUmInternal;
+ if (!userManagerInternal.isVisibleBackgroundFullUser(rootTask.mUserId)) {
+ return defaultTaskDisplayArea;
+ }
+ int toDisplayId = userManagerInternal.getMainDisplayAssignedToUser(rootTask.mUserId);
+ if (toDisplayId == INVALID_DISPLAY) {
+ return defaultTaskDisplayArea;
+ }
+ DisplayContent dc = mRootWindowContainer.getDisplayContent(toDisplayId);
+ return dc != null ? dc.getDefaultTaskDisplayArea() : defaultTaskDisplayArea;
+ }
+
/** Whether this task display area can request orientation. */
boolean canSpecifyOrientation(@ScreenOrientation int orientation) {
// Only allow to specify orientation if this TDA is the last focused one on this logical
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 655a6fb..0a47522 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2925,6 +2925,9 @@
final TaskFragment taskFragment = target.asTaskFragment();
final boolean isEmbeddedTaskFragment = taskFragment != null
&& taskFragment.isEmbedded();
+ final IBinder taskFragmentToken =
+ taskFragment != null ? taskFragment.getFragmentToken() : null;
+ change.setTaskFragmentToken(taskFragmentToken);
final ActivityRecord activityRecord = target.asActivityRecord();
if (task != null) {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 9d46529..7c3f0f2 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -35,6 +35,7 @@
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR;
+import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
@@ -115,7 +116,6 @@
import com.android.server.wm.SurfaceAnimator.AnimationType;
import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
import com.android.server.wm.utils.AlwaysTruePredicate;
-import com.android.window.flags.Flags;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
@@ -457,7 +457,7 @@
source.setFrame(provider.getArbitraryRectangle())
.updateSideHint(getBounds())
.setBoundingRects(provider.getBoundingRects());
- if (Flags.enableCaptionCompatInsetForceConsumption()) {
+ if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isEnabled()) {
source.setFlags(provider.getFlags());
}
mLocalInsetsSources.put(id, source);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 33f2dd1..b8f47cc 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -9212,6 +9212,8 @@
final InputApplicationHandle applicationHandle;
final String name;
Objects.requireNonNull(outInputChannel);
+ Objects.requireNonNull(inputTransferToken);
+
synchronized (mGlobalLock) {
WindowState hostWindowState = hostInputTransferToken != null
? mInputToWindowMap.get(hostInputTransferToken.getToken()) : null;
@@ -9236,6 +9238,7 @@
Objects.requireNonNull(transferFromToken);
Objects.requireNonNull(transferToToken);
+ final int callingUid = Binder.getCallingUid();
final long identity = Binder.clearCallingIdentity();
boolean didTransfer;
try {
@@ -9245,12 +9248,14 @@
// represents an embedded window so transfer from host to embedded.
WindowState windowStateTo = mInputToWindowMap.get(transferToToken.getToken());
if (windowStateTo != null) {
- didTransfer = mEmbeddedWindowController.transferToHost(transferFromToken,
+ didTransfer = mEmbeddedWindowController.transferToHost(callingUid,
+ transferFromToken,
windowStateTo);
} else {
WindowState windowStateFrom = mInputToWindowMap.get(
transferFromToken.getToken());
- didTransfer = mEmbeddedWindowController.transferToEmbedded(windowStateFrom,
+ didTransfer = mEmbeddedWindowController.transferToEmbedded(callingUid,
+ windowStateFrom,
transferToToken);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 976be4a..32fe303 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -204,6 +204,9 @@
// Set to true when process was launched with a wrapper attached
private volatile boolean mUsingWrapper;
+ /** Whether this process has ever completed ActivityThread#handleBindApplication. */
+ boolean mHasEverAttached;
+
/** Non-null if this process may have a window. */
@Nullable
Session mWindowSession;
@@ -326,6 +329,7 @@
public static final int ACTIVITY_STATE_FLAG_HAS_ACTIVITY_IN_VISIBLE_TASK = 1 << 22;
public static final int ACTIVITY_STATE_FLAG_RESUMED_SPLIT_SCREEN = 1 << 23;
public static final int ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM = 1 << 24;
+ public static final int ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE = 1 << 25;
public static final int ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER = 0x0000ffff;
/**
@@ -1293,8 +1297,12 @@
if (hasResumedFreeform
&& com.android.window.flags.Flags.processPriorityPolicyForMultiWindowMode()
// Exclude task layer 1 because it is already the top most.
- && minTaskLayer > 1 && minTaskLayer <= 1 + MAX_NUM_PERCEPTIBLE_FREEFORM) {
- stateFlags |= ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM;
+ && minTaskLayer > 1) {
+ if (minTaskLayer <= 1 + MAX_NUM_PERCEPTIBLE_FREEFORM) {
+ stateFlags |= ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM;
+ } else {
+ stateFlags |= ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE;
+ }
}
stateFlags |= minTaskLayer & ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER;
if (visible) {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 8c4448e..d2493c5 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -1808,9 +1808,30 @@
ATRACE_CALL();
JNIEnv* env = jniEnv();
- for (int32_t iconId = static_cast<int32_t>(PointerIconStyle::TYPE_CONTEXT_MENU);
- iconId <= static_cast<int32_t>(PointerIconStyle::TYPE_HANDWRITING); ++iconId) {
- const PointerIconStyle pointerIconStyle = static_cast<PointerIconStyle>(iconId);
+ constexpr static std::array ADDITIONAL_STYLES{PointerIconStyle::TYPE_CONTEXT_MENU,
+ PointerIconStyle::TYPE_HAND,
+ PointerIconStyle::TYPE_HELP,
+ PointerIconStyle::TYPE_WAIT,
+ PointerIconStyle::TYPE_CELL,
+ PointerIconStyle::TYPE_CROSSHAIR,
+ PointerIconStyle::TYPE_TEXT,
+ PointerIconStyle::TYPE_VERTICAL_TEXT,
+ PointerIconStyle::TYPE_ALIAS,
+ PointerIconStyle::TYPE_COPY,
+ PointerIconStyle::TYPE_NO_DROP,
+ PointerIconStyle::TYPE_ALL_SCROLL,
+ PointerIconStyle::TYPE_HORIZONTAL_DOUBLE_ARROW,
+ PointerIconStyle::TYPE_VERTICAL_DOUBLE_ARROW,
+ PointerIconStyle::TYPE_TOP_RIGHT_DOUBLE_ARROW,
+ PointerIconStyle::TYPE_TOP_LEFT_DOUBLE_ARROW,
+ PointerIconStyle::TYPE_ZOOM_IN,
+ PointerIconStyle::TYPE_ZOOM_OUT,
+ PointerIconStyle::TYPE_GRAB,
+ PointerIconStyle::TYPE_GRABBING,
+ PointerIconStyle::TYPE_HANDWRITING,
+ PointerIconStyle::TYPE_SPOT_HOVER};
+
+ for (const auto pointerIconStyle : ADDITIONAL_STYLES) {
PointerIcon pointerIcon = loadPointerIcon(env, displayId, pointerIconStyle);
(*outResources)[pointerIconStyle] = toSpriteIcon(pointerIcon);
if (!pointerIcon.bitmapFrames.empty()) {
@@ -2704,12 +2725,13 @@
}
static void nativeSetKeyRepeatConfiguration(JNIEnv* env, jobject nativeImplObj, jint timeoutMs,
- jint delayMs) {
+ jint delayMs, jboolean keyRepeatEnabled) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getDispatcher().setKeyRepeatConfiguration(std::chrono::milliseconds(
timeoutMs),
std::chrono::milliseconds(
- delayMs));
+ delayMs),
+ keyRepeatEnabled);
}
static jobject createInputSensorInfo(JNIEnv* env, jstring name, jstring vendor, jint version,
@@ -3008,7 +3030,7 @@
{"setDisplayEligibilityForPointerCapture", "(IZ)V",
(void*)nativeSetDisplayEligibilityForPointerCapture},
{"setMotionClassifierEnabled", "(Z)V", (void*)nativeSetMotionClassifierEnabled},
- {"setKeyRepeatConfiguration", "(II)V", (void*)nativeSetKeyRepeatConfiguration},
+ {"setKeyRepeatConfiguration", "(IIZ)V", (void*)nativeSetKeyRepeatConfiguration},
{"getSensorList", "(I)[Landroid/hardware/input/InputSensorInfo;",
(void*)nativeGetSensorList},
{"getTouchpadHardwareProperties",
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 24ee46f..f271162 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -102,9 +102,6 @@
PolicyEnforcerCallbacks.setAutoTimezoneEnabled(value, context),
new BooleanPolicySerializer());
- // This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the
- // actual policy with the correct arguments (packageName and permission name)
- // when reading the policies from xml.
static final PolicyDefinition<Integer> GENERIC_PERMISSION_GRANT =
new PolicyDefinition<>(
new PackagePermissionPolicyKey(DevicePolicyIdentifiers.PERMISSION_GRANT_POLICY),
@@ -123,10 +120,6 @@
PolicyEnforcerCallbacks::setPermissionGrantState,
new IntegerPolicySerializer());
- /**
- * Passing in {@code null} for {@code packageName} or {@code permissionName} will return a
- * {@link #GENERIC_PERMISSION_GRANT}.
- */
static PolicyDefinition<Integer> PERMISSION_GRANT(
@NonNull String packageName, @NonNull String permissionName) {
Objects.requireNonNull(packageName, "packageName must not be null");
@@ -170,9 +163,6 @@
PolicyEnforcerCallbacks::setUserControlDisabledPackages,
new PackageSetPolicySerializer());
- // This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the
- // actual policy with the correct arguments (i.e. packageName) when reading the policies from
- // xml.
static PolicyDefinition<ComponentName> GENERIC_PERSISTENT_PREFERRED_ACTIVITY =
new PolicyDefinition<>(
new IntentFilterPolicyKey(
@@ -184,10 +174,6 @@
PolicyEnforcerCallbacks::addPersistentPreferredActivity,
new ComponentNamePolicySerializer());
- /**
- * Passing in {@code null} for {@code intentFilter} will return
- * {@link #GENERIC_PERSISTENT_PREFERRED_ACTIVITY}.
- */
static PolicyDefinition<ComponentName> PERSISTENT_PREFERRED_ACTIVITY(
@NonNull IntentFilter intentFilter) {
Objects.requireNonNull(intentFilter, "intentFilter must not be null");
@@ -197,9 +183,6 @@
intentFilter));
}
- // This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the
- // actual policy with the correct arguments (i.e. packageName) when reading the policies from
- // xml.
static PolicyDefinition<Boolean> GENERIC_PACKAGE_UNINSTALL_BLOCKED =
new PolicyDefinition<>(
new PackagePolicyKey(
@@ -209,10 +192,6 @@
PolicyEnforcerCallbacks::setUninstallBlocked,
new BooleanPolicySerializer());
- /**
- * Passing in {@code null} for {@code packageName} will return
- * {@link #GENERIC_PACKAGE_UNINSTALL_BLOCKED}.
- */
static PolicyDefinition<Boolean> PACKAGE_UNINSTALL_BLOCKED(@NonNull String packageName) {
Objects.requireNonNull(packageName, "packageName must not be null");
return GENERIC_PACKAGE_UNINSTALL_BLOCKED.createPolicyDefinition(
@@ -220,9 +199,6 @@
DevicePolicyIdentifiers.PACKAGE_UNINSTALL_BLOCKED_POLICY, packageName));
}
- // This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the
- // actual policy with the correct arguments (i.e. packageName) when reading the policies from
- // xml.
static PolicyDefinition<Bundle> GENERIC_APPLICATION_RESTRICTIONS =
new PolicyDefinition<>(
new PackagePolicyKey(
@@ -237,10 +213,6 @@
PolicyEnforcerCallbacks::setApplicationRestrictions,
new BundlePolicySerializer());
- /**
- * Passing in {@code null} for {@code packageName} will return
- * {@link #GENERIC_APPLICATION_RESTRICTIONS}.
- */
static PolicyDefinition<Bundle> APPLICATION_RESTRICTIONS(@NonNull String packageName) {
Objects.requireNonNull(packageName, "packageName must not be null");
return GENERIC_APPLICATION_RESTRICTIONS.createPolicyDefinition(
@@ -266,9 +238,6 @@
PolicyEnforcerCallbacks::noOp,
new IntegerPolicySerializer());
- // This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the
- // actual policy with the correct arguments (i.e. packageName) when reading the policies from
- // xml.
static PolicyDefinition<Boolean> GENERIC_APPLICATION_HIDDEN =
new PolicyDefinition<>(
new PackagePolicyKey(
@@ -281,10 +250,6 @@
PolicyEnforcerCallbacks::setApplicationHidden,
new BooleanPolicySerializer());
- /**
- * Passing in {@code null} for {@code packageName} will return
- * {@link #GENERIC_APPLICATION_HIDDEN}.
- */
static PolicyDefinition<Boolean> APPLICATION_HIDDEN(@NonNull String packageName) {
Objects.requireNonNull(packageName, "packageName must not be null");
return GENERIC_APPLICATION_HIDDEN.createPolicyDefinition(
@@ -292,9 +257,6 @@
DevicePolicyIdentifiers.APPLICATION_HIDDEN_POLICY, packageName));
}
- // This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the
- // actual policy with the correct arguments (i.e. packageName) when reading the policies from
- // xml.
static PolicyDefinition<Boolean> GENERIC_ACCOUNT_MANAGEMENT_DISABLED =
new PolicyDefinition<>(
new AccountTypePolicyKey(
@@ -305,10 +267,6 @@
PolicyEnforcerCallbacks::noOp,
new BooleanPolicySerializer());
- /**
- * Passing in {@code null} for {@code accountType} will return
- * {@link #GENERIC_ACCOUNT_MANAGEMENT_DISABLED}.
- */
static PolicyDefinition<Boolean> ACCOUNT_MANAGEMENT_DISABLED(@NonNull String accountType) {
Objects.requireNonNull(accountType, "accountType must not be null");
return GENERIC_ACCOUNT_MANAGEMENT_DISABLED.createPolicyDefinition(
@@ -668,8 +626,6 @@
throw new UnsupportedOperationException("Non-coexistable global policies not supported,"
+ "please add support.");
}
- // TODO: maybe use this instead of manually adding to the map
-// sPolicyDefinitions.put(policyDefinitionKey, this);
}
void saveToXml(TypedXmlSerializer serializer) throws IOException {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index ab459df..3b334ec 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -107,7 +107,7 @@
import com.android.internal.os.RuntimeInit;
import com.android.internal.policy.AttributeCache;
import com.android.internal.protolog.ProtoLog;
-import com.android.internal.protolog.ProtoLogConfigurationService;
+import com.android.internal.protolog.ProtoLogConfigurationServiceImpl;
import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.EmergencyAffordanceManager;
@@ -436,6 +436,10 @@
private static final String PROFILING_SERVICE_JAR_PATH =
"/apex/com.android.profiling/javalib/service-profiling.jar";
+ private static final String RANGING_APEX_SERVICE_JAR_PATH =
+ "/apex/com.android.uwb/javalib/service-ranging.jar";
+ private static final String RANGING_SERVICE_CLASS = "com.android.server.ranging.RangingService";
+
private static final String TETHERING_CONNECTOR_CLASS = "android.net.ITetheringConnector";
private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
@@ -1097,7 +1101,7 @@
if (android.tracing.Flags.clientSideProtoLogging()) {
t.traceBegin("StartProtoLogConfigurationService");
ServiceManager.addService(
- Context.PROTOLOG_CONFIGURATION_SERVICE, new ProtoLogConfigurationService());
+ Context.PROTOLOG_CONFIGURATION_SERVICE, new ProtoLogConfigurationServiceImpl());
t.traceEnd();
}
@@ -3015,6 +3019,17 @@
t.traceEnd();
}
+ if (com.android.ranging.flags.Flags.rangingStackEnabled()) {
+ if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_UWB)
+ || context.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_WIFI_RTT)) {
+ t.traceBegin("RangingService");
+ mSystemServiceManager.startServiceFromJar(RANGING_SERVICE_CLASS,
+ RANGING_APEX_SERVICE_JAR_PATH);
+ t.traceEnd();
+ }
+ }
+
t.traceBegin("StartBootPhaseDeviceSpecificServicesReady");
mSystemServiceManager.startBootPhase(t, SystemService.PHASE_DEVICE_SPECIFIC_SERVICES_READY);
t.traceEnd();
diff --git a/services/lint-baseline.xml b/services/lint-baseline.xml
index a311d07..95da56d 100644
--- a/services/lint-baseline.xml
+++ b/services/lint-baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
+<issues format="6" by="lint 8.4.0-alpha08" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha08">
<issue
id="SimpleManualPermissionEnforcement"
@@ -8,7 +8,7 @@
errorLine2=" ^">
<location
file="frameworks/base/services/java/com/android/server/SystemConfigService.java"
- line="46"
+ line="50"
column="13"/>
</issue>
@@ -19,7 +19,7 @@
errorLine2=" ^">
<location
file="frameworks/base/services/java/com/android/server/SystemConfigService.java"
- line="54"
+ line="58"
column="13"/>
</issue>
@@ -30,7 +30,7 @@
errorLine2=" ^">
<location
file="frameworks/base/services/java/com/android/server/SystemConfigService.java"
- line="67"
+ line="71"
column="13"/>
</issue>
@@ -41,7 +41,7 @@
errorLine2=" ^">
<location
file="frameworks/base/services/java/com/android/server/SystemConfigService.java"
- line="76"
+ line="80"
column="13"/>
</issue>
@@ -52,8 +52,30 @@
errorLine2=" ^">
<location
file="frameworks/base/services/java/com/android/server/SystemConfigService.java"
- line="107"
+ line="111"
column="13"/>
</issue>
-</issues>
\ No newline at end of file
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="ISystemConfig permission check should be converted to @EnforcePermission annotation"
+ errorLine1=" getContext().enforceCallingOrSelfPermission("
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/java/com/android/server/SystemConfigService.java"
+ line="127"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="ISystemConfig permission check should be converted to @EnforcePermission annotation"
+ errorLine1=" getContext().enforceCallingOrSelfPermission("
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/java/com/android/server/SystemConfigService.java"
+ line="137"
+ column="13"/>
+ </issue>
+
+</issues>
diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
index 63cf7bf..c05c381 100644
--- a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
+++ b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
@@ -139,16 +139,15 @@
runtimeSearchSession.put(putDocumentsRequest).get()
staticSearchSession.put(putDocumentsRequest).get()
val metadataSyncAdapter =
- MetadataSyncAdapter(
- testExecutor,
- runtimeSearchSession,
+ MetadataSyncAdapter(testExecutor, packageManager, appSearchManager)
+
+ val submitSyncRequest =
+ metadataSyncAdapter.trySyncAppFunctionMetadataBlocking(
staticSearchSession,
- packageManager,
+ runtimeSearchSession,
)
- val submitSyncRequest = metadataSyncAdapter.submitSyncRequest()
-
- assertThat(submitSyncRequest.get()).isTrue()
+ assertThat(submitSyncRequest).isInstanceOf(Unit::class.java)
}
@Test
@@ -182,16 +181,15 @@
PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build()
staticSearchSession.put(putDocumentsRequest).get()
val metadataSyncAdapter =
- MetadataSyncAdapter(
- testExecutor,
- runtimeSearchSession,
+ MetadataSyncAdapter(testExecutor, packageManager, appSearchManager)
+
+ val submitSyncRequest =
+ metadataSyncAdapter.trySyncAppFunctionMetadataBlocking(
staticSearchSession,
- packageManager,
+ runtimeSearchSession,
)
- val submitSyncRequest = metadataSyncAdapter.submitSyncRequest()
-
- assertThat(submitSyncRequest.get()).isTrue()
+ assertThat(submitSyncRequest).isInstanceOf(Unit::class.java)
}
@Test
@@ -239,16 +237,15 @@
PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build()
runtimeSearchSession.put(putDocumentsRequest).get()
val metadataSyncAdapter =
- MetadataSyncAdapter(
- testExecutor,
- runtimeSearchSession,
+ MetadataSyncAdapter(testExecutor, packageManager, appSearchManager)
+
+ val submitSyncRequest =
+ metadataSyncAdapter.trySyncAppFunctionMetadataBlocking(
staticSearchSession,
- packageManager,
+ runtimeSearchSession,
)
- val submitSyncRequest = metadataSyncAdapter.submitSyncRequest()
-
- assertThat(submitSyncRequest.get()).isTrue()
+ assertThat(submitSyncRequest).isInstanceOf(Unit::class.java)
}
@Test
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 8b80f85..255dcb0 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -27,6 +27,7 @@
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY;
import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
+import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.server.display.ExternalDisplayPolicy.ENABLE_ON_CONNECT;
@@ -195,8 +196,8 @@
private static final String VIRTUAL_DISPLAY_NAME = "Test Virtual Display";
private static final String PACKAGE_NAME = "com.android.frameworks.displayservicetests";
private static final long STANDARD_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED
- | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
- | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
+ | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+ | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
private static final long STANDARD_AND_CONNECTION_DISPLAY_EVENTS =
STANDARD_DISPLAY_EVENTS | DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED;
@@ -238,6 +239,8 @@
private UserManager mUserManager;
+ private int[] mAllowedHdrOutputTypes;
+
private final DisplayManagerService.Injector mShortMockedInjector =
new DisplayManagerService.Injector() {
@Override
@@ -256,11 +259,12 @@
displayAdapterListener, flags,
mMockedDisplayNotificationManager,
new LocalDisplayAdapter.Injector() {
- @Override
- public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
- return mSurfaceControlProxy;
- }
- });
+ @Override
+ public LocalDisplayAdapter.SurfaceControlProxy
+ getSurfaceControlProxy() {
+ return mSurfaceControlProxy;
+ }
+ });
}
@Override
@@ -320,7 +324,7 @@
@Override
int setHdrConversionMode(int conversionMode, int preferredHdrOutputType,
- int[] autoHdrTypes) {
+ int[] allowedHdrOutputTypes) {
mHdrConversionMode = conversionMode;
mPreferredHdrOutputType = preferredHdrOutputType;
return Display.HdrCapabilities.HDR_TYPE_INVALID;
@@ -1295,11 +1299,11 @@
.setUniqueId("uniqueId --- mirror display");
assertThrows(SecurityException.class, () -> {
localService.createVirtualDisplay(
- builder.build(),
- mMockAppToken /* callback */,
- null /* virtualDeviceToken */,
- mock(DisplayWindowPolicyController.class),
- PACKAGE_NAME);
+ builder.build(),
+ mMockAppToken /* callback */,
+ null /* virtualDeviceToken */,
+ mock(DisplayWindowPolicyController.class),
+ PACKAGE_NAME);
});
}
@@ -1433,7 +1437,7 @@
// The virtual display should not have FLAG_ALWAYS_UNLOCKED set.
assertEquals(0, (displayManager.getDisplayDeviceInfoInternal(displayId).flags
- & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED));
+ & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED));
}
/**
@@ -1466,7 +1470,7 @@
// The virtual display should not have FLAG_PRESENTATION set.
assertEquals(0, (displayManager.getDisplayDeviceInfoInternal(displayId).flags
- & DisplayDeviceInfo.FLAG_PRESENTATION));
+ & DisplayDeviceInfo.FLAG_PRESENTATION));
}
@Test
@@ -2358,6 +2362,7 @@
HdrConversionMode.HDR_CONVERSION_FORCE,
Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION);
displayManager.setHdrConversionModeInternal(mode);
+
assertEquals(mode, displayManager.getHdrConversionModeSettingInternal());
assertEquals(mode.getConversionMode(), mHdrConversionMode);
assertEquals(mode.getPreferredHdrOutputType(), mPreferredHdrOutputType);
@@ -2402,6 +2407,86 @@
}
@Test
+ public void testSetAreUserDisabledHdrTypesAllowed_withFalse_whenHdrDisabled_stripsHdrType() {
+ DisplayManagerService displayManager = new DisplayManagerService(
+ mContext, new BasicInjector() {
+ @Override
+ int setHdrConversionMode(int conversionMode, int preferredHdrOutputType,
+ int[] allowedTypes) {
+ mAllowedHdrOutputTypes = allowedTypes;
+ return Display.HdrCapabilities.HDR_TYPE_INVALID;
+ }
+
+ // Overriding this method to capture the allowed HDR type
+ @Override
+ int[] getSupportedHdrOutputTypes() {
+ return new int[]{Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION};
+ }
+ });
+
+ // Setup: no HDR types disabled, userDisabledTypes allowed, system conversion
+ displayManager.setUserDisabledHdrTypesInternal(new int [0]);
+ displayManager.setAreUserDisabledHdrTypesAllowedInternal(true);
+ displayManager.setHdrConversionModeInternal(
+ new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM));
+
+ assertEquals(1, mAllowedHdrOutputTypes.length);
+ assertTrue(Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION == mAllowedHdrOutputTypes[0]);
+
+ // Action: disable Dolby Vision, set userDisabledTypes not allowed
+ displayManager.setUserDisabledHdrTypesInternal(
+ new int [] {Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION});
+ displayManager.setAreUserDisabledHdrTypesAllowedInternal(false);
+
+ assertEquals(0, mAllowedHdrOutputTypes.length);
+ }
+
+ @Test
+ public void testGetEnabledHdrTypesLocked_whenTypesDisabled_stripsDisabledTypes() {
+ DisplayManagerService displayManager = new DisplayManagerService(
+ mContext, new BasicInjector() {
+ @Override
+ int[] getSupportedHdrOutputTypes() {
+ return new int[]{Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION};
+ }
+ });
+
+ displayManager.setUserDisabledHdrTypesInternal(new int [0]);
+ displayManager.setAreUserDisabledHdrTypesAllowedInternal(true);
+ int [] enabledHdrOutputTypes = displayManager.getEnabledHdrOutputTypes();
+ assertEquals(1, enabledHdrOutputTypes.length);
+ assertTrue(Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION == enabledHdrOutputTypes[0]);
+
+ displayManager.setAreUserDisabledHdrTypesAllowedInternal(false);
+ enabledHdrOutputTypes = displayManager.getEnabledHdrOutputTypes();
+ assertEquals(1, enabledHdrOutputTypes.length);
+ assertTrue(Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION == enabledHdrOutputTypes[0]);
+
+ displayManager.setUserDisabledHdrTypesInternal(
+ new int [] {Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION});
+ enabledHdrOutputTypes = displayManager.getEnabledHdrOutputTypes();
+ assertEquals(0, enabledHdrOutputTypes.length);
+ }
+
+ @Test
+ public void testSetHdrConversionModeInternal_isForceSdrIsUpdated() {
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
+ FakeDisplayDevice displayDevice =
+ createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
+ LogicalDisplay logicalDisplay =
+ logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
+
+ displayManager.setHdrConversionModeInternal(
+ new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, HDR_TYPE_INVALID));
+ assertTrue(logicalDisplay.getDisplayInfoLocked().isForceSdr);
+
+ displayManager.setHdrConversionModeInternal(
+ new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM));
+ assertFalse(logicalDisplay.getDisplayInfoLocked().isForceSdr);
+ }
+
+ @Test
public void testReturnsRefreshRateForDisplayAndSensor_proximitySensorSet() {
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
DisplayManagerInternal localService = displayManager.new LocalService();
@@ -3505,7 +3590,7 @@
}
private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager,
- Display.Mode[] modes) {
+ Display.Mode[] modes) {
FakeDisplayDevice displayDevice = new FakeDisplayDevice();
DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
displayDeviceInfo.supportedModes = modes;
@@ -3761,9 +3846,9 @@
public void setUserPreferredDisplayModeLocked(Display.Mode preferredMode) {
for (Display.Mode mode : mDisplayDeviceInfo.supportedModes) {
if (mode.matchesIfValid(
- preferredMode.getPhysicalWidth(),
- preferredMode.getPhysicalHeight(),
- preferredMode.getRefreshRate())) {
+ preferredMode.getPhysicalWidth(),
+ preferredMode.getPhysicalHeight(),
+ preferredMode.getRefreshRate())) {
mPreferredMode = mode;
break;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java
index 8b65337..32135f1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java
@@ -22,9 +22,10 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
@@ -36,6 +37,7 @@
import android.media.projection.MediaProjectionInfo;
import android.media.projection.MediaProjectionManager;
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;
@@ -137,9 +139,17 @@
mSensitiveContentProtectionManagerService.mNotificationListener =
spy(mSensitiveContentProtectionManagerService.mNotificationListener);
- doCallRealMethod()
- .when(mSensitiveContentProtectionManagerService.mNotificationListener)
- .onListenerConnected();
+
+ // Unexpected NLS interactions when registered cause test flakes. For purposes of this test,
+ // the test will control any NLS calls.
+ try {
+ doNothing().when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .registerAsSystemService(any(), any(), anyInt());
+ doNothing().when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .unregisterAsSystemService();
+ } catch (RemoteException e) {
+ // Intra-process call, should never happen.
+ }
// Setup RankingMap and two possilbe rankings
when(mSensitiveRanking.hasSensitiveContent()).thenReturn(true);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 5ec5302..f6ad07d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -62,6 +62,7 @@
import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
import static com.android.server.am.ProcessList.SCHED_GROUP_DEFAULT;
+import static com.android.server.am.ProcessList.SCHED_GROUP_FOREGROUND_WINDOW;
import static com.android.server.am.ProcessList.SCHED_GROUP_RESTRICTED;
import static com.android.server.am.ProcessList.SCHED_GROUP_TOP_APP;
import static com.android.server.am.ProcessList.SCHED_GROUP_TOP_APP_BOUND;
@@ -534,6 +535,14 @@
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ, SCHED_GROUP_TOP_APP);
assertEquals("perceptible-freeform-activity", app.mState.getAdjType());
+
+ doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_VISIBLE
+ | WindowProcessController.ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE)
+ .when(wpc).getActivityStateFlags();
+ updateOomAdj(app);
+ assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ,
+ SCHED_GROUP_FOREGROUND_WINDOW);
+ assertEquals("vis-multi-window-activity", app.mState.getAdjType());
}
@SuppressWarnings("GuardedBy")
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java
index a82658b..3062d51 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java
@@ -16,13 +16,19 @@
package com.android.server.pm;
+import static android.media.AudioAttributes.USAGE_ALARM;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+import static org.testng.AssertJUnit.assertEquals;
import android.app.Notification;
import android.app.NotificationManager;
@@ -31,6 +37,9 @@
import android.media.AudioAttributes;
import android.media.AudioFocusInfo;
import android.media.AudioManager;
+import android.media.AudioPlaybackConfiguration;
+import android.media.PlayerProxy;
+import android.media.audiopolicy.AudioPolicy;
import android.os.Build;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -45,6 +54,10 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Stack;
+
@RunWith(JUnit4.class)
public class BackgroundUserSoundNotifierTest {
@@ -63,7 +76,10 @@
MockitoAnnotations.initMocks(this);
mSpiedContext = spy(mRealContext);
mUsersToRemove = new ArraySet<>();
- mUserManager = UserManager.get(mRealContext);
+
+ mUserManager = spy(mSpiedContext.getSystemService(UserManager.class));
+ doReturn(mUserManager)
+ .when(mSpiedContext).getSystemService(UserManager.class);
doReturn(mNotificationManager)
.when(mSpiedContext).getSystemService(NotificationManager.class);
mBackgroundUserSoundNotifier = new BackgroundUserSoundNotifier(mSpiedContext);
@@ -74,12 +90,9 @@
mUsersToRemove.stream().toList().forEach(this::removeUser);
}
@Test
- public void testAlarmOnBackgroundUser_ForegroundUserNotified() throws RemoteException {
- AudioAttributes aa = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_ALARM).build();
- UserInfo user = createUser("User",
- UserManager.USER_TYPE_FULL_SECONDARY,
- 0);
+ public void testAlarmOnBackgroundUser_foregroundUserNotified() throws RemoteException {
+ AudioAttributes aa = new AudioAttributes.Builder().setUsage(USAGE_ALARM).build();
+ UserInfo user = createUser("User", UserManager.USER_TYPE_FULL_SECONDARY, 0);
final int fgUserId = mSpiedContext.getUserId();
final int bgUserUid = user.id * 100000;
doReturn(UserHandle.of(fgUserId)).when(mSpiedContext).getUser();
@@ -95,10 +108,9 @@
}
@Test
- public void testMediaOnBackgroundUser_ForegroundUserNotNotified() throws RemoteException {
+ public void testMediaOnBackgroundUser_foregroundUserNotNotified() throws RemoteException {
AudioAttributes aa = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA).build();
- UserInfo user = createUser("User", UserManager.USER_TYPE_FULL_SECONDARY, 0);
final int bgUserUid = mSpiedContext.getUserId() * 100000;
AudioFocusInfo afi = new AudioFocusInfo(aa, bgUserUid, "",
/* packageName= */ "com.android.car.audio", AudioManager.AUDIOFOCUS_GAIN,
@@ -109,9 +121,9 @@
}
@Test
- public void testAlarmOnForegroundUser_ForegroundUserNotNotified() throws RemoteException {
+ public void testAlarmOnForegroundUser_foregroundUserNotNotified() throws RemoteException {
AudioAttributes aa = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_ALARM).build();
+ .setUsage(USAGE_ALARM).build();
final int fgUserId = mSpiedContext.getUserId();
final int fgUserUid = fgUserId * 100000;
doReturn(UserHandle.of(fgUserId)).when(mSpiedContext).getUser();
@@ -123,6 +135,109 @@
verifyZeroInteractions(mNotificationManager);
}
+ @Test
+ public void testMuteAlarmSounds() {
+ final int fgUserId = mSpiedContext.getUserId();
+ int bgUserId = fgUserId + 1;
+ int bgUserUid = bgUserId * 100000;
+ mBackgroundUserSoundNotifier.mNotificationClientUid = bgUserUid;
+
+ AudioManager mockAudioManager = mock(AudioManager.class);
+ when(mSpiedContext.getSystemService(AudioManager.class)).thenReturn(mockAudioManager);
+
+ AudioPlaybackConfiguration apc1 = mock(AudioPlaybackConfiguration.class);
+ when(apc1.getClientUid()).thenReturn(bgUserUid);
+ when(apc1.getPlayerProxy()).thenReturn(mock(PlayerProxy.class));
+
+ AudioPlaybackConfiguration apc2 = mock(AudioPlaybackConfiguration.class);
+ when(apc2.getClientUid()).thenReturn(bgUserUid + 1);
+ when(apc2.getPlayerProxy()).thenReturn(mock(PlayerProxy.class));
+
+ List<AudioPlaybackConfiguration> configs = new ArrayList<>();
+ configs.add(apc1);
+ configs.add(apc2);
+ when(mockAudioManager.getActivePlaybackConfigurations()).thenReturn(configs);
+
+ AudioPolicy mockAudioPolicy = mock(AudioPolicy.class);
+
+ AudioAttributes aa = new AudioAttributes.Builder().setUsage(USAGE_ALARM).build();
+ AudioFocusInfo afi = new AudioFocusInfo(aa, bgUserUid, "", /* packageName= */ "",
+ AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0,
+ Build.VERSION.SDK_INT);
+ Stack<AudioFocusInfo> focusStack = new Stack<>();
+ focusStack.add(afi);
+ doReturn(focusStack).when(mockAudioPolicy).getFocusStack();
+ mBackgroundUserSoundNotifier.mFocusControlAudioPolicy = mockAudioPolicy;
+
+ mBackgroundUserSoundNotifier.muteAlarmSounds(mSpiedContext);
+
+ verify(apc1.getPlayerProxy()).stop();
+ verify(apc2.getPlayerProxy(), never()).stop();
+ }
+
+ @Test
+ public void testOnAudioFocusGrant_alarmOnBackgroundUser_notifiesForegroundUser() {
+ final int fgUserId = mSpiedContext.getUserId();
+ UserInfo bgUser = createUser("Background User", UserManager.USER_TYPE_FULL_SECONDARY, 0);
+ int bgUserUid = bgUser.id * 100000;
+
+ AudioAttributes aa = new AudioAttributes.Builder().setUsage(USAGE_ALARM).build();
+ AudioFocusInfo afi = new AudioFocusInfo(aa, bgUserUid, "", "",
+ AudioManager.AUDIOFOCUS_GAIN, 0, 0, Build.VERSION.SDK_INT);
+
+ mBackgroundUserSoundNotifier.getAudioPolicyFocusListener()
+ .onAudioFocusGrant(afi, AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+
+ verify(mNotificationManager)
+ .notifyAsUser(eq(BackgroundUserSoundNotifier.class.getSimpleName()),
+ eq(afi.getClientUid()), any(Notification.class),
+ eq(UserHandle.of(fgUserId)));
+ }
+
+
+ @Test
+ public void testCreateNotification_UserSwitcherEnabled_bothActionsAvailable() {
+ String userName = "BgUser";
+
+ doReturn(true).when(mUserManager).isUserSwitcherEnabled();
+ doReturn(UserManager.SWITCHABILITY_STATUS_OK)
+ .when(mUserManager).getUserSwitchability(any());
+
+ Notification notification = mBackgroundUserSoundNotifier.createNotification(userName,
+ mSpiedContext);
+
+ assertEquals("Alarm for BgUser", notification.extras.getString(
+ Notification.EXTRA_TITLE));
+ assertEquals(Notification.CATEGORY_REMINDER, notification.category);
+ assertEquals(Notification.VISIBILITY_PUBLIC, notification.visibility);
+ assertEquals(com.android.internal.R.drawable.ic_audio_alarm,
+ notification.getSmallIcon().getResId());
+
+ assertEquals(2, notification.actions.length);
+ assertEquals(mSpiedContext.getString(
+ com.android.internal.R.string.bg_user_sound_notification_button_mute),
+ notification.actions[0].title);
+ assertEquals(mSpiedContext.getString(
+ com.android.internal.R.string.bg_user_sound_notification_button_switch_user),
+ notification.actions[1].title);
+ }
+
+ @Test
+ public void testCreateNotification_UserSwitcherDisabled_onlyMuteActionAvailable() {
+ String userName = "BgUser";
+
+ doReturn(false).when(mUserManager).isUserSwitcherEnabled();
+ doReturn(UserManager.SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED)
+ .when(mUserManager).getUserSwitchability(any());
+
+ Notification notification = mBackgroundUserSoundNotifier.createNotification(userName,
+ mSpiedContext);
+
+ assertEquals(1, notification.actions.length);
+ assertEquals(mSpiedContext.getString(
+ com.android.internal.R.string.bg_user_sound_notification_button_mute),
+ notification.actions[0].title);
+ }
private UserInfo createUser(String name, String userType, int flags) {
UserInfo user = mUserManager.createUser(name, userType, flags);
diff --git a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
index 1a398c5..e0c393c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
@@ -100,6 +100,7 @@
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
+import com.android.server.pm.UserManagerInternal;
import org.junit.After;
import org.junit.Before;
@@ -145,6 +146,7 @@
private static final String URI_SCHEME_PACKAGE = "package";
private static final int TEST_USER_ID = 50;
+ private static final int TEST_VISIBLE_BACKGROUND_USER_ID = 51;
private static final UserInfo TEST_USER =
new UserInfo(TEST_USER_ID, "user", UserInfo.FLAG_FULL);
private static final int PARENT_USER_ID = 60;
@@ -170,6 +172,7 @@
private @Mock KeyStoreAuthorization mKeyStoreAuthorization;
private @Mock LockPatternUtils mLockPatternUtils;
private @Mock LockSettingsInternal mLockSettingsInternal;
+ private @Mock UserManagerInternal mUserManagerInternal;
private @Mock PackageManager mPackageManager;
private @Mock UserManager mUserManager;
private @Mock IWindowManager mWindowManager;
@@ -224,6 +227,7 @@
when(mUserManager.getAliveUsers()).thenReturn(List.of(TEST_USER));
when(mUserManager.getEnabledProfileIds(TEST_USER_ID)).thenReturn(new int[0]);
when(mUserManager.getUserInfo(TEST_USER_ID)).thenReturn(TEST_USER);
+ when(mUserManager.isVisibleBackgroundUsersSupported()).thenReturn(false);
when(mWindowManager.isKeyguardLocked()).thenReturn(true);
@@ -593,6 +597,54 @@
verify(mTrustListener, never()).onTrustManagedChanged(anyBoolean(), anyInt());
}
+ @Test
+ public void testDeviceLocked_visibleBackgroundUser_userLocked() throws RemoteException {
+ setupVisibleBackgroundUser(/* visible= */ true, /* unlocked= */ false);
+ mService.waitForIdle();
+ mTrustManager.reportEnabledTrustAgentsChanged(TEST_VISIBLE_BACKGROUND_USER_ID);
+ mService.waitForIdle();
+ assertThat(mService.isDeviceLockedInner(TEST_VISIBLE_BACKGROUND_USER_ID)).isTrue();
+ }
+
+ @Test
+ public void testDeviceLocked_visibleBackgroundUser_userUnlocked() throws RemoteException {
+ setupVisibleBackgroundUser(/* visible= */ true, /* unlocked= */ true);
+ mService.waitForIdle();
+ mTrustManager.reportEnabledTrustAgentsChanged(TEST_VISIBLE_BACKGROUND_USER_ID);
+ mService.waitForIdle();
+ assertThat(mService.isDeviceLockedInner(TEST_VISIBLE_BACKGROUND_USER_ID)).isFalse();
+ }
+
+ @Test
+ public void testDeviceLocked_invisibleBackgroundUser_userUnlocked() throws RemoteException {
+ setupVisibleBackgroundUser(/* visible= */ false, /* unlocked= */ true);
+ mService.waitForIdle();
+ mTrustManager.reportEnabledTrustAgentsChanged(TEST_VISIBLE_BACKGROUND_USER_ID);
+ mService.waitForIdle();
+ assertThat(mService.isDeviceLockedInner(TEST_VISIBLE_BACKGROUND_USER_ID)).isTrue();
+ }
+
+ private void setupVisibleBackgroundUser(boolean visible, boolean unlocked) {
+ UserInfo info = new UserInfo(TEST_VISIBLE_BACKGROUND_USER_ID, "visible bg user",
+ UserInfo.FLAG_FULL);
+
+ when(mActivityManager.isUserRunning(TEST_VISIBLE_BACKGROUND_USER_ID)).thenReturn(true);
+
+ when(mLockPatternUtils.isSecure(TEST_VISIBLE_BACKGROUND_USER_ID)).thenReturn(true);
+
+ when(mUserManager.getAliveUsers()).thenReturn(List.of(TEST_USER, info));
+ when(mUserManager.getEnabledProfileIds(TEST_VISIBLE_BACKGROUND_USER_ID)).thenReturn(
+ new int[0]);
+ when(mUserManager.getUserInfo(TEST_VISIBLE_BACKGROUND_USER_ID)).thenReturn(info);
+ when(mUserManager.isUserUnlocked(TEST_VISIBLE_BACKGROUND_USER_ID)).thenReturn(unlocked);
+ when(mUserManager.isVisibleBackgroundUsersSupported()).thenReturn(true);
+
+ LocalServices.removeServiceForTest(UserManagerInternal.class);
+ LocalServices.addService(UserManagerInternal.class, mUserManagerInternal);
+ when(mUserManagerInternal.isVisibleBackgroundFullUser(
+ TEST_VISIBLE_BACKGROUND_USER_ID)).thenReturn(visible);
+ }
+
private void setUpRenewableTrust(ITrustAgentService trustAgent) throws RemoteException {
ITrustAgentServiceCallback callback = getCallback(trustAgent);
callback.setManagingTrust(true);
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index 15ae463..0b762df 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -290,7 +290,7 @@
final WallpaperData fallbackData = mService.mFallbackWallpaper;
assertEquals("Fallback wallpaper component should be ImageWallpaper.",
- sImageWallpaperComponentName, fallbackData.wallpaperComponent);
+ sImageWallpaperComponentName, fallbackData.getComponent());
verifyLastWallpaperData(USER_SYSTEM, sDefaultWallpaperComponent);
verifyDisplayData();
@@ -580,7 +580,7 @@
final WallpaperData lastData = mService.mLastWallpaper;
assertNotNull("Last wallpaper must not be null", lastData);
assertEquals("Last wallpaper component must be equals.", expectedComponent,
- lastData.wallpaperComponent);
+ lastData.getComponent());
assertEquals("The user id in last wallpaper should be the last switched user",
lastUserId, lastData.userId);
assertNotNull("Must exist user data connection on last wallpaper data",
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index a25621a..390eb93 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -61,7 +61,6 @@
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doNothing;
@@ -95,7 +94,6 @@
import android.os.Message;
import android.os.PowerManagerInternal;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.IStorageManager;
@@ -214,10 +212,7 @@
doNothing().when(mInjector).activityManagerOnUserStopped(anyInt());
doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt());
doNothing().when(mInjector).taskSupervisorRemoveUser(anyInt());
- doAnswer(invocation -> {
- ((Runnable) invocation.getArgument(0)).run();
- return null;
- }).when(mInjector).showKeyguard(any());
+ doNothing().when(mInjector).lockDeviceNowAndWaitForKeyguardShown();
mockIsUsersOnSecondaryDisplaysEnabled(false);
// All UserController params are set to default.
@@ -432,6 +427,7 @@
mUserController.registerUserSwitchObserver(observer, "mock");
// Start user -- this will update state of mUserController
mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
+ verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID));
Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
assertNotNull(reportMsg);
UserState userState = (UserState) reportMsg.obj;
@@ -440,7 +436,6 @@
// Call dispatchUserSwitch and verify that observer was called only once
mInjector.mHandler.clearAllRecordedMessages();
mUserController.dispatchUserSwitch(userState, oldUserId, newUserId);
- verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID));
verify(observer, times(1)).onUserSwitching(eq(TEST_USER_ID), any());
Set<Integer> expectedCodes = Collections.singleton(CONTINUE_USER_SWITCH_MSG);
Set<Integer> actualCodes = mInjector.mHandler.getMessageCodes();
@@ -463,6 +458,7 @@
mUserController.registerUserSwitchObserver(observer, "mock");
// Start user -- this will update state of mUserController
mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
+ verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID));
Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
assertNotNull(reportMsg);
UserState userState = (UserState) reportMsg.obj;
@@ -471,7 +467,6 @@
// Call dispatchUserSwitch and verify that observer was called only once
mInjector.mHandler.clearAllRecordedMessages();
mUserController.dispatchUserSwitch(userState, oldUserId, newUserId);
- verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID));
verify(observer, times(1)).onUserSwitching(eq(TEST_USER_ID), any());
// Verify that CONTINUE_USER_SWITCH_MSG is not sent (triggers timeout)
Set<Integer> actualCodes = mInjector.mHandler.getMessageCodes();
@@ -554,6 +549,7 @@
expectedCodes.add(REPORT_USER_SWITCH_COMPLETE_MSG);
if (backgroundUserStopping) {
expectedCodes.add(CLEAR_USER_JOURNEY_SESSION_MSG);
+ expectedCodes.add(0); // this is for directly posting in stopping.
}
if (expectScheduleBackgroundUserStopping) {
expectedCodes.add(SCHEDULED_STOP_BACKGROUND_USER_MSG);
@@ -1579,13 +1575,21 @@
// mock the device to be secure in order to expect the keyguard to be shown
when(mInjector.mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true);
- // call real showKeyguard method for this test
- doCallRealMethod().when(mInjector).showKeyguard(any());
+ // call real lockDeviceNowAndWaitForKeyguardShown method for this test
+ doCallRealMethod().when(mInjector).lockDeviceNowAndWaitForKeyguardShown();
- mUserController.completeUserSwitch(TEST_USER_ID1, TEST_USER_ID2);
+ // call startUser on a thread because we're expecting it to be blocked
+ Thread threadStartUser = new Thread(()-> {
+ mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
+ });
+ threadStartUser.start();
- // make sure the switch is stalled by checking the UserSwitchingDialog is not dismissed yet
- verify(mInjector, never()).dismissUserSwitchingDialog(any());
+ // make sure the switch is stalled...
+ Thread.sleep(2000);
+ // by checking REPORT_USER_SWITCH_MSG is not sent yet
+ assertNull(mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG));
+ // and the thread is still alive
+ assertTrue(threadStartUser.isAlive());
// mock send the keyguard shown event
ArgumentCaptor<ActivityTaskManagerInternal.ScreenObserver> captor = ArgumentCaptor.forClass(
@@ -1593,42 +1597,12 @@
verify(mInjector.mActivityTaskManagerInternal).registerScreenObserver(captor.capture());
captor.getValue().onKeyguardStateChanged(true);
- // verify the switch now moves on by checking the UserSwitchingDialog is dismissed
- verify(mInjector, atLeastOnce()).dismissUserSwitchingDialog(any());
-
- // verify that SHOW_KEYGUARD_TIMEOUT is ignored and does not crash the system
- try {
- mInjector.mHandler.processPostDelayedCallbacksWithin(
- UserController.SHOW_KEYGUARD_TIMEOUT_MS);
- } catch (RuntimeException e) {
- throw new AssertionError(
- "SHOW_KEYGUARD_TIMEOUT is not ignored and crashed the system", e);
- }
- }
-
- @Test
- public void testRuntimeExceptionIsThrownIfTheKeyguardIsNotShown() throws Exception {
- // enable user switch ui, because keyguard is only shown then
- mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
- /* backgroundUserScheduledStopTimeSecs= */ -1);
-
- // mock the device to be secure in order to expect the keyguard to be shown
- when(mInjector.mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true);
-
- // suppress showKeyguard method for this test
- doNothing().when(mInjector).showKeyguard(any());
-
- mUserController.completeUserSwitch(TEST_USER_ID1, TEST_USER_ID2);
-
- // verify that the system has crashed
- assertThrows("Should have thrown RuntimeException", RuntimeException.class, () -> {
- mInjector.mHandler.processPostDelayedCallbacksWithin(
- UserController.SHOW_KEYGUARD_TIMEOUT_MS);
- });
-
- // make sure the UserSwitchingDialog is not dismissed
- verify(mInjector, never()).dismissUserSwitchingDialog(any());
+ // verify the switch now moves on...
+ Thread.sleep(1000);
+ // by checking REPORT_USER_SWITCH_MSG is sent
+ assertNotNull(mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG));
+ // and the thread is finished
+ assertFalse(threadStartUser.isAlive());
}
private void setUpAndStartUserInBackground(int userId) throws Exception {
@@ -1989,9 +1963,7 @@
Set<Integer> getMessageCodes() {
Set<Integer> result = new LinkedHashSet<>();
for (Message msg : mMessages) {
- if (msg.what != 0) { // ignore mHandle.post and mHandler.postDelayed messages
- result.add(msg.what);
- }
+ result.add(msg.what);
}
return result;
}
@@ -2015,28 +1987,14 @@
@Override
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
- final Runnable cb = msg.getCallback();
- if (cb != null && uptimeMillis <= SystemClock.uptimeMillis()) {
- // run mHandler.post calls immediately
- cb.run();
- return true;
- }
Message copy = new Message();
copy.copyFrom(msg);
- copy.setCallback(cb);
mMessages.add(copy);
- return super.sendMessageAtTime(msg, uptimeMillis);
- }
-
- public void processPostDelayedCallbacksWithin(long millis) {
- final long whenMax = SystemClock.uptimeMillis() + millis;
- for (Message msg : mMessages) {
- final Runnable cb = msg.getCallback();
- if (cb != null && msg.getWhen() <= whenMax) {
- msg.setCallback(null);
- cb.run();
- }
+ if (msg.getCallback() != null) {
+ msg.getCallback().run();
+ msg.setCallback(null);
}
+ return super.sendMessageAtTime(msg, uptimeMillis);
}
}
}
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 44aa868..a55aa23 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
@@ -30,7 +30,6 @@
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.system.Os;
@@ -41,8 +40,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.text.flags.Flags;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -1106,7 +1103,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1126,7 +1122,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureMissingCase_fontFamilyInstalled_fontInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1);
@@ -1146,7 +1141,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureMissingCase_fontFileInstalled_fontFamilyInstallLater() {
// Install font file, foo.ttf and bar.ttf
installTestFontFile(2 /* numFonts */, 1 /* version */);
@@ -1166,7 +1160,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureMissingCase_fontFileInstalled_fontFileInstallLater() {
// Install font file, foo.ttf and bar.ttf
installTestFontFile(2 /* numFonts */, 1 /* version */);
@@ -1186,7 +1179,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1206,7 +1198,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureAllMissingCase_fontFamilyInstalled_fontInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1226,7 +1217,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureAllMissingCase_fontFileInstalled_fontFamilyInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1246,7 +1236,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureAllMissingCase_fontFileInstalled_fontFileInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1266,7 +1255,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1286,7 +1274,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontMissingCase_fontFamilyInstalled_fontInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1);
@@ -1306,7 +1293,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontMissingCase_fontFileInstalled_fontFamilyInstallLater() {
// Install font file, foo.ttf and bar.ttf
installTestFontFile(2 /* numFonts */, 1 /* version */);
@@ -1326,7 +1312,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontMissingCase_fontFileInstalled_fontFileInstallLater() {
// Install font file, foo.ttf and bar.ttf
installTestFontFile(2 /* numFonts */, 1 /* version */);
@@ -1346,7 +1331,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1366,7 +1350,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontAllMissingCase_fontFamilyInstalled_fontInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1386,7 +1369,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontAllMissingCase_fontFileInstalled_fontFamilyInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1406,7 +1388,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontAllMissingCase_fontFileInstalled_fontFileInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1426,7 +1407,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontDirAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1446,7 +1426,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontDirAllMissingCase_fontFamilyInstalled_fontInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1466,7 +1445,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontDirAllMissingCase_fontFileInstalled_fontFamilyInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1486,7 +1464,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontDirAllMissingCase_fontFileInstalled_fontFileInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1506,7 +1483,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void dirContentAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1527,7 +1503,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void dirContentAllMissingCase_fontFamilyInstalled_fontInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1548,7 +1523,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void dirContentAllMissingCase_fontFileInstalled_fontFamilyInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1569,7 +1543,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void dirContentAllMissingCase_fontFileInstalled_fontFileInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 95a7f4b..a0005d9 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -25,6 +25,7 @@
import static com.android.server.hdmi.HdmiCecLocalDevicePlayback.STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_BOOT_UP;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
+import static com.android.server.hdmi.PowerStatusMonitorActionFromPlayback.MONITORING_INTERVAL_MS;
import static com.google.common.truth.Truth.assertThat;
@@ -145,6 +146,11 @@
protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
// do nothing
}
+
+ @Override
+ protected boolean isHdmiControlEnhancedBehaviorFlagEnabled() {
+ return true;
+ }
};
mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
@@ -2556,6 +2562,44 @@
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(unexpectedMessage);
}
+ @Test
+ public void powerStatusMonitorActionFromPlayback_TvReportPowerOff_goToSleep() {
+ mHdmiControlService.onWakeUp(HdmiControlService.WAKE_UP_SCREEN_ON);
+ mTestLooper.dispatchAll();
+
+ assertThat(mHdmiCecLocalDevicePlayback.getActions(
+ PowerStatusMonitorActionFromPlayback.class)).hasSize(1);
+ assertThat(mPowerManager.isInteractive()).isTrue();
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.moveTimeForward(MONITORING_INTERVAL_MS);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage givePowerStatus =
+ HdmiCecMessageBuilder.buildGiveDevicePowerStatus(mPlaybackLogicalAddress,
+ Constants.ADDR_TV);
+ HdmiCecMessage reportPowerStatusTvOn =
+ HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV, mPlaybackLogicalAddress,
+ HdmiControlManager.POWER_STATUS_ON);
+ HdmiCecMessage reportPowerStatusTvStandby =
+ HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV, mPlaybackLogicalAddress,
+ HdmiControlManager.POWER_STATUS_STANDBY);
+
+ assertThat(mNativeWrapper.getResultMessages().contains(givePowerStatus)).isTrue();
+ mNativeWrapper.onCecMessage(reportPowerStatusTvOn);
+ mTestLooper.dispatchAll();
+
+ assertThat(mPowerManager.isInteractive()).isTrue();
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.moveTimeForward(MONITORING_INTERVAL_MS);
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages().contains(givePowerStatus)).isTrue();
+ mNativeWrapper.onCecMessage(reportPowerStatusTvStandby);
+ mTestLooper.dispatchAll();
+
+ assertThat(mPowerManager.isInteractive()).isFalse();
+ }
+
private void skipActiveSourceLostUi(long idleDuration) {
mTestLooper.moveTimeForward(POPUP_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
mTestLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index 689b241..abc9ce3 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -50,11 +50,12 @@
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
+import android.annotation.SuppressLint;
import android.app.ActivityManagerInternal;
import android.app.ActivityOptions.LaunchCookie;
+import android.app.AppOpsManager;
import android.app.KeyguardManager;
import android.content.Context;
-import android.content.ContextWrapper;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.ApplicationInfoFlags;
@@ -72,6 +73,7 @@
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.testing.TestableContext;
import android.view.ContentRecordingSession;
import android.view.ContentRecordingSession.RecordContent;
@@ -99,13 +101,14 @@
/**
* Tests for the {@link MediaProjectionManagerService} class.
- *
+ * <p>
* Build/Install/Run:
* atest FrameworksServicesTests:MediaProjectionManagerServiceTest
*/
@SmallTest
@Presubmit
@RunWith(AndroidJUnit4.class)
+@SuppressLint({"UseCheckPermission", "VisibleForTests", "MissingPermission"})
public class MediaProjectionManagerServiceTest {
private static final int UID = 10;
private static final String PACKAGE_NAME = "test.package";
@@ -151,7 +154,10 @@
}
};
- private Context mContext;
+ @Rule
+ public final TestableContext mContext = spy(
+ new TestableContext(InstrumentationRegistry.getInstrumentation().getContext()));
+
private MediaProjectionManagerService mService;
private OffsettableClock mClock;
private ContentRecordingSession mWaitingDisplaySession =
@@ -169,6 +175,8 @@
@Mock
private KeyguardManager mKeyguardManager;
@Mock
+ AppOpsManager mAppOpsManager;
+ @Mock
private IMediaProjectionWatcherCallback mWatcherCallback;
@Mock
private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
@@ -185,10 +193,9 @@
LocalServices.removeServiceForTest(WindowManagerInternal.class);
LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal);
- mContext = spy(new ContextWrapper(
- InstrumentationRegistry.getInstrumentation().getTargetContext()));
- doReturn(mPackageManager).when(mContext).getPackageManager();
- doReturn(mKeyguardManager).when(mContext).getSystemService(eq(Context.KEYGUARD_SERVICE));
+ mContext.addMockSystemService(AppOpsManager.class, mAppOpsManager);
+ mContext.addMockSystemService(KeyguardManager.class, mKeyguardManager);
+ mContext.setMockPackageManager(mPackageManager);
mClock = new OffsettableClock.Stopped();
mWaitingDisplaySession.setWaitingForConsent(true);
@@ -291,6 +298,27 @@
assertThat(mService.getActiveProjectionInfo()).isNotNull();
}
+ @SuppressLint("MissingPermission")
+ @EnableFlags(android.companion.virtualdevice.flags
+ .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+ @Test
+ public void testCreateProjection_keyguardLocked_AppOpMediaProjection()
+ throws NameNotFoundException {
+ MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+ doReturn(true).when(mAppOpsManager).isOperationActive(eq(AppOpsManager.OP_PROJECT_MEDIA),
+ eq(projection.uid), eq(projection.packageName));
+ doReturn(true).when(mKeyguardManager).isKeyguardLocked();
+
+ doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+ RECORD_SENSITIVE_CONTENT, projection.packageName);
+
+ projection.start(mIMediaProjectionCallback);
+ projection.notifyVirtualDisplayCreated(10);
+
+ // The projection was started because it was allowed to capture the keyguard.
+ assertThat(mService.getActiveProjectionInfo()).isNotNull();
+ }
+
@Test
public void testCreateProjection_attemptReuse_noPriorProjectionGrant()
throws NameNotFoundException {
diff --git a/services/tests/servicestests/src/com/android/server/supervision/OWNERS b/services/tests/servicestests/src/com/android/server/supervision/OWNERS
new file mode 100644
index 0000000..a5de8007
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/supervision/OWNERS
@@ -0,0 +1 @@
+include /services/supervision/OWNERS
\ No newline at end of file
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 96ddf80..b8f9767 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -2815,7 +2815,8 @@
}
@Test
- @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST})
public void testOnlyForceGroupIfNeeded_newNotification_notAutogrouped() {
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, null, false);
when(mGroupHelper.onNotificationPosted(any(), anyBoolean())).thenReturn(false);
@@ -2834,7 +2835,8 @@
}
@Test
- @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST})
public void testOnlyForceGroupIfNeeded_newNotification_wasAutogrouped() {
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, null, false);
when(mGroupHelper.onNotificationPosted(any(), anyBoolean())).thenReturn(true);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java
index 4d2396c..65b4ac1 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java
@@ -104,6 +104,12 @@
}
@Test
+ public void createVibrationEffectFromSoundUri_opaqueUri() {
+ Uri uri = Uri.parse("a:b#c");
+ assertNull(mVibratorHelper.createVibrationEffectFromSoundUri(uri));
+ }
+
+ @Test
public void createVibrationEffectFromSoundUri_uriWithoutRequiredQueryParameter() {
Uri uri = Settings.System.DEFAULT_NOTIFICATION_URI;
assertNull(mVibratorHelper.createVibrationEffectFromSoundUri(uri));
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
index 8ee7e03..84c4f62 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -32,6 +32,7 @@
import static android.service.notification.Condition.STATE_TRUE;
import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_ON;
import static android.service.notification.ZenModeConfig.XML_VERSION_MODES_API;
+import static android.service.notification.ZenModeConfig.XML_VERSION_MODES_UI;
import static android.service.notification.ZenModeConfig.ZEN_TAG;
import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_DEACTIVATE;
import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_NONE;
@@ -1169,6 +1170,23 @@
assertThat(suppressedEffectsOf(result)).isEqualTo(suppressedEffectsOf(policy));
}
+ @Test
+ public void readXml_fixesWronglyDisabledManualRule() throws Exception {
+ ZenModeConfig config = getCustomConfig();
+ if (!Flags.modesUi()) {
+ config.manualRule = new ZenModeConfig.ZenRule();
+ config.manualRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ }
+ config.manualRule.enabled = false;
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ writeConfigXml(config, XML_VERSION_MODES_UI, /* forBackup= */ false, baos);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ ZenModeConfig fromXml = readConfigXml(bais);
+
+ assertThat(fromXml.manualRule.enabled).isTrue();
+ }
+
private static String suppressedEffectsOf(Policy policy) {
return suppressedEffectsToString(policy.suppressedVisualEffects) + "("
+ policy.suppressedVisualEffects + ")";
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
index 6076d33..f7127df 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
@@ -18,6 +18,7 @@
import static android.os.VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
import static android.os.VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF;
+import static android.os.VibrationAttributes.USAGE_HARDWARE_FEEDBACK;
import static android.os.VibrationAttributes.USAGE_IME_FEEDBACK;
import static android.os.VibrationAttributes.USAGE_TOUCH;
import static android.os.VibrationEffect.Composition.PRIMITIVE_CLICK;
@@ -105,7 +106,7 @@
@Before
public void setUp() {
- mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+ mSetFlagsRule.disableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
}
@Test
@@ -398,7 +399,7 @@
HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
for (int effectId : BIOMETRIC_FEEDBACK_CONSTANTS) {
- VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback(
+ VibrationAttributes attrs = provider.getVibrationAttributes(
effectId, /* flags */ 0, /* privFlags */ 0);
assertThat(attrs.getUsage()).isEqualTo(VibrationAttributes.USAGE_COMMUNICATION_REQUEST);
}
@@ -408,7 +409,7 @@
public void testVibrationAttribute_forNotBypassingIntensitySettings() {
HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
- VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback(
+ VibrationAttributes attrs = provider.getVibrationAttributes(
SAFE_MODE_ENABLED, /* flags */ 0, /* privFlags */ 0);
assertThat(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)).isFalse();
@@ -418,7 +419,7 @@
public void testVibrationAttribute_forByassingIntensitySettings() {
HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
- VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback(
+ VibrationAttributes attrs = provider.getVibrationAttributes(
SAFE_MODE_ENABLED,
/* flags */ HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING, /* privFlags */ 0);
@@ -431,7 +432,7 @@
HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
for (int effectId : SCROLL_FEEDBACK_CONSTANTS) {
- VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback(
+ VibrationAttributes attrs = provider.getVibrationAttributes(
effectId, /* flags */ 0, /* privFlags */ 0);
assertWithMessage("Expected FLAG_BYPASS_INTERRUPTION_POLICY for effect " + effectId)
.that(attrs.isFlagSet(FLAG_BYPASS_INTERRUPTION_POLICY)).isTrue();
@@ -444,7 +445,7 @@
HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
for (int effectId : SCROLL_FEEDBACK_CONSTANTS) {
- VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback(
+ VibrationAttributes attrs = provider.getVibrationAttributes(
effectId, /* flags */ 0, /* privFlags */ 0);
assertWithMessage("Expected no FLAG_BYPASS_INTERRUPTION_POLICY for effect " + effectId)
.that(attrs.isFlagSet(FLAG_BYPASS_INTERRUPTION_POLICY)).isFalse();
@@ -452,11 +453,64 @@
}
@Test
+ public void testVibrationAttribute_scrollFeedback_inputCustomizedFlag_useTouchUsage() {
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+ HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
+
+ for (int effectId : SCROLL_FEEDBACK_CONSTANTS) {
+ VibrationAttributes attrs = provider.getVibrationAttributes(effectId, /* flags */
+ 0, /* privFlags */ 0);
+ assertWithMessage("Expected USAGE_TOUCH for scroll effect " + effectId
+ + ", if no input customization").that(attrs.getUsage()).isEqualTo(USAGE_TOUCH);
+ }
+ }
+
+ @Test
+ public void testVibrationAttribute_scrollFeedback_noInputCustomizedFlag_useHardwareFeedback() {
+ HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
+
+ for (int effectId : SCROLL_FEEDBACK_CONSTANTS) {
+ VibrationAttributes attrs = provider.getVibrationAttributes(effectId, /* flags */
+ 0, /* privFlags */ 0);
+ assertWithMessage("Expected USAGE_HARDWARE_FEEDBACK for scroll effect " + effectId
+ + ", if no input customization").that(attrs.getUsage()).isEqualTo(
+ USAGE_HARDWARE_FEEDBACK);
+ }
+ }
+
+ @Test
+ public void testVibrationAttribute_scrollFeedback_rotaryInputSource_useHardwareFeedback() {
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+ HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
+
+ for (int effectId : SCROLL_FEEDBACK_CONSTANTS) {
+ VibrationAttributes attrs = provider.getVibrationAttributes(
+ effectId, InputDevice.SOURCE_ROTARY_ENCODER, /* flags */ 0, /* privFlags */ 0);
+ assertWithMessage(
+ "Expected USAGE_HARDWARE_FEEDBACK for input source SOURCE_ROTARY_ENCODER").that(
+ attrs.getUsage()).isEqualTo(USAGE_HARDWARE_FEEDBACK);
+ }
+ }
+
+ @Test
+ public void testVibrationAttribute_scrollFeedback_touchInputSource_useTouchUsage() {
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+ HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
+
+ for (int effectId : SCROLL_FEEDBACK_CONSTANTS) {
+ VibrationAttributes attrs = provider.getVibrationAttributes(
+ effectId, InputDevice.SOURCE_TOUCHSCREEN, /* flags */ 0, /* privFlags */ 0);
+ assertWithMessage("Expected USAGE_TOUCH for input source SOURCE_TOUCHSCREEN").that(
+ attrs.getUsage()).isEqualTo(USAGE_TOUCH);
+ }
+ }
+
+ @Test
public void testVibrationAttribute_notIme_useTouchUsage() {
HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
- VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback(
+ VibrationAttributes attrs = provider.getVibrationAttributes(
effectId, /* flags */ 0, /* privFlags */ 0);
assertWithMessage("Expected USAGE_TOUCH for effect " + effectId)
.that(attrs.getUsage()).isEqualTo(USAGE_TOUCH);
@@ -468,7 +522,7 @@
HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
- VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback(
+ VibrationAttributes attrs = provider.getVibrationAttributes(
effectId, /* flags */ 0,
HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS);
assertWithMessage("Expected USAGE_IME_FEEDBACK for effect " + effectId)
diff --git a/services/tests/wmtests/src/com/android/server/policy/MetaKeyEventsInterceptionTests.java b/services/tests/wmtests/src/com/android/server/policy/MetaKeyEventsInterceptionTests.java
deleted file mode 100644
index b979335..0000000
--- a/services/tests/wmtests/src/com/android/server/policy/MetaKeyEventsInterceptionTests.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.policy;
-
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.view.KeyEvent;
-import android.view.WindowManager;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.policy.KeyInterceptionInfo;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Testing {@link PhoneWindowManager} functionality of letting app intercepting key events
- * containing META.
- */
-@SmallTest
-public class MetaKeyEventsInterceptionTests extends ShortcutKeyTestBase {
-
- private static final List<KeyEvent> META_KEY_EVENTS = Arrays.asList(
- new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_META_LEFT),
- new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_META_RIGHT),
- new KeyEvent(/* downTime= */ 0, /* eventTime= */
- 0, /* action= */ 0, /* code= */ 0, /* repeat= */ 0,
- /* metaState= */ KeyEvent.META_META_ON));
-
- @Before
- public void setUp() {
- setUpPhoneWindowManager();
- }
-
- @Test
- public void doesntInterceptMetaKeyEvents_whenWindowAskedForIt() {
- mPhoneWindowManager.overrideFocusedWindowButtonOverridePermission(/* granted= */ true);
- setWindowKeyInterceptionWithPrivateFlags(PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS);
-
- META_KEY_EVENTS.forEach(keyEvent -> {
- assertKeyInterceptionResult(keyEvent, /* intercepted= */ false);
- });
- }
-
- @Test
- public void interceptsMetaKeyEvents_whenWindowDoesntHaveFlagSet() {
- mPhoneWindowManager.overrideFocusedWindowButtonOverridePermission(/* granted= */ true);
- setWindowKeyInterceptionWithPrivateFlags(0);
-
- META_KEY_EVENTS.forEach(keyEvent -> {
- assertKeyInterceptionResult(keyEvent, /* intercepted= */ true);
- });
- }
-
- @Test
- public void interceptsMetaKeyEvents_whenWindowDoesntHavePermission() {
- mPhoneWindowManager.overrideFocusedWindowButtonOverridePermission(/* granted= */ false);
- setWindowKeyInterceptionWithPrivateFlags(PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS);
-
- META_KEY_EVENTS.forEach(keyEvent -> {
- assertKeyInterceptionResult(keyEvent, /* intercepted= */ true);
- });
- }
-
- private void setWindowKeyInterceptionWithPrivateFlags(int privateFlags) {
- KeyInterceptionInfo info = new KeyInterceptionInfo(
- WindowManager.LayoutParams.TYPE_APPLICATION, privateFlags, "title", 0);
- mPhoneWindowManager.overrideWindowKeyInterceptionInfo(info);
- }
-
- private void assertKeyInterceptionResult(KeyEvent keyEvent, boolean intercepted) {
- long result = mPhoneWindowManager.interceptKeyBeforeDispatching(keyEvent);
- int expected = intercepted ? -1 : 0;
- assertThat(result).isEqualTo(expected);
- }
-}
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 0b55e2b..98401b3 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -52,7 +52,6 @@
import static org.mockito.Mockito.description;
import static org.mockito.Mockito.mockingDetails;
import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.when;
import static org.mockito.Mockito.withSettings;
import android.app.ActivityManagerInternal;
@@ -634,10 +633,6 @@
.when(mButtonOverridePermissionChecker).canAppOverrideSystemKey(any(), anyInt());
}
- void overrideWindowKeyInterceptionInfo(KeyInterceptionInfo info) {
- when(mWindowManagerInternal.getKeyInterceptionInfoFromToken(any())).thenReturn(info);
- }
-
void overrideKeyEventPolicyFlags(int flags) {
mKeyEventPolicyFlags = flags;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 957b5e0..ae0c6e5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -331,6 +331,7 @@
final WindowProcessController proc = mSystemServicesTestRule.addProcess(
activity.packageName, activity.processName,
6789 /* pid */, activity.info.applicationInfo.uid);
+ assertFalse(proc.mHasEverAttached);
try {
mRootWindowContainer.attachApplication(proc);
verify(mSupervisor).realStartActivityLocked(eq(topActivity), eq(proc),
@@ -338,6 +339,15 @@
} catch (RemoteException e) {
e.rethrowAsRuntimeException();
}
+
+ // Verify that onProcessRemoved won't clear the launching activities if an attached process
+ // is died. Because in real case, it should be handled from WindowProcessController's
+ // and ActivityRecord's handleAppDied to decide whether to remove the activities.
+ assertTrue(proc.mHasEverAttached);
+ assertTrue(mAtm.mStartingProcessActivities.isEmpty());
+ mAtm.mStartingProcessActivities.add(activity);
+ mAtm.mInternal.onProcessRemoved(proc.mName, proc.mUid);
+ assertFalse(mAtm.mStartingProcessActivities.isEmpty());
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index f743401..7bce828 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -1640,7 +1640,7 @@
.build();
setUpApp(display);
prepareUnresizable(mActivity, /* maxAspect */ 0f, SCREEN_ORIENTATION_PORTRAIT);
- mActivity.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FREEFORM);
assertFalse(mActivity.inSizeCompatMode());
// Resize app to make original app bounds larger than parent bounds.
@@ -1667,7 +1667,7 @@
.build();
setUpApp(display);
prepareUnresizable(mActivity, /* maxAspect */ 0f, SCREEN_ORIENTATION_PORTRAIT);
- mActivity.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FREEFORM);
assertFalse(mActivity.inSizeCompatMode());
// Resize app to make original app bounds smaller than parent bounds.
@@ -1692,7 +1692,7 @@
.build();
setUpApp(display);
prepareUnresizable(mActivity, /* maxAspect */ 0f, SCREEN_ORIENTATION_PORTRAIT);
- mActivity.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FREEFORM);
assertFalse(mActivity.inSizeCompatMode());
final Rect originalAppBounds = mActivity.getBounds();
@@ -1705,6 +1705,38 @@
assertEquals(originalAppBounds, mActivity.getBounds());
}
+ /**
+ * Test that when desktop mode is enabled, a freeform unresizeable activity is not up-scaled
+ * when exiting freeform despite its larger parent bounds.
+ */
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ public void testCompatScaling_freeformUnresizeableApp_exitFreeform_notScaled() {
+ doReturn(true).when(() ->
+ DesktopModeHelper.canEnterDesktopMode(any()));
+ final int dw = 600;
+ final int dh = 800;
+ final DisplayContent display = new TestDisplayContent.Builder(mAtm, dw, dh)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM)
+ .build();
+ setUpApp(display);
+ prepareUnresizable(mActivity, /* maxAspect */ 0f, SCREEN_ORIENTATION_PORTRAIT);
+ mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FREEFORM);
+ final Rect originalAppBounds = mActivity.getBounds();
+
+ assertFalse(mActivity.inSizeCompatMode());
+
+ // Resize app to make original app bounds smaller than parent bounds.
+ mTask.getWindowConfiguration().setAppBounds(
+ new Rect(0, 0, dw + 300, dh + 400));
+ // Change windowing mode from freeform to fullscreen
+ mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ mActivity.onConfigurationChanged(mTask.getConfiguration());
+ // App should enter size compat mode but remain its original size.
+ assertTrue(mActivity.inSizeCompatMode());
+ assertEquals(originalAppBounds, mActivity.getBounds());
+ }
+
@Test
public void testGetLetterboxInnerBounds_noScalingApplied() {
// Set up a display in portrait and ignoring orientation request.
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index b80610a..4ccbc32 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -816,7 +816,8 @@
* @param callingUid pass Binder.callingUid().
*/
public static void enforceShellOnly(int callingUid, String message) {
- if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) {
+ if (UserHandle.isSameApp(callingUid, Process.SHELL_UID)
+ || UserHandle.isSameApp(callingUid, Process.ROOT_UID)) {
return; // okay
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 41223db..2ef0573 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -5038,7 +5038,7 @@
* {@code true} - Enable NI SUPL message injection.
*/
@FlaggedApi(android.location.flags.Flags
- .FLAG_ENABLE_NI_SUPL_MESSAGE_INJECTION_BY_CARRIER_CONFIG)
+ .FLAG_ENABLE_NI_SUPL_MESSAGE_INJECTION_BY_CARRIER_CONFIG_BUGFIX)
public static final String KEY_ENABLE_NI_SUPL_MESSAGE_INJECTION_BOOL =
KEY_PREFIX + "enable_ni_supl_message_injection_bool";
@@ -5059,7 +5059,7 @@
defaults.putInt(KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT,
SUPL_EMERGENCY_MODE_TYPE_CP_ONLY);
defaults.putStringArray(KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY, null);
- if (android.location.flags.Flags.enableNiSuplMessageInjectionByCarrierConfig()) {
+ if (android.location.flags.Flags.enableNiSuplMessageInjectionByCarrierConfigBugfix()) {
defaults.putBoolean(KEY_ENABLE_NI_SUPL_MESSAGE_INJECTION_BOOL, false);
}
return defaults;
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 51e0c33..6faef7e 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -3785,7 +3785,7 @@
}
private boolean isSystemProcess() {
- return Process.myUid() == Process.SYSTEM_UID;
+ return UserHandle.isSameApp(Process.myUid(), Process.SYSTEM_UID);
}
/**
diff --git a/telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl
index 66a20ae..50e3a0e 100644
--- a/telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl
@@ -34,4 +34,12 @@
* @param isEmergency True means satellite enabled for emergency mode, false otherwise.
*/
void onEmergencyModeChanged(in boolean isEmergency);
+
+ /**
+ * Indicates that the satellite registration failed with following failure code
+ *
+ * @param causeCode the primary failure cause code of the procedure.
+ * For LTE (EMM), cause codes are TS 24.301 Sec 9.9.3.9
+ */
+ void onRegistrationFailure(in int causeCode);
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 90dae3b..4eefaac 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -19,6 +19,7 @@
import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
+import android.annotation.Hide;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1579,6 +1580,13 @@
executor.execute(() -> Binder.withCleanCallingIdentity(() ->
callback.onEmergencyModeChanged(isEmergency)));
}
+
+ @Hide
+ @Override
+ public void onRegistrationFailure(int causeCode) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onRegistrationFailure(causeCode)));
+ }
};
sSatelliteModemStateCallbackMap.put(callback, internalCallback);
return telephony.registerForSatelliteModemStateChanged(internalCallback);
diff --git a/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java
index 423a785..13af469 100644
--- a/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java
@@ -45,4 +45,13 @@
*/
@FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
default void onEmergencyModeChanged(boolean isEmergency) {};
+
+ /**
+ * Indicates that the satellite registration failed with following failure code
+ *
+ * @param causeCode the primary failure cause code of the procedure.
+ * For LTE (EMM), cause codes are TS 24.301 Sec 9.9.3.9
+ * @hide
+ */
+ default void onRegistrationFailure(int causeCode) {};
}
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt
index 92b6b93..82e53c8 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt
@@ -54,7 +54,7 @@
}
transitions {
device.sleep()
- wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
+ wmHelper.StateSyncBuilder().withKeyguardShowing().waitForAndVerify()
UnlockScreenRule.unlockScreen(device)
wmHelper.StateSyncBuilder().withImeShown().waitForAndVerify()
}
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/StartMediaProjectionAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/StartMediaProjectionAppHelper.kt
new file mode 100644
index 0000000..69fde01
--- /dev/null
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/StartMediaProjectionAppHelper.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.helpers
+
+import android.app.Instrumentation
+import android.tools.device.apphelpers.StandardAppHelper
+import android.tools.helpers.SYSTEMUI_PACKAGE
+import android.tools.traces.component.ComponentNameMatcher
+import android.tools.traces.parsers.WindowManagerStateHelper
+import android.tools.traces.parsers.toFlickerComponent
+import android.util.Log
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.UiObjectNotFoundException
+import androidx.test.uiautomator.UiScrollable
+import androidx.test.uiautomator.UiSelector
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import java.util.regex.Pattern
+
+class StartMediaProjectionAppHelper
+@JvmOverloads
+constructor(
+ instr: Instrumentation,
+ launcherName: String = ActivityOptions.StartMediaProjectionActivity.LABEL,
+ component: ComponentNameMatcher =
+ ActivityOptions.StartMediaProjectionActivity.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component) {
+ private val packageManager = instr.context.packageManager
+
+ fun startEntireScreenMediaProjection(wmHelper: WindowManagerStateHelper) {
+ clickStartMediaProjectionButton()
+ chooseEntireScreenOption()
+ startScreenSharing()
+ wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+ }
+
+ fun startSingleAppMediaProjection(
+ wmHelper: WindowManagerStateHelper,
+ targetApp: StandardAppHelper
+ ) {
+ clickStartMediaProjectionButton()
+ chooseSingleAppOption()
+ startScreenSharing()
+ selectTargetApp(targetApp.appName)
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withWindowSurfaceAppeared(targetApp)
+ .waitForAndVerify()
+ }
+
+ private fun clickStartMediaProjectionButton() {
+ findObject(By.res(packageName, START_MEDIA_PROJECTION_BUTTON_ID)).also { it.click() }
+ }
+
+ private fun chooseEntireScreenOption() {
+ findObject(By.res(SCREEN_SHARE_OPTIONS_PATTERN)).also { it.click() }
+
+ val entireScreenString = getSysUiResourceString(ENTIRE_SCREEN_STRING_RES_NAME)
+ findObject(By.text(entireScreenString)).also { it.click() }
+ }
+
+ private fun selectTargetApp(targetAppName: String) {
+ // Scroll to to find target app to launch then click app icon it to start capture
+ val scrollable = UiScrollable(UiSelector().scrollable(true))
+ try {
+ scrollable.scrollForward()
+ if (!scrollable.scrollIntoView(UiSelector().text(targetAppName))) {
+ Log.e(TAG, "Didn't find target app when scrolling")
+ return
+ }
+ } catch (e: UiObjectNotFoundException) {
+ Log.d(TAG, "There was no scrolling (UI may not be scrollable")
+ }
+
+ findObject(By.text(targetAppName)).also { it.click() }
+ }
+
+ private fun chooseSingleAppOption() {
+ findObject(By.res(SCREEN_SHARE_OPTIONS_PATTERN)).also { it.click() }
+
+ val singleAppString = getSysUiResourceString(SINGLE_APP_STRING_RES_NAME)
+ findObject(By.text(singleAppString)).also { it.click() }
+ }
+
+ private fun startScreenSharing() {
+ findObject(By.res(ACCEPT_RESOURCE_ID)).also { it.click() }
+ }
+
+ private fun findObject(selector: BySelector): UiObject2 =
+ uiDevice.wait(Until.findObject(selector), TIMEOUT) ?: error("Can't find object $selector")
+
+ private fun getSysUiResourceString(resName: String): String =
+ with(packageManager.getResourcesForApplication(SYSTEMUI_PACKAGE)) {
+ getString(getIdentifier(resName, "string", SYSTEMUI_PACKAGE))
+ }
+
+ companion object {
+ const val TAG: String = "StartMediaProjectionAppHelper"
+ const val TIMEOUT: Long = 5000L
+ const val ACCEPT_RESOURCE_ID: String = "android:id/button1"
+ const val START_MEDIA_PROJECTION_BUTTON_ID: String = "button_start_mp"
+ val SCREEN_SHARE_OPTIONS_PATTERN: Pattern =
+ Pattern.compile("$SYSTEMUI_PACKAGE:id/screen_share_mode_(options|spinner)")
+ const val ENTIRE_SCREEN_STRING_RES_NAME: String =
+ "screen_share_permission_dialog_option_entire_screen"
+ const val SINGLE_APP_STRING_RES_NAME: String =
+ "screen_share_permission_dialog_option_single_app"
+ }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/Android.bp b/tests/FlickerTests/test-apps/flickerapp/Android.bp
index a186679..c55df86 100644
--- a/tests/FlickerTests/test-apps/flickerapp/Android.bp
+++ b/tests/FlickerTests/test-apps/flickerapp/Android.bp
@@ -47,6 +47,7 @@
"wm-flicker-common-app-helpers",
"wm-flicker-common-assertions",
"wm-flicker-window-extensions",
+ "wm-shell-flicker-utils",
],
}
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 45260bd..f891606 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -21,6 +21,15 @@
android:targetSdkVersion="35"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
+ <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+ <uses-permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE"/>
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION"/>
+ <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<application android:allowBackup="false"
android:supportsRtl="true">
@@ -106,6 +115,17 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ <activity android:name=".StartMediaProjectionActivity"
+ android:theme="@style/CutoutNever"
+ android:resizeableActivity="false"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.StartMediaProjectionActivity"
+ android:label="StartMediaProjectionActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
<activity android:name=".PortraitImmersiveActivity"
android:taskAffinity="com.android.server.wm.flicker.testapp.PortraitImmersiveActivity"
android:immersive="true"
@@ -404,6 +424,11 @@
android:name="android.voice_interaction"
android:resource="@xml/interaction_service"/>
</service>
+ <service android:name="com.android.wm.shell.flicker.utils.MediaProjectionService"
+ android:foregroundServiceType="mediaProjection"
+ android:label="WMShellTestsMediaProjectionService"
+ android:enabled="true">
+ </service>
</application>
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION"/>
</manifest>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml
new file mode 100644
index 0000000..46f01e6
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:background="@android:color/holo_orange_light">
+
+ <Button
+ android:id="@+id/button_start_mp"
+ android:layout_width="500dp"
+ android:layout_height="500dp"
+ android:gravity="center_vertical|center_horizontal"
+ android:text="Start Media Projection"
+ android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 80c1dd0..e4de2c5 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -85,6 +85,12 @@
FLICKER_APP_PACKAGE + ".NonResizeablePortraitActivity");
}
+ public static class StartMediaProjectionActivity {
+ public static final String LABEL = "StartMediaProjectionActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".StartMediaProjectionActivity");
+ }
+
public static class PortraitImmersiveActivity {
public static final String LABEL = "PortraitImmersiveActivity";
public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/StartMediaProjectionActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/StartMediaProjectionActivity.java
new file mode 100644
index 0000000..a24a482
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/StartMediaProjectionActivity.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.testapp;
+
+import static com.android.wm.shell.flicker.utils.MediaProjectionUtils.EXTRA_MESSENGER;
+import static com.android.wm.shell.flicker.utils.MediaProjectionUtils.MSG_SERVICE_DESTROYED;
+import static com.android.wm.shell.flicker.utils.MediaProjectionUtils.MSG_START_FOREGROUND_DONE;
+import static com.android.wm.shell.flicker.utils.MediaProjectionUtils.REQUEST_CODE;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.hardware.display.VirtualDisplay;
+import android.media.ImageReader;
+import android.media.projection.MediaProjection;
+import android.media.projection.MediaProjectionManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Messenger;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.widget.Button;
+
+import com.android.wm.shell.flicker.utils.MediaProjectionService;
+
+public class StartMediaProjectionActivity extends Activity {
+
+ private static final String TAG = "StartMediaProjectionActivity";
+ private MediaProjectionManager mService;
+ private ImageReader mImageReader;
+ private VirtualDisplay mVirtualDisplay;
+ private MediaProjection mMediaProjection;
+ private MediaProjection.Callback mMediaProjectionCallback = new MediaProjection.Callback() {
+ @Override
+ public void onStop() {
+ super.onStop();
+ }
+
+ @Override
+ public void onCapturedContentResize(int width, int height) {
+ super.onCapturedContentResize(width, height);
+ }
+
+ @Override
+ public void onCapturedContentVisibilityChanged(boolean isVisible) {
+ super.onCapturedContentVisibilityChanged(isVisible);
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ mService = getSystemService(MediaProjectionManager.class);
+ setContentView(R.layout.activity_start_media_projection);
+
+ Button startMediaProjectionButton = findViewById(R.id.button_start_mp);
+ startMediaProjectionButton.setOnClickListener(v ->
+ startActivityForResult(mService.createScreenCaptureIntent(), REQUEST_CODE));
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode != REQUEST_CODE) {
+ throw new IllegalStateException("Unknown request code: " + requestCode);
+ }
+ if (resultCode != RESULT_OK) {
+ throw new IllegalStateException("User denied screen sharing permission");
+ }
+ Log.d(TAG, "onActivityResult");
+ startMediaProjectionService(resultCode, data);
+ }
+
+ private void startMediaProjectionService(int resultCode, Intent resultData) {
+ final Messenger messenger = new Messenger(new Handler(Looper.getMainLooper(),
+ msg -> {
+ switch (msg.what) {
+ case MSG_START_FOREGROUND_DONE:
+ setupMediaProjection(resultCode, resultData);
+ return true;
+ case MSG_SERVICE_DESTROYED:
+ return true;
+ }
+ Log.e(TAG, "Unknown message from the FlickerMPService: " + msg.what);
+ return false;
+ }
+ ));
+
+ final Intent intent = new Intent()
+ .setComponent(new ComponentName(this, MediaProjectionService.class))
+ .putExtra(EXTRA_MESSENGER, messenger);
+ startForegroundService(intent);
+ }
+
+ private void setupMediaProjection(int resultCode, Intent resultData) {
+ mMediaProjection = mService.getMediaProjection(resultCode, resultData);
+ if (mMediaProjection == null) {
+ throw new IllegalStateException("cannot create new MediaProjection");
+ }
+
+ mMediaProjection.registerCallback(
+ mMediaProjectionCallback, new Handler(Looper.getMainLooper()));
+
+ Rect displayBounds = getWindowManager().getMaximumWindowMetrics().getBounds();
+ mImageReader = ImageReader.newInstance(
+ displayBounds.width(), displayBounds.height(), PixelFormat.RGBA_8888, 1);
+
+ mVirtualDisplay = mMediaProjection.createVirtualDisplay(
+ "DanielDisplay",
+ displayBounds.width(),
+ displayBounds.height(),
+ DisplayMetrics.DENSITY_HIGH,
+ /* flags= */ 0,
+ mImageReader.getSurface(),
+ new VirtualDisplay.Callback() {
+ @Override
+ public void onStopped() {
+ if (mMediaProjection != null) {
+ if (mMediaProjectionCallback != null) {
+ mMediaProjection.unregisterCallback(mMediaProjectionCallback);
+ mMediaProjectionCallback = null;
+ }
+ mMediaProjection.stop();
+ mMediaProjection = null;
+ }
+ if (mImageReader != null) {
+ mImageReader = null;
+ }
+ if (mVirtualDisplay != null) {
+ mVirtualDisplay.getSurface().release();
+ mVirtualDisplay.release();
+ mVirtualDisplay = null;
+ }
+ }
+ },
+ new Handler(Looper.getMainLooper())
+ );
+ }
+
+}
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index 8829f74..351ec463 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -17,8 +17,12 @@
package com.android.server.input
+import android.Manifest
import android.content.Context
import android.content.ContextWrapper
+import android.content.PermissionChecker
+import android.content.pm.PackageManager
+import android.content.pm.PackageManagerInternal
import android.hardware.display.DisplayManager
import android.hardware.display.DisplayViewport
import android.hardware.display.VirtualDisplay
@@ -27,20 +31,30 @@
import android.os.InputEventInjectionSync
import android.os.SystemClock
import android.os.test.TestLooper
+import android.platform.test.annotations.EnableFlags
import android.platform.test.annotations.Presubmit
-import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import android.platform.test.flag.junit.SetFlagsRule
import android.provider.Settings
import android.view.View.OnKeyListener
import android.view.InputDevice
+import android.view.KeyCharacterMap
import android.view.KeyEvent
import android.view.SurfaceHolder
import android.view.SurfaceView
+import android.view.WindowManager
import android.test.mock.MockContentResolver
import androidx.test.platform.app.InstrumentationRegistry
-import com.android.internal.util.test.FakeSettingsProvider
-import com.google.common.truth.Truth.assertThat
import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.internal.policy.KeyInterceptionInfo
+import com.android.internal.util.test.FakeSettingsProvider
+import com.android.modules.utils.testing.ExtendedMockitoRule
+import com.android.server.LocalServices
+import com.android.server.wm.WindowManagerInternal
+import com.google.common.truth.Truth.assertThat
import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
@@ -49,15 +63,15 @@
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyFloat
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when`
import org.mockito.stubbing.OngoingStubbing
/**
@@ -69,15 +83,29 @@
@Presubmit
class InputManagerServiceTests {
- @get:Rule
- val mockitoRule = MockitoJUnit.rule()!!
+ companion object {
+ val ACTION_KEY_EVENTS = listOf(
+ KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_META_LEFT),
+ KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_META_RIGHT),
+ KeyEvent( /* downTime= */0, /* eventTime= */0, /* action= */0, /* code= */0,
+ /* repeat= */0, KeyEvent.META_META_ON
+ )
+ )
+ }
+
+ @JvmField
+ @Rule
+ val extendedMockitoRule =
+ ExtendedMockitoRule.Builder(this).mockStatic(LocalServices::class.java)
+ .mockStatic(PermissionChecker::class.java).build()!!
+
+ @JvmField
+ @Rule
+ val setFlagsRule = SetFlagsRule()
@get:Rule
val fakeSettingsProviderRule = FakeSettingsProvider.rule()!!
- @get:Rule
- val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()!!
-
@Mock
private lateinit var native: NativeInputManagerService
@@ -85,8 +113,17 @@
private lateinit var wmCallbacks: InputManagerService.WindowManagerCallbacks
@Mock
+ private lateinit var windowManagerInternal: WindowManagerInternal
+
+ @Mock
+ private lateinit var packageManagerInternal: PackageManagerInternal
+
+ @Mock
private lateinit var uEventManager: UEventManager
+ @Mock
+ private lateinit var kbdController: InputManagerService.KeyboardBacklightControllerInterface
+
private lateinit var service: InputManagerService
private lateinit var localService: InputManagerInternal
private lateinit var context: Context
@@ -113,11 +150,29 @@
override fun registerLocalService(service: InputManagerInternal?) {
localService = service!!
}
+
+ override fun getKeyboardBacklightController(
+ nativeService: NativeInputManagerService?,
+ dataStore: PersistentDataStore?
+ ): InputManagerService.KeyboardBacklightControllerInterface {
+ return kbdController
+ }
})
inputManagerGlobalSession = InputManagerGlobal.createTestSession(service)
val inputManager = InputManager(context)
whenever(context.getSystemService(InputManager::class.java)).thenReturn(inputManager)
whenever(context.getSystemService(Context.INPUT_SERVICE)).thenReturn(inputManager)
+ whenever(context.checkCallingOrSelfPermission(Manifest.permission.MANAGE_KEY_GESTURES))
+ .thenReturn(
+ PackageManager.PERMISSION_GRANTED
+ )
+
+ ExtendedMockito.doReturn(windowManagerInternal).`when` {
+ LocalServices.getService(eq(WindowManagerInternal::class.java))
+ }
+ ExtendedMockito.doReturn(packageManagerInternal).`when` {
+ LocalServices.getService(eq(PackageManagerInternal::class.java))
+ }
assertTrue("Local service must be registered", this::localService.isInitialized)
service.setWindowManagerCallbacks(wmCallbacks)
@@ -157,9 +212,10 @@
verify(native).setMotionClassifierEnabled(anyBoolean())
verify(native).setMaximumObscuringOpacityForTouch(anyFloat())
verify(native).setStylusPointerIconEnabled(anyBoolean())
- // Called twice at boot, since there are individual callbacks to update the
- // key repeat timeout and the key repeat delay.
- verify(native, times(2)).setKeyRepeatConfiguration(anyInt(), anyInt())
+ // Called thrice at boot, since there are individual callbacks to update the
+ // key repeat timeout, the key repeat delay and whether key repeat enabled.
+ verify(native, times(3)).setKeyRepeatConfiguration(anyInt(), anyInt(),
+ anyBoolean())
}
@Test
@@ -195,7 +251,7 @@
}
@Test
- fun testAddAndRemoveVirtualmKeyboardLayoutAssociation() {
+ fun testAddAndRemoveVirtualKeyboardLayoutAssociation() {
val inputPort = "input port"
val languageTag = "language"
val layoutType = "layoutType"
@@ -206,6 +262,48 @@
verify(native, times(2)).changeKeyboardLayoutAssociation()
}
+ @Test
+ fun testActionKeyEventsForwardedToFocusedWindow_whenCorrectlyRequested() {
+ service.systemRunning()
+ overrideSendActionKeyEventsToFocusedWindow(
+ /* hasPermission = */true,
+ /* hasPrivateFlag = */true
+ )
+ whenever(wmCallbacks.interceptKeyBeforeDispatching(any(), any(), anyInt())).thenReturn(-1)
+
+ for (event in ACTION_KEY_EVENTS) {
+ assertEquals(0, service.interceptKeyBeforeDispatching(null, event, 0))
+ }
+ }
+
+ @Test
+ fun testActionKeyEventsNotForwardedToFocusedWindow_whenNoPermissions() {
+ service.systemRunning()
+ overrideSendActionKeyEventsToFocusedWindow(
+ /* hasPermission = */false,
+ /* hasPrivateFlag = */true
+ )
+ whenever(wmCallbacks.interceptKeyBeforeDispatching(any(), any(), anyInt())).thenReturn(-1)
+
+ for (event in ACTION_KEY_EVENTS) {
+ assertNotEquals(0, service.interceptKeyBeforeDispatching(null, event, 0))
+ }
+ }
+
+ @Test
+ fun testActionKeyEventsNotForwardedToFocusedWindow_whenNoPrivateFlag() {
+ service.systemRunning()
+ overrideSendActionKeyEventsToFocusedWindow(
+ /* hasPermission = */true,
+ /* hasPrivateFlag = */false
+ )
+ whenever(wmCallbacks.interceptKeyBeforeDispatching(any(), any(), anyInt())).thenReturn(-1)
+
+ for (event in ACTION_KEY_EVENTS) {
+ assertNotEquals(0, service.interceptKeyBeforeDispatching(null, event, 0))
+ }
+ }
+
private fun createVirtualDisplays(count: Int): List<VirtualDisplay> {
val displayManager: DisplayManager = context.getSystemService(
DisplayManager::class.java
@@ -373,6 +471,91 @@
verify(mockOnKeyListener).onKey(mockSurfaceView2, KeyEvent.KEYCODE_A, upEvent)
verify(mockOnKeyListener, never()).onKey(mockSurfaceView1, KeyEvent.KEYCODE_A, upEvent)
}
+
+ @Test
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+ fun handleKeyGestures_keyboardBacklight() {
+ service.systemRunning()
+
+ val backlightDownEvent = createKeyEvent(KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN)
+ service.interceptKeyBeforeDispatching(null, backlightDownEvent, /* policyFlags = */0)
+ verify(kbdController).decrementKeyboardBacklight(anyInt())
+
+ val backlightUpEvent = createKeyEvent(KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP)
+ service.interceptKeyBeforeDispatching(null, backlightUpEvent, /* policyFlags = */0)
+ verify(kbdController).incrementKeyboardBacklight(anyInt())
+ }
+
+ @Test
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+ fun handleKeyGestures_toggleCapsLock() {
+ service.systemRunning()
+
+ val metaDownEvent = createKeyEvent(KeyEvent.KEYCODE_META_LEFT)
+ service.interceptKeyBeforeDispatching(null, metaDownEvent, /* policyFlags = */0)
+ val altDownEvent =
+ createKeyEvent(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.META_META_ON, KeyEvent.ACTION_DOWN)
+ service.interceptKeyBeforeDispatching(null, altDownEvent, /* policyFlags = */0)
+ val altUpEvent =
+ createKeyEvent(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.META_META_ON, KeyEvent.ACTION_UP)
+ service.interceptKeyBeforeDispatching(null, altUpEvent, /* policyFlags = */0)
+
+ verify(native).toggleCapsLock(anyInt())
+ }
+
+ fun overrideSendActionKeyEventsToFocusedWindow(
+ hasPermission: Boolean,
+ hasPrivateFlag: Boolean
+ ) {
+ ExtendedMockito.doReturn(
+ if (hasPermission) {
+ PermissionChecker.PERMISSION_GRANTED
+ } else {
+ PermissionChecker.PERMISSION_HARD_DENIED
+ }
+ ).`when` {
+ PermissionChecker.checkPermissionForDataDelivery(
+ any(),
+ eq(Manifest.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW),
+ anyInt(),
+ anyInt(),
+ any(),
+ any(),
+ any()
+ )
+ }
+
+ val info = KeyInterceptionInfo(
+ /* type = */0,
+ if (hasPrivateFlag) {
+ WindowManager.LayoutParams.PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS
+ } else {
+ 0
+ },
+ "title",
+ /* uid = */0
+ )
+ whenever(windowManagerInternal.getKeyInterceptionInfoFromToken(any())).thenReturn(info)
+ }
+
+ private fun createKeyEvent(
+ keycode: Int,
+ modifierState: Int = 0,
+ action: Int = KeyEvent.ACTION_DOWN
+ ): KeyEvent {
+ return KeyEvent(
+ /* downTime = */0,
+ /* eventTime = */0,
+ action,
+ keycode,
+ /* repeat = */0,
+ modifierState,
+ KeyCharacterMap.VIRTUAL_KEYBOARD,
+ /* scancode = */0,
+ /* flags = */0,
+ InputDevice.SOURCE_KEYBOARD
+ )
+ }
}
private fun <T> whenever(methodCall: T): OngoingStubbing<T> = `when`(methodCall)
diff --git a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
index 681b7f2..b3a998e 100644
--- a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
+++ b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
@@ -16,6 +16,7 @@
package com.android.server.input.debug;
+import static android.view.InputDevice.SOURCE_MOUSE;
import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
import static org.junit.Assert.assertEquals;
@@ -29,7 +30,9 @@
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
+import android.hardware.input.InputManager;
import android.testing.TestableContext;
+import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
@@ -60,13 +63,15 @@
*/
@RunWith(AndroidJUnit4.class)
public class TouchpadDebugViewTest {
- private static final int TOUCHPAD_DEVICE_ID = 6;
+ private static final int TOUCHPAD_DEVICE_ID = 60;
private TouchpadDebugView mTouchpadDebugView;
private WindowManager.LayoutParams mWindowLayoutParams;
@Mock
WindowManager mWindowManager;
+ @Mock
+ InputManager mInputManager;
Rect mWindowBounds;
WindowMetrics mWindowMetrics;
@@ -79,12 +84,21 @@
mTestableContext = new TestableContext(context);
mTestableContext.addMockSystemService(WindowManager.class, mWindowManager);
+ mTestableContext.addMockSystemService(InputManager.class, mInputManager);
mWindowBounds = new Rect(0, 0, 2560, 1600);
mWindowMetrics = new WindowMetrics(mWindowBounds, new WindowInsets(mWindowBounds), 1.0f);
when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
+ InputDevice inputDevice = new InputDevice.Builder()
+ .setId(TOUCHPAD_DEVICE_ID)
+ .setSources(InputDevice.SOURCE_TOUCHPAD | SOURCE_MOUSE)
+ .setName("Test Device " + TOUCHPAD_DEVICE_ID)
+ .build();
+
+ when(mInputManager.getInputDevice(TOUCHPAD_DEVICE_ID)).thenReturn(inputDevice);
+
mTouchpadDebugView = new TouchpadDebugView(mTestableContext, TOUCHPAD_DEVICE_ID,
new TouchpadHardwareProperties.Builder(0f, 0f, 500f,
500f, 45f, 47f, -4f, 5f, (short) 10, true,
@@ -341,4 +355,43 @@
mTouchpadDebugView.updateGestureInfo(gestureType, TOUCHPAD_DEVICE_ID);
assertEquals(child.getText().toString(), TouchpadDebugView.getGestureText(gestureType));
}
-}
+
+ @Test
+ public void testTwoFingerDrag() {
+ float offsetX = ViewConfiguration.get(mTestableContext).getScaledTouchSlop() + 10;
+ float offsetY = ViewConfiguration.get(mTestableContext).getScaledTouchSlop() + 10;
+
+ // Simulate ACTION_DOWN event (gesture starts).
+ MotionEvent actionDown = new MotionEventBuilder(MotionEvent.ACTION_DOWN, SOURCE_MOUSE)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(40f)
+ .y(40f)
+ )
+ .classification(MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE)
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionDown);
+
+ // Simulate ACTION_MOVE event (dragging with two fingers, processed as one pointer).
+ MotionEvent actionMove = new MotionEventBuilder(MotionEvent.ACTION_MOVE, SOURCE_MOUSE)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(40f + offsetX)
+ .y(40f + offsetY)
+ )
+ .classification(MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE)
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionMove);
+
+ // Simulate ACTION_UP event (gesture ends).
+ MotionEvent actionUp = new MotionEventBuilder(MotionEvent.ACTION_UP, SOURCE_MOUSE)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(40f + offsetX)
+ .y(40f + offsetY)
+ )
+ .classification(MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE)
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionUp);
+
+ // Verify that no updateViewLayout is called (as expected for a two-finger drag gesture).
+ verify(mWindowManager, times(0)).updateViewLayout(any(), any());
+ }
+}
\ No newline at end of file
diff --git a/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt b/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt
index aa73c39..c61a250 100644
--- a/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt
+++ b/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt
@@ -36,7 +36,6 @@
import com.android.cts.input.inputeventmatchers.withPressure
import com.android.cts.input.inputeventmatchers.withRawCoords
import com.android.cts.input.inputeventmatchers.withSource
-import java.io.InputStream
import junit.framework.Assert.fail
import org.hamcrest.Matchers.allOf
import org.junit.Before
@@ -130,17 +129,18 @@
scenario.virtualDisplay.display.uniqueId!!,
)
- injectUinputEvents()
+ injectUinputEvents().use {
+ if (DEBUG_RECEIVED_EVENTS) {
+ printReceivedEventsToLogcat(scenario.activity)
+ fail("Test cannot pass in debug mode!")
+ }
- if (DEBUG_RECEIVED_EVENTS) {
- printReceivedEventsToLogcat(scenario.activity)
- fail("Test cannot pass in debug mode!")
+ val verifier = EventVerifier(
+ BatchedEventSplitter { scenario.activity.getInputEvent() }
+ )
+ verifyEvents(verifier)
+ scenario.activity.assertNoEvents()
}
-
- val verifier =
- EventVerifier(BatchedEventSplitter { scenario.activity.getInputEvent() })
- verifyEvents(verifier)
- scenario.activity.assertNoEvents()
} finally {
inputManager.removeUniqueIdAssociationByPort(inputPort)
}
@@ -162,14 +162,32 @@
}
}
- private fun injectUinputEvents() {
+ /**
+ * Plays back the evemu recording associated with the current test case by injecting it via
+ * the `uinput` shell command in interactive mode. The recording playback will begin
+ * immediately, and the shell command (and the associated input device) will remain alive
+ * until the returned [AutoCloseable] is closed.
+ */
+ private fun injectUinputEvents(): AutoCloseable {
val fds = instrumentation.uiAutomation!!.executeShellCommandRw("uinput -")
+ // We do not need to use stdout in this test.
+ fds[0].close()
- ParcelFileDescriptor.AutoCloseOutputStream(fds[1]).use { stdIn ->
- val inputStream: InputStream = instrumentation.context.resources.openRawResource(
+ return ParcelFileDescriptor.AutoCloseOutputStream(fds[1]).also { stdin ->
+ instrumentation.context.resources.openRawResource(
testData.uinputRecordingResource,
- )
- stdIn.write(inputStream.readBytes())
+ ).use { inputStream ->
+ stdin.write(inputStream.readBytes())
+
+ // TODO(b/367419268): Remove extra event injection when uinput parsing is fixed.
+ // Inject an extra sync event with an arbitrarily large timestamp, because the
+ // uinput command will not process the last event until either the next event is
+ // parsed, or fd is closed. Injecting this sync allows us complete injection of
+ // the evemu recording and extend the lifetime of the input device by keeping this
+ // fd open.
+ stdin.write("\nE: 9999.99 0 0 0\n".toByteArray())
+ stdin.flush()
+ }
}
}
diff --git a/tests/Tracing/Android.bp b/tests/Tracing/Android.bp
new file mode 100644
index 0000000..90998e6
--- /dev/null
+++ b/tests/Tracing/Android.bp
@@ -0,0 +1,33 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_team: "trendy_team_windowing_tools",
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "TracingTests",
+ proto: {
+ type: "nano",
+ },
+ // Include some source files directly to be able to access package members
+ srcs: ["src/**/*.java"],
+ libs: ["android.test.runner.stubs.system"],
+ static_libs: [
+ "junit",
+ "androidx.test.rules",
+ "mockito-target-minus-junit4",
+ "truth",
+ "platform-test-annotations",
+ "flickerlib-parsers",
+ "perfetto_trace_java_protos",
+ "flickerlib-trace_processor_shell",
+ ],
+ java_resource_dirs: ["res"],
+ certificate: "platform",
+ platform_apis: true,
+ test_suites: ["device-tests"],
+}
diff --git a/tests/Tracing/AndroidManifest.xml b/tests/Tracing/AndroidManifest.xml
new file mode 100644
index 0000000..7254f81
--- /dev/null
+++ b/tests/Tracing/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tracing.tests">
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+ <uses-permission android:name="android.permission.BIND_WALLPAPER"/>
+ <!-- Allow the test to connect to perfetto trace processor -->
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <application
+ android:requestLegacyExternalStorage="true"
+ android:networkSecurityConfig="@xml/network_security_config">
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.tracing.tests"
+ android:label="Tracing Tests"/>
+</manifest>
diff --git a/tests/Tracing/AndroidTest.xml b/tests/Tracing/AndroidTest.xml
new file mode 100644
index 0000000..9a40420
--- /dev/null
+++ b/tests/Tracing/AndroidTest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<configuration description="Runs tests for tracing classes/utilities.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="TracingTests.apk" />
+ </target_preparer>
+
+ <!-- Needed for pushing the trace config file -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="framework-base-presubmit" />
+ <option name="test-tag" value="TracingTests" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.tracing.tests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ </test>
+
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="pull-pattern-keys" value="perfetto_file_path"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.tracing.tests/files"/>
+ <option name="collect-on-run-ended-only" value="true"/>
+ <option name="clean-up" value="true"/>
+ </metrics_collector>
+</configuration>
\ No newline at end of file
diff --git a/tests/Internal/src/com/android/internal/protolog/OWNERS b/tests/Tracing/OWNERS
similarity index 80%
rename from tests/Internal/src/com/android/internal/protolog/OWNERS
rename to tests/Tracing/OWNERS
index 18cf2be..4a50338 100644
--- a/tests/Internal/src/com/android/internal/protolog/OWNERS
+++ b/tests/Tracing/OWNERS
@@ -1,3 +1,3 @@
-# ProtoLog owners
+# Tracing owners
# Bug component: 1157642
include platform/development:/tools/winscope/OWNERS
diff --git a/tests/Tracing/TEST_MAPPING b/tests/Tracing/TEST_MAPPING
new file mode 100644
index 0000000..7f58fce
--- /dev/null
+++ b/tests/Tracing/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "postsubmit": [
+ {
+ "name": "TracingTests"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tests/Tracing/res/xml/network_security_config.xml b/tests/Tracing/res/xml/network_security_config.xml
new file mode 100644
index 0000000..fdf1dbb
--- /dev/null
+++ b/tests/Tracing/res/xml/network_security_config.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<network-security-config>
+ <domain-config cleartextTrafficPermitted="true">
+ <domain includeSubdomains="true">localhost</domain>
+ </domain-config>
+</network-security-config>
diff --git a/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java b/tests/Tracing/src/android/tracing/perfetto/DataSourceTest.java
similarity index 100%
rename from core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java
rename to tests/Tracing/src/android/tracing/perfetto/DataSourceTest.java
diff --git a/core/tests/coretests/src/android/tracing/perfetto/TestDataSource.java b/tests/Tracing/src/android/tracing/perfetto/TestDataSource.java
similarity index 100%
rename from core/tests/coretests/src/android/tracing/perfetto/TestDataSource.java
rename to tests/Tracing/src/android/tracing/perfetto/TestDataSource.java
diff --git a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java b/tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
similarity index 99%
rename from tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
rename to tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
index 9657225..8913e8c 100644
--- a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
@@ -180,7 +180,6 @@
verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
LogLevel.INFO), eq("test 5"));
- verify(mReader, never()).getViewerString(anyLong());
}
@Test
diff --git a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogViewerConfigReaderTest.java b/tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogViewerConfigReaderTest.java
similarity index 100%
rename from tests/Internal/src/com/android/internal/protolog/LegacyProtoLogViewerConfigReaderTest.java
rename to tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogViewerConfigReaderTest.java
diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
similarity index 94%
rename from tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
rename to tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
index e841d9e..cfb2645 100644
--- a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
@@ -16,6 +16,8 @@
package com.android.internal.protolog;
+import static android.tools.traces.Utils.executeShellCommand;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
@@ -42,12 +44,13 @@
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.internal.protolog.ProtoLogConfigurationService.ViewerConfigFileTracer;
+import com.android.internal.protolog.ProtoLogConfigurationServiceImpl.ViewerConfigFileTracer;
import com.android.internal.protolog.common.IProtoLogGroup;
import com.android.internal.protolog.common.LogDataType;
import com.android.internal.protolog.common.LogLevel;
import com.google.common.truth.Truth;
+import com.google.protobuf.InvalidProtocolBufferException;
import org.junit.After;
import org.junit.Before;
@@ -57,12 +60,14 @@
import org.junit.runners.JUnit4;
import org.mockito.Mockito;
+import perfetto.protos.PerfettoConfig.TracingServiceState;
import perfetto.protos.Protolog;
import perfetto.protos.ProtologCommon;
import java.io.File;
import java.io.IOException;
import java.util.List;
+import java.util.Optional;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
@@ -166,7 +171,8 @@
return new ProtoInputStream(sViewerConfigBuilder.build().toByteArray());
});
};
- sProtoLogConfigurationService = new ProtoLogConfigurationService(dataSourceBuilder, tracer);
+ sProtoLogConfigurationService =
+ new ProtoLogConfigurationServiceImpl(dataSourceBuilder, tracer);
if (android.tracing.Flags.clientSideProtoLogging()) {
sProtoLog = new PerfettoProtoLogImpl(
@@ -177,6 +183,8 @@
viewerConfigInputStreamProvider, sReader, () -> sCacheUpdater.run(),
TestProtoLogGroup.values(), dataSourceBuilder, sProtoLogConfigurationService);
}
+
+ waitDataSourceIsAvailable();
}
@Before
@@ -862,6 +870,54 @@
.isEqualTo("This message should also be logged 567");
}
+ private static void waitDataSourceIsAvailable() {
+ final int timeoutMs = 10000;
+ final int busyWaitIntervalMs = 100;
+
+ int elapsedMs = 0;
+
+ while (!isDataSourceAvailable()) {
+ SystemClock.sleep(busyWaitIntervalMs);
+ elapsedMs += busyWaitIntervalMs;
+ if (elapsedMs >= timeoutMs) {
+ throw new RuntimeException("Data source didn't become available."
+ + " Waited for: " + timeoutMs + " ms");
+ }
+ }
+ }
+
+ private static boolean isDataSourceAvailable() {
+ byte[] proto = executeShellCommand("perfetto --query-raw");
+
+ try {
+ TracingServiceState state = TracingServiceState.parseFrom(proto);
+
+ Optional<Integer> producerId = Optional.empty();
+
+ for (TracingServiceState.Producer producer : state.getProducersList()) {
+ if (producer.getPid() == android.os.Process.myPid()) {
+ producerId = Optional.of(producer.getId());
+ break;
+ }
+ }
+
+ if (producerId.isEmpty()) {
+ return false;
+ }
+
+ for (TracingServiceState.DataSource ds : state.getDataSourcesList()) {
+ if (ds.getDsDescriptor().getName().equals(TEST_PROTOLOG_DATASOURCE_NAME)
+ && ds.getProducerId() == producerId.get()) {
+ return true;
+ }
+ }
+ } catch (InvalidProtocolBufferException e) {
+ throw new RuntimeException(e);
+ }
+
+ return false;
+ }
+
private enum TestProtoLogGroup implements IProtoLogGroup {
TEST_GROUP(true, true, false, "TEST_TAG");
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java
similarity index 86%
rename from tests/Internal/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java
rename to tests/Tracing/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java
index aba6722..be0c7da 100644
--- a/tests/Internal/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java
@@ -22,6 +22,7 @@
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.times;
+import android.os.Binder;
import android.platform.test.annotations.Presubmit;
import org.junit.Test;
@@ -44,6 +45,8 @@
ProtoLogConfigurationService mProtoLogConfigurationService;
@Mock
PrintWriter mPrintWriter;
+ @Mock
+ Binder mMockBinder;
@Test
public void printsHelpForAllAvailableCommands() {
@@ -70,7 +73,7 @@
final ProtoLogCommandHandler cmdHandler =
new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
FileDescriptor.err, new String[] { "groups", "list" });
Mockito.verify(mPrintWriter, times(1))
@@ -84,7 +87,7 @@
final ProtoLogCommandHandler cmdHandler =
new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
FileDescriptor.err, new String[] { "groups" });
Mockito.verify(mPrintWriter, times(1))
@@ -99,7 +102,7 @@
final ProtoLogCommandHandler cmdHandler =
new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
FileDescriptor.err, new String[] { "groups", "status", "MY_GROUP" });
Mockito.verify(mPrintWriter, times(1))
@@ -114,7 +117,7 @@
final ProtoLogCommandHandler cmdHandler =
new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
FileDescriptor.err, new String[] { "groups", "status", "MY_GROUP" });
Mockito.verify(mPrintWriter, times(1))
@@ -128,7 +131,7 @@
final ProtoLogCommandHandler cmdHandler =
new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
FileDescriptor.err, new String[] { "groups", "status" });
Mockito.verify(mPrintWriter, times(1))
@@ -140,7 +143,7 @@
final ProtoLogCommandHandler cmdHandler =
new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
FileDescriptor.err, new String[] { "logcat" });
Mockito.verify(mPrintWriter, times(1))
@@ -152,11 +155,11 @@
final ProtoLogCommandHandler cmdHandler =
new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
FileDescriptor.err, new String[] { "logcat", "enable", "MY_GROUP" });
Mockito.verify(mProtoLogConfigurationService).enableProtoLogToLogcat("MY_GROUP");
- cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
FileDescriptor.err,
new String[] { "logcat", "enable", "MY_GROUP", "MY_OTHER_GROUP" });
Mockito.verify(mProtoLogConfigurationService)
@@ -168,11 +171,11 @@
final ProtoLogCommandHandler cmdHandler =
new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
FileDescriptor.err, new String[] { "logcat", "disable", "MY_GROUP" });
Mockito.verify(mProtoLogConfigurationService).disableProtoLogToLogcat("MY_GROUP");
- cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
FileDescriptor.err,
new String[] { "logcat", "disable", "MY_GROUP", "MY_OTHER_GROUP" });
Mockito.verify(mProtoLogConfigurationService)
@@ -184,7 +187,7 @@
final ProtoLogCommandHandler cmdHandler =
new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
FileDescriptor.err, new String[] { "logcat", "enable" });
Mockito.verify(mPrintWriter).println(contains("Incomplete command"));
}
@@ -194,7 +197,7 @@
final ProtoLogCommandHandler cmdHandler =
new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
FileDescriptor.err, new String[] { "logcat", "disable" });
Mockito.verify(mPrintWriter).println(contains("Incomplete command"));
}
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java
similarity index 86%
rename from tests/Internal/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java
rename to tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java
index e1bdd77..a3d03a8 100644
--- a/tests/Internal/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java
@@ -150,11 +150,11 @@
@Test
public void canRegisterClientWithGroupsOnly() throws RemoteException {
- final ProtoLogConfigurationService service = new ProtoLogConfigurationService();
+ final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl();
- final ProtoLogConfigurationService.RegisterClientArgs args =
- new ProtoLogConfigurationService.RegisterClientArgs()
- .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+ final ProtoLogConfigurationServiceImpl.RegisterClientArgs args =
+ new ProtoLogConfigurationServiceImpl.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs
.GroupConfig(TEST_GROUP, true));
service.registerClient(mMockClient, args);
@@ -165,11 +165,11 @@
@Test
public void willDumpViewerConfigOnlyOnceOnTraceStop()
throws RemoteException, InvalidProtocolBufferException {
- final ProtoLogConfigurationService service = new ProtoLogConfigurationService();
+ final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl();
- final ProtoLogConfigurationService.RegisterClientArgs args =
- new ProtoLogConfigurationService.RegisterClientArgs()
- .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+ final ProtoLogConfigurationServiceImpl.RegisterClientArgs args =
+ new ProtoLogConfigurationServiceImpl.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs
.GroupConfig(TEST_GROUP, true))
.setViewerConfigFile(mViewerConfigFile.getAbsolutePath());
service.registerClient(mMockClient, args);
@@ -200,13 +200,13 @@
@Test
public void willDumpViewerConfigOnLastClientDisconnected()
throws RemoteException, FileNotFoundException {
- final ProtoLogConfigurationService.ViewerConfigFileTracer tracer =
- Mockito.mock(ProtoLogConfigurationService.ViewerConfigFileTracer.class);
- final ProtoLogConfigurationService service = new ProtoLogConfigurationService(tracer);
+ final ProtoLogConfigurationServiceImpl.ViewerConfigFileTracer tracer =
+ Mockito.mock(ProtoLogConfigurationServiceImpl.ViewerConfigFileTracer.class);
+ final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(tracer);
- final ProtoLogConfigurationService.RegisterClientArgs args =
- new ProtoLogConfigurationService.RegisterClientArgs()
- .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+ final ProtoLogConfigurationServiceImpl.RegisterClientArgs args =
+ new ProtoLogConfigurationServiceImpl.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs
.GroupConfig(TEST_GROUP, true))
.setViewerConfigFile(mViewerConfigFile.getAbsolutePath());
service.registerClient(mMockClient, args);
@@ -225,10 +225,10 @@
@Test
public void sendEnableLoggingToLogcatToClient() throws RemoteException {
- final var service = new ProtoLogConfigurationService();
+ final var service = new ProtoLogConfigurationServiceImpl();
- final var args = new ProtoLogConfigurationService.RegisterClientArgs()
- .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+ final var args = new ProtoLogConfigurationServiceImpl.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs
.GroupConfig(TEST_GROUP, false));
service.registerClient(mMockClient, args);
@@ -242,11 +242,11 @@
@Test
public void sendDisableLoggingToLogcatToClient() throws RemoteException {
- final ProtoLogConfigurationService service = new ProtoLogConfigurationService();
+ final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl();
- final ProtoLogConfigurationService.RegisterClientArgs args =
- new ProtoLogConfigurationService.RegisterClientArgs()
- .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+ final ProtoLogConfigurationServiceImpl.RegisterClientArgs args =
+ new ProtoLogConfigurationServiceImpl.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs
.GroupConfig(TEST_GROUP, true));
service.registerClient(mMockClient, args);
@@ -260,11 +260,11 @@
@Test
public void doNotSendLoggingToLogcatToClientWithoutRegisteredGroup() throws RemoteException {
- final ProtoLogConfigurationService service = new ProtoLogConfigurationService();
+ final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl();
- final ProtoLogConfigurationService.RegisterClientArgs args =
- new ProtoLogConfigurationService.RegisterClientArgs()
- .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+ final ProtoLogConfigurationServiceImpl.RegisterClientArgs args =
+ new ProtoLogConfigurationServiceImpl.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs
.GroupConfig(TEST_GROUP, false));
service.registerClient(mMockClient, args);
@@ -277,15 +277,15 @@
@Test
public void handlesToggleToLogcatBeforeClientIsRegistered() throws RemoteException {
- final ProtoLogConfigurationService service = new ProtoLogConfigurationService();
+ final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl();
Truth.assertThat(service.getGroups()).asList().doesNotContain(TEST_GROUP);
service.enableProtoLogToLogcat(TEST_GROUP);
Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue();
- final ProtoLogConfigurationService.RegisterClientArgs args =
- new ProtoLogConfigurationService.RegisterClientArgs()
- .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+ final ProtoLogConfigurationServiceImpl.RegisterClientArgs args =
+ new ProtoLogConfigurationServiceImpl.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs
.GroupConfig(TEST_GROUP, false));
service.registerClient(mMockClient, args);
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogImplTest.java
similarity index 100%
rename from tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java
rename to tests/Tracing/src/com/android/internal/protolog/ProtoLogImplTest.java
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java
similarity index 100%
rename from tests/Internal/src/com/android/internal/protolog/ProtoLogTest.java
rename to tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
similarity index 98%
rename from tests/Internal/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
rename to tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
index be0e8bc..28d7b42 100644
--- a/tests/Internal/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
@@ -27,7 +27,6 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import perfetto.protos.Protolog;
import perfetto.protos.ProtologCommon;
@Presubmit
@@ -48,7 +47,7 @@
.setTag(TEST_GROUP_TAG)
).addGroups(
perfetto.protos.Protolog.ProtoLogViewerConfig.Group.newBuilder()
- .setId(1)
+ .setId(2)
.setName(OTHER_TEST_GROUP_NAME)
.setTag(OTHER_TEST_GROUP_TAG)
).addMessages(
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtologDataSourceTest.java
similarity index 98%
rename from tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java
rename to tests/Tracing/src/com/android/internal/protolog/ProtologDataSourceTest.java
index 9a062e3..ce519b7a 100644
--- a/tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtologDataSourceTest.java
@@ -67,7 +67,8 @@
@Test
public void allEnabledTraceMode() {
- final ProtoLogDataSource ds = new ProtoLogDataSource((idx, c) -> {}, () -> {}, (idx, c) -> {});
+ final ProtoLogDataSource ds =
+ new ProtoLogDataSource((idx, c) -> {}, () -> {}, (idx, c) -> {});
final ProtoLogDataSource.TlsState tlsState = createTlsState(
DataSourceConfigOuterClass.DataSourceConfig.newBuilder().setProtologConfig(
diff --git a/tests/Internal/src/com/android/internal/protolog/common/LogDataTypeTest.java b/tests/Tracing/src/com/android/internal/protolog/common/LogDataTypeTest.java
similarity index 100%
rename from tests/Internal/src/com/android/internal/protolog/common/LogDataTypeTest.java
rename to tests/Tracing/src/com/android/internal/protolog/common/LogDataTypeTest.java
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index 580efe1..4cb7c91 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -146,6 +146,8 @@
private static final LinkProperties TEST_LP_1 = new LinkProperties();
private static final LinkProperties TEST_LP_2 = new LinkProperties();
+ private static final int ACTIVE_MODEM_COUNT = 2;
+
static {
TEST_LP_1.setInterfaceName(TEST_IFACE_NAME);
TEST_LP_2.setInterfaceName(TEST_IFACE_NAME_2);
@@ -233,6 +235,7 @@
setupSystemService(mMockContext, mUserManager, Context.USER_SERVICE, UserManager.class);
doReturn(TEST_USER_HANDLE).when(mUserManager).getMainUser();
+ doReturn(ACTIVE_MODEM_COUNT).when(mTelMgr).getActiveModemCount();
doReturn(TEST_PACKAGE_NAME).when(mMockContext).getOpPackageName();
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt
index 290e7be..9467434 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt
@@ -22,6 +22,7 @@
import com.google.android.lint.aidl.EnforcePermissionDetector
import com.google.android.lint.aidl.PermissionAnnotationDetector
import com.google.android.lint.aidl.SimpleManualPermissionEnforcementDetector
+import com.google.android.lint.aidl.SimpleRequiresNoPermissionDetector
import com.google.auto.service.AutoService
@AutoService(IssueRegistry::class)
@@ -34,6 +35,7 @@
EnforcePermissionDetector.ISSUE_MISUSING_ENFORCE_PERMISSION,
PermissionAnnotationDetector.ISSUE_MISSING_PERMISSION_ANNOTATION,
SimpleManualPermissionEnforcementDetector.ISSUE_SIMPLE_MANUAL_PERMISSION_ENFORCEMENT,
+ SimpleRequiresNoPermissionDetector.ISSUE_SIMPLE_REQUIRES_NO_PERMISSION,
)
override val api: Int
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleRequiresNoPermissionDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleRequiresNoPermissionDetector.kt
new file mode 100644
index 0000000..1a13c02
--- /dev/null
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleRequiresNoPermissionDetector.kt
@@ -0,0 +1,118 @@
+/*
+ * 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.google.android.lint.aidl
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import org.jetbrains.uast.UastCallKind
+import org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.visitor.AbstractUastVisitor
+
+/**
+ * Ensures all AIDL implementations hosted by system_server which don't call other methods are
+ * annotated with @RequiresNoPermission. AIDL Interfaces part of `exemptAidlInterfaces` are skipped
+ * during this search to ensure the detector targets only new AIDL Interfaces.
+ */
+class SimpleRequiresNoPermissionDetector : AidlImplementationDetector() {
+ override fun visitAidlMethod(
+ context: JavaContext,
+ node: UMethod,
+ interfaceName: String,
+ body: UBlockExpression
+ ) {
+ if (!isSystemServicePath(context)) return
+ if (context.evaluator.isAbstract(node)) return
+
+ val fullyQualifiedInterfaceName =
+ getContainingAidlInterfaceQualified(context, node) ?: return
+ if (exemptAidlInterfaces.contains(fullyQualifiedInterfaceName)) return
+
+ if (node.hasAnnotation(ANNOTATION_REQUIRES_NO_PERMISSION)) return
+
+ if (!isCallingMethod(node)) {
+ context.report(
+ ISSUE_SIMPLE_REQUIRES_NO_PERMISSION,
+ node,
+ context.getLocation(node),
+ """
+ Method ${node.name} doesn't perform any permission checks, meaning it should \
+ be annotated with @RequiresNoPermission.
+ """.trimMargin()
+ )
+ }
+ }
+
+ private fun isCallingMethod(node: UMethod): Boolean {
+ val uCallExpressionVisitor = UCallExpressionVisitor()
+ node.accept(uCallExpressionVisitor)
+
+ return uCallExpressionVisitor.isCallingMethod
+ }
+
+ /**
+ * Visits the body of a `UMethod` and determines if it encounters a `UCallExpression` which is
+ * a `UastCallKind.METHOD_CALL`. `isCallingMethod` will hold the result of the search procedure.
+ */
+ private class UCallExpressionVisitor : AbstractUastVisitor() {
+ var isCallingMethod = false
+
+ override fun visitElement(node: UElement): Boolean {
+ // Stop the search early when a method call has been found.
+ return isCallingMethod
+ }
+
+ override fun visitCallExpression(node: UCallExpression): Boolean {
+ if (node.kind != UastCallKind.METHOD_CALL) return false
+
+ isCallingMethod = true
+ return true
+ }
+ }
+
+ companion object {
+
+ private val EXPLANATION = """
+ Method implementations of AIDL Interfaces hosted by the `system_server` which do not
+ call any other methods should be annotated with @RequiresNoPermission. That is because
+ not calling any other methods implies that the method does not perform any permission
+ checking.
+
+ Please migrate to an @RequiresNoPermission annotation.
+ """.trimIndent()
+
+ @JvmField
+ val ISSUE_SIMPLE_REQUIRES_NO_PERMISSION = Issue.create(
+ id = "SimpleRequiresNoPermission",
+ briefDescription = "System Service APIs not calling other methods should use @RNP",
+ explanation = EXPLANATION,
+ category = Category.SECURITY,
+ priority = 5,
+ severity = Severity.ERROR,
+ implementation = Implementation(
+ SimpleRequiresNoPermissionDetector::class.java,
+ Scope.JAVA_FILE_SCOPE
+ ),
+ )
+ }
+}
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/PermissionAnnotationDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/PermissionAnnotationDetectorTest.kt
index 92d0829..824be93 100644
--- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/PermissionAnnotationDetectorTest.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/PermissionAnnotationDetectorTest.kt
@@ -17,7 +17,6 @@
package com.google.android.lint.aidl
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.TestFile
import com.android.tools.lint.checks.infrastructure.TestLintTask
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
@@ -64,7 +63,7 @@
"""
package com.android.server;
public class Bar extends IBar.Stub {
- public void testMethod() { }
+ public void testMethod(int parameter1, int parameter2) { }
}
"""
)
@@ -75,8 +74,8 @@
.expect(
"""
src/frameworks/base/services/java/com/android/server/Bar.java:3: Error: The method testMethod is not permission-annotated. [MissingPermissionAnnotation]
- public void testMethod() { }
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ public void testMethod(int parameter1, int parameter2) { }
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 errors, 0 warnings
"""
)
@@ -90,7 +89,7 @@
"""
package com.android.server;
public class Bar extends IBar.Stub {
- public void testMethod() { }
+ public void testMethod(int parameter1, int parameter2) { }
}
"""
)
@@ -132,7 +131,7 @@
"""
package com.android.server;
public abstract class Bar extends IBar.Stub {
- public abstract void testMethod();
+ public abstract void testMethod(int parameter1, int parameter2);
}
"""
)
@@ -177,50 +176,6 @@
.expectClean()
}
- /* Stubs */
-
- // A service with permission annotation on the method.
- private val interfaceIFoo: TestFile = java(
- """
- public interface IFoo extends android.os.IInterface {
- public static abstract class Stub extends android.os.Binder implements IFoo {
- }
- @Override
- @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
- public void testMethod();
- @Override
- @android.annotation.RequiresNoPermission
- public void testMethodNoPermission();
- @Override
- @android.annotation.PermissionManuallyEnforced
- public void testMethodManual();
- }
- """
- ).indented()
-
- // A service with no permission annotation.
- private val interfaceIBar: TestFile = java(
- """
- public interface IBar extends android.os.IInterface {
- public static abstract class Stub extends android.os.Binder implements IBar {
- }
- public void testMethod();
- }
- """
- ).indented()
-
- // A service whose AIDL Interface is exempted.
- private val interfaceIExempted: TestFile = java(
- """
- package android.accessibilityservice;
- public interface IBrailleDisplayConnection extends android.os.IInterface {
- public static abstract class Stub extends android.os.Binder implements IBrailleDisplayConnection {
- }
- public void testMethod();
- }
- """
- ).indented()
-
private val stubs = arrayOf(interfaceIFoo, interfaceIBar, interfaceIExempted)
private fun createVisitedPath(filename: String) =
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleRequiresNoPermissionDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleRequiresNoPermissionDetectorTest.kt
new file mode 100644
index 0000000..a33b48c
--- /dev/null
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleRequiresNoPermissionDetectorTest.kt
@@ -0,0 +1,244 @@
+/*
+ * 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.google.android.lint.aidl
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+
+class SimpleRequiresNoPermissionDetectorTest : LintDetectorTest() {
+ override fun getDetector(): Detector = SimpleRequiresNoPermissionDetector()
+ override fun getIssues(): List<Issue> = listOf(
+ SimpleRequiresNoPermissionDetector
+ .ISSUE_SIMPLE_REQUIRES_NO_PERMISSION
+ )
+
+ override fun lint(): TestLintTask = super.lint().allowMissingSdk()
+
+ fun testRequiresNoPermissionUsedCorrectly_shouldNotWarn() {
+ lint()
+ .files(
+ java(
+ createVisitedPath("Foo.java"),
+ """
+ package com.android.server;
+ public class Foo extends IFoo.Stub {
+ private int memberInt;
+
+ @Override
+ @android.annotation.RequiresNoPermission
+ public void testMethodNoPermission(int parameter1, int parameter2) {
+ if (parameter1 < parameter2) {
+ memberInt = parameter1;
+ } else {
+ memberInt = parameter2;
+ }
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .run()
+ .expectClean()
+ }
+
+ fun testMissingRequiresNoPermission_shouldWarn() {
+ lint()
+ .files(
+ java(
+ createVisitedPath("Bar.java"),
+ """
+ package com.android.server;
+ public class Bar extends IBar.Stub {
+ private int memberInt;
+
+ @Override
+ public void testMethod(int parameter1, int parameter2) {
+ if (parameter1 < parameter2) {
+ memberInt = parameter1;
+ } else {
+ memberInt = parameter2;
+ }
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/frameworks/base/services/java/com/android/server/Bar.java:5: Error: Method testMethod doesn't perform any permission checks, meaning it should be annotated with @RequiresNoPermission. [SimpleRequiresNoPermission]
+ @Override
+ ^
+ 1 errors, 0 warnings
+ """
+ )
+ }
+
+ fun testMethodOnlyPerformsConstructorCall_shouldWarn() {
+ lint()
+ .files(
+ java(
+ createVisitedPath("Bar.java"),
+ """
+ package com.android.server;
+ public class Bar extends IBar.Stub {
+ private IntPair memberIntPair;
+
+ @Override
+ public void testMethod(int parameter1, int parameter2) {
+ memberIntPair = new IntPair(parameter1, parameter2);
+ }
+
+ private static class IntPair {
+ public int first;
+ public int second;
+
+ public IntPair(int first, int second) {
+ this.first = first;
+ this.second = second;
+ }
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/frameworks/base/services/java/com/android/server/Bar.java:5: Error: Method testMethod doesn't perform any permission checks, meaning it should be annotated with @RequiresNoPermission. [SimpleRequiresNoPermission]
+ @Override
+ ^
+ 1 errors, 0 warnings
+ """
+ )
+ }
+
+ fun testMissingRequiresNoPermissionInIgnoredDirectory_shouldNotWarn() {
+ lint()
+ .files(
+ java(
+ ignoredPath,
+ """
+ package com.android.server;
+ public class Bar extends IBar.Stub {
+ @Override
+ public void testMethod(int parameter1, int parameter2) {}
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .run()
+ .expectClean()
+ }
+
+ fun testMissingRequiresNoPermissionAbstractMethod_shouldNotWarn() {
+ lint()
+ .files(
+ java(
+ createVisitedPath("Bar.java"),
+ """
+ package com.android.server;
+ public abstract class Bar extends IBar.Stub {
+ private int memberInt;
+
+ @Override
+ public abstract void testMethodNoPermission(int parameter1, int parameter2);
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .run()
+ .expectClean()
+ }
+
+ // If this test fails, consider the following steps:
+ // 1. Pick the first entry (interface) from `exemptAidlInterfaces`.
+ // 2. Change `interfaceIExempted` to use that interface.
+ // 3. Change this test's class to extend the interface's Stub.
+ fun testMissingRequiresNoPermissionAidlInterfaceExempted_shouldNotWarn() {
+ lint()
+ .files(
+ java(
+ createVisitedPath("Bar.java"),
+ """
+ package com.android.server;
+ public class Bar extends android.accessibilityservice.IBrailleDisplayConnection.Stub {
+ public void testMethod(int parameter1, int parameter2) {}
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .run()
+ .expectClean()
+ }
+
+ fun testMethodMakesAnotherMethodCall_shouldNotWarn() {
+ lint()
+ .files(
+ java(
+ createVisitedPath("Bar.java"),
+ """
+ package com.android.server;
+ public class Bar extends IBar.Stub {
+ private int memberInt;
+
+ @Override
+ public void testMethod(int parameter1, int parameter2) {
+ if (!hasPermission()) return;
+
+ if (parameter1 < parameter2) {
+ memberInt = parameter1;
+ } else {
+ memberInt = parameter2;
+ }
+ }
+
+ private bool hasPermission() {
+ // Perform a permission check.
+ return true;
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .run()
+ .expectClean()
+ }
+
+ private val stubs = arrayOf(interfaceIFoo, interfaceIBar, interfaceIExempted)
+
+ private fun createVisitedPath(filename: String) =
+ "src/frameworks/base/services/java/com/android/server/$filename"
+
+ private val ignoredPath = "src/test/pkg/TestClass.java"
+}
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt
index 2ec8fdd..18a8f18 100644
--- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt
@@ -85,4 +85,46 @@
}
}
""".trimIndent()
-)
\ No newline at end of file
+)
+
+// A service with permission annotation on the method.
+val interfaceIFoo: TestFile = java(
+ """
+ public interface IFoo extends android.os.IInterface {
+ public static abstract class Stub extends android.os.Binder implements IFoo {
+ }
+ @Override
+ @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+ public void testMethod();
+ @Override
+ @android.annotation.RequiresNoPermission
+ public void testMethodNoPermission(int parameter1, int parameter2);
+ @Override
+ @android.annotation.PermissionManuallyEnforced
+ public void testMethodManual();
+ }
+ """
+).indented()
+
+// A service with no permission annotation.
+val interfaceIBar: TestFile = java(
+ """
+ public interface IBar extends android.os.IInterface {
+ public static abstract class Stub extends android.os.Binder implements IBar {
+ }
+ public void testMethod(int parameter1, int parameter2);
+ }
+ """
+).indented()
+
+// A service whose AIDL Interface is exempted.
+val interfaceIExempted: TestFile = java(
+ """
+ package android.accessibilityservice;
+ public interface IBrailleDisplayConnection extends android.os.IInterface {
+ public static abstract class Stub extends android.os.Binder implements IBrailleDisplayConnection {
+ }
+ public void testMethod();
+ }
+ """
+).indented()
diff --git a/wifi/java/src/android/net/wifi/WifiMigration.java b/wifi/java/src/android/net/wifi/WifiMigration.java
index 7df1d4b..f1850dd 100644
--- a/wifi/java/src/android/net/wifi/WifiMigration.java
+++ b/wifi/java/src/android/net/wifi/WifiMigration.java
@@ -100,6 +100,39 @@
public @interface UserStoreFileId { }
/**
+ * Keystore migration was completed successfully.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_LEGACY_KEYSTORE_TO_WIFI_BLOBSTORE_MIGRATION_READ_ONLY)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int KEYSTORE_MIGRATION_SUCCESS_MIGRATION_COMPLETE = 0;
+
+ /**
+ * Keystore migration was not needed.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_LEGACY_KEYSTORE_TO_WIFI_BLOBSTORE_MIGRATION_READ_ONLY)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int KEYSTORE_MIGRATION_SUCCESS_MIGRATION_NOT_NEEDED = 1;
+
+ /**
+ * Keystore migration failed because an exception was encountered.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_LEGACY_KEYSTORE_TO_WIFI_BLOBSTORE_MIGRATION_READ_ONLY)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int KEYSTORE_MIGRATION_FAILURE_ENCOUNTERED_EXCEPTION = 2;
+
+ /** @hide */
+ @IntDef(prefix = { "KEYSTORE_MIGRATION_" }, value = {
+ KEYSTORE_MIGRATION_SUCCESS_MIGRATION_COMPLETE,
+ KEYSTORE_MIGRATION_SUCCESS_MIGRATION_NOT_NEEDED,
+ KEYSTORE_MIGRATION_FAILURE_ENCOUNTERED_EXCEPTION
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface KeystoreMigrationStatus { }
+
+ /**
* Mapping of Store file Id to Store file names.
*
* NOTE: This is the default path for the files on AOSP devices. If the OEM has modified
@@ -572,14 +605,17 @@
/**
* Migrate any certificates in Legacy Keystore to the newer WifiBlobstore database.
*
+ * If there are no certificates to migrate, this method will return immediately.
+ *
* @hide
*/
@FlaggedApi(Flags.FLAG_LEGACY_KEYSTORE_TO_WIFI_BLOBSTORE_MIGRATION_READ_ONLY)
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- public static void migrateLegacyKeystoreToWifiBlobstore() {
+ public static @KeystoreMigrationStatus int migrateLegacyKeystoreToWifiBlobstore() {
if (!WifiBlobStore.supplicantCanAccessBlobstore()) {
+ // Supplicant cannot access WifiBlobstore, so keep the certs in Legacy Keystore
Log.i(TAG, "Avoiding migration since supplicant cannot access WifiBlobstore");
- return;
+ return KEYSTORE_MIGRATION_SUCCESS_MIGRATION_NOT_NEEDED;
}
final long identity = Binder.clearCallingIdentity();
try {
@@ -587,7 +623,7 @@
String[] legacyAliases = legacyKeystore.list("", Process.WIFI_UID);
if (legacyAliases == null || legacyAliases.length == 0) {
Log.i(TAG, "No aliases need to be migrated");
- return;
+ return KEYSTORE_MIGRATION_SUCCESS_MIGRATION_NOT_NEEDED;
}
WifiBlobStore wifiBlobStore = WifiBlobStore.getInstance();
@@ -605,14 +641,17 @@
legacyKeystore.remove(legacyAlias, Process.WIFI_UID);
}
Log.i(TAG, "Successfully migrated aliases from Legacy Keystore");
+ return KEYSTORE_MIGRATION_SUCCESS_MIGRATION_COMPLETE;
} catch (ServiceSpecificException e) {
if (e.errorCode == ILegacyKeystore.ERROR_SYSTEM_ERROR) {
Log.i(TAG, "Legacy Keystore service has been deprecated");
- } else {
- Log.e(TAG, "Encountered an exception while migrating aliases. " + e);
+ return KEYSTORE_MIGRATION_SUCCESS_MIGRATION_NOT_NEEDED;
}
+ Log.e(TAG, "Encountered a ServiceSpecificException while migrating aliases. " + e);
+ return KEYSTORE_MIGRATION_FAILURE_ENCOUNTERED_EXCEPTION;
} catch (Exception e) {
Log.e(TAG, "Encountered an exception while migrating aliases. " + e);
+ return KEYSTORE_MIGRATION_FAILURE_ENCOUNTERED_EXCEPTION;
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
index f68ae2c..fc4a909 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
@@ -173,6 +173,10 @@
}
}
}
+
+ Executor getExecutor() {
+ return mExecutor;
+ }
}
private ISharedConnectivityService mService;
@@ -188,7 +192,7 @@
private final String mServicePackageName;
private final String mIntentAction;
private ServiceConnection mServiceConnection;
- private UserManager mUserManager;
+ private final UserManager mUserManager;
/**
* Creates a new instance of {@link SharedConnectivityManager}.
@@ -316,15 +320,19 @@
private void registerCallbackInternal(SharedConnectivityClientCallback callback,
SharedConnectivityCallbackProxy proxy) {
- try {
- mService.registerCallback(proxy);
- synchronized (mProxyDataLock) {
- mProxyMap.put(callback, proxy);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Exception in registerCallback", e);
- callback.onRegisterCallbackFailed(e);
- }
+ proxy.getExecutor().execute(
+ () -> {
+ try {
+ mService.registerCallback(proxy);
+ synchronized (mProxyDataLock) {
+ mProxyMap.put(callback, proxy);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception in registerCallback", e);
+ callback.onRegisterCallbackFailed(e);
+ }
+ }
+ );
}
/**
diff --git a/wifi/tests/src/android/net/wifi/WifiMigrationTest.java b/wifi/tests/src/android/net/wifi/WifiMigrationTest.java
index d95069d..0aa299f 100644
--- a/wifi/tests/src/android/net/wifi/WifiMigrationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiMigrationTest.java
@@ -16,6 +16,7 @@
package android.net.wifi;
+import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
@@ -27,6 +28,8 @@
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.withSettings;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
import android.security.legacykeystore.ILegacyKeystore;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
@@ -77,7 +80,8 @@
@Test
public void testKeystoreMigrationAvoidedOnLegacyVendorPartition() {
when(WifiBlobStore.supplicantCanAccessBlobstore()).thenReturn(false);
- WifiMigration.migrateLegacyKeystoreToWifiBlobstore();
+ assertEquals(WifiMigration.KEYSTORE_MIGRATION_SUCCESS_MIGRATION_NOT_NEEDED,
+ WifiMigration.migrateLegacyKeystoreToWifiBlobstore());
verifyNoMoreInteractions(mLegacyKeystore, mWifiBlobStore);
}
@@ -88,7 +92,8 @@
@Test
public void testKeystoreMigrationNoLegacyAliases() throws Exception {
when(mLegacyKeystore.list(anyString(), anyInt())).thenReturn(new String[0]);
- WifiMigration.migrateLegacyKeystoreToWifiBlobstore();
+ assertEquals(WifiMigration.KEYSTORE_MIGRATION_SUCCESS_MIGRATION_NOT_NEEDED,
+ WifiMigration.migrateLegacyKeystoreToWifiBlobstore());
verify(mLegacyKeystore).list(anyString(), anyInt());
verifyNoMoreInteractions(mLegacyKeystore, mWifiBlobStore);
}
@@ -104,7 +109,8 @@
when(mLegacyKeystore.list(anyString(), anyInt())).thenReturn(legacyAliases);
when(mWifiBlobStore.list(anyString())).thenReturn(blobstoreAliases);
- WifiMigration.migrateLegacyKeystoreToWifiBlobstore();
+ assertEquals(WifiMigration.KEYSTORE_MIGRATION_SUCCESS_MIGRATION_COMPLETE,
+ WifiMigration.migrateLegacyKeystoreToWifiBlobstore());
verify(mWifiBlobStore, times(legacyAliases.length)).put(anyString(), any(byte[].class));
}
@@ -122,9 +128,35 @@
when(mWifiBlobStore.list(anyString())).thenReturn(blobstoreAliases);
// Expect that only the unique legacy alias is migrated to the blobstore
- WifiMigration.migrateLegacyKeystoreToWifiBlobstore();
+ assertEquals(WifiMigration.KEYSTORE_MIGRATION_SUCCESS_MIGRATION_COMPLETE,
+ WifiMigration.migrateLegacyKeystoreToWifiBlobstore());
verify(mWifiBlobStore).list(anyString());
verify(mWifiBlobStore).put(eq(uniqueLegacyAlias), any(byte[].class));
verifyNoMoreInteractions(mWifiBlobStore);
}
+
+ /**
+ * Verify that the Keystore migration is skipped if Legacy Keystore is deprecated,
+ * since the migration is not needed.
+ */
+ @Test
+ public void testKeystoreMigrationAvoidedIfLegacyKsDeprecated() throws Exception {
+ // Legacy Keystore will throw a ServiceSpecificException with
+ // code ERROR_SYSTEM_ERROR if a method is deprecated
+ when(mLegacyKeystore.list(anyString(), anyInt())).thenThrow(
+ new ServiceSpecificException(ILegacyKeystore.ERROR_SYSTEM_ERROR));
+ assertEquals(WifiMigration.KEYSTORE_MIGRATION_SUCCESS_MIGRATION_NOT_NEEDED,
+ WifiMigration.migrateLegacyKeystoreToWifiBlobstore());
+ }
+
+ /**
+ * Verify that the Keystore migration method returns a failure code when an
+ * unexpected exception is encountered.
+ */
+ @Test
+ public void testKeystoreMigrationFailsIfExceptionEncountered() throws Exception {
+ when(mLegacyKeystore.list(anyString(), anyInt())).thenThrow(new RemoteException());
+ assertEquals(WifiMigration.KEYSTORE_MIGRATION_FAILURE_ENCOUNTERED_EXCEPTION,
+ WifiMigration.migrateLegacyKeystoreToWifiBlobstore());
+ }
}