Merge "[Audiosharing] Fix hysteresis mode" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index ad84900..6393fdb 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -72,6 +72,7 @@
"android.service.dreams.flags-aconfig-java",
"android.service.notification.flags-aconfig-java",
"android.service.appprediction.flags-aconfig-java",
+ "android.service.quickaccesswallet.flags-aconfig-java",
"android.service.voice.flags-aconfig-java",
"android.speech.flags-aconfig-java",
"android.systemserver.flags-aconfig-java",
@@ -111,6 +112,7 @@
"framework_graphics_flags_java_lib",
"hwui_flags_java_lib",
"interaction_jank_monitor_flags_lib",
+ "keystore2_flags_java-framework",
"libcore_exported_aconfig_flags_lib",
"libcore_readonly_aconfig_flags_lib",
"libgui_flags_java_lib",
@@ -1774,3 +1776,18 @@
aconfig_declarations: "aconfig_settingslib_flags",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+
+// Quick Access Wallet
+aconfig_declarations {
+ name: "android.service.quickaccesswallet.flags-aconfig",
+ package: "android.service.quickaccesswallet",
+ exportable: true,
+ container: "system",
+ srcs: ["core/java/android/service/quickaccesswallet/flags.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.service.quickaccesswallet.flags-aconfig-java",
+ aconfig_declarations: "android.service.quickaccesswallet.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 14e2387..b43905b 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -451,7 +451,7 @@
auto token = SurfaceComposerClient::getPhysicalDisplayToken(
event.header.displayId);
- auto firstDisplay = mBootAnimation->mDisplays.front();
+ auto& firstDisplay = mBootAnimation->mDisplays.front();
if (token != firstDisplay.displayToken) {
// ignore hotplug of a secondary display
continue;
diff --git a/core/api/current.txt b/core/api/current.txt
index ea6d1a5..dd0c1d0 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8075,8 +8075,10 @@
method @NonNull @WorkerThread public android.os.Bundle getApplicationRestrictions(@Nullable android.content.ComponentName, String);
method @Deprecated @Nullable public String getApplicationRestrictionsManagingPackage(@NonNull android.content.ComponentName);
method @RequiresPermission(anyOf={android.Manifest.permission.SET_TIME, "android.permission.QUERY_ADMIN_POLICY"}, conditional=true) public boolean getAutoTimeEnabled(@Nullable android.content.ComponentName);
+ method @FlaggedApi("android.app.admin.flags.set_auto_time_enabled_coexistence") @RequiresPermission(anyOf={android.Manifest.permission.SET_TIME, "android.permission.QUERY_ADMIN_POLICY"}, conditional=true) public int getAutoTimePolicy();
method @Deprecated public boolean getAutoTimeRequired();
method @RequiresPermission(anyOf={android.Manifest.permission.SET_TIME_ZONE, "android.permission.QUERY_ADMIN_POLICY"}, conditional=true) public boolean getAutoTimeZoneEnabled(@Nullable android.content.ComponentName);
+ method @FlaggedApi("android.app.admin.flags.set_auto_time_zone_enabled_coexistence") @RequiresPermission(anyOf={android.Manifest.permission.SET_TIME_ZONE, "android.permission.QUERY_ADMIN_POLICY"}, conditional=true) public int getAutoTimeZonePolicy();
method @NonNull public java.util.List<android.os.UserHandle> getBindDeviceAdminTargetUsers(@NonNull android.content.ComponentName);
method public boolean getBluetoothContactSharingDisabled(@NonNull android.content.ComponentName);
method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_CAMERA, conditional=true) public boolean getCameraDisabled(@Nullable android.content.ComponentName);
@@ -8232,8 +8234,10 @@
method @WorkerThread public void setApplicationRestrictions(@Nullable android.content.ComponentName, String, android.os.Bundle);
method @Deprecated public void setApplicationRestrictionsManagingPackage(@NonNull android.content.ComponentName, @Nullable String) throws android.content.pm.PackageManager.NameNotFoundException;
method @RequiresPermission(value=android.Manifest.permission.SET_TIME, conditional=true) public void setAutoTimeEnabled(@Nullable android.content.ComponentName, boolean);
+ method @FlaggedApi("android.app.admin.flags.set_auto_time_enabled_coexistence") @RequiresPermission(value=android.Manifest.permission.SET_TIME, conditional=true) public void setAutoTimePolicy(int);
method @Deprecated public void setAutoTimeRequired(@NonNull android.content.ComponentName, boolean);
method @RequiresPermission(value=android.Manifest.permission.SET_TIME_ZONE, conditional=true) public void setAutoTimeZoneEnabled(@Nullable android.content.ComponentName, boolean);
+ method @FlaggedApi("android.app.admin.flags.set_auto_time_zone_enabled_coexistence") @RequiresPermission(value=android.Manifest.permission.SET_TIME_ZONE, conditional=true) public void setAutoTimeZonePolicy(int);
method public void setBackupServiceEnabled(@NonNull android.content.ComponentName, boolean);
method public void setBluetoothContactSharingDisabled(@NonNull android.content.ComponentName, boolean);
method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_CAMERA, conditional=true) public void setCameraDisabled(@Nullable android.content.ComponentName, boolean);
@@ -8352,6 +8356,12 @@
field public static final String ACTION_SET_NEW_PASSWORD = "android.app.action.SET_NEW_PASSWORD";
field public static final String ACTION_START_ENCRYPTION = "android.app.action.START_ENCRYPTION";
field public static final String ACTION_SYSTEM_UPDATE_POLICY_CHANGED = "android.app.action.SYSTEM_UPDATE_POLICY_CHANGED";
+ field @FlaggedApi("android.app.admin.flags.set_auto_time_enabled_coexistence") public static final int AUTO_TIME_DISABLED = 1; // 0x1
+ field @FlaggedApi("android.app.admin.flags.set_auto_time_enabled_coexistence") public static final int AUTO_TIME_ENABLED = 2; // 0x2
+ field @FlaggedApi("android.app.admin.flags.set_auto_time_enabled_coexistence") public static final int AUTO_TIME_NOT_CONTROLLED_BY_POLICY = 0; // 0x0
+ field @FlaggedApi("android.app.admin.flags.set_auto_time_zone_enabled_coexistence") public static final int AUTO_TIME_ZONE_DISABLED = 1; // 0x1
+ field @FlaggedApi("android.app.admin.flags.set_auto_time_zone_enabled_coexistence") public static final int AUTO_TIME_ZONE_ENABLED = 2; // 0x2
+ field @FlaggedApi("android.app.admin.flags.set_auto_time_zone_enabled_coexistence") public static final int AUTO_TIME_ZONE_NOT_CONTROLLED_BY_POLICY = 0; // 0x0
field @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") public static final int CONTENT_PROTECTION_DISABLED = 1; // 0x1
field @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") public static final int CONTENT_PROTECTION_ENABLED = 2; // 0x2
field @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") public static final int CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY = 0; // 0x0
@@ -37819,6 +37829,7 @@
field public static final String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS";
field public static final String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
field @Deprecated public static final String ACTION_FINGERPRINT_ENROLL = "android.settings.FINGERPRINT_ENROLL";
+ field @FlaggedApi("android.provider.system_regional_preferences_api_enabled") public static final String ACTION_FIRST_DAY_OF_WEEK_SETTINGS = "android.settings.FIRST_DAY_OF_WEEK_SETTINGS";
field public static final String ACTION_HARD_KEYBOARD_SETTINGS = "android.settings.HARD_KEYBOARD_SETTINGS";
field public static final String ACTION_HOME_SETTINGS = "android.settings.HOME_SETTINGS";
field public static final String ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS = "android.settings.IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS";
@@ -37839,6 +37850,7 @@
field public static final String ACTION_MANAGE_SUPERVISOR_RESTRICTED_SETTING = "android.settings.MANAGE_SUPERVISOR_RESTRICTED_SETTING";
field public static final String ACTION_MANAGE_UNKNOWN_APP_SOURCES = "android.settings.MANAGE_UNKNOWN_APP_SOURCES";
field public static final String ACTION_MANAGE_WRITE_SETTINGS = "android.settings.action.MANAGE_WRITE_SETTINGS";
+ field @FlaggedApi("android.provider.system_regional_preferences_api_enabled") public static final String ACTION_MEASUREMENT_SYSTEM_SETTINGS = "android.settings.MEASUREMENT_SYSTEM_SETTINGS";
field public static final String ACTION_MEMORY_CARD_SETTINGS = "android.settings.MEMORY_CARD_SETTINGS";
field public static final String ACTION_NETWORK_OPERATOR_SETTINGS = "android.settings.NETWORK_OPERATOR_SETTINGS";
field public static final String ACTION_NFCSHARING_SETTINGS = "android.settings.NFCSHARING_SETTINGS";
@@ -37849,12 +37861,14 @@
field public static final String ACTION_NOTIFICATION_LISTENER_DETAIL_SETTINGS = "android.settings.NOTIFICATION_LISTENER_DETAIL_SETTINGS";
field public static final String ACTION_NOTIFICATION_LISTENER_SETTINGS = "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS";
field public static final String ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS = "android.settings.NOTIFICATION_POLICY_ACCESS_SETTINGS";
+ field @FlaggedApi("android.provider.system_regional_preferences_api_enabled") public static final String ACTION_NUMBERING_SYSTEM_SETTINGS = "android.settings.NUMBERING_SYSTEM_SETTINGS";
field public static final String ACTION_PRINT_SETTINGS = "android.settings.ACTION_PRINT_SETTINGS";
field public static final String ACTION_PRIVACY_SETTINGS = "android.settings.PRIVACY_SETTINGS";
field public static final String ACTION_PROCESS_WIFI_EASY_CONNECT_URI = "android.settings.PROCESS_WIFI_EASY_CONNECT_URI";
field public static final String ACTION_QUICK_ACCESS_WALLET_SETTINGS = "android.settings.QUICK_ACCESS_WALLET_SETTINGS";
field public static final String ACTION_QUICK_LAUNCH_SETTINGS = "android.settings.QUICK_LAUNCH_SETTINGS";
field public static final String ACTION_REGIONAL_PREFERENCES_SETTINGS = "android.settings.REGIONAL_PREFERENCES_SETTINGS";
+ field @FlaggedApi("android.provider.system_regional_preferences_api_enabled") public static final String ACTION_REGION_SETTINGS = "android.settings.REGION_SETTINGS";
field public static final String ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
field public static final String ACTION_REQUEST_MANAGE_MEDIA = "android.settings.REQUEST_MANAGE_MEDIA";
field @FlaggedApi("com.android.media.flags.enable_privileged_routing_for_media_routing_control") public static final String ACTION_REQUEST_MEDIA_ROUTING_CONTROL = "android.settings.REQUEST_MEDIA_ROUTING_CONTROL";
@@ -37870,6 +37884,7 @@
field public static final String ACTION_SOUND_SETTINGS = "android.settings.SOUND_SETTINGS";
field @Deprecated public static final String ACTION_STORAGE_VOLUME_ACCESS_SETTINGS = "android.settings.STORAGE_VOLUME_ACCESS_SETTINGS";
field public static final String ACTION_SYNC_SETTINGS = "android.settings.SYNC_SETTINGS";
+ field @FlaggedApi("android.provider.system_regional_preferences_api_enabled") public static final String ACTION_TEMPERATURE_UNIT_SETTINGS = "android.settings.TEMPERATURE_UNIT_SETTINGS";
field public static final String ACTION_USAGE_ACCESS_SETTINGS = "android.settings.USAGE_ACCESS_SETTINGS";
field public static final String ACTION_USER_DICTIONARY_SETTINGS = "android.settings.USER_DICTIONARY_SETTINGS";
field public static final String ACTION_VOICE_CONTROL_AIRPLANE_MODE = "android.settings.VOICE_CONTROL_AIRPLANE_MODE";
@@ -40697,7 +40712,7 @@
field @NonNull public static final android.os.Parcelable.Creator<android.service.autofill.FillRequest> CREATOR;
field public static final int FLAG_COMPATIBILITY_MODE_REQUEST = 2; // 0x2
field public static final int FLAG_MANUAL_REQUEST = 1; // 0x1
- field public static final int FLAG_SUPPORTS_FILL_DIALOG = 64; // 0x40
+ field @Deprecated @FlaggedApi("android.service.autofill.fill_dialog_improvements") public static final int FLAG_SUPPORTS_FILL_DIALOG = 64; // 0x40
}
public final class FillResponse implements android.os.Parcelable {
@@ -51391,6 +51406,7 @@
method @Nullable public android.view.SurfaceControl.Transaction buildReparentTransaction(@NonNull android.view.SurfaceControl);
method public default int getBufferTransformHint();
method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") @NonNull public default android.window.InputTransferToken getInputTransferToken();
+ method @FlaggedApi("com.android.window.flags.jank_api") @NonNull public default android.view.SurfaceControl.OnJankDataListenerRegistration registerOnJankDataListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.SurfaceControl.OnJankDataListener);
method public default void removeOnBufferTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnBufferTransformHintChangedListener);
method public default void setChildBoundingInsets(@NonNull android.graphics.Rect);
method public default void setTouchableRegion(@Nullable android.graphics.Region);
@@ -51648,6 +51664,7 @@
field public static final int DEADLINE = 13; // 0xd
field public static final int DRAW_DURATION = 4; // 0x4
field public static final int FIRST_DRAW_FRAME = 9; // 0x9
+ field @FlaggedApi("com.android.window.flags.jank_api") public static final int FRAME_TIMELINE_VSYNC_ID = 14; // 0xe
field public static final int GPU_DURATION = 12; // 0xc
field public static final int INPUT_HANDLING_DURATION = 1; // 0x1
field public static final int INTENDED_VSYNC_TIMESTAMP = 10; // 0xa
@@ -53085,6 +53102,26 @@
method @NonNull public android.view.SurfaceControl.Builder setParent(@Nullable android.view.SurfaceControl);
}
+ @FlaggedApi("com.android.window.flags.jank_api") public static class SurfaceControl.JankData {
+ method public long getActualAppFrameTimeNanos();
+ method public int getJankType();
+ method public long getScheduledAppFrameTimeNanos();
+ method public long getVsyncId();
+ field public static final int JANK_APPLICATION = 2; // 0x2
+ field public static final int JANK_COMPOSER = 1; // 0x1
+ field public static final int JANK_NONE = 0; // 0x0
+ field public static final int JANK_OTHER = 4; // 0x4
+ }
+
+ @FlaggedApi("com.android.window.flags.jank_api") public static interface SurfaceControl.OnJankDataListener {
+ method public void onJankDataAvailable(@NonNull java.util.List<android.view.SurfaceControl.JankData>);
+ }
+
+ @FlaggedApi("com.android.window.flags.jank_api") public static class SurfaceControl.OnJankDataListenerRegistration {
+ method public void flush();
+ method public void removeAfter(long);
+ }
+
public static class SurfaceControl.Transaction implements java.io.Closeable android.os.Parcelable {
ctor public SurfaceControl.Transaction();
method @NonNull public android.view.SurfaceControl.Transaction addTransactionCommittedListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.SurfaceControl.TransactionCommittedListener);
@@ -54043,6 +54080,7 @@
field @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") public static final int DRAG_FLAG_GLOBAL_SAME_APPLICATION = 4096; // 0x1000
field public static final int DRAG_FLAG_GLOBAL_URI_READ = 1; // 0x1
field public static final int DRAG_FLAG_GLOBAL_URI_WRITE = 2; // 0x2
+ field @FlaggedApi("com.android.window.flags.supports_drag_assistant_to_multiwindow") public static final int DRAG_FLAG_HIDE_CALLING_TASK_ON_DRAG_START = 16384; // 0x4000
field public static final int DRAG_FLAG_OPAQUE = 512; // 0x200
field @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") public static final int DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG = 8192; // 0x2000
field @Deprecated public static final int DRAWING_CACHE_QUALITY_AUTO = 0; // 0x0
@@ -56537,13 +56575,13 @@
method public void notifyViewExited(@NonNull android.view.View, int);
method public void notifyViewVisibilityChanged(@NonNull android.view.View, boolean);
method public void notifyViewVisibilityChanged(@NonNull android.view.View, int, boolean);
- method public void notifyVirtualViewsReady(@NonNull android.view.View, @NonNull android.util.SparseArray<android.view.autofill.VirtualViewFillInfo>);
+ method @Deprecated @FlaggedApi("android.service.autofill.fill_dialog_improvements") public void notifyVirtualViewsReady(@NonNull android.view.View, @NonNull android.util.SparseArray<android.view.autofill.VirtualViewFillInfo>);
method public void registerCallback(@Nullable android.view.autofill.AutofillManager.AutofillCallback);
method public void requestAutofill(@NonNull android.view.View);
method public void requestAutofill(@NonNull android.view.View, int, @NonNull android.graphics.Rect);
method public void setUserData(@Nullable android.service.autofill.UserData);
- method public boolean showAutofillDialog(@NonNull android.view.View);
- method public boolean showAutofillDialog(@NonNull android.view.View, int);
+ method @Deprecated @FlaggedApi("android.service.autofill.fill_dialog_improvements") public boolean showAutofillDialog(@NonNull android.view.View);
+ method @Deprecated @FlaggedApi("android.service.autofill.fill_dialog_improvements") public boolean showAutofillDialog(@NonNull android.view.View, int);
method public void unregisterCallback(@Nullable android.view.autofill.AutofillManager.AutofillCallback);
field public static final String EXTRA_ASSIST_STRUCTURE = "android.view.autofill.extra.ASSIST_STRUCTURE";
field public static final String EXTRA_AUTHENTICATION_RESULT = "android.view.autofill.extra.AUTHENTICATION_RESULT";
@@ -56896,6 +56934,7 @@
ctor public EditorInfo();
method public int describeContents();
method public void dump(android.util.Printer, String);
+ method @FlaggedApi("android.view.inputmethod.public_autofill_id_in_editorinfo") @Nullable public android.view.autofill.AutofillId getAutofillId();
method @Nullable public CharSequence getInitialSelectedText(int);
method @Nullable public android.view.inputmethod.SurroundingText getInitialSurroundingText(@IntRange(from=0) int, @IntRange(from=0) int, int);
method @Nullable public CharSequence getInitialTextAfterCursor(@IntRange(from=0) int, int);
@@ -56905,6 +56944,7 @@
method @NonNull public java.util.List<java.lang.Class<? extends android.view.inputmethod.HandwritingGesture>> getSupportedHandwritingGestures();
method @FlaggedApi("android.view.inputmethod.editorinfo_handwriting_enabled") public boolean isStylusHandwritingEnabled();
method public final void makeCompatible(int);
+ method @FlaggedApi("android.view.inputmethod.public_autofill_id_in_editorinfo") public void setAutofillId(@Nullable android.view.autofill.AutofillId);
method public void setInitialSurroundingSubText(@NonNull CharSequence, int);
method public void setInitialSurroundingText(@NonNull CharSequence);
method public void setInitialToolType(int);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 0e521c6..ed95fdd 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -709,8 +709,8 @@
field public static final String OPSTR_READ_MEDIA_IMAGES = "android:read_media_images";
field public static final String OPSTR_READ_MEDIA_VIDEO = "android:read_media_video";
field public static final String OPSTR_READ_MEDIA_VISUAL_USER_SELECTED = "android:read_media_visual_user_selected";
- field @FlaggedApi("android.permission.flags.platform_oxygen_saturation_enabled") public static final String OPSTR_READ_OXYGEN_SATURATION = "android:read_oxygen_saturation";
- field @FlaggedApi("android.permission.flags.platform_skin_temperature_enabled") public static final String OPSTR_READ_SKIN_TEMPERATURE = "android:read_skin_temperature";
+ field @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled") public static final String OPSTR_READ_OXYGEN_SATURATION = "android:read_oxygen_saturation";
+ field @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled") public static final String OPSTR_READ_SKIN_TEMPERATURE = "android:read_skin_temperature";
field public static final String OPSTR_READ_WRITE_HEALTH_DATA = "android:read_write_health_data";
field public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO = "android:receive_ambient_trigger_audio";
field public static final String OPSTR_RECEIVE_EMERGENCY_BROADCAST = "android:receive_emergency_broadcast";
@@ -1368,6 +1368,7 @@
method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long, boolean);
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean packageHasActiveAdmins(String);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS, android.Manifest.permission.PROVISION_DEMO_DEVICE}) public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException;
+ method @FlaggedApi("android.app.admin.flags.remove_managed_profile_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean removeManagedProfile();
method @RequiresPermission(android.Manifest.permission.TRIGGER_LOST_MODE) public void sendLostModeLocationUpdate(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException;
method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS) public void setApplicationExemptions(@NonNull String, @NonNull java.util.Set<java.lang.Integer>) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -1377,7 +1378,8 @@
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setDpcDownloaded(boolean);
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setMaxPolicyStorageLimit(int);
method @Deprecated @RequiresPermission(value=android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS, conditional=true) public void setProfileOwnerCanAccessDeviceIds(@NonNull android.content.ComponentName);
- method public void setSecondaryLockscreenEnabled(@NonNull android.content.ComponentName, boolean);
+ method @Deprecated @FlaggedApi("android.app.admin.flags.secondary_lockscreen_api_enabled") public void setSecondaryLockscreenEnabled(@NonNull android.content.ComponentName, boolean);
+ method @FlaggedApi("android.app.admin.flags.secondary_lockscreen_api_enabled") public void setSecondaryLockscreenEnabled(boolean, @Nullable android.os.PersistableBundle);
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setUserProvisioningState(int, @NonNull android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public boolean shouldAllowBypassingDevicePolicyManagementRoleQualification();
field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED";
@@ -8245,6 +8247,26 @@
method public void onSetMain(boolean);
}
+ @FlaggedApi("android.media.tv.flags.tif_extension_standardization") public final class TvInputServiceExtensionManager {
+ method @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE) public int registerExtensionIBinder(@NonNull String, @NonNull android.os.IBinder);
+ field public static final String IBROADCAST_TIME = "android.media.tv.extension.time.BroadcastTime";
+ field public static final String ICAM_APP_INFO_SERVICE = "android.media.tv.extension.cam.ICamAppInfoService";
+ field public static final String ICLIENT_TOKEN = "android.media.tv.extension.clienttoken.IClientToken";
+ field public static final String IDATA_SERVICE_SIGNAL_INFO = "android.media.tv.extension.teletext.IDataServiceSignalInfo";
+ field public static final String IEVENT_MONITOR = "android.media.tv.extension.event.IEventMonitor";
+ field public static final String IHDMI_SIGNAL_INTERFACE = "android.media.tv.extension.signal.IHdmiSignalInterface";
+ field public static final String IOAD_UPDATE_INTERFACE = "android.media.tv.extension.oad.IOadUpdateInterface";
+ field public static final String IRATING_INTERFACE = "android.media.tv.extension.rating.IRatingInterface";
+ field public static final String IRECORDED_CONTENTS = "android.media.tv.extension.pvr.IRecordedContents";
+ field public static final String ISCAN_INTERFACE = "android.media.tv.extension.scan.IScanInterface";
+ field public static final String ISCREEN_MODE_SETTINGS = "android.media.tv.extension.screenmode.IScreenModeSettings";
+ field public static final String ISERVICE_LIST_EDIT_LISTENER = "android.media.tv.extension.servicedb.IServiceListEditListener";
+ field public static final int REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED = 2; // 0x2
+ field public static final int REGISTER_FAIL_NAME_NOT_STANDARDIZED = 1; // 0x1
+ field public static final int REGISTER_FAIL_REMOTE_EXCEPTION = 3; // 0x3
+ field public static final int REGISTER_SUCCESS = 0; // 0x0
+ }
+
public abstract static class TvRecordingClient.RecordingCallback {
method public void onEvent(String, String, android.os.Bundle);
}
@@ -12531,8 +12553,19 @@
}
@FlaggedApi("android.security.aapm_api") public final class AdvancedProtectionManager {
+ method @NonNull public android.content.Intent createSupportIntent(@NonNull String, @Nullable String);
method @NonNull @RequiresPermission(android.Manifest.permission.SET_ADVANCED_PROTECTION_MODE) public java.util.List<android.security.advancedprotection.AdvancedProtectionFeature> getAdvancedProtectionFeatures();
method @RequiresPermission(android.Manifest.permission.SET_ADVANCED_PROTECTION_MODE) public void setAdvancedProtectionEnabled(boolean);
+ field @FlaggedApi("android.security.aapm_api") public static final String ACTION_SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG = "android.security.advancedprotection.action.SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG";
+ field public static final String EXTRA_SUPPORT_DIALOG_FEATURE = "android.security.advancedprotection.extra.SUPPORT_DIALOG_FEATURE";
+ field public static final String EXTRA_SUPPORT_DIALOG_TYPE = "android.security.advancedprotection.extra.SUPPORT_DIALOG_TYPE";
+ field public static final String FEATURE_ID_DISALLOW_CELLULAR_2G = "android.security.advancedprotection.feature_disallow_2g";
+ field public static final String FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES = "android.security.advancedprotection.feature_disallow_install_unknown_sources";
+ field public static final String FEATURE_ID_DISALLOW_USB = "android.security.advancedprotection.feature_disallow_usb";
+ field public static final String FEATURE_ID_DISALLOW_WEP = "android.security.advancedprotection.feature_disallow_wep";
+ field public static final String FEATURE_ID_ENABLE_MTE = "android.security.advancedprotection.feature_enable_mte";
+ field public static final String SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION = "android.security.advancedprotection.type_blocked_interaction";
+ field public static final String SUPPORT_DIALOG_TYPE_DISABLED_SETTING = "android.security.advancedprotection.type_disabled_setting";
}
}
@@ -18794,6 +18827,10 @@
method @Deprecated public void openFileChooser(android.webkit.ValueCallback<android.net.Uri>, String, String);
}
+ public abstract static class WebChromeClient.FileChooserParams {
+ field @FlaggedApi("android.webkit.file_system_access") public static final long ENABLE_FILE_SYSTEM_ACCESS = 364980165L; // 0x15c127c5L
+ }
+
public abstract class WebHistoryItem implements java.lang.Cloneable {
method @Deprecated public abstract int getId();
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index c8ecfa9..1192713 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2065,6 +2065,29 @@
method public boolean isAidlHal();
}
+ public final class MediaCodec {
+ method @FlaggedApi("android.media.codec.codec_availability") @NonNull public static java.util.List<android.media.MediaCodec.GlobalResourceInfo> getGloballyAvailableResources();
+ method @FlaggedApi("android.media.codec.codec_availability") @NonNull public java.util.List<android.media.MediaCodec.InstanceResourceInfo> getRequiredResources();
+ }
+
+ public abstract static class MediaCodec.Callback {
+ method @FlaggedApi("android.media.codec.codec_availability") public void onRequiredResourcesChanged(@NonNull android.media.MediaCodec);
+ }
+
+ @FlaggedApi("android.media.codec.codec_availability") public static final class MediaCodec.GlobalResourceInfo {
+ ctor public MediaCodec.GlobalResourceInfo();
+ method public long getAvailable();
+ method public long getCapacity();
+ method @NonNull public String getName();
+ }
+
+ @FlaggedApi("android.media.codec.codec_availability") public static final class MediaCodec.InstanceResourceInfo {
+ ctor public MediaCodec.InstanceResourceInfo();
+ method @NonNull public String getName();
+ method public long getPerFrameCount();
+ method public long getStaticCount();
+ }
+
public static final class MediaCodecInfo.VideoCapabilities.PerformancePoint {
ctor public MediaCodecInfo.VideoCapabilities.PerformancePoint(int, int, int, int, @NonNull android.util.Size);
ctor public MediaCodecInfo.VideoCapabilities.PerformancePoint(@NonNull android.media.MediaCodecInfo.VideoCapabilities.PerformancePoint, @NonNull android.util.Size);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index b447897..ab75069 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1017,6 +1017,12 @@
public static final int PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL = 1 << 6;
/**
+ * @hide
+ * Process is guaranteed cpu time (IE. it will not be frozen).
+ */
+ public static final int PROCESS_CAPABILITY_CPU_TIME = 1 << 7;
+
+ /**
* @hide all capabilities, the ORing of all flags in {@link ProcessCapability}.
*
* Don't expose it as TestApi -- we may add new capabilities any time, which could
@@ -1028,7 +1034,8 @@
| PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK
| PROCESS_CAPABILITY_BFSL
| PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK
- | PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL;
+ | PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL
+ | PROCESS_CAPABILITY_CPU_TIME;
/**
* All implicit capabilities. This capability set is currently only used for processes under
@@ -1053,6 +1060,7 @@
pw.print((caps & PROCESS_CAPABILITY_BFSL) != 0 ? 'F' : '-');
pw.print((caps & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0 ? 'U' : '-');
pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL) != 0 ? 'A' : '-');
+ pw.print((caps & PROCESS_CAPABILITY_CPU_TIME) != 0 ? 'T' : '-');
}
/** @hide */
@@ -1065,6 +1073,7 @@
sb.append((caps & PROCESS_CAPABILITY_BFSL) != 0 ? 'F' : '-');
sb.append((caps & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0 ? 'U' : '-');
sb.append((caps & PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL) != 0 ? 'A' : '-');
+ sb.append((caps & PROCESS_CAPABILITY_CPU_TIME) != 0 ? 'T' : '-');
}
/**
@@ -5291,7 +5300,6 @@
if (!exported) {
/*
RuntimeException here = new RuntimeException("here");
- here.fillInStackTrace();
Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid,
here);
*/
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 60b8f80..cb7b115 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1961,12 +1961,8 @@
@Override
public void dumpCacheInfo(ParcelFileDescriptor pfd, String[] args) {
- try {
- PropertyInvalidatedCache.dumpCacheInfo(pfd, args);
- BroadcastStickyCache.dump(pfd);
- } finally {
- IoUtils.closeQuietly(pfd);
- }
+ PropertyInvalidatedCache.dumpCacheInfo(pfd, args);
+ IoUtils.closeQuietly(pfd);
}
private File getDatabasesDir(Context context) {
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index fd70f4f..8b37dbd 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2527,12 +2527,12 @@
/** @hide Access to read oxygen saturation. */
@SystemApi
- @FlaggedApi(Flags.FLAG_PLATFORM_OXYGEN_SATURATION_ENABLED)
+ @FlaggedApi(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
public static final String OPSTR_READ_OXYGEN_SATURATION = "android:read_oxygen_saturation";
/** @hide Access to read skin temperature. */
@SystemApi
- @FlaggedApi(Flags.FLAG_PLATFORM_SKIN_TEMPERATURE_ENABLED)
+ @FlaggedApi(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
public static final String OPSTR_READ_SKIN_TEMPERATURE = "android:read_skin_temperature";
/** @hide Access to ranging */
@@ -2616,8 +2616,8 @@
OP_POST_NOTIFICATION,
// Health
Flags.replaceBodySensorPermissionEnabled() ? OP_READ_HEART_RATE : OP_NONE,
- Flags.platformSkinTemperatureEnabled() ? OP_READ_SKIN_TEMPERATURE : OP_NONE,
- Flags.platformOxygenSaturationEnabled() ? OP_READ_OXYGEN_SATURATION : OP_NONE,
+ Flags.replaceBodySensorPermissionEnabled() ? OP_READ_SKIN_TEMPERATURE : OP_NONE,
+ Flags.replaceBodySensorPermissionEnabled() ? OP_READ_OXYGEN_SATURATION : OP_NONE,
};
/**
@@ -3132,7 +3132,7 @@
.setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
new AppOpInfo.Builder(OP_READ_SKIN_TEMPERATURE, OPSTR_READ_SKIN_TEMPERATURE,
"READ_SKIN_TEMPERATURE").setPermission(
- Flags.platformSkinTemperatureEnabled()
+ Flags.replaceBodySensorPermissionEnabled()
? HealthPermissions.READ_SKIN_TEMPERATURE : null)
.setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
new AppOpInfo.Builder(OP_RANGING, OPSTR_RANGING, "RANGING")
@@ -3141,7 +3141,7 @@
.setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
new AppOpInfo.Builder(OP_READ_OXYGEN_SATURATION, OPSTR_READ_OXYGEN_SATURATION,
"READ_OXYGEN_SATURATION").setPermission(
- Flags.platformOxygenSaturationEnabled()
+ Flags.replaceBodySensorPermissionEnabled()
? HealthPermissions.READ_OXYGEN_SATURATION : null)
.setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
};
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index fb5a12b..e2479169 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1835,7 +1835,6 @@
if (false) {
RuntimeException e = new RuntimeException("here");
- e.fillInStackTrace();
Log.w(TAG, "Getting drawable 0x" + Integer.toHexString(resId)
+ " from package " + packageName
+ ": app scale=" + r.getCompatibilityInfo().applicationScale
diff --git a/core/java/android/app/BroadcastStickyCache.java b/core/java/android/app/BroadcastStickyCache.java
deleted file mode 100644
index ea81731..0000000
--- a/core/java/android/app/BroadcastStickyCache.java
+++ /dev/null
@@ -1,269 +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.app;
-
-import android.annotation.IntRange;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.hardware.usb.UsbManager;
-import android.media.AudioManager;
-import android.net.ConnectivityManager;
-import android.net.TetheringManager;
-import android.net.nsd.NsdManager;
-import android.net.wifi.WifiManager;
-import android.net.wifi.p2p.WifiP2pManager;
-import android.os.ParcelFileDescriptor;
-import android.os.SystemProperties;
-import android.os.UpdateLock;
-import android.telephony.TelephonyManager;
-import android.util.ArrayMap;
-import android.util.IndentingPrintWriter;
-import android.view.WindowManagerPolicyConstants;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.FastPrintWriter;
-
-import java.io.FileOutputStream;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
-/** @hide */
-public class BroadcastStickyCache {
-
- private static final String[] CACHED_BROADCAST_ACTIONS = {
- AudioManager.ACTION_HDMI_AUDIO_PLUG,
- AudioManager.ACTION_HEADSET_PLUG,
- AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED,
- AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED,
- AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
- AudioManager.RINGER_MODE_CHANGED_ACTION,
- ConnectivityManager.CONNECTIVITY_ACTION,
- Intent.ACTION_BATTERY_CHANGED,
- Intent.ACTION_DEVICE_STORAGE_FULL,
- Intent.ACTION_DEVICE_STORAGE_LOW,
- Intent.ACTION_SIM_STATE_CHANGED,
- NsdManager.ACTION_NSD_STATE_CHANGED,
- TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED,
- TetheringManager.ACTION_TETHER_STATE_CHANGED,
- UpdateLock.UPDATE_LOCK_CHANGED,
- UsbManager.ACTION_USB_STATE,
- WifiManager.ACTION_WIFI_SCAN_AVAILABILITY_CHANGED,
- WifiManager.NETWORK_STATE_CHANGED_ACTION,
- WifiManager.SUPPLICANT_STATE_CHANGED_ACTION,
- WifiManager.WIFI_STATE_CHANGED_ACTION,
- WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION,
- WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED,
- "android.net.conn.INET_CONDITION_ACTION" // ConnectivityManager.INET_CONDITION_ACTION
- };
-
- @GuardedBy("sCachedStickyBroadcasts")
- private static final ArrayList<CachedStickyBroadcast> sCachedStickyBroadcasts =
- new ArrayList<>();
-
- @GuardedBy("sCachedPropertyHandles")
- private static final ArrayMap<String, SystemProperties.Handle> sCachedPropertyHandles =
- new ArrayMap<>();
-
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- public static boolean useCache(@Nullable IntentFilter filter) {
- if (!shouldCache(filter)) {
- return false;
- }
- synchronized (sCachedStickyBroadcasts) {
- final CachedStickyBroadcast cachedStickyBroadcast = getValueUncheckedLocked(filter);
- if (cachedStickyBroadcast == null) {
- return false;
- }
- final long version = cachedStickyBroadcast.propertyHandle.getLong(-1 /* def */);
- return version > 0 && cachedStickyBroadcast.version == version;
- }
- }
-
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- public static void add(@Nullable IntentFilter filter, @Nullable Intent intent) {
- if (!shouldCache(filter)) {
- return;
- }
- synchronized (sCachedStickyBroadcasts) {
- CachedStickyBroadcast cachedStickyBroadcast = getValueUncheckedLocked(filter);
- if (cachedStickyBroadcast == null) {
- final String key = getKey(filter.getAction(0));
- final SystemProperties.Handle handle = SystemProperties.find(key);
- final long version = handle == null ? -1 : handle.getLong(-1 /* def */);
- if (version == -1) {
- return;
- }
- cachedStickyBroadcast = new CachedStickyBroadcast(filter, handle);
- sCachedStickyBroadcasts.add(cachedStickyBroadcast);
- cachedStickyBroadcast.intent = intent;
- cachedStickyBroadcast.version = version;
- } else {
- cachedStickyBroadcast.intent = intent;
- cachedStickyBroadcast.version = cachedStickyBroadcast.propertyHandle
- .getLong(-1 /* def */);
- }
- }
- }
-
- private static boolean shouldCache(@Nullable IntentFilter filter) {
- if (!Flags.useStickyBcastCache()) {
- return false;
- }
- if (filter == null || filter.safeCountActions() != 1) {
- return false;
- }
- if (!ArrayUtils.contains(CACHED_BROADCAST_ACTIONS, filter.getAction(0))) {
- return false;
- }
- return true;
- }
-
- @VisibleForTesting
- @NonNull
- public static String getKey(@NonNull String action) {
- return "cache_key.system_server.sticky_bcast." + action;
- }
-
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- @Nullable
- public static Intent getIntentUnchecked(@NonNull IntentFilter filter) {
- synchronized (sCachedStickyBroadcasts) {
- final CachedStickyBroadcast cachedStickyBroadcast = getValueUncheckedLocked(filter);
- return cachedStickyBroadcast.intent;
- }
- }
-
- @GuardedBy("sCachedStickyBroadcasts")
- @Nullable
- private static CachedStickyBroadcast getValueUncheckedLocked(@NonNull IntentFilter filter) {
- for (int i = sCachedStickyBroadcasts.size() - 1; i >= 0; --i) {
- final CachedStickyBroadcast cachedStickyBroadcast = sCachedStickyBroadcasts.get(i);
- if (IntentFilter.filterEquals(filter, cachedStickyBroadcast.filter)) {
- return cachedStickyBroadcast;
- }
- }
- return null;
- }
-
- public static void incrementVersion(@NonNull String action) {
- if (!shouldIncrementVersion(action)) {
- return;
- }
- final String key = getKey(action);
- synchronized (sCachedPropertyHandles) {
- SystemProperties.Handle handle = sCachedPropertyHandles.get(key);
- final long version;
- if (handle == null) {
- handle = SystemProperties.find(key);
- if (handle != null) {
- sCachedPropertyHandles.put(key, handle);
- }
- }
- version = handle == null ? 0 : handle.getLong(0 /* def */);
- SystemProperties.set(key, String.valueOf(version + 1));
- if (handle == null) {
- sCachedPropertyHandles.put(key, SystemProperties.find(key));
- }
- }
- }
-
- public static void incrementVersionIfExists(@NonNull String action) {
- if (!shouldIncrementVersion(action)) {
- return;
- }
- final String key = getKey(action);
- synchronized (sCachedPropertyHandles) {
- final SystemProperties.Handle handle = sCachedPropertyHandles.get(key);
- if (handle == null) {
- return;
- }
- final long version = handle.getLong(0 /* def */);
- SystemProperties.set(key, String.valueOf(version + 1));
- }
- }
-
- private static boolean shouldIncrementVersion(@NonNull String action) {
- if (!Flags.useStickyBcastCache()) {
- return false;
- }
- if (!ArrayUtils.contains(CACHED_BROADCAST_ACTIONS, action)) {
- return false;
- }
- return true;
- }
-
- @VisibleForTesting
- public static void clearForTest() {
- synchronized (sCachedStickyBroadcasts) {
- sCachedStickyBroadcasts.clear();
- }
- synchronized (sCachedPropertyHandles) {
- sCachedPropertyHandles.clear();
- }
- }
-
- public static void dump(@NonNull ParcelFileDescriptor pfd) {
- if (!Flags.useStickyBcastCache()) {
- return;
- }
- final PrintWriter pw = new FastPrintWriter(new FileOutputStream(pfd.getFileDescriptor()));
- synchronized (sCachedStickyBroadcasts) {
- dumpLocked(pw);
- }
- pw.flush();
- }
-
- @GuardedBy("sCachedStickyBroadcasts")
- private static void dumpLocked(@NonNull PrintWriter pw) {
- final IndentingPrintWriter ipw = new IndentingPrintWriter(
- pw, " " /* singleIndent */, " " /* prefix */);
- ipw.println("Cached sticky broadcasts:");
- ipw.increaseIndent();
- final int count = sCachedStickyBroadcasts.size();
- if (count == 0) {
- ipw.println("<empty>");
- } else {
- for (int i = 0; i < count; ++i) {
- final CachedStickyBroadcast cachedStickyBroadcast = sCachedStickyBroadcasts.get(i);
- ipw.print("Entry #"); ipw.print(i); ipw.println(":");
- ipw.increaseIndent();
- ipw.print("filter="); ipw.println(cachedStickyBroadcast.filter.toLongString());
- ipw.print("intent="); ipw.println(cachedStickyBroadcast.intent);
- ipw.print("version="); ipw.println(cachedStickyBroadcast.version);
- ipw.print("handle="); ipw.println(cachedStickyBroadcast.propertyHandle);
- ipw.decreaseIndent();
- }
- }
- ipw.decreaseIndent();
- }
-
- private static final class CachedStickyBroadcast {
- @NonNull public final IntentFilter filter;
- @Nullable public Intent intent;
- @IntRange(from = 0) public long version;
- @NonNull public final SystemProperties.Handle propertyHandle;
-
- CachedStickyBroadcast(@NonNull IntentFilter filter,
- @NonNull SystemProperties.Handle propertyHandle) {
- this.filter = filter;
- this.propertyHandle = propertyHandle;
- }
- }
-}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 3ae60d71..cd56957 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1922,19 +1922,10 @@
}
}
try {
- final Intent intent;
- if (receiver == null && BroadcastStickyCache.useCache(filter)) {
- intent = BroadcastStickyCache.getIntentUnchecked(filter);
- } else {
- intent = ActivityManager.getService().registerReceiverWithFeature(
- mMainThread.getApplicationThread(), mBasePackageName, getAttributionTag(),
- AppOpsManager.toReceiverId(receiver), rd, filter, broadcastPermission,
- userId,
- flags);
- if (receiver == null) {
- BroadcastStickyCache.add(filter, intent);
- }
- }
+ final Intent intent = ActivityManager.getService().registerReceiverWithFeature(
+ mMainThread.getApplicationThread(), mBasePackageName, getAttributionTag(),
+ AppOpsManager.toReceiverId(receiver), rd, filter, broadcastPermission, userId,
+ flags);
if (intent != null) {
intent.setExtrasClassLoader(getClassLoader());
// TODO: determine at registration time if caller is
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 0654ac2..9bb16ae 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -124,7 +124,7 @@
boolean onlyHasDefaultChannel(String pkg, int uid);
boolean areChannelsBypassingDnd();
ParceledListSlice getNotificationChannelsBypassingDnd(String pkg, int uid);
- List<String> getPackagesBypassingDnd(int userId, boolean includeConversationChannels);
+ ParceledListSlice getPackagesBypassingDnd(int userId);
boolean isPackagePaused(String pkg);
void deleteNotificationHistoryItem(String pkg, int uid, long postedTime);
boolean isPermissionFixed(String pkg, int userId);
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index b8233bc..3d85ea6 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -1660,7 +1660,6 @@
}
RuntimeException ex = new IllegalArgumentException(
"Originally unregistered here:");
- ex.fillInStackTrace();
rd.setUnregisterLocation(ex);
holder.put(r, rd);
}
@@ -1860,7 +1859,6 @@
mInstrumentation = instrumentation;
mRegistered = registered;
mLocation = new IntentReceiverLeaked(null);
- mLocation.fillInStackTrace();
}
void validate(Context context, Handler activityThread) {
@@ -2000,7 +1998,6 @@
}
RuntimeException ex = new IllegalArgumentException(
"Originally unbound here:");
- ex.fillInStackTrace();
sd.setUnbindLocation(ex);
holder.put(c, sd);
}
@@ -2076,7 +2073,6 @@
mActivityThread = activityThread;
mActivityExecutor = null;
mLocation = new ServiceConnectionLeaked(null);
- mLocation.fillInStackTrace();
mFlags = flags;
}
@@ -2088,7 +2084,6 @@
mActivityThread = null;
mActivityExecutor = activityExecutor;
mLocation = new ServiceConnectionLeaked(null);
- mLocation.fillInStackTrace();
mFlags = flags;
}
diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java
index e2de716..a70d493 100644
--- a/core/java/android/app/LoaderManager.java
+++ b/core/java/android/app/LoaderManager.java
@@ -83,7 +83,7 @@
* transactions while in this call, since it can happen after an
* activity's state is saved. See {@link FragmentManager#beginTransaction()
* FragmentManager.openTransaction()} for further discussion on this.
- *
+ *
* <p>This function is guaranteed to be called prior to the release of
* the last data that was supplied for this Loader. At this point
* you should remove all use of the old data (since it will be released
@@ -127,7 +127,7 @@
*/
public void onLoaderReset(Loader<D> loader);
}
-
+
/**
* Ensures a loader is initialized and active. If the loader doesn't
* already exist, one is created and (if the activity/fragment is currently
@@ -228,7 +228,7 @@
boolean mStarted;
boolean mRetaining;
boolean mRetainingStarted;
-
+
boolean mCreatingLoader;
private FragmentHostCallback mHost;
@@ -249,13 +249,13 @@
boolean mListenerRegistered;
LoaderInfo mPendingLoader;
-
+
public LoaderInfo(int id, Bundle args, LoaderManager.LoaderCallbacks<Object> callbacks) {
mId = id;
mArgs = args;
mCallbacks = callbacks;
}
-
+
void start() {
if (mRetaining && mRetainingStarted) {
// Our owner is started, but we were being retained from a
@@ -271,7 +271,7 @@
}
mStarted = true;
-
+
if (DEBUG) Log.v(TAG, " Starting: " + this);
if (mLoader == null && mCallbacks != null) {
mLoader = mCallbacks.onCreateLoader(mId, mArgs);
@@ -291,7 +291,7 @@
mLoader.startLoading();
}
}
-
+
void retain() {
if (DEBUG) Log.v(TAG, " Retaining: " + this);
mRetaining = true;
@@ -299,7 +299,7 @@
mStarted = false;
mCallbacks = null;
}
-
+
void finishRetain() {
if (mRetaining) {
if (DEBUG) Log.v(TAG, " Finished Retaining: " + this);
@@ -324,7 +324,7 @@
callOnLoadFinished(mLoader, mData);
}
}
-
+
void reportStart() {
if (mStarted) {
if (mReportNextStart) {
@@ -430,7 +430,7 @@
@Override
public void onLoadComplete(Loader<Object> loader, Object data) {
if (DEBUG) Log.v(TAG, "onLoadComplete: " + this);
-
+
if (mDestroyed) {
if (DEBUG) Log.v(TAG, " Ignoring load complete -- destroyed");
return;
@@ -442,7 +442,7 @@
if (DEBUG) Log.v(TAG, " Ignoring load complete -- not active");
return;
}
-
+
LoaderInfo pending = mPendingLoader;
if (pending != null) {
// There is a new request pending and we were just
@@ -455,7 +455,7 @@
installLoader(pending);
return;
}
-
+
// Notify of the new data so the app can switch out the old data before
// we try to destroy it.
if (mData != data || !mHaveData) {
@@ -503,7 +503,7 @@
mDeliveredData = true;
}
}
-
+
@Override
public String toString() {
StringBuilder sb = new StringBuilder(64);
@@ -543,13 +543,13 @@
}
}
}
-
+
LoaderManagerImpl(String who, FragmentHostCallback host, boolean started) {
mWho = who;
mHost = host;
mStarted = started;
}
-
+
void updateHostController(FragmentHostCallback host) {
mHost = host;
}
@@ -557,7 +557,7 @@
public FragmentHostCallback getFragmentHostCallback() {
return mHost;
}
-
+
private LoaderInfo createLoader(int id, Bundle args,
LoaderManager.LoaderCallbacks<Object> callback) {
LoaderInfo info = new LoaderInfo(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
@@ -565,7 +565,7 @@
info.mLoader = (Loader<Object>)loader;
return info;
}
-
+
private LoaderInfo createAndInstallLoader(int id, Bundle args,
LoaderManager.LoaderCallbacks<Object> callback) {
try {
@@ -577,7 +577,7 @@
mCreatingLoader = false;
}
}
-
+
void installLoader(LoaderInfo info) {
mLoaders.put(info.mId, info);
if (mStarted) {
@@ -587,23 +587,23 @@
info.start();
}
}
-
+
/**
* Call to initialize a particular ID with a Loader. If this ID already
* has a Loader associated with it, it is left unchanged and any previous
* callbacks replaced with the newly provided ones. If there is not currently
* a Loader for the ID, a new one is created and started.
- *
+ *
* <p>This function should generally be used when a component is initializing,
* to ensure that a Loader it relies on is created. This allows it to re-use
* an existing Loader's data if there already is one, so that for example
* when an {@link Activity} is re-created after a configuration change it
* does not need to re-create its loaders.
- *
+ *
* <p>Note that in the case where an existing Loader is re-used, the
* <var>args</var> given here <em>will be ignored</em> because you will
* continue using the previous Loader.
- *
+ *
* @param id A unique (to this LoaderManager instance) identifier under
* which to manage the new Loader.
* @param args Optional arguments that will be propagated to
@@ -617,9 +617,9 @@
if (mCreatingLoader) {
throw new IllegalStateException("Called while creating a loader");
}
-
+
LoaderInfo info = mLoaders.get(id);
-
+
if (DEBUG) Log.v(TAG, "initLoader in " + this + ": args=" + args);
if (info == null) {
@@ -630,30 +630,30 @@
if (DEBUG) Log.v(TAG, " Re-using existing loader " + info);
info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
}
-
+
if (info.mHaveData && mStarted) {
// If the loader has already generated its data, report it now.
info.callOnLoadFinished(info.mLoader, info.mData);
}
-
+
return (Loader<D>)info.mLoader;
}
-
+
/**
* Call to re-create the Loader associated with a particular ID. If there
* is currently a Loader associated with this ID, it will be
* canceled/stopped/destroyed as appropriate. A new Loader with the given
* arguments will be created and its data delivered to you once available.
- *
+ *
* <p>This function does some throttling of Loaders. If too many Loaders
* have been created for the given ID but not yet generated their data,
* new calls to this function will create and return a new Loader but not
* actually start it until some previous loaders have completed.
- *
+ *
* <p>After calling this function, any previous Loaders associated with
* this ID will be considered invalid, and you will receive no further
* data updates from them.
- *
+ *
* @param id A unique (to this LoaderManager instance) identifier under
* which to manage the new Loader.
* @param args Optional arguments that will be propagated to
@@ -667,7 +667,7 @@
if (mCreatingLoader) {
throw new IllegalStateException("Called while creating a loader");
}
-
+
LoaderInfo info = mLoaders.get(id);
if (DEBUG) Log.v(TAG, "restartLoader in " + this + ": args=" + args);
if (info != null) {
@@ -706,7 +706,7 @@
info.mPendingLoader = null;
}
if (DEBUG) Log.v(TAG, " Enqueuing as new pending loader");
- info.mPendingLoader = createLoader(id, args,
+ info.mPendingLoader = createLoader(id, args,
(LoaderManager.LoaderCallbacks<Object>)callback);
return (Loader<D>)info.mPendingLoader.mLoader;
}
@@ -719,11 +719,11 @@
mInactiveLoaders.put(id, info);
}
}
-
+
info = createAndInstallLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
return (Loader<D>)info.mLoader;
}
-
+
/**
* Rip down, tear apart, shred to pieces a current Loader ID. After returning
* from this function, any Loader objects associated with this ID are
@@ -735,7 +735,7 @@
if (mCreatingLoader) {
throw new IllegalStateException("Called while creating a loader");
}
-
+
if (DEBUG) Log.v(TAG, "destroyLoader in " + this + " of " + id);
int idx = mLoaders.indexOfKey(id);
if (idx >= 0) {
@@ -763,7 +763,7 @@
if (mCreatingLoader) {
throw new IllegalStateException("Called while creating a loader");
}
-
+
LoaderInfo loaderInfo = mLoaders.get(id);
if (loaderInfo != null) {
if (loaderInfo.mPendingLoader != null) {
@@ -773,16 +773,15 @@
}
return null;
}
-
+
void doStart() {
if (DEBUG) Log.v(TAG, "Starting in " + this);
if (mStarted) {
RuntimeException e = new RuntimeException("here");
- e.fillInStackTrace();
Log.w(TAG, "Called doStart when already started: " + this, e);
return;
}
-
+
mStarted = true;
// Call out to sub classes so they can start their loaders
@@ -791,12 +790,11 @@
mLoaders.valueAt(i).start();
}
}
-
+
void doStop() {
if (DEBUG) Log.v(TAG, "Stopping in " + this);
if (!mStarted) {
RuntimeException e = new RuntimeException("here");
- e.fillInStackTrace();
Log.w(TAG, "Called doStop when not started: " + this, e);
return;
}
@@ -806,12 +804,11 @@
}
mStarted = false;
}
-
+
void doRetain() {
if (DEBUG) Log.v(TAG, "Retaining in " + this);
if (!mStarted) {
RuntimeException e = new RuntimeException("here");
- e.fillInStackTrace();
Log.w(TAG, "Called doRetain when not started: " + this, e);
return;
}
@@ -822,7 +819,7 @@
mLoaders.valueAt(i).retain();
}
}
-
+
void finishRetain() {
if (mRetaining) {
if (DEBUG) Log.v(TAG, "Finished Retaining in " + this);
@@ -833,7 +830,7 @@
}
}
}
-
+
void doReportNextStart() {
for (int i = mLoaders.size()-1; i >= 0; i--) {
mLoaders.valueAt(i).mReportNextStart = true;
@@ -854,7 +851,7 @@
}
mLoaders.clear();
}
-
+
if (DEBUG) Log.v(TAG, "Destroying Inactive in " + this);
for (int i = mInactiveLoaders.size()-1; i >= 0; i--) {
mInactiveLoaders.valueAt(i).destroy();
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 0381ee0..3d9c55c 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5003,7 +5003,7 @@
/**
* Sets a very short string summarizing the most critical information contained in the
- * notification. Suggested max length is 5 characters, and there is no guarantee how much or
+ * notification. Suggested max length is 7 characters, and there is no guarantee how much or
* how little of this text will be shown.
*/
@FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index a458b4e..087e246 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -174,22 +174,54 @@
}
/**
- * Apply the registered library paths to the passed impl object
- * @return the hash code for the current version of the registered paths
+ * Apply the registered library paths to the passed AssetManager. If may create a new
+ * AssetManager if any changes are needed and it isn't allowed to reuse the old one.
+ *
+ * @return new AssetManager and the hash code for the current version of the registered paths
*/
- public int updateResourceImplWithRegisteredLibs(@NonNull ResourcesImpl impl) {
+ public @NonNull Pair<AssetManager, Integer> updateResourceImplAssetsWithRegisteredLibs(
+ @NonNull AssetManager assets, boolean reuseAssets) {
if (!Flags.registerResourcePaths()) {
- return 0;
+ return new Pair<>(assets, 0);
}
- final var collector = new PathCollector(null);
- final int size = mSharedLibAssetsMap.size();
- for (int i = 0; i < size; i++) {
- final var libraryKey = mSharedLibAssetsMap.valueAt(i).getResourcesKey();
- collector.appendKey(libraryKey);
+ final int size;
+ final PathCollector collector;
+
+ synchronized (mLock) {
+ size = mSharedLibAssetsMap.size();
+ if (assets == AssetManager.getSystem()) {
+ return new Pair<>(assets, size);
+ }
+ collector = new PathCollector(resourcesKeyFromAssets(assets));
+ for (int i = 0; i < size; i++) {
+ final var libraryKey = mSharedLibAssetsMap.valueAt(i).getResourcesKey();
+ collector.appendKey(libraryKey);
+ }
}
- impl.getAssets().addPresetApkKeys(extractApkKeys(collector.collectedKey()));
- return size;
+ if (collector.isSameAsOriginal()) {
+ return new Pair<>(assets, size);
+ }
+ if (reuseAssets) {
+ assets.addPresetApkKeys(extractApkKeys(collector.collectedKey()));
+ return new Pair<>(assets, size);
+ }
+ final var newAssetsBuilder = new AssetManager.Builder();
+ for (final var asset : assets.getApkAssets()) {
+ if (!asset.isForLoader()) {
+ newAssetsBuilder.addApkAssets(asset);
+ }
+ }
+ for (final var key : extractApkKeys(collector.collectedKey())) {
+ try {
+ final var asset = loadApkAssets(key);
+ newAssetsBuilder.addApkAssets(asset);
+ } catch (IOException e) {
+ Log.e(TAG, "Couldn't load assets for key " + key, e);
+ }
+ }
+ assets.getLoaders().forEach(newAssetsBuilder::addLoader);
+ return new Pair<>(newAssetsBuilder.build(), size);
}
public static class ApkKey {
@@ -624,6 +656,23 @@
return apkKeys;
}
+ private ResourcesKey resourcesKeyFromAssets(@NonNull AssetManager assets) {
+ final var libs = new ArrayList<String>();
+ final var overlays = new ArrayList<String>();
+ for (final ApkAssets asset : assets.getApkAssets()) {
+ if (asset.isSystem() || asset.isForLoader()) {
+ continue;
+ }
+ if (asset.isOverlay()) {
+ overlays.add(asset.getAssetPath());
+ } else if (asset.isSharedLib()) {
+ libs.add(asset.getAssetPath());
+ }
+ }
+ return new ResourcesKey(null, null, overlays.toArray(new String[0]),
+ libs.toArray(new String[0]), 0, null, null);
+ }
+
/**
* Creates an AssetManager from the paths within the ResourcesKey.
*
@@ -752,7 +801,7 @@
final Configuration config = generateConfig(key);
final DisplayMetrics displayMetrics = getDisplayMetrics(generateDisplayId(key), daj);
- final ResourcesImpl impl = new ResourcesImpl(assets, displayMetrics, config, daj);
+ final ResourcesImpl impl = new ResourcesImpl(assets, displayMetrics, config, daj, true);
if (DEBUG) {
Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
@@ -1137,7 +1186,6 @@
synchronized (mLock) {
if (DEBUG) {
Throwable here = new Throwable();
- here.fillInStackTrace();
Slog.w(TAG, "!! Create resources for key=" + key, here);
}
@@ -1158,7 +1206,6 @@
synchronized (mLock) {
if (DEBUG) {
Throwable here = new Throwable();
- here.fillInStackTrace();
Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here);
}
@@ -1302,7 +1349,6 @@
if (DEBUG) {
Throwable here = new Throwable();
- here.fillInStackTrace();
Slog.d(TAG, "updating resources override for activity=" + activityToken
+ " from oldConfig="
+ Configuration.resourceQualifierString(oldConfig)
@@ -1835,31 +1881,32 @@
for (int i = 0; i < resourcesCount; i++) {
final WeakReference<Resources> ref = mAllResourceReferences.get(i);
final Resources r = ref != null ? ref.get() : null;
- if (r != null) {
- final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
- if (key != null) {
- final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key);
- if (impl == null) {
- throw new Resources.NotFoundException("failed to redirect ResourcesImpl");
- }
- r.setImpl(impl);
- } else {
- // ResourcesKey is null which means the ResourcesImpl could belong to a
- // Resources created by application through Resources constructor and was not
- // managed by ResourcesManager, so the ResourcesImpl needs to be recreated to
- // have shared library asset paths appended if there are any.
- if (r.getImpl() != null) {
- final ResourcesImpl oldImpl = r.getImpl();
- final AssetManager oldAssets = oldImpl.getAssets();
- // ResourcesImpl constructor will help to append shared library asset paths.
- if (oldAssets != AssetManager.getSystem() && oldAssets.isUpToDate()) {
- final ResourcesImpl newImpl = new ResourcesImpl(oldAssets,
- oldImpl.getMetrics(), oldImpl.getConfiguration(),
- oldImpl.getDisplayAdjustments());
+ if (r == null) {
+ continue;
+ }
+ final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
+ if (key != null) {
+ final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key);
+ if (impl == null) {
+ throw new Resources.NotFoundException("failed to redirect ResourcesImpl");
+ }
+ r.setImpl(impl);
+ } else {
+ // ResourcesKey is null which means the ResourcesImpl could belong to a
+ // Resources created by application through Resources constructor and was not
+ // managed by ResourcesManager, so the ResourcesImpl needs to be recreated to
+ // have shared library asset paths appended if there are any.
+ final ResourcesImpl oldImpl = r.getImpl();
+ if (oldImpl != null) {
+ final AssetManager oldAssets = oldImpl.getAssets();
+ // ResourcesImpl constructor will help to append shared library asset paths.
+ if (oldAssets != AssetManager.getSystem()) {
+ if (oldAssets.isUpToDate()) {
+ final ResourcesImpl newImpl = new ResourcesImpl(oldImpl);
r.setImpl(newImpl);
} else {
- Slog.w(TAG, "Skip appending shared library asset paths for the "
- + "Resource as its assets are not up to date.");
+ Slog.w(TAG, "Skip appending shared library asset paths for "
+ + "the Resources as its assets are not up to date.");
}
}
}
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 637187e..5ed1f4e 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -177,10 +177,6 @@
{
"file_patterns": ["(/|^)AppOpsManager.java"],
"name": "CtsAppOpsTestCases"
- },
- {
- "file_patterns": ["(/|^)BroadcastStickyCache.java"],
- "name": "BroadcastUnitTests"
}
]
}
diff --git a/core/java/android/app/ZenBypassingApp.java b/core/java/android/app/ZenBypassingApp.java
new file mode 100644
index 0000000..89bcfa2
--- /dev/null
+++ b/core/java/android/app/ZenBypassingApp.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+public final class ZenBypassingApp implements Parcelable {
+
+ @NonNull private String mPkg;
+ private boolean mAllChannelsBypass;
+
+
+ public ZenBypassingApp(@NonNull String pkg, boolean allChannelsBypass) {
+ mPkg = pkg;
+ mAllChannelsBypass = allChannelsBypass;
+ }
+
+ public ZenBypassingApp(Parcel source) {
+ mPkg = source.readString();
+ mAllChannelsBypass = source.readBoolean();
+ }
+
+ @NonNull
+ public String getPkg() {
+ return mPkg;
+ }
+
+ public boolean doAllChannelsBypass() {
+ return mAllChannelsBypass;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mPkg);
+ dest.writeBoolean(mAllChannelsBypass);
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<ZenBypassingApp> CREATOR
+ = new Parcelable.Creator<ZenBypassingApp>() {
+ @Override
+ public ZenBypassingApp createFromParcel(Parcel source) {
+ return new ZenBypassingApp(source);
+ }
+ @Override
+ public ZenBypassingApp[] newArray(int size) {
+ return new ZenBypassingApp[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ZenBypassingApp)) return false;
+ ZenBypassingApp that = (ZenBypassingApp) o;
+ return mAllChannelsBypass == that.mAllChannelsBypass && Objects.equals(mPkg,
+ that.mPkg);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPkg, mAllChannelsBypass);
+ }
+
+ @Override
+ public String toString() {
+ return "ZenBypassingApp{" +
+ "mPkg='" + mPkg + '\'' +
+ ", mAllChannelsBypass=" + mAllChannelsBypass +
+ '}';
+ }
+}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 102540c..c789c41 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -55,8 +55,10 @@
import static android.Manifest.permission.SET_TIME_ZONE;
import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
import static android.app.admin.flags.Flags.FLAG_DEVICE_THEFT_API_ENABLED;
+import static android.app.admin.flags.Flags.FLAG_REMOVE_MANAGED_PROFILE_ENABLED;
import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
import static android.app.admin.flags.Flags.onboardingConsentlessBugreports;
+import static android.app.admin.flags.Flags.FLAG_SECONDARY_LOCKSCREEN_API_ENABLED;
import static android.content.Intent.LOCAL_FLAG_FROM_SYSTEM;
import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
@@ -8918,12 +8920,9 @@
/**
* Called by a device owner, a profile owner for the primary user or a profile
* owner of an organization-owned managed profile to turn auto time on and off.
- * Callers are recommended to use {@link UserManager#DISALLOW_CONFIG_DATE_TIME}
- * to prevent the user from changing this setting.
* <p>
- * If user restriction {@link UserManager#DISALLOW_CONFIG_DATE_TIME} is used,
- * no user will be able set the date and time. Instead, the network date
- * and time will be used.
+ * Callers are recommended to use {@link UserManager#DISALLOW_CONFIG_DATE_TIME} to prevent the
+ * user from changing this setting, that way no user will be able set the date and time zone.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
* caller is not a device admin.
@@ -8936,7 +8935,13 @@
throwIfParentInstance("setAutoTimeEnabled");
if (mService != null) {
try {
- mService.setAutoTimeEnabled(admin, mContext.getPackageName(), enabled);
+ if (Flags.setAutoTimeEnabledCoexistence()) {
+ mService.setAutoTimePolicy(mContext.getPackageName(),
+ enabled ? DevicePolicyManager.AUTO_TIME_ENABLED
+ : DevicePolicyManager.AUTO_TIME_DISABLED);
+ } else {
+ mService.setAutoTimeEnabled(admin, mContext.getPackageName(), enabled);
+ }
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -8966,14 +8971,102 @@
}
/**
+ * Specifies that the auto time state is not controlled by device policy.
+ *
+ * @see #setAutoTimePolicy(ComponentName, int)
+ */
+ @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ENABLED_COEXISTENCE)
+ public static final int AUTO_TIME_NOT_CONTROLLED_BY_POLICY = 0;
+
+ /**
+ * Specifies the "disabled" auto time state.
+ *
+ * @see #setAutoTimePolicy(ComponentName, int)
+ */
+ @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ENABLED_COEXISTENCE)
+ public static final int AUTO_TIME_DISABLED = 1;
+
+ /**
+ * Specifies the "enabled" auto time state.
+ *
+ * @see #setAutoTimePolicy(ComponentName, int)
+ */
+ @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ENABLED_COEXISTENCE)
+ public static final int AUTO_TIME_ENABLED = 2;
+
+ /**
+ * Flags supplied to {@link #setAutoTimePolicy}(ComponentName, int)}.
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "AUTO_TIME_" }, value = {
+ AUTO_TIME_NOT_CONTROLLED_BY_POLICY,
+ AUTO_TIME_DISABLED,
+ AUTO_TIME_ENABLED
+ })
+ public @interface AutoTimePolicy {}
+
+ /**
+ * Called by a device owner, a profile owner for the primary user or a profile owner of an
+ * organization-owned managed profile to turn auto time on and off i.e. Whether time should be
+ * obtained automatically from the network or not.
+ * <p>
+ * Callers are recommended to use {@link UserManager#DISALLOW_CONFIG_DATE_TIME} to prevent the
+ * user from changing this setting, that way no user will be able set the date and time zone.
+ *
+ * @param policy The desired state among {@link #AUTO_TIME_ENABLED} to enable,
+ * {@link #AUTO_TIME_DISABLED} to disable and
+ * {@link #AUTO_TIME_NOT_CONTROLLED_BY_POLICY} to unset the policy.
+ * @throws SecurityException if caller is not a device owner, a profile owner for the
+ * primary user, or a profile owner of an organization-owned managed profile, or if the caller
+ * does not hold the required permission.
+ */
+ @SupportsCoexistence
+ @RequiresPermission(value = SET_TIME, conditional = true)
+ @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ENABLED_COEXISTENCE)
+ public void setAutoTimePolicy(@AutoTimePolicy int policy) {
+ throwIfParentInstance("setAutoTimePolicy");
+ if (mService != null) {
+ try {
+ mService.setAutoTimePolicy(mContext.getPackageName(), policy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Returns current auto time policy's state.
+ *
+ * @return One of {@link #AUTO_TIME_ENABLED} if enabled, {@link #AUTO_TIME_DISABLED} if disabled
+ * and {@link #AUTO_TIME_NOT_CONTROLLED_BY_POLICY} if it's not controlled by
+ * policy.
+ * @throws SecurityException if caller is not a device owner, a profile owner for the
+ * primary user, or a profile owner of an organization-owned managed profile, or if the caller
+ * does not hold the required permission.
+ */
+ @SupportsCoexistence
+ @RequiresPermission(anyOf = {SET_TIME, QUERY_ADMIN_POLICY}, conditional = true)
+ @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ENABLED_COEXISTENCE)
+ public @AutoTimePolicy int getAutoTimePolicy() {
+ throwIfParentInstance("getAutoTimePolicy");
+ if (mService != null) {
+ try {
+ return mService.getAutoTimePolicy(mContext.getPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return DevicePolicyManager.AUTO_TIME_NOT_CONTROLLED_BY_POLICY;
+ }
+
+ /**
* Called by a device owner, a profile owner for the primary user or a profile
* owner of an organization-owned managed profile to turn auto time zone on and off.
- * Callers are recommended to use {@link UserManager#DISALLOW_CONFIG_DATE_TIME}
- * to prevent the user from changing this setting.
* <p>
- * If user restriction {@link UserManager#DISALLOW_CONFIG_DATE_TIME} is used,
- * no user will be able set the date and time zone. Instead, the network date
- * and time zone will be used.
+ * Callers are recommended to use {@link UserManager#DISALLOW_CONFIG_DATE_TIME} to prevent the
+ * user from changing this setting, that way no user will be able set the date and time zone.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with or Null if the
* caller is not a device admin.
@@ -8981,13 +9074,17 @@
* @throws SecurityException if caller is not a device owner, a profile owner for the
* primary user, or a profile owner of an organization-owned managed profile.
*/
- @SupportsCoexistence
@RequiresPermission(value = SET_TIME_ZONE, conditional = true)
public void setAutoTimeZoneEnabled(@Nullable ComponentName admin, boolean enabled) {
throwIfParentInstance("setAutoTimeZone");
if (mService != null) {
try {
- mService.setAutoTimeZoneEnabled(admin, mContext.getPackageName(), enabled);
+ if (Flags.setAutoTimeZoneEnabledCoexistence()) {
+ mService.setAutoTimeZonePolicy(mContext.getPackageName(),
+ enabled ? AUTO_TIME_ZONE_ENABLED : AUTO_TIME_ZONE_DISABLED );
+ } else {
+ mService.setAutoTimeZoneEnabled(admin, mContext.getPackageName(), enabled);
+ }
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -9017,6 +9114,96 @@
}
/**
+ * Specifies that the auto time zone state is not controlled by device policy.
+ *
+ * @see #setAutoTimeZonePolicy(int)
+ */
+ @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ZONE_ENABLED_COEXISTENCE)
+ public static final int AUTO_TIME_ZONE_NOT_CONTROLLED_BY_POLICY = 0;
+
+ /**
+ * Specifies the "disabled" auto time zone state.
+ *
+ * @see #setAutoTimeZonePolicy(int)
+ */
+ @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ZONE_ENABLED_COEXISTENCE)
+ public static final int AUTO_TIME_ZONE_DISABLED = 1;
+
+ /**
+ * Specifies the "enabled" auto time zone state.
+ *
+ * @see #setAutoTimeZonePolicy(int)
+ */
+ @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ZONE_ENABLED_COEXISTENCE)
+ public static final int AUTO_TIME_ZONE_ENABLED = 2;
+
+ /**
+ * Flags supplied to {@link #setAutoTimeZonePolicy}(int)}.
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "AUTO_TIME_ZONE_" }, value = {
+ AUTO_TIME_ZONE_NOT_CONTROLLED_BY_POLICY,
+ AUTO_TIME_ZONE_DISABLED,
+ AUTO_TIME_ZONE_ENABLED
+ })
+ public @interface AutoTimeZonePolicy {}
+
+ /**
+ * Called by a device owner, a profile owner for the primary user or a profile owner of an
+ * organization-owned managed profile to turn auto time zone on and off.
+ * <p>
+ * Callers are recommended to use {@link UserManager#DISALLOW_CONFIG_DATE_TIME} to prevent the
+ * user from changing this setting, that way no user will be able set the date and time zone.
+ *
+ * @param policy The desired state among {@link #AUTO_TIME_ZONE_ENABLED} to enable it,
+ * {@link #AUTO_TIME_ZONE_DISABLED} to disable it or
+ * {@link #AUTO_TIME_ZONE_NOT_CONTROLLED_BY_POLICY} to unset the policy.
+ * @throws SecurityException if caller is not a device owner, a profile owner for the primary
+ * user, or a profile owner of an organization-owned managed profile, or if the caller does not
+ * hold the required permission.
+ */
+ @SupportsCoexistence
+ @RequiresPermission(value = SET_TIME_ZONE, conditional = true)
+ @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ZONE_ENABLED_COEXISTENCE)
+ public void setAutoTimeZonePolicy(@AutoTimeZonePolicy int policy) {
+ throwIfParentInstance("setAutoTimeZonePolicy");
+ if (mService != null) {
+ try {
+ mService.setAutoTimeZonePolicy(mContext.getPackageName(), policy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Returns auto time zone policy's current state.
+ *
+ * @return One of {@link #AUTO_TIME_ZONE_ENABLED} if enabled, {@link #AUTO_TIME_ZONE_DISABLED}
+ * if disabled and {@link #AUTO_TIME_ZONE_NOT_CONTROLLED_BY_POLICY} if the state is not
+ * controlled by policy.
+ * @throws SecurityException if caller is not a device owner, a profile owner for the
+ * primary user, or a profile owner of an organization-owned managed profile, or if the caller
+ * does not hold the required permission.
+ */
+ @SupportsCoexistence
+ @RequiresPermission(anyOf = {SET_TIME_ZONE, QUERY_ADMIN_POLICY}, conditional = true)
+ @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ZONE_ENABLED_COEXISTENCE)
+ public @AutoTimeZonePolicy int getAutoTimeZonePolicy() {
+ throwIfParentInstance("getAutoTimeZonePolicy");
+ if (mService != null) {
+ try {
+ return mService.getAutoTimeZonePolicy(mContext.getPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return DevicePolicyManager.AUTO_TIME_ZONE_NOT_CONTROLLED_BY_POLICY;
+ }
+
+ /**
* TODO (b/137101239): remove this method in follow-up CL
* since it's only used for split system user.
* Called by a device owner to set whether all users created on the device should be ephemeral.
@@ -12550,28 +12737,43 @@
* @param enabled Whether or not the lockscreen needs to be shown.
* @throws SecurityException if {@code admin} is not a device or profile owner.
* @see #isSecondaryLockscreenEnabled
+ * @deprecated Use {@link #setSecondaryLockscreenEnabled(boolean,PersistableBundle)} instead.
* @hide
- **/
+ */
+ @Deprecated
@SystemApi
+ @FlaggedApi(FLAG_SECONDARY_LOCKSCREEN_API_ENABLED)
public void setSecondaryLockscreenEnabled(@NonNull ComponentName admin, boolean enabled) {
- setSecondaryLockscreenEnabled(admin, enabled, null);
+ throwIfParentInstance("setSecondaryLockscreenEnabled");
+ if (mService != null) {
+ try {
+ mService.setSecondaryLockscreenEnabled(admin, enabled, null);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
/**
* Called by the system supervision app to set whether a secondary lockscreen needs to be shown.
*
- * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
- * caller is not a device admin.
+ * <p>The secondary lockscreen will by displayed after the primary keyguard security screen
+ * requirements are met.
+ *
+ * <p>This API, and associated APIs, can only be called by the default supervision app.
+ *
* @param enabled Whether or not the lockscreen needs to be shown.
* @param options A {@link PersistableBundle} to supply options to the lock screen.
* @hide
*/
- public void setSecondaryLockscreenEnabled(@Nullable ComponentName admin, boolean enabled,
+ @SystemApi
+ @FlaggedApi(FLAG_SECONDARY_LOCKSCREEN_API_ENABLED)
+ public void setSecondaryLockscreenEnabled(boolean enabled,
@Nullable PersistableBundle options) {
throwIfParentInstance("setSecondaryLockscreenEnabled");
if (mService != null) {
try {
- mService.setSecondaryLockscreenEnabled(admin, enabled, options);
+ mService.setSecondaryLockscreenEnabled(null, enabled, options);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -16962,6 +17164,30 @@
}
/**
+ * Removes a manged profile from the device only when called from a managed profile's context
+ *
+ * @param user UserHandle of the profile to be removed
+ * @return {@code true} when removal of managed profile was successful, {@code false} when
+ * removal was unsuccessful or throws IllegalArgumentException when provided user was not a
+ * managed profile
+ * @hide
+ */
+ @SystemApi
+ @UserHandleAware
+ @FlaggedApi(FLAG_REMOVE_MANAGED_PROFILE_ENABLED)
+ @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ public boolean removeManagedProfile() {
+ if (mService == null) {
+ throw new IllegalStateException("Could not find DevicePolicyManagerService");
+ }
+ try {
+ return mService.removeManagedProfile(myUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Called when a managed profile has been provisioned.
*
* @throws SecurityException if the caller does not hold
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index a4e2b8f..a406802 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -375,9 +375,15 @@
void setAutoTimeEnabled(in ComponentName who, String callerPackageName, boolean enabled);
boolean getAutoTimeEnabled(in ComponentName who, String callerPackageName);
+ void setAutoTimePolicy(String callerPackageName, int policy);
+ int getAutoTimePolicy(String callerPackageName);
+
void setAutoTimeZoneEnabled(in ComponentName who, String callerPackageName, boolean enabled);
boolean getAutoTimeZoneEnabled(in ComponentName who, String callerPackageName);
+ void setAutoTimeZonePolicy(String callerPackageName, int policy);
+ int getAutoTimeZonePolicy(String callerPackageName);
+
void setForceEphemeralUsers(in ComponentName who, boolean forceEpehemeralUsers);
boolean getForceEphemeralUsers(in ComponentName who);
@@ -567,6 +573,8 @@
void finalizeWorkProfileProvisioning(in UserHandle managedProfileUser, in Account migratedAccount);
+ boolean removeManagedProfile(int userId);
+
void setDeviceOwnerType(in ComponentName admin, in int deviceOwnerType);
int getDeviceOwnerType(in ComponentName admin);
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index be24bfa..404471e 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -343,13 +343,20 @@
}
flag {
- name: "user_provisioning_same_state"
- namespace: "enterprise"
- description: "Handle exceptions while setting same provisioning state."
- bug: "326441417"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
+ name: "user_provisioning_same_state"
+ namespace: "enterprise"
+ description: "Handle exceptions while setting same provisioning state."
+ bug: "326441417"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "remove_managed_profile_enabled"
+ namespace: "enterprise"
+ description: "API that removes a given managed profile."
+ bug: "372652841"
}
flag {
@@ -374,3 +381,11 @@
description: "Split up existing create and provision managed profile API."
bug: "375382324"
}
+
+flag {
+ name: "secondary_lockscreen_api_enabled"
+ is_exported: true
+ namespace: "enterprise"
+ description: "Add new API for secondary lockscreen"
+ bug: "336297680"
+}
diff --git a/core/java/android/app/jank/JankDataProcessor.java b/core/java/android/app/jank/JankDataProcessor.java
index 3783a5f..7525d04 100644
--- a/core/java/android/app/jank/JankDataProcessor.java
+++ b/core/java/android/app/jank/JankDataProcessor.java
@@ -70,8 +70,8 @@
for (int j = 0; j < mPendingStates.size(); j++) {
StateData pendingState = mPendingStates.get(j);
// This state was active during the frame
- if (frame.frameVsyncId >= pendingState.mVsyncIdStart
- && frame.frameVsyncId <= pendingState.mVsyncIdEnd) {
+ if (frame.getVsyncId() >= pendingState.mVsyncIdStart
+ && frame.getVsyncId() <= pendingState.mVsyncIdEnd) {
recordFrameCount(frame, pendingState, activityName, appUid);
pendingState.mProcessed = true;
@@ -131,14 +131,14 @@
mPendingJankStats.put(stateData.mStateDataKey, jankStats);
}
// This state has already been accounted for
- if (jankStats.processedVsyncId == frameData.frameVsyncId) return;
+ if (jankStats.processedVsyncId == frameData.getVsyncId()) return;
jankStats.mTotalFrames += 1;
- if (frameData.jankType == JankData.JANK_APPLICATION) {
+ if ((frameData.getJankType() & JankData.JANK_APPLICATION) != 0) {
jankStats.mJankyFrames += 1;
}
- jankStats.recordFrameOverrun(frameData.actualAppFrameTimeNs);
- jankStats.processedVsyncId = frameData.frameVsyncId;
+ jankStats.recordFrameOverrun(frameData.getActualAppFrameTimeNanos());
+ jankStats.processedVsyncId = frameData.getVsyncId();
}
diff --git a/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig b/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig
index 8b6441a..74a96c8 100644
--- a/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig
+++ b/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig
@@ -8,3 +8,10 @@
description: "Make methods on OnDeviceIntelligenceManager available for local inference."
bug: "304755128"
}
+flag {
+ name: "enable_on_device_intelligence_module"
+ is_exported: true
+ namespace: "ondeviceintelligence"
+ description: "Enable migration to mainline module and related changes."
+ bug: "376427781"
+}
\ No newline at end of file
diff --git a/core/java/android/app/wallpaper/WallpaperDescription.java b/core/java/android/app/wallpaper/WallpaperDescription.java
index 8ffda72..4a142bb 100644
--- a/core/java/android/app/wallpaper/WallpaperDescription.java
+++ b/core/java/android/app/wallpaper/WallpaperDescription.java
@@ -113,7 +113,7 @@
/** @return the description for this wallpaper */
@NonNull
public List<CharSequence> getDescription() {
- return new ArrayList<>();
+ return mDescription;
}
/** @return the {@link Uri} for the action associated with the wallpaper, or {@code null} if not
diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java
index 964a8be..a816294 100644
--- a/core/java/android/content/BroadcastReceiver.java
+++ b/core/java/android/content/BroadcastReceiver.java
@@ -356,7 +356,6 @@
}
RuntimeException e = new RuntimeException(
"BroadcastReceiver trying to return result during a non-ordered broadcast");
- e.fillInStackTrace();
Log.e("BroadcastReceiver", e.getMessage(), e);
}
}
@@ -768,7 +767,6 @@
}
RuntimeException e = new RuntimeException(
"BroadcastReceiver trying to return result during a non-ordered broadcast");
- e.fillInStackTrace();
Log.e("BroadcastReceiver", e.getMessage(), e);
}
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index b776b59..8853304 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -87,7 +87,6 @@
import android.util.Log;
import android.util.proto.ProtoOutputStream;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.XmlUtils;
import com.android.modules.expresslog.Counter;
@@ -12304,7 +12303,6 @@
}
/** @hide */
- @VisibleForTesting
public Set<NestedIntentKey> getExtraIntentKeys() {
return mCreatorTokenInfo == null ? null : mCreatorTokenInfo.mNestedIntentKeys;
}
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index b4b5a7f..9ba5a35 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -359,3 +359,10 @@
bug: "377474232"
is_fixed_read_only: true
}
+
+flag {
+ name: "support_minor_versions_in_minsdkversion"
+ namespace: "package_manager_service"
+ description: "Block app installations that specify an incompatible minor SDK version"
+ bug: "377474232"
+}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 3d89ce1..813208d 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -330,6 +330,17 @@
is_fixed_read_only: true
}
+flag {
+ name: "cache_user_info_read_only"
+ namespace: "multiuser"
+ description: "Cache UserInfo to avoid unnecessary binder calls"
+ bug: "161915546"
+ 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 {
name: "enable_private_space_features"
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index 68b5d78..908999b 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -124,11 +124,13 @@
@Nullable
@GuardedBy("this")
- private final StringBlock mStringBlock; // null or closed if mNativePtr = 0.
+ private StringBlock mStringBlock; // null or closed if mNativePtr = 0.
@PropertyFlags
private final int mFlags;
+ private final boolean mIsOverlay;
+
@Nullable
private final AssetsProvider mAssets;
@@ -302,40 +304,43 @@
private ApkAssets(@FormatType int format, @NonNull String path, @PropertyFlags int flags,
@Nullable AssetsProvider assets) throws IOException {
+ this(format, flags, assets);
Objects.requireNonNull(path, "path");
- mFlags = flags;
mNativePtr = nativeLoad(format, path, flags, assets);
mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
- mAssets = assets;
}
private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd,
@NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets)
throws IOException {
+ this(format, flags, assets);
Objects.requireNonNull(fd, "fd");
Objects.requireNonNull(friendlyName, "friendlyName");
- mFlags = flags;
mNativePtr = nativeLoadFd(format, fd, friendlyName, flags, assets);
mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
- mAssets = assets;
}
private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd,
@NonNull String friendlyName, long offset, long length, @PropertyFlags int flags,
@Nullable AssetsProvider assets) throws IOException {
+ this(format, flags, assets);
Objects.requireNonNull(fd, "fd");
Objects.requireNonNull(friendlyName, "friendlyName");
- mFlags = flags;
mNativePtr = nativeLoadFdOffsets(format, fd, friendlyName, offset, length, flags, assets);
mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
- mAssets = assets;
}
private ApkAssets(@PropertyFlags int flags, @Nullable AssetsProvider assets) {
- mFlags = flags;
+ this(FORMAT_APK, flags, assets);
mNativePtr = nativeLoadEmpty(flags, assets);
mStringBlock = null;
+ }
+
+ private ApkAssets(@FormatType int format, @PropertyFlags int flags,
+ @Nullable AssetsProvider assets) {
+ mFlags = flags;
mAssets = assets;
+ mIsOverlay = format == FORMAT_IDMAP;
}
@UnsupportedAppUsage
@@ -425,6 +430,18 @@
}
}
+ public boolean isSystem() {
+ return (mFlags & PROPERTY_SYSTEM) != 0;
+ }
+
+ public boolean isSharedLib() {
+ return (mFlags & PROPERTY_DYNAMIC) != 0;
+ }
+
+ public boolean isOverlay() {
+ return mIsOverlay;
+ }
+
@Override
public String toString() {
return "ApkAssets{path=" + getDebugName() + "}";
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 6fd4d01..4551bd5 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -973,9 +973,9 @@
* Open an asset using ACCESS_STREAMING mode. This provides access to
* files that have been bundled with an application as assets -- that is,
* files placed in to the "assets" directory.
- *
+ *
* @param fileName The name of the asset to open. This name can be hierarchical.
- *
+ *
* @see #open(String, int)
* @see #list
*/
@@ -988,10 +988,10 @@
* read its contents. This provides access to files that have been bundled
* with an application as assets -- that is, files placed in to the
* "assets" directory.
- *
+ *
* @param fileName The name of the asset to open. This name can be hierarchical.
* @param accessMode Desired access mode for retrieving the data.
- *
+ *
* @see #ACCESS_UNKNOWN
* @see #ACCESS_STREAMING
* @see #ACCESS_RANDOM
@@ -1037,14 +1037,14 @@
/**
* Return a String array of all the assets at the given path.
- *
+ *
* @param path A relative path within the assets, i.e., "docs/home.html".
- *
+ *
* @return String[] Array of strings, one for each asset. These file
* names are relative to 'path'. You can open the file by
* concatenating 'path' and a name in the returned string (via
* File) and passing that to open().
- *
+ *
* @see #open
*/
public @Nullable String[] list(@NonNull String path) throws IOException {
@@ -1167,20 +1167,20 @@
return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]);
}
}
-
+
/**
* Retrieve a parser for a compiled XML file.
- *
+ *
* @param fileName The name of the file to retrieve.
*/
public @NonNull XmlResourceParser openXmlResourceParser(@NonNull String fileName)
throws IOException {
return openXmlResourceParser(0, fileName);
}
-
+
/**
* Retrieve a parser for a compiled XML file.
- *
+ *
* @param cookie Identifier of the package to be opened.
* @param fileName The name of the file to retrieve.
*/
@@ -1200,7 +1200,7 @@
/**
* Retrieve a non-asset as a compiled XML file. Not for use by applications.
- *
+ *
* @param fileName The name of the file to retrieve.
* @hide
*/
@@ -1211,7 +1211,7 @@
/**
* Retrieve a non-asset as a compiled XML file. Not for use by
* applications.
- *
+ *
* @param cookie Identifier of the package to be opened.
* @param fileName Name of the asset to retrieve.
* @hide
@@ -1675,7 +1675,6 @@
mRefStacks = new HashMap<>();
}
RuntimeException ex = new RuntimeException();
- ex.fillInStackTrace();
mRefStacks.put(id, ex);
}
mNumRefs++;
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index e6b9342..bcaceb2 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -203,9 +203,25 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics,
@Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) {
- mAssets = assets;
- mAppliedSharedLibsHash =
- ResourcesManager.getInstance().updateResourceImplWithRegisteredLibs(this);
+ // Don't reuse assets by default as we have no control over whether they're already
+ // inside some other ResourcesImpl.
+ this(assets, metrics, config, displayAdjustments, false);
+ }
+
+ public ResourcesImpl(@NonNull ResourcesImpl orig) {
+ // We know for sure that the other assets are in use, so can't reuse the object here.
+ this(orig.getAssets(), orig.getMetrics(), orig.getConfiguration(),
+ orig.getDisplayAdjustments(), false);
+ }
+
+ public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics,
+ @Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments,
+ boolean reuseAssets) {
+ final var assetsAndHash =
+ ResourcesManager.getInstance().updateResourceImplAssetsWithRegisteredLibs(assets,
+ reuseAssets);
+ mAssets = assetsAndHash.first;
+ mAppliedSharedLibsHash = assetsAndHash.second;
mMetrics.setToDefaults();
mDisplayAdjustments = displayAdjustments;
mConfiguration.setToDefaults();
diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig
index d243575..6c35d10 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -3,6 +3,16 @@
flag {
namespace: "credential_manager"
+ name: "ttl_fix_enabled"
+ description: "Enable fix for transaction too large issue"
+ bug: "371052524"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ namespace: "credential_manager"
name: "settings_activity_enabled"
is_exported: true
description: "Enable the Credential Manager Settings Activity APIs"
diff --git a/core/java/android/hardware/display/DisplayTopology.java b/core/java/android/hardware/display/DisplayTopology.java
index e349b81..f00c3a5 100644
--- a/core/java/android/hardware/display/DisplayTopology.java
+++ b/core/java/android/hardware/display/DisplayTopology.java
@@ -23,6 +23,7 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
+import android.graphics.PointF;
import android.graphics.RectF;
import android.os.Parcel;
import android.os.Parcelable;
@@ -150,6 +151,138 @@
}
}
+ /**
+ * Rearranges the topology toward the positions given for each display. The width and height of
+ * each display, as well as the primary display, are not changed by this call.
+ * <p>
+ * Upon returning, the topology will be valid and normalized with each display as close to the
+ * requested positions as possible.
+ *
+ * @param newPos the desired positions (upper-left corner) of each display. The keys in the map
+ * are the display IDs.
+ * @throws IllegalArgumentException if the keys in {@code positions} are not the exact display
+ * IDs in this topology, no more, no less
+ */
+ public void rearrange(Map<Integer, PointF> newPos) {
+ var availableParents = new ArrayList<TreeNode>();
+
+ availableParents.addLast(mRoot);
+
+ var needsParent = allNodesIdMap();
+
+ // In the case of missing items, if this check doesn't detect it, a NPE will be thrown
+ // later.
+ if (needsParent.size() != newPos.size()) {
+ throw new IllegalArgumentException("newPos has wrong number of entries: " + newPos);
+ }
+
+ mRoot.mChildren.clear();
+ for (TreeNode n : needsParent.values()) {
+ n.mChildren.clear();
+ }
+
+ needsParent.remove(mRoot.mDisplayId);
+ // Start with a root island and add children to it one-by-one until the island consists of
+ // all the displays. The root island begins with only the root node, which has no
+ // parent. Then we greedily choose an optimal pairing of two nodes, consisting of a node
+ // from the island and a node not yet in the island. This is repeating until all nodes are
+ // in the island.
+ //
+ // The optimal pair is the pair which has the smallest deviation. The deviation consists of
+ // an x-axis component and a y-axis component, called xDeviation and yDeviation.
+ //
+ // The deviations are like distances but a little different. They are calculated in two
+ // steps. The first step calculates both axes in a similar way. The next step compares the
+ // two values and chooses which axis to attach along. Depending on which axis is chosen,
+ // the deviation for one axis is updated. See below for details.
+ while (!needsParent.isEmpty()) {
+ double bestDist = Double.POSITIVE_INFINITY;
+ TreeNode bestChild = null, bestParent = null;
+
+ for (var child : needsParent.values()) {
+ PointF childPos = newPos.get(child.mDisplayId);
+ float childRight = childPos.x + child.getWidth();
+ float childBottom = childPos.y + child.getHeight();
+ for (var parent : availableParents) {
+ PointF parentPos = newPos.get(parent.mDisplayId);
+ float parentRight = parentPos.x + parent.getWidth();
+ float parentBottom = parentPos.y + parent.getHeight();
+
+ // This is the smaller of the two ranges minus the amount of overlap shared
+ // between them. The "amount of overlap" is negative if there is no overlap, but
+ // this does not make a parenting ineligible, because we allow for attaching at
+ // the corner and for floating point error. The overlap is more negative the
+ // farther apart the closest corner pair is.
+ //
+ // For each axis, this calculates (SmallerRange - Overlap). If one range lies
+ // completely in the other (or they are equal), the axis' deviation will be
+ // zero.
+ //
+ // The "SmallerRange," which refers to smaller of the widths of the two rects,
+ // or smaller of the heights of the two rects, is added to the deviation so that
+ // a maximum overlap results in a deviation of zero.
+ float xSmallerRange = Math.min(child.getWidth(), parent.getWidth());
+ float ySmallerRange = Math.min(child.getHeight(), parent.getHeight());
+ float xOverlap
+ = Math.min(parentRight, childRight)
+ - Math.max(parentPos.x, childPos.x);
+ float yOverlap
+ = Math.min(parentBottom, childBottom)
+ - Math.max(parentPos.y, childPos.y);
+ float xDeviation = xSmallerRange - xOverlap;
+ float yDeviation = ySmallerRange - yOverlap;
+
+ float offset;
+ int pos;
+ if (xDeviation <= yDeviation) {
+ if (childPos.y < parentPos.y) {
+ yDeviation = childBottom - parentPos.y;
+ pos = POSITION_TOP;
+ } else {
+ yDeviation = parentBottom - childPos.y;
+ pos = POSITION_BOTTOM;
+ }
+ offset = childPos.x - parentPos.x;
+ } else {
+ if (childPos.x < parentPos.x) {
+ xDeviation = childRight - parentPos.x;
+ pos = POSITION_LEFT;
+ } else {
+ xDeviation = parentRight - childPos.x;
+ pos = POSITION_RIGHT;
+ }
+ offset = childPos.y - parentPos.y;
+ }
+
+ double dist = Math.hypot(xDeviation, yDeviation);
+ if (dist >= bestDist) {
+ continue;
+ }
+
+ bestDist = dist;
+ bestChild = child;
+ bestParent = parent;
+ // Eagerly update the child's parenting info, even though we may not use it, in
+ // which case it will be overwritten later.
+ bestChild.mPosition = pos;
+ bestChild.mOffset = offset;
+ }
+ }
+
+ assert bestParent != null & bestChild != null;
+
+ bestParent.addChild(bestChild);
+ if (null == needsParent.remove(bestChild.mDisplayId)) {
+ throw new IllegalStateException("child not in pending set! " + bestChild);
+ }
+ availableParents.add(bestChild);
+ }
+
+ // The conversion may have introduced an intersection of two display rects. If they are
+ // bigger than our error tolerance, this function will remove them.
+ normalize();
+ }
+
@Override
public int describeContents() {
return 0;
@@ -450,6 +583,20 @@
return a == b || (Float.isNaN(a) && Float.isNaN(b)) || Math.abs(a - b) < EPSILON;
}
+ private Map<Integer, TreeNode> allNodesIdMap() {
+ var pend = new ArrayDeque<TreeNode>();
+ var found = new HashMap<Integer, TreeNode>();
+
+ pend.push(mRoot);
+ do {
+ TreeNode node = pend.pop();
+ found.put(node.mDisplayId, node);
+ pend.addAll(node.mChildren);
+ } while (!pend.isEmpty());
+
+ return found;
+ }
+
public static final class TreeNode implements Parcelable {
public static final int POSITION_LEFT = 0;
public static final int POSITION_TOP = 1;
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index 96f6ad1..71b60cf 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -31,6 +31,7 @@
import static com.android.hardware.input.Flags.touchpadThreeFingerTapShortcut;
import static com.android.hardware.input.Flags.touchpadVisualizer;
import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
+import static com.android.hardware.input.Flags.useKeyGestureEventHandlerMultiPressGestures;
import static com.android.input.flags.Flags.FLAG_KEYBOARD_REPEAT_KEYS;
import static com.android.input.flags.Flags.enableInputFilterRustImpl;
import static com.android.input.flags.Flags.keyboardRepeatKeys;
@@ -1147,4 +1148,13 @@
public static boolean isCustomizableInputGesturesFeatureFlagEnabled() {
return enableCustomizableInputGestures() && useKeyGestureEventHandler();
}
+
+ /**
+ * Whether multi-key gestures are supported using {@code KeyGestureEventHandler}
+ *
+ * @hide
+ */
+ public static boolean doesKeyGestureEventHandlerSupportMultiKeyGestures() {
+ return useKeyGestureEventHandler() && useKeyGestureEventHandlerMultiPressGestures();
+ }
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 8c3f0ef..70a1909 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -3436,7 +3436,7 @@
initialize();
mInlineSuggestionSessionController.notifyOnStartInput(
editorInfo == null ? null : editorInfo.packageName,
- editorInfo == null ? null : editorInfo.autofillId);
+ editorInfo == null ? null : editorInfo.getAutofillId());
if (DEBUG) Log.v(TAG, "CALL: onStartInput");
onStartInput(editorInfo, restarting);
if (mDecorViewVisible) {
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index f728552..bf7116d 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -1371,7 +1371,6 @@
writeInt(N);
if (DEBUG_ARRAY_MAP) {
RuntimeException here = new RuntimeException("here");
- here.fillInStackTrace();
Log.d(TAG, "Writing " + N + " ArrayMap entries", here);
}
int startPos;
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 4bc8fe0..9ab9228 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -3928,9 +3928,9 @@
final int callingUid = Binder.getCallingUid();
final int processUid = Process.myUid();
- if (Build.isDebuggable() && callingUid != processUid) {
- Log.w(TAG, "Uid " + processUid + " is fetching a copy of UserProperties on"
- + " behalf of callingUid " + callingUid + ". Possibly"
+ if (processUid == Process.SYSTEM_UID && callingUid != processUid) {
+ Log.w(TAG, "The System (uid " + processUid + ") is fetching a copy of"
+ + " UserProperties on behalf of callingUid " + callingUid + ". Possibly"
+ " it should carefully first clearCallingIdentity or perhaps use"
+ " UserManagerInternal.getUserProperties() instead?",
new Throwable());
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 92c5c20..6264fbb 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -284,7 +284,7 @@
is_fixed_read_only: true
is_exported: true
namespace: "android_health_services"
- description: "This fixed read-only flag is used to enable replacing permission BODY_SENSORS (and BODY_SENSORS_BACKGROUND) with granular health permission READ_HEART_RATE (and READ_HEALTH_DATA_IN_BACKGROUND)"
+ description: "Enables replacement of BODY_SENSORS/BODY_SENSORS_BACKGROUND permissions with granular health permissions READ_HEART_RATE, READ_SKIN_TEMPERATURE, READ_OXYGEN_SATURATION, and READ_HEALTH_DATA_IN_BACKGROUND"
bug: "364638912"
}
@@ -300,24 +300,6 @@
}
flag {
- name: "platform_skin_temperature_enabled"
- is_fixed_read_only: true
- is_exported: true
- namespace: "android_health_services"
- description: "This fixed read-only flag is used to enable platform support for Skin Temperature."
- bug: "369872443"
-}
-
-flag {
- name: "platform_oxygen_saturation_enabled"
- is_fixed_read_only: true
- is_exported: true
- namespace: "android_health_services"
- description: "This fixed read-only flag is used to enable platform support for Oxygen Saturation (SpO2)."
- bug: "369873227"
-}
-
-flag {
name: "allow_host_permission_dialogs_on_virtual_devices"
is_exported: true
namespace: "permissions"
@@ -400,3 +382,21 @@
description: "Uses the SysUi process to host the SelectionToolbarRenderService."
bug: "363318732"
}
+
+flag{
+ name: "note_op_batching_enabled"
+ is_fixed_read_only: true
+ is_exported: true
+ namespace: "permissions"
+ description: "Batch noteOperations on the client to reduce binder call volume"
+ bug: "366013082"
+}
+
+flag {
+ name: "supervision_role_permission_update_enabled"
+ is_fixed_read_only: true
+ is_exported: true
+ namespace: "supervision"
+ description: "This flag is used to enable all the remaining permissions required to the supervision role"
+ bug: "367333883"
+}
\ No newline at end of file
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ef35171..d2a20b6 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1217,6 +1217,69 @@
"android.settings.REGIONAL_PREFERENCES_SETTINGS";
/**
+ * Activity Action: Show screen for allowing the region configuration.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ */
+ @FlaggedApi(Flags.FLAG_SYSTEM_REGIONAL_PREFERENCES_API_ENABLED)
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_REGION_SETTINGS =
+ "android.settings.REGION_SETTINGS";
+
+ /**
+ * Activity Action: Show first day of week configuration settings.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ */
+ @FlaggedApi(Flags.FLAG_SYSTEM_REGIONAL_PREFERENCES_API_ENABLED)
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_FIRST_DAY_OF_WEEK_SETTINGS =
+ "android.settings.FIRST_DAY_OF_WEEK_SETTINGS";
+
+ /**
+ * Activity Action: Show temperature unit configuration settings.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ */
+ @FlaggedApi(Flags.FLAG_SYSTEM_REGIONAL_PREFERENCES_API_ENABLED)
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_TEMPERATURE_UNIT_SETTINGS =
+ "android.settings.TEMPERATURE_UNIT_SETTINGS";
+
+ /**
+ * Activity Action: Show numbering system configuration settings.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ */
+ @FlaggedApi(Flags.FLAG_SYSTEM_REGIONAL_PREFERENCES_API_ENABLED)
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_NUMBERING_SYSTEM_SETTINGS =
+ "android.settings.NUMBERING_SYSTEM_SETTINGS";
+
+ /**
+ * Activity Action: Show measurement system configuration settings.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ */
+ @FlaggedApi(Flags.FLAG_SYSTEM_REGIONAL_PREFERENCES_API_ENABLED)
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_MEASUREMENT_SYSTEM_SETTINGS =
+ "android.settings.MEASUREMENT_SYSTEM_SETTINGS";
+
+ /**
* Activity Action: Show settings to allow configuration of lockscreen.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
diff --git a/core/java/android/provider/flags.aconfig b/core/java/android/provider/flags.aconfig
index 4c63673..fff5363 100644
--- a/core/java/android/provider/flags.aconfig
+++ b/core/java/android/provider/flags.aconfig
@@ -63,3 +63,11 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "system_regional_preferences_api_enabled"
+ is_exported: true
+ namespace: "globalintl"
+ description: "Feature flag for regional preferences APIs"
+ bug: "370379000"
+}
diff --git a/core/java/android/security/advancedprotection/AdvancedProtectionManager.java b/core/java/android/security/advancedprotection/AdvancedProtectionManager.java
index 6f3e3d8..9fe0dda 100644
--- a/core/java/android/security/advancedprotection/AdvancedProtectionManager.java
+++ b/core/java/android/security/advancedprotection/AdvancedProtectionManager.java
@@ -16,20 +16,30 @@
package android.security.advancedprotection;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.StringDef;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.content.Context;
+import android.content.Intent;
import android.os.Binder;
import android.os.RemoteException;
import android.security.Flags;
import android.util.Log;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.List;
+import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
@@ -45,6 +55,139 @@
public final class AdvancedProtectionManager {
private static final String TAG = "AdvancedProtectionMgr";
+ /**
+ * Advanced Protection's identifier for setting policies or restrictions in DevicePolicyManager.
+ *
+ * @hide */
+ public static final String ADVANCED_PROTECTION_SYSTEM_ENTITY =
+ "android.security.advancedprotection";
+
+ /**
+ * Feature identifier for disallowing 2G.
+ *
+ * @hide */
+ @SystemApi
+ public static final String FEATURE_ID_DISALLOW_CELLULAR_2G =
+ "android.security.advancedprotection.feature_disallow_2g";
+
+ /**
+ * Feature identifier for disallowing install of unknown sources.
+ *
+ * @hide */
+ @SystemApi
+ public static final String FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES =
+ "android.security.advancedprotection.feature_disallow_install_unknown_sources";
+
+ /**
+ * Feature identifier for disallowing USB.
+ *
+ * @hide */
+ @SystemApi
+ public static final String FEATURE_ID_DISALLOW_USB =
+ "android.security.advancedprotection.feature_disallow_usb";
+
+ /**
+ * Feature identifier for disallowing WEP.
+ *
+ * @hide */
+ @SystemApi
+ public static final String FEATURE_ID_DISALLOW_WEP =
+ "android.security.advancedprotection.feature_disallow_wep";
+
+ /**
+ * Feature identifier for enabling MTE.
+ *
+ * @hide */
+ @SystemApi
+ public static final String FEATURE_ID_ENABLE_MTE =
+ "android.security.advancedprotection.feature_enable_mte";
+
+ /** @hide */
+ @StringDef(prefix = { "FEATURE_ID_" }, value = {
+ FEATURE_ID_DISALLOW_CELLULAR_2G,
+ FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES,
+ FEATURE_ID_DISALLOW_USB,
+ FEATURE_ID_DISALLOW_WEP,
+ FEATURE_ID_ENABLE_MTE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FeatureId {}
+
+ private static final Set<String> ALL_FEATURE_IDS = Set.of(
+ FEATURE_ID_DISALLOW_CELLULAR_2G,
+ FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES,
+ FEATURE_ID_DISALLOW_USB,
+ FEATURE_ID_DISALLOW_WEP,
+ FEATURE_ID_ENABLE_MTE);
+
+ /**
+ * Activity Action: Show a dialog with disabled by advanced protection message.
+ * <p> If a user action or a setting toggle is disabled by advanced protection, this dialog can
+ * be triggered to let the user know about this.
+ * <p>
+ * Input:
+ * <p>{@link #EXTRA_SUPPORT_DIALOG_FEATURE}: The feature identifier.
+ * <p>{@link #EXTRA_SUPPORT_DIALOG_TYPE}: The type of the action.
+ * <p>
+ * Output: Nothing.
+ *
+ * @hide */
+ @SystemApi
+ @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+ @FlaggedApi(android.security.Flags.FLAG_AAPM_API)
+ public static final String ACTION_SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG =
+ "android.security.advancedprotection.action.SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG";
+
+ /**
+ * A string extra used with {@link #createSupportIntent} to identify the feature that needs to
+ * show a support dialog explaining it was disabled by advanced protection.
+ *
+ * @hide */
+ @FeatureId
+ @SystemApi
+ public static final String EXTRA_SUPPORT_DIALOG_FEATURE =
+ "android.security.advancedprotection.extra.SUPPORT_DIALOG_FEATURE";
+
+ /**
+ * A string extra used with {@link #createSupportIntent} to identify the type of the action that
+ * needs to be explained in the support dialog.
+ *
+ * @hide */
+ @SupportDialogType
+ @SystemApi
+ public static final String EXTRA_SUPPORT_DIALOG_TYPE =
+ "android.security.advancedprotection.extra.SUPPORT_DIALOG_TYPE";
+
+ /**
+ * Type for {@link #EXTRA_SUPPORT_DIALOG_TYPE} indicating a user performed an action that was
+ * blocked by advanced protection.
+ *
+ * @hide */
+ @SystemApi
+ public static final String SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION =
+ "android.security.advancedprotection.type_blocked_interaction";
+
+ /**
+ * Type for {@link #EXTRA_SUPPORT_DIALOG_TYPE} indicating a user pressed on a setting toggle
+ * that was disabled by advanced protection.
+ *
+ * @hide */
+ @SystemApi
+ public static final String SUPPORT_DIALOG_TYPE_DISABLED_SETTING =
+ "android.security.advancedprotection.type_disabled_setting";
+
+ /** @hide */
+ @StringDef(prefix = { "SUPPORT_DIALOG_TYPE_" }, value = {
+ SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION,
+ SUPPORT_DIALOG_TYPE_DISABLED_SETTING,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SupportDialogType {}
+
+ private static final Set<String> ALL_SUPPORT_DIALOG_TYPES = Set.of(
+ SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION,
+ SUPPORT_DIALOG_TYPE_DISABLED_SETTING);
+
private final ConcurrentHashMap<Callback, IAdvancedProtectionCallback>
mCallbackMap = new ConcurrentHashMap<>();
@@ -164,6 +307,43 @@
}
/**
+ * Called by a feature to display a support dialog when a feature was disabled by advanced
+ * protection. This returns an intent that can be used with
+ * {@link Context#startActivity(Intent)} to display the dialog.
+ *
+ * <p>Note that this method doesn't check if the feature is actually disabled, i.e. this method
+ * will always return an intent.
+ *
+ * @param featureId The feature identifier.
+ * @param type The type of the feature describing the action that needs to be explained
+ * in the dialog or null for default explanation.
+ * @return Intent An intent to be used to start the dialog-activity that explains a feature was
+ * disabled by advanced protection.
+ * @hide
+ */
+ @SystemApi
+ public @NonNull Intent createSupportIntent(@NonNull @FeatureId String featureId,
+ @Nullable @SupportDialogType String type) {
+ Objects.requireNonNull(featureId);
+ if (!ALL_FEATURE_IDS.contains(featureId)) {
+ throw new IllegalArgumentException(featureId + " is not a valid feature ID. See"
+ + " FEATURE_ID_* APIs.");
+ }
+ if (type != null && !ALL_SUPPORT_DIALOG_TYPES.contains(type)) {
+ throw new IllegalArgumentException(type + " is not a valid type. See"
+ + " SUPPORT_DIALOG_TYPE_* APIs.");
+ }
+
+ Intent intent = new Intent(ACTION_SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG);
+ intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(EXTRA_SUPPORT_DIALOG_FEATURE, featureId);
+ if (type != null) {
+ intent.putExtra(EXTRA_SUPPORT_DIALOG_TYPE, type);
+ }
+ return intent;
+ }
+
+ /**
* A callback class for monitoring changes to Advanced Protection state
*
* <p>To register a callback, implement this interface, and register it with
diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java
index ca20801..be4629a 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -16,6 +16,9 @@
package android.service.autofill;
+import static android.service.autofill.Flags.FLAG_FILL_DIALOG_IMPROVEMENTS;
+
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -100,7 +103,12 @@
/**
* Indicates the request supports fill dialog presentation for the fields, the
* system will send the request when the activity just started.
+ *
+ * @deprecated All requests would support fill dialog by default.
+ * Presence of this flag isn't needed.
*/
+ @FlaggedApi(FLAG_FILL_DIALOG_IMPROVEMENTS)
+ @Deprecated
public static final @RequestFlags int FLAG_SUPPORTS_FILL_DIALOG = 0x40;
/**
@@ -588,10 +596,10 @@
};
@DataClass.Generated(
- time = 1701010178309L,
+ time = 1730991738865L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/service/autofill/FillRequest.java",
- inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_SUPPORTS_FILL_DIALOG\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_IME_SHOWING\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_RESET_FILL_DIALOG_STATE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PCC_DETECTION\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_SCREEN_HAS_CREDMAN_FIELD\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_REQUESTS_CREDMAN_SERVICE\npublic static final int INVALID_REQUEST_ID\nprivate final int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mHints\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate final @android.annotation.Nullable android.content.IntentSender mDelayedFillIntentSender\nprivate void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+ inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.annotation.FlaggedApi @java.lang.Deprecated @android.service.autofill.FillRequest.RequestFlags int FLAG_SUPPORTS_FILL_DIALOG\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_IME_SHOWING\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_RESET_FILL_DIALOG_STATE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PCC_DETECTION\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_SCREEN_HAS_CREDMAN_FIELD\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_REQUESTS_CREDMAN_SERVICE\npublic static final int INVALID_REQUEST_ID\nprivate final int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mHints\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate final @android.annotation.Nullable android.content.IntentSender mDelayedFillIntentSender\nprivate void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/service/quickaccesswallet/flags.aconfig b/core/java/android/service/quickaccesswallet/flags.aconfig
new file mode 100644
index 0000000..07311d5
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.service.quickaccesswallet"
+container: "system"
+
+flag {
+ name: "launch_wallet_option_on_power_double_tap"
+ namespace: "wallet_integrations"
+ description: "Option to launch the Wallet app on double-tap of the power button"
+ bug: "378469025"
+}
\ No newline at end of file
diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java
index ae12132..a42eece 100644
--- a/core/java/android/text/Html.java
+++ b/core/java/android/text/Html.java
@@ -614,7 +614,7 @@
if (style[j] instanceof TypefaceSpan) {
String s = ((TypefaceSpan) style[j]).getFamily();
- if (s.equals("monospace")) {
+ if ("monospace".equals(s)) {
out.append("</tt>");
}
}
diff --git a/core/java/android/util/ArrayMap.java b/core/java/android/util/ArrayMap.java
index 174e0c8..7ee0ff1 100644
--- a/core/java/android/util/ArrayMap.java
+++ b/core/java/android/util/ArrayMap.java
@@ -649,7 +649,6 @@
}
if (index > 0 && mHashes[index-1] > hash) {
RuntimeException e = new RuntimeException("here");
- e.fillInStackTrace();
Log.w(TAG, "New hash " + hash
+ " is before end of array hash " + mHashes[index-1]
+ " at index " + index + (DEBUG ? " key " + key : ""), e);
diff --git a/core/java/android/util/ArraySet.java b/core/java/android/util/ArraySet.java
index bfbca07..1344bb9 100644
--- a/core/java/android/util/ArraySet.java
+++ b/core/java/android/util/ArraySet.java
@@ -526,7 +526,6 @@
// Cannot optimize since it would break the sorted order - fallback to add()
if (DEBUG) {
RuntimeException e = new RuntimeException("here");
- e.fillInStackTrace();
Log.w(TAG, "New hash " + hash
+ " is before end of array hash " + mHashes[index - 1]
+ " at index " + index, e);
diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index 5406cf5..264db4a 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -15,9 +15,11 @@
*/
package android.view;
+import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.UiThread;
import android.content.Context;
import android.graphics.Rect;
@@ -29,6 +31,8 @@
import com.android.window.flags.Flags;
+import java.util.concurrent.Executor;
+
/**
* Provides an interface to the root-Surface of a View Hierarchy or Window. This
* is used in combination with the {@link android.view.SurfaceControl} API to enable
@@ -202,4 +206,21 @@
throw new UnsupportedOperationException("The getInputTransferToken needs to be "
+ "implemented before making this call.");
}
+
+ /**
+ * Registers a {@link OnJankDataListener} to receive jank classification data about rendered
+ * frames.
+ *
+ * @param executor The executor on which the listener will be invoked.
+ * @param listener The listener to add.
+ * @return The {@link OnJankDataListenerRegistration} for the listener.
+ */
+ @NonNull
+ @FlaggedApi(Flags.FLAG_JANK_API)
+ @SuppressLint("PairedRegistration")
+ default SurfaceControl.OnJankDataListenerRegistration registerOnJankDataListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull SurfaceControl.OnJankDataListener listener) {
+ return SurfaceControl.OnJankDataListenerRegistration.NONE;
+ }
}
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 7e24749..5a71282 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -189,6 +189,11 @@
@UnsupportedAppUsage
private long mLastFrameTimeNanos;
+ // Keeps track of the last scheduled frame time without additional offsets
+ // added from buffer stuffing recovery. Used to compare timing of vsyncs to
+ // determine idle state.
+ private long mLastNoOffsetFrameTimeNanos;
+
/** DO NOT USE since this will not updated when screen refresh changes. */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R,
publicAlternatives = "Use {@link android.view.Display#getRefreshRate} instead")
@@ -203,6 +208,50 @@
private final FrameData mFrameData = new FrameData();
private volatile boolean mInDoFrameCallback = false;
+ private static class BufferStuffingData {
+ enum RecoveryAction {
+ // No recovery
+ NONE,
+ // Recovery has started, adds a negative offset
+ OFFSET,
+ // Recovery has started, delays a frame to return buffer count
+ // back toward threshold.
+ DELAY_FRAME
+ }
+ // The maximum number of times frames will be delayed per buffer stuffing event.
+ // Since buffer stuffing can persist for several consecutive frames following the
+ // initial missed frame, we want to adjust the timeline with enough frame delays and
+ // offsets to return the queued buffer count back to threshold.
+ public static final int MAX_FRAME_DELAYS = 3;
+
+ // Whether buffer stuffing recovery has begun. Recovery can only end
+ // when events are idle.
+ public boolean isRecovering = false;
+
+ // The number of frames delayed so far during recovery. Used to compare with
+ // MAX_FRAME_DELAYS to safeguard against excessive frame delays during recovery.
+ // Also used as unique cookie for tracing.
+ public int numberFrameDelays = 0;
+
+ // The number of additional frame delays scheduled during recovery to wait for the next
+ // vsync. These are scheduled when frame times appear to go backward or frames are
+ // being skipped due to FPSDivisor.
+ public int numberWaitsForNextVsync = 0;
+
+ /**
+ * After buffer stuffing recovery has ended with a detected idle state, the
+ * recovery data trackers can be reset in preparation for any future
+ * stuffing events.
+ */
+ public void reset() {
+ isRecovering = false;
+ numberFrameDelays = 0;
+ numberWaitsForNextVsync = 0;
+ }
+ }
+
+ private final BufferStuffingData mBufferStuffingData = new BufferStuffingData();
+
/**
* Contains information about the current frame for jank-tracking,
* mainly timings of key events along with a bit of metadata about
@@ -850,13 +899,99 @@
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
+ // Conducts logic for beginning or ending buffer stuffing recovery.
+ // Returns an enum for the recovery action that should be taken in doFrame().
+ BufferStuffingData.RecoveryAction checkBufferStuffingRecovery(long frameTimeNanos,
+ DisplayEventReceiver.VsyncEventData vsyncEventData) {
+ // Canned animations can recover from buffer stuffing whenever more
+ // than 2 buffers are queued.
+ if (vsyncEventData.numberQueuedBuffers > 2) {
+ mBufferStuffingData.isRecovering = true;
+ // Intentional frame delay that can happen at most MAX_FRAME_DELAYS times per
+ // buffer stuffing event until the buffer count returns to threshold. The
+ // delayed frames are compensated for by the negative offsets added to the
+ // animation timestamps.
+ if (mBufferStuffingData.numberFrameDelays < mBufferStuffingData.MAX_FRAME_DELAYS) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.asyncTraceForTrackBegin(
+ Trace.TRACE_TAG_VIEW, "Buffer stuffing recovery", "Thread "
+ + android.os.Process.myTid() + ", recover frame #"
+ + mBufferStuffingData.numberFrameDelays,
+ mBufferStuffingData.numberFrameDelays);
+ }
+ mBufferStuffingData.numberFrameDelays++;
+ scheduleVsyncLocked();
+ return BufferStuffingData.RecoveryAction.DELAY_FRAME;
+ }
+ }
+
+ if (mBufferStuffingData.isRecovering) {
+ // Includes an additional expected frame delay from the natural scheduling
+ // of the next vsync event.
+ int totalFrameDelays = mBufferStuffingData.numberFrameDelays
+ + mBufferStuffingData.numberWaitsForNextVsync + 1;
+ long vsyncsSinceLastCallback =
+ (frameTimeNanos - mLastNoOffsetFrameTimeNanos) / mLastFrameIntervalNanos;
+
+ // Detected idle state due to a longer inactive period since the last vsync callback
+ // than the total expected number of vsync frame delays. End buffer stuffing recovery.
+ // There are no frames to animate and offsets no longer need to be added
+ // since the idle state gives the animation a chance to catch up.
+ if (vsyncsSinceLastCallback > totalFrameDelays) {
+ if (DEBUG_JANK) {
+ Log.d(TAG, "End buffer stuffing recovery");
+ }
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ for (int i = 0; i < mBufferStuffingData.numberFrameDelays; i++) {
+ Trace.asyncTraceForTrackEnd(
+ Trace.TRACE_TAG_VIEW, "Buffer stuffing recovery", i);
+ }
+ }
+ mBufferStuffingData.reset();
+
+ } else {
+ if (DEBUG_JANK) {
+ Log.d(TAG, "Adjust animation timeline with a negative offset");
+ }
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.instantForTrack(
+ Trace.TRACE_TAG_VIEW, "Buffer stuffing recovery",
+ "Negative offset added to animation");
+ }
+ return BufferStuffingData.RecoveryAction.OFFSET;
+ }
+ }
+ return BufferStuffingData.RecoveryAction.NONE;
+ }
+
void doFrame(long frameTimeNanos, int frame,
DisplayEventReceiver.VsyncEventData vsyncEventData) {
final long startNanos;
final long frameIntervalNanos = vsyncEventData.frameInterval;
boolean resynced = false;
+ long offsetFrameTimeNanos = frameTimeNanos;
+
+ // Evaluate if buffer stuffing recovery needs to start or end, and
+ // what actions need to be taken for recovery.
+ switch (checkBufferStuffingRecovery(frameTimeNanos, vsyncEventData)) {
+ case NONE:
+ // Without buffer stuffing recovery, offsetFrameTimeNanos is
+ // synonymous with frameTimeNanos.
+ break;
+ case OFFSET:
+ // Add animation offset. Used to update frame timeline with
+ // offset before jitter is calculated.
+ offsetFrameTimeNanos = frameTimeNanos - frameIntervalNanos;
+ break;
+ case DELAY_FRAME:
+ // Intentional frame delay to help restore queued buffer count to threshold.
+ return;
+ default:
+ break;
+ }
+
try {
- FrameTimeline timeline = mFrameData.update(frameTimeNanos, vsyncEventData);
+ FrameTimeline timeline = mFrameData.update(offsetFrameTimeNanos, vsyncEventData);
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.traceBegin(
Trace.TRACE_TAG_VIEW, "Choreographer#doFrame " + timeline.mVsyncId);
@@ -867,15 +1002,18 @@
traceMessage("Frame not scheduled");
return; // no work to do
}
+ mLastNoOffsetFrameTimeNanos = frameTimeNanos;
if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) {
mDebugPrintNextFrameTimeDelta = false;
Log.d(TAG, "Frame time delta: "
- + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
+ + ((offsetFrameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
}
- long intendedFrameTimeNanos = frameTimeNanos;
+ long intendedFrameTimeNanos = offsetFrameTimeNanos;
startNanos = System.nanoTime();
+ // Calculating jitter involves using the original frame time without
+ // adjustments from buffer stuffing
final long jitterNanos = startNanos - frameTimeNanos;
if (jitterNanos >= frameIntervalNanos) {
frameTimeNanos = startNanos;
@@ -899,6 +1037,13 @@
+ " ms in the past.");
}
}
+ if (mBufferStuffingData.isRecovering) {
+ frameTimeNanos -= frameIntervalNanos;
+ if (DEBUG_JANK) {
+ Log.d(TAG, "Adjusted animation timeline with a negative offset after"
+ + " jitter calculation");
+ }
+ }
timeline = mFrameData.update(
frameTimeNanos, mDisplayEventReceiver, jitterNanos);
resynced = true;
@@ -910,6 +1055,9 @@
+ "previously skipped frame. Waiting for next vsync.");
}
traceMessage("Frame time goes backward");
+ if (mBufferStuffingData.isRecovering) {
+ mBufferStuffingData.numberWaitsForNextVsync++;
+ }
scheduleVsyncLocked();
return;
}
@@ -918,6 +1066,9 @@
long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
traceMessage("Frame skipped due to FPSDivisor");
+ if (mBufferStuffingData.isRecovering) {
+ mBufferStuffingData.numberWaitsForNextVsync++;
+ }
scheduleVsyncLocked();
return;
}
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 0241e94..a1a9fc6 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1786,7 +1786,12 @@
* {@code getWindowManager()} or {@code getSystemService(Context.WINDOW_SERVICE)}), the
* returned metrics provide the size of the current app window. As a result, in
* multi-window mode, the returned size can be smaller than the size of the device
- * screen.
+ * screen. System decoration handling may vary depending on API level:
+ * <ul>
+ * <li>API level 35 and above, the window size will be returned.
+ * <li>API level 34 and below, the window size minus system decoration areas and
+ * display cutout is returned.
+ * </ul>
* <li>If metrics are requested from a non-activity context (for example, the application
* context, where the WindowManager is accessed by
* {@code getApplicationContext().getSystemService(Context.WINDOW_SERVICE)}), the
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index fc7a65d..bb233d2 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -207,6 +207,8 @@
// reasonable timestamps.
public int frameTimelinesLength = 1;
+ public int numberQueuedBuffers = 0;
+
VsyncEventData() {
frameTimelines = new FrameTimeline[FRAME_TIMELINES_CAPACITY];
for (int i = 0; i < frameTimelines.length; i++) {
@@ -217,11 +219,13 @@
// Called from native code.
@SuppressWarnings("unused")
VsyncEventData(FrameTimeline[] frameTimelines, int preferredFrameTimelineIndex,
- int frameTimelinesLength, long frameInterval) {
+ int frameTimelinesLength, long frameInterval,
+ int numberQueuedBuffers) {
this.frameTimelines = frameTimelines;
this.preferredFrameTimelineIndex = preferredFrameTimelineIndex;
this.frameTimelinesLength = frameTimelinesLength;
this.frameInterval = frameInterval;
+ this.numberQueuedBuffers = numberQueuedBuffers;
}
void copyFrom(VsyncEventData other) {
@@ -231,6 +235,7 @@
for (int i = 0; i < frameTimelines.length; i++) {
frameTimelines[i].copyFrom(other.frameTimelines[i]);
}
+ numberQueuedBuffers = other.numberQueuedBuffers;
}
public FrameTimeline preferredFrameTimeline() {
diff --git a/core/java/android/view/FrameMetrics.java b/core/java/android/view/FrameMetrics.java
index 9e25a3e..58b2a67 100644
--- a/core/java/android/view/FrameMetrics.java
+++ b/core/java/android/view/FrameMetrics.java
@@ -18,10 +18,13 @@
import static android.graphics.FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
+import com.android.window.flags.Flags;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -177,6 +180,16 @@
public static final int DEADLINE = 13;
/**
+ * Metric identifier for the frame's VSync identifier.
+ * <p>
+ * The id that corresponds to the chosen frame timeline, used to correlate a frame produced
+ * by HWUI with the timeline data from the compositor.
+ * </p>
+ */
+ @FlaggedApi(Flags.FLAG_JANK_API)
+ public static final int FRAME_TIMELINE_VSYNC_ID = 14;
+
+ /**
* Identifiers for metrics available for each frame.
*
* {@see #getMetric(int)}
@@ -337,7 +350,8 @@
* @return the value of the metric or -1 if it is not available.
*/
public long getMetric(@Metric int id) {
- if (id < UNKNOWN_DELAY_DURATION || id > DEADLINE) {
+ if (id < UNKNOWN_DELAY_DURATION
+ || id > (Flags.jankApi() ? FRAME_TIMELINE_VSYNC_ID : DEADLINE)) {
return -1;
}
@@ -351,6 +365,8 @@
return mTimingData[Index.INTENDED_VSYNC];
} else if (id == VSYNC_TIMESTAMP) {
return mTimingData[Index.VSYNC];
+ } else if (id == FRAME_TIMELINE_VSYNC_ID) {
+ return mTimingData[Index.FRAME_TIMELINE_VSYNC_ID];
}
int durationsIdx = 2 * id;
@@ -358,4 +374,3 @@
- mTimingData[DURATIONS[durationsIdx]];
}
}
-
diff --git a/core/java/android/view/HapticScrollFeedbackProvider.java b/core/java/android/view/HapticScrollFeedbackProvider.java
index 0001176..c3fb855 100644
--- a/core/java/android/view/HapticScrollFeedbackProvider.java
+++ b/core/java/android/view/HapticScrollFeedbackProvider.java
@@ -16,6 +16,8 @@
package android.view;
+import static android.view.flags.Flags.dynamicViewRotaryHapticsConfiguration;
+
import android.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
@@ -41,13 +43,8 @@
private final View mView;
private final ViewConfiguration mViewConfig;
- /**
- * Flag to disable the logic in this class if the View-based scroll haptics implementation is
- * enabled. If {@code false}, this class will continue to run despite the View's scroll
- * haptics implementation being enabled. This value should be set to {@code true} when this
- * class is directly used by the View class.
- */
- private final boolean mDisabledIfViewPlaysScrollHaptics;
+ /** Whether or not this provider is being used directly by the View class. */
+ private final boolean mIsFromView;
// Info about the cause of the latest scroll event.
@@ -65,17 +62,23 @@
private boolean mHapticScrollFeedbackEnabled = false;
public HapticScrollFeedbackProvider(@NonNull View view) {
- this(view, ViewConfiguration.get(view.getContext()),
- /* disabledIfViewPlaysScrollHaptics= */ true);
+ this(view, ViewConfiguration.get(view.getContext()), /* isFromView= */ false);
}
/** @hide */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public HapticScrollFeedbackProvider(
- View view, ViewConfiguration viewConfig, boolean disabledIfViewPlaysScrollHaptics) {
+ View view, ViewConfiguration viewConfig, boolean isFromView) {
mView = view;
mViewConfig = viewConfig;
- mDisabledIfViewPlaysScrollHaptics = disabledIfViewPlaysScrollHaptics;
+ mIsFromView = isFromView;
+ if (dynamicViewRotaryHapticsConfiguration() && !isFromView) {
+ // Disable the View class's rotary scroll feedback logic if this provider is not being
+ // directly used by the View class. This is to avoid double rotary scroll feedback:
+ // one from the View class, and one from this provider instance (i.e. mute the View
+ // class's rotary feedback and enable this provider).
+ view.disableRotaryScrollFeedback();
+ }
}
@Override
@@ -151,7 +154,8 @@
mAxis = axis;
mDeviceId = deviceId;
- if (mDisabledIfViewPlaysScrollHaptics
+ if (!dynamicViewRotaryHapticsConfiguration()
+ && !mIsFromView
&& (source == InputDevice.SOURCE_ROTARY_ENCODER)
&& mViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled()) {
mHapticScrollFeedbackEnabled = false;
diff --git a/core/java/android/view/ImeFocusController.java b/core/java/android/view/ImeFocusController.java
index 9714896..2d2f79d 100644
--- a/core/java/android/view/ImeFocusController.java
+++ b/core/java/android/view/ImeFocusController.java
@@ -23,6 +23,7 @@
import android.annotation.UiThread;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.InputMethodManager;
import com.android.internal.inputmethod.InputMethodDebug;
@@ -150,6 +151,17 @@
if (!mHasImeFocus || isInLocalFocusMode(windowAttribute)) {
return InputMethodManager.DISPATCH_NOT_HANDLED;
}
+ if (Flags.refactorInsetsController() && event instanceof KeyEvent keyEvent
+ && keyEvent.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+ final var insetsController = mViewRootImpl.getInsetsController();
+ if (insetsController.getAnimationType(WindowInsets.Type.ime())
+ == InsetsController.ANIMATION_TYPE_HIDE
+ || insetsController.isPredictiveBackImeHideAnimInProgress()) {
+ // if there is an ongoing hide animation, the back event should not be dispatched
+ // to the IME.
+ return InputMethodManager.DISPATCH_NOT_HANDLED;
+ }
+ }
final InputMethodManager imm =
mViewRootImpl.mContext.getSystemService(InputMethodManager.class);
if (imm == null) {
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 26ca813..b0813f3 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1910,7 +1910,8 @@
mImeSourceConsumer.onWindowFocusLost();
}
- @VisibleForTesting
+ /** Returns the current {@link AnimationType} of an {@link InsetsType}. */
+ @VisibleForTesting(visibility = PACKAGE)
public @AnimationType int getAnimationType(@InsetsType int type) {
for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
InsetsAnimationControlRunner control = mRunningAnimations.get(i).runner;
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 206c737..68efa79 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -22,6 +22,7 @@
import static android.graphics.Matrix.MSKEW_Y;
import static android.graphics.Matrix.MTRANS_X;
import static android.graphics.Matrix.MTRANS_Y;
+import static android.view.flags.Flags.bufferStuffingRecovery;
import static android.view.SurfaceControlProto.HASH_CODE;
import static android.view.SurfaceControlProto.LAYER_ID;
import static android.view.SurfaceControlProto.NAME;
@@ -410,8 +411,19 @@
/**
* Jank information to be fed back via {@link OnJankDataListener}.
- * @hide
+ * <p>
+ * Apps may register a {@link OnJankDataListener} to get periodic batches of jank classification
+ * data from the (<a
+ * href="https://source.android.com/docs/core/graphics/surfaceflinger-windowmanagersystem">
+ * composer</a> regarding rendered frames. A frame is considered janky if it did not reach the
+ * display at the intended time, typically due to missing a rendering deadline. This API
+ * provides information that can be used to identify the root cause of the scheduling misses
+ * and provides overall frame scheduling statistics.
+ * <p>
+ * This API can be used in conjunction with the {@link FrameMetrics} API by associating jank
+ * classification data with {@link FrameMetrics} data via the frame VSync id.
*/
+ @FlaggedApi(Flags.FLAG_JANK_API)
public static class JankData {
/**
@@ -428,29 +440,105 @@
@Retention(RetentionPolicy.SOURCE)
public @interface JankType {}
- // No Jank
+ /**
+ * No jank detected, the frame was on time.
+ */
public static final int JANK_NONE = 0;
- // Jank caused by the composer missing a deadline
+
+ /**
+ * Bitmask for jank due to deadlines missed by the composer.
+ */
public static final int JANK_COMPOSER = 1 << 0;
- // Jank caused by the application missing the composer's deadline
+
+ /**
+ * Bitmask for jank due to deadlines missed by the application.
+ */
public static final int JANK_APPLICATION = 1 << 1;
- // Jank due to other unknown reasons
+
+ /**
+ * Bitmask for jank due to deadlines missed by other system components.
+ */
public static final int JANK_OTHER = 1 << 2;
+ private final long mFrameVsyncId;
+ private final @JankType int mJankType;
+ private final long mFrameIntervalNs;
+ private final long mScheduledAppFrameTimeNs;
+ private final long mActualAppFrameTimeNs;
+
+ /**
+ * @hide
+ */
public JankData(long frameVsyncId, @JankType int jankType, long frameIntervalNs,
long scheduledAppFrameTimeNs, long actualAppFrameTimeNs) {
- this.frameVsyncId = frameVsyncId;
- this.jankType = jankType;
- this.frameIntervalNs = frameIntervalNs;
- this.scheduledAppFrameTimeNs = scheduledAppFrameTimeNs;
- this.actualAppFrameTimeNs = actualAppFrameTimeNs;
+ mFrameVsyncId = frameVsyncId;
+ mJankType = jankType;
+ mFrameIntervalNs = frameIntervalNs;
+ mScheduledAppFrameTimeNs = scheduledAppFrameTimeNs;
+ mActualAppFrameTimeNs = actualAppFrameTimeNs;
}
- public final long frameVsyncId;
- public final @JankType int jankType;
- public final long frameIntervalNs;
- public final long scheduledAppFrameTimeNs;
- public final long actualAppFrameTimeNs;
+ /**
+ * Returns the id of the frame for this jank classification.
+ *
+ * @see FrameMetrics#FRAME_TIMELINE_VSYNC_ID
+ * @see Choreographer.FrameTimeline#getVsyncId
+ * @see Transaction#setFrameTimeline
+ * @return the frame id
+ */
+ public long getVsyncId() {
+ return mFrameVsyncId;
+ }
+
+ /**
+ * Returns the bitmask indicating the types of jank observed.
+ *
+ * @return the jank type bitmask
+ */
+ public @JankType int getJankType() {
+ return mJankType;
+ }
+
+ /**
+ * Returns the time between frame VSyncs in nanoseconds.
+ *
+ * @return the frame interval in ns
+ * @hide
+ */
+ public long getFrameIntervalNanos() {
+ return mFrameIntervalNs;
+ }
+
+ /**
+ * Returns the duration in nanoseconds the application was scheduled to use to render this
+ * frame.
+ * <p>
+ * Note that this may be higher than the frame interval to allow for CPU/GPU
+ * parallelization of work.
+ *
+ * @return scheduled app time in ns
+ */
+ public long getScheduledAppFrameTimeNanos() {
+ return mScheduledAppFrameTimeNs;
+ }
+
+ /**
+ * Returns the actual time in nanoseconds taken by the application to render this frame.
+ *
+ * @return the actual app time in ns
+ */
+ public long getActualAppFrameTimeNanos() {
+ return mActualAppFrameTimeNs;
+ }
+
+ @Override
+ public String toString() {
+ return "JankData{vsync=" + mFrameVsyncId
+ + ", jankType=0x" + Integer.toHexString(mJankType)
+ + ", frameInterval=" + mFrameIntervalNs + "ns"
+ + ", scheduledAppTime=" + mScheduledAppFrameTimeNs + "ns"
+ + ", actualAppTime=" + mActualAppFrameTimeNs + "ns}";
+ }
}
/**
@@ -458,12 +546,13 @@
* surface.
*
* @see JankData
- * @see #addJankDataListener
- * @hide
+ * @see #addOnJankDataListener
*/
+ @FlaggedApi(Flags.FLAG_JANK_API)
public interface OnJankDataListener {
/**
- * Called when new jank classifications are available.
+ * Called when new jank classifications are available. The listener is invoked out of band
+ * of the rendered frames with jank classification data for a batch of frames.
*/
void onJankDataAvailable(@NonNull List<JankData> jankData);
@@ -471,9 +560,22 @@
/**
* Handle to a registered {@link OnJankDatalistener}.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_JANK_API)
public static class OnJankDataListenerRegistration {
+ /** @hide */
+ public static final OnJankDataListenerRegistration NONE =
+ new OnJankDataListenerRegistration() {
+ @Override
+ public void flush() {}
+
+ @Override
+ public void removeAfter(long afterVsync) {}
+
+ @Override
+ public void release() {}
+ };
+
private final long mNativeObject;
private static final NativeAllocationRegistry sRegistry =
@@ -484,6 +586,11 @@
private final Runnable mFreeNativeResources;
private boolean mRemoved = false;
+ private OnJankDataListenerRegistration() {
+ mNativeObject = 0;
+ mFreeNativeResources = () -> {};
+ }
+
OnJankDataListenerRegistration(SurfaceControl surface, OnJankDataListener listener) {
mNativeObject = nativeCreateJankDataListenerWrapper(surface.mNativeObject, listener);
mFreeNativeResources = (mNativeObject == 0) ? () -> {} :
@@ -499,10 +606,17 @@
}
/**
- * Request the removal of the registered listener after the VSync with the provided ID. Use
- * a value <= 0 for afterVsync to remove the listener immediately. The given listener will
- * not be removed before the given VSync, but may still reveive data for frames past the
- * provided VSync.
+ * Schedule the removal of the registered listener after the frame with the provided id.
+ * <p>
+ * Because jank classification is only possible after frames have been displayed, the
+ * callbacks are always delayed. To ensure receipt of all jank classification data, an
+ * application can schedule the removal to happen no sooner than after the data for the
+ * frame with the provided id has been provided.
+ * <p>
+ * Use a value <= 0 for afterVsync to remove the listener immediately, ensuring no future
+ * callbacks.
+ *
+ * @param afterVsync the id of the Vsync after which to remove the listener
*/
public void removeAfter(long afterVsync) {
mRemoved = true;
@@ -511,6 +625,7 @@
/**
* Free the native resources associated with the listener registration.
+ * @hide
*/
public void release() {
if (!mRemoved) {
@@ -663,6 +778,13 @@
public static final int CAN_OCCLUDE_PRESENTATION = 0x00001000;
/**
+ * Indicates that the SurfaceControl should recover from buffer stuffing when
+ * possible. This is the case when the SurfaceControl is a ViewRootImpl.
+ * @hide
+ */
+ public static final int RECOVERABLE_FROM_BUFFER_STUFFING = 0x00002000;
+
+ /**
* Surface creation flag: Creates a surface where color components are interpreted
* as "non pre-multiplied" by their alpha channel. Of course this flag is
* meaningless for surfaces without an alpha channel. By default
@@ -4867,6 +4989,23 @@
nativeSetDesiredPresentTimeNanos(mNativeObject, desiredPresentTimeNanos);
return this;
}
+
+ /**
+ * Specifies that the SurfaceControl is a buffer producer that should recover from buffer
+ * stuffing, meaning that the SurfaceControl is a ViewRootImpl.
+ *
+ * @hide
+ */
+ @NonNull
+ public Transaction setRecoverableFromBufferStuffing(@NonNull SurfaceControl sc) {
+ if (bufferStuffingRecovery()) {
+ checkPreconditions(sc);
+ nativeSetFlags(mNativeObject, sc.mNativeObject, RECOVERABLE_FROM_BUFFER_STUFFING,
+ RECOVERABLE_FROM_BUFFER_STUFFING);
+ }
+ return this;
+ }
+
/**
* Writes the transaction to parcel, clearing the transaction as if it had been applied so
* it can be used to store future transactions. It's the responsibility of the parcel
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 0b61c94..049189f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -65,6 +65,7 @@
import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP;
import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION;
import static com.android.window.flags.Flags.FLAG_DELEGATE_UNHANDLED_DRAGS;
+import static com.android.window.flags.Flags.FLAG_SUPPORTS_DRAG_ASSISTANT_TO_MULTIWINDOW;
import static java.lang.Math.max;
@@ -5552,10 +5553,11 @@
/**
* Flag indicating that this drag will result in the caller activity's task to be hidden for the
- * duration of the drag, this means that the source activity will not receive drag events for
- * the current drag gesture. Only the current voice interaction service may use this flag.
- * @hide
+ * duration of the drag, which means that the source activity will not receive drag events for
+ * the current drag gesture. Only the current
+ * {@link android.service.voice.VoiceInteractionService} may use this flag.
*/
+ @FlaggedApi(FLAG_SUPPORTS_DRAG_ASSISTANT_TO_MULTIWINDOW)
public static final int DRAG_FLAG_HIDE_CALLING_TASK_ON_DRAG_START = 1 << 14;
/**
@@ -16752,9 +16754,7 @@
mPrivateFlags4 |= PFLAG4_ROTARY_HAPTICS_DETERMINED;
}
}
- final boolean processForRotaryScrollHaptics =
- isRotaryEncoderEvent && ((mPrivateFlags4 & PFLAG4_ROTARY_HAPTICS_ENABLED) != 0);
- if (processForRotaryScrollHaptics) {
+ if (isRotaryEncoderEvent && ((mPrivateFlags4 & PFLAG4_ROTARY_HAPTICS_ENABLED) != 0)) {
mPrivateFlags4 &= ~PFLAG4_ROTARY_HAPTICS_SCROLL_SINCE_LAST_ROTARY_INPUT;
mPrivateFlags4 |= PFLAG4_ROTARY_HAPTICS_WAITING_FOR_SCROLL_EVENT;
}
@@ -16771,7 +16771,10 @@
// Process scroll haptics after `onGenericMotionEvent`, since that's where scrolling usually
// happens. Some views may return false from `onGenericMotionEvent` even if they have done
// scrolling, so disregard the return value when processing for scroll haptics.
- if (processForRotaryScrollHaptics) {
+ // Check for `PFLAG4_ROTARY_HAPTICS_ENABLED` again, because the View implementation may
+ // call `disableRotaryScrollFeedback` in `onGenericMotionEvent`, which could change the
+ // value of `PFLAG4_ROTARY_HAPTICS_ENABLED`.
+ if (isRotaryEncoderEvent && ((mPrivateFlags4 & PFLAG4_ROTARY_HAPTICS_ENABLED) != 0)) {
if ((mPrivateFlags4 & PFLAG4_ROTARY_HAPTICS_SCROLL_SINCE_LAST_ROTARY_INPUT) != 0) {
doRotaryProgressForScrollHaptics(event);
} else {
@@ -18714,7 +18717,7 @@
private HapticScrollFeedbackProvider getScrollFeedbackProvider() {
if (mScrollFeedbackProvider == null) {
mScrollFeedbackProvider = new HapticScrollFeedbackProvider(this,
- ViewConfiguration.get(mContext), /* disabledIfViewPlaysScrollHaptics= */ false);
+ ViewConfiguration.get(mContext), /* isFromView= */ true);
}
return mScrollFeedbackProvider;
}
@@ -18744,6 +18747,21 @@
}
/**
+ * Disables the rotary scroll feedback implementation of the View class.
+ *
+ * <p>Note that this does NOT disable all rotary scroll feedback; it just disables the logic
+ * implemented within the View class. The child implementation of the View may implement its own
+ * rotary scroll feedback logic or use {@link ScrollFeedbackProvider} to generate rotary scroll
+ * feedback.
+ */
+ void disableRotaryScrollFeedback() {
+ // Force set PFLAG4_ROTARY_HAPTICS_DETERMINED to avoid recalculating
+ // PFLAG4_ROTARY_HAPTICS_ENABLED under any circumstance.
+ mPrivateFlags4 |= PFLAG4_ROTARY_HAPTICS_DETERMINED;
+ mPrivateFlags4 &= ~PFLAG4_ROTARY_HAPTICS_ENABLED;
+ }
+
+ /**
* This is called in response to an internal scroll in this view (i.e., the
* view scrolled its own contents). This is typically as a result of
* {@link #scrollBy(int, int)} or {@link #scrollTo(int, int)} having been
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 75d2da1..e50662a 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -140,6 +140,8 @@
import android.animation.AnimationHandler;
import android.animation.LayoutTransition;
import android.annotation.AnyThread;
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
@@ -1250,7 +1252,6 @@
mExtraDisplayListenerLogging = !TextUtils.isEmpty(name) && name.equals(mBasePackageName);
mThread = Thread.currentThread();
mLocation = new WindowLeaked(null);
- mLocation.fillInStackTrace();
mWidth = -1;
mHeight = -1;
mDirty = new Rect();
@@ -2758,6 +2759,9 @@
// Only call transferFrom if the surface has changed to prevent inc the generation ID and
// causing EGL resources to be recreated.
mSurface.transferFrom(blastSurface);
+
+ // Since the SurfaceControl is a VRI, indicate that it can recover from buffer stuffing.
+ mTransaction.setRecoverableFromBufferStuffing(mSurfaceControl).applyAsyncUnsafe();
}
private void setBoundsLayerCrop(Transaction t) {
@@ -11897,6 +11901,20 @@
}
/**
+ * {@inheritDoc}
+ */
+ @NonNull
+ @Override
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_JANK_API)
+ public SurfaceControl.OnJankDataListenerRegistration registerOnJankDataListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull SurfaceControl.OnJankDataListener listener) {
+ SurfaceControl.OnJankDataListener wrapped = (data) ->
+ executor.execute(() -> listener.onJankDataAvailable(data));
+ return mSurfaceControl.addOnJankDataListener(wrapped);
+ }
+
+ /**
* Class for managing the accessibility interaction connection
* based on the global accessibility state.
*/
diff --git a/core/java/android/view/WindowMetrics.java b/core/java/android/view/WindowMetrics.java
index 8bcc9de..12af692 100644
--- a/core/java/android/view/WindowMetrics.java
+++ b/core/java/android/view/WindowMetrics.java
@@ -107,8 +107,8 @@
* and display cutout areas depending on the calling context and target SDK level. Please refer
* to {@link Display#getSize(Point)} for details.
* <p>
- * The value reported by {@link Display#getSize(Point)} excluding system decoration areas can be
- * obtained by using:
+ * The following code snippet shows how to get the bounds excluding navigation bars and display
+ * cutout:
* <pre class="prettyprint">
* final WindowMetrics metrics = windowManager.getCurrentWindowMetrics();
* // Gets all excluding insets
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 1a45939..52c5af8 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -25,12 +25,14 @@
import static android.service.autofill.FillRequest.FLAG_SUPPORTS_FILL_DIALOG;
import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED;
import static android.service.autofill.FillRequest.FLAG_VIEW_REQUESTS_CREDMAN_SERVICE;
+import static android.service.autofill.Flags.FLAG_FILL_DIALOG_IMPROVEMENTS;
import static android.view.ContentInfo.SOURCE_AUTOFILL;
import static android.view.autofill.Helper.sDebug;
import static android.view.autofill.Helper.sVerbose;
import static android.view.autofill.Helper.toList;
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1607,7 +1609,12 @@
* the virtual view in the host view.
*
* @throws IllegalArgumentException if the {@code infos} was empty
+ *
+ * @deprecated This function will not do anything. Showing fill dialog is now fully controlled
+ * by the framework and the autofill provider.
*/
+ @FlaggedApi(FLAG_FILL_DIALOG_IMPROVEMENTS)
+ @Deprecated
public void notifyVirtualViewsReady(
@NonNull View view, @NonNull SparseArray<VirtualViewFillInfo> infos) {
Objects.requireNonNull(infos);
@@ -4034,8 +4041,13 @@
* receiving a focus event. The autofill suggestions shown will include content for
* related views as well.
* @return {@code true} if the autofill dialog is being shown
+ *
+ * @deprecated This function will not do anything. Showing fill dialog is now fully controlled
+ * by the framework and the autofill provider.
*/
// TODO(b/210926084): Consider whether to include the one-time show logic within this method.
+ @FlaggedApi(FLAG_FILL_DIALOG_IMPROVEMENTS)
+ @Deprecated
public boolean showAutofillDialog(@NonNull View view) {
Objects.requireNonNull(view);
if (shouldShowAutofillDialog(view, view.getAutofillId())) {
@@ -4073,7 +4085,12 @@
* suggestions.
* @param virtualId id identifying the virtual view inside the host view.
* @return {@code true} if the autofill dialog is being shown
+ *
+ * @deprecated This function will not do anything. Showing fill dialog is now fully controlled
+ * by the framework and the autofill provider.
*/
+ @FlaggedApi(FLAG_FILL_DIALOG_IMPROVEMENTS)
+ @Deprecated
public boolean showAutofillDialog(@NonNull View view, int virtualId) {
Objects.requireNonNull(view);
if (shouldShowAutofillDialog(view, getAutofillId(view, virtualId))) {
diff --git a/core/java/android/view/flags/scroll_feedback_flags.aconfig b/core/java/android/view/flags/scroll_feedback_flags.aconfig
index 658aa29..b180e58 100644
--- a/core/java/android/view/flags/scroll_feedback_flags.aconfig
+++ b/core/java/android/view/flags/scroll_feedback_flags.aconfig
@@ -23,3 +23,10 @@
bug: "331830899"
is_fixed_read_only: true
}
+
+flag {
+ namespace: "wear_frameworks"
+ name: "dynamic_view_rotary_haptics_configuration"
+ description: "Whether ScrollFeedbackProvider dynamically disables View-based rotary haptics."
+ bug: "377998870 "
+}
diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig
index 1b86f96..3b6343e 100644
--- a/core/java/android/view/flags/view_flags.aconfig
+++ b/core/java/android/view/flags/view_flags.aconfig
@@ -132,4 +132,12 @@
description: "Use refactored round scrollbar."
bug: "333417898"
is_fixed_read_only: true
+}
+
+flag {
+ name: "buffer_stuffing_recovery"
+ namespace: "window_surfaces"
+ description: "Recover from buffer stuffing when SurfaceFlinger misses a frame"
+ bug: "294922229"
+ is_fixed_read_only: true
}
\ No newline at end of file
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index fb3e083..a560339 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -24,6 +24,7 @@
import static android.view.inputmethod.EditorInfoProto.PRIVATE_IME_OPTIONS;
import static android.view.inputmethod.EditorInfoProto.TARGET_INPUT_METHOD_USER_ID;
import static android.view.inputmethod.Flags.FLAG_EDITORINFO_HANDWRITING_ENABLED;
+import static android.view.inputmethod.Flags.FLAG_PUBLIC_AUTOFILL_ID_IN_EDITORINFO;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
@@ -470,12 +471,10 @@
public String packageName;
/**
- * Autofill Id for the field that's currently on focus.
- *
- * <p> Marked as hide since it's only used by framework.</p>
- * @hide
+ * Autofill Id for the field that's currently on focus. See link {@link AutofillId} for more
+ * details. It is set by {@link View#getAutofillId()}
*/
- public AutofillId autofillId;
+ private AutofillId autofillId;
/**
* Identifier for the editor's field. This is optional, and may be
@@ -1200,6 +1199,28 @@
}
/**
+ * Returns the {@link AutofillId} of the view that this {@link EditorInfo} is associated with.
+ * The value is filled in with the result of {@link android.view.View#getAutofillId()
+ * View.getAutofillId()} on the view that is being edited.
+ *
+ * Note: For virtual view(e.g. Compose or Webview), default behavior is the autofillId is the id
+ * of the container view, unless the virtual view provider sets the virtual id when the
+ * InputMethodManager calls {@link android.view.View#onCreateInputConnection()} on the container
+ * view.
+ */
+ @FlaggedApi(FLAG_PUBLIC_AUTOFILL_ID_IN_EDITORINFO)
+ @Nullable
+ public AutofillId getAutofillId() {
+ return autofillId;
+ }
+
+ /** Sets the {@link AutofillId} of the view that this {@link EditorInfo} is associated with. */
+ @FlaggedApi(FLAG_PUBLIC_AUTOFILL_ID_IN_EDITORINFO)
+ public void setAutofillId(@Nullable AutofillId autofillId) {
+ this.autofillId = autofillId;
+ }
+
+ /**
* Export the state of {@link EditorInfo} into a protocol buffer output stream.
*
* @param proto Stream to write the state to
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 73f9d9f..6303c76 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2471,6 +2471,11 @@
return;
}
+ if (Flags.refactorInsetsController()) {
+ showSoftInput(rootView, statsToken, flags, resultReceiver, reason);
+ return;
+ }
+
ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
// Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread.
@@ -5174,7 +5179,7 @@
// system can verify the consistency between the uid of this process and package name passed
// from here. See comment of Context#getOpPackageName() for details.
editorInfo.packageName = servedView.getContext().getOpPackageName();
- editorInfo.autofillId = servedView.getAutofillId();
+ editorInfo.setAutofillId(servedView.getAutofillId());
editorInfo.fieldId = servedView.getId();
final InputConnection ic = servedView.onCreateInputConnection(editorInfo);
if (DEBUG) Log.v(TAG, "Starting input: editorInfo=" + editorInfo + " ic=" + ic);
@@ -5183,7 +5188,7 @@
// This ensures that even disconnected EditorInfos have well-defined attributes,
// making them consistently and straightforwardly comparable.
if (ic == null) {
- editorInfo.autofillId = AutofillId.NO_AUTOFILL_ID;
+ editorInfo.setAutofillId(AutofillId.NO_AUTOFILL_ID);
editorInfo.fieldId = 0;
}
return new Pair<>(ic, editorInfo);
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index edd9d6c..e619ab0 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -165,4 +165,13 @@
description: "Writing tools API"
bug: "373788889"
is_fixed_read_only: true
-}
\ No newline at end of file
+}
+
+flag {
+ name: "public_autofill_id_in_editorinfo"
+ is_exported: true
+ namespace: "input_method"
+ description: "Guarding public API autofillId in editor info"
+ bug: "342672560"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index 877fa74..1baf3b8 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -20,6 +20,8 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
@@ -552,6 +554,23 @@
* Parameters used in the {@link #onShowFileChooser} method.
*/
public static abstract class FileChooserParams {
+ /**
+ * Enable File System Access for webview.
+ *
+ * File System Access JS APIs window.showOpenFileChooser(), showDirectoryChooser(), and
+ * showSaveFilePicker() will invoke #onFileChooser(). Additional MODE_OPEN_FOLDER will be
+ * returned by #getMode(), #getIntent() will return ACTION_OPEN_DOCUMENT,
+ * ACTION_OPEN_DOCUMENT_TREE, and ACTION_CREATE_DOCUMENT rather than the current
+ * ACTION_GET_CONTENT, and new function #getPermissionMode() will be available.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @FlaggedApi(android.webkit.Flags.FLAG_FILE_SYSTEM_ACCESS)
+ @SystemApi
+ public static final long ENABLE_FILE_SYSTEM_ACCESS = 364980165L;
+
/** @hide */
@IntDef(prefix = { "MODE_" }, value = {
MODE_OPEN,
diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig
index 392c307..96b9dc7 100644
--- a/core/java/android/window/flags/window_surfaces.aconfig
+++ b/core/java/android/window/flags/window_surfaces.aconfig
@@ -97,3 +97,12 @@
is_fixed_read_only: true
bug: "308662081"
}
+
+flag {
+ name: "jank_api"
+ namespace: "window_surfaces"
+ description: "Adds the jank data listener to AttachedSurfaceControl"
+ is_fixed_read_only: true
+ is_exported: true
+ bug: "293949943"
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index d9de38a..4f924a82 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -160,12 +160,21 @@
}
flag {
- name: "delegate_unhandled_drags"
- is_exported: true
- namespace: "multitasking"
- description: "Enables delegating unhandled drags to SystemUI"
- bug: "320797628"
- is_fixed_read_only: true
+ name: "delegate_unhandled_drags"
+ is_exported: true
+ namespace: "multitasking"
+ description: "Enables delegating unhandled drags to SystemUI"
+ bug: "320797628"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "supports_drag_assistant_to_multiwindow"
+ is_exported: true
+ namespace: "multitasking"
+ description: "Enables support for dragging the assistant into multiwindow"
+ bug: "371206207"
+ is_fixed_read_only: true
}
flag {
diff --git a/core/java/com/android/internal/app/AppLocaleCollector.java b/core/java/com/android/internal/app/AppLocaleCollector.java
index 56f633f..ca1fc0a 100644
--- a/core/java/com/android/internal/app/AppLocaleCollector.java
+++ b/core/java/com/android/internal/app/AppLocaleCollector.java
@@ -41,7 +41,7 @@
import java.util.stream.Collectors;
/** The Locale data collector for per-app language. */
-public class AppLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase {
+public class AppLocaleCollector implements LocaleCollectorBase {
private static final String TAG = AppLocaleCollector.class.getSimpleName();
private final Context mContext;
private final String mAppPackageName;
@@ -167,8 +167,8 @@
}
@Override
- public HashSet<String> getIgnoredLocaleList(boolean translatedOnly) {
- HashSet<String> langTagsToIgnore = new HashSet<>();
+ public Set<String> getIgnoredLocaleList(boolean translatedOnly) {
+ Set<String> langTagsToIgnore = new HashSet<>();
if (mAppCurrentLocale != null) {
langTagsToIgnore.add(mAppCurrentLocale.getLocale().toLanguageTag());
diff --git a/core/java/com/android/internal/app/LocaleCollectorBase.java b/core/java/com/android/internal/app/LocaleCollectorBase.java
new file mode 100644
index 0000000..f839077
--- /dev/null
+++ b/core/java/com/android/internal/app/LocaleCollectorBase.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.internal.app;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * The interface which provides the locale list.
+ */
+public interface LocaleCollectorBase {
+
+ /** Gets the ignored locale list. */
+ Set<String> getIgnoredLocaleList(boolean translatedOnly);
+
+ /** Gets the supported locale list. */
+ Set<LocaleStore.LocaleInfo> getSupportedLocaleList(LocaleStore.LocaleInfo parent,
+ boolean translatedOnly, boolean isForCountryMode);
+
+ /** Indicates if the class work for specific package. */
+ boolean hasSpecificPackageName();
+}
diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java
index ef4acd1..ffffefa 100644
--- a/core/java/com/android/internal/app/LocalePickerWithRegion.java
+++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java
@@ -44,7 +44,10 @@
* <p>It shows suggestions at the top, then the rest of the locales.
* Allows the user to search for locales using both their native name and their name in the
* default locale.</p>
+ *
+ * @deprecated use SettingLib's widget instead of customized UIs.
*/
+@Deprecated
public class LocalePickerWithRegion extends ListFragment implements SearchView.OnQueryTextListener {
private static final String TAG = LocalePickerWithRegion.class.getSimpleName();
private static final String PARENT_FRAGMENT_NAME = "localeListEditor";
@@ -78,21 +81,6 @@
default void onParentLocaleSelected(LocaleStore.LocaleInfo locale) {}
}
- /**
- * The interface which provides the locale list.
- */
- interface LocaleCollectorBase {
- /** Gets the ignored locale list. */
- HashSet<String> getIgnoredLocaleList(boolean translatedOnly);
-
- /** Gets the supported locale list. */
- Set<LocaleStore.LocaleInfo> getSupportedLocaleList(LocaleStore.LocaleInfo parent,
- boolean translatedOnly, boolean isForCountryMode);
-
- /** Indicates if the class work for specific package. */
- boolean hasSpecificPackageName();
- }
-
private static LocalePickerWithRegion createNumberingSystemPicker(
LocaleSelectedListener listener, LocaleStore.LocaleInfo parent,
boolean translatedOnly, OnActionExpandListener onActionExpandListener,
diff --git a/core/java/com/android/internal/app/SystemLocaleCollector.java b/core/java/com/android/internal/app/SystemLocaleCollector.java
index 416f510..c7931cb 100644
--- a/core/java/com/android/internal/app/SystemLocaleCollector.java
+++ b/core/java/com/android/internal/app/SystemLocaleCollector.java
@@ -24,7 +24,7 @@
import java.util.Set;
/** The Locale data collector for System language. */
-class SystemLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase {
+public class SystemLocaleCollector implements LocaleCollectorBase {
private final Context mContext;
private LocaleList mExplicitLocales;
@@ -32,14 +32,14 @@
this(context, null);
}
- SystemLocaleCollector(Context context, LocaleList explicitLocales) {
+ public SystemLocaleCollector(Context context, LocaleList explicitLocales) {
mContext = context;
mExplicitLocales = explicitLocales;
}
@Override
- public HashSet<String> getIgnoredLocaleList(boolean translatedOnly) {
- HashSet<String> ignoreList = new HashSet<>();
+ public Set<String> getIgnoredLocaleList(boolean translatedOnly) {
+ Set<String> ignoreList = new HashSet<>();
if (!translatedOnly) {
final LocaleList userLocales = LocalePicker.getLocales();
final String[] langTags = userLocales.toLanguageTags().split(",");
diff --git a/core/java/com/android/internal/app/procstats/AssociationState.java b/core/java/com/android/internal/app/procstats/AssociationState.java
index a21a842..543adac 100644
--- a/core/java/com/android/internal/app/procstats/AssociationState.java
+++ b/core/java/com/android/internal/app/procstats/AssociationState.java
@@ -257,7 +257,6 @@
if (VALIDATE_TIMES) {
if (mActiveDuration > mAssociationState.mTotalActiveDuration) {
RuntimeException ex = new RuntimeException();
- ex.fillInStackTrace();
Slog.w(TAG, "Source act duration " + mActiveDurations
+ " exceeds total " + mAssociationState.mTotalActiveDuration
+ " in procstate " + mActiveProcState + " in source "
@@ -650,7 +649,6 @@
+ mySrc.mKey.mProcess + " to assoc " + mName);
if ((mySrc.mDuration + otherSrc.mDuration) > mTotalDuration) {
RuntimeException ex = new RuntimeException();
- ex.fillInStackTrace();
Slog.w(TAG, "Source tot duration " + mySrc.mDuration + "+"
+ otherSrc.mDuration
+ (newSrc ? " (new)" : " (old)") + " exceeds total "
@@ -665,7 +663,6 @@
+ mySrc.mKey.mProcess + " to assoc " + mName);
if ((mySrc.mActiveDuration + otherSrc.mActiveDuration) > mTotalDuration) {
RuntimeException ex = new RuntimeException();
- ex.fillInStackTrace();
Slog.w(TAG, "Source act duration " + mySrc.mActiveDuration + "+"
+ otherSrc.mActiveDuration
+ (newSrc ? " (new)" : " (old)") + " exceeds total "
@@ -746,14 +743,12 @@
if (VALIDATE_TIMES) {
if (src.mDuration > mTotalDuration) {
RuntimeException ex = new RuntimeException();
- ex.fillInStackTrace();
Slog.w(TAG, "Reading tot duration " + src.mDuration
+ " exceeds total " + mTotalDuration + " in source "
+ src.mKey.mProcess + " to assoc " + mName, ex);
}
if (src.mActiveDurations == null && src.mActiveDuration > mTotalDuration) {
RuntimeException ex = new RuntimeException();
- ex.fillInStackTrace();
Slog.w(TAG, "Reading act duration " + src.mActiveDuration
+ " exceeds total " + mTotalDuration + " in source "
+ src.mKey.mProcess + " to assoc " + mName, ex);
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index 0dbdb36..7523a2d 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -538,7 +538,6 @@
public void incActiveServices(String serviceName) {
if (DEBUG && "".equals(mName)) {
RuntimeException here = new RuntimeException("here");
- here.fillInStackTrace();
Slog.d(TAG, "incActiveServices: " + this + " service=" + serviceName
+ " to " + (mNumActiveServices+1), here);
}
@@ -551,7 +550,6 @@
public void decActiveServices(String serviceName) {
if (DEBUG && "".equals(mName)) {
RuntimeException here = new RuntimeException("here");
- here.fillInStackTrace();
Slog.d(TAG, "decActiveServices: " + this + " service=" + serviceName
+ " to " + (mNumActiveServices-1), here);
}
@@ -569,7 +567,6 @@
public void incStartedServices(int memFactor, long now, String serviceName) {
if (false) {
RuntimeException here = new RuntimeException("here");
- here.fillInStackTrace();
Slog.d(TAG, "incStartedServices: " + this + " service=" + serviceName
+ " to " + (mNumStartedServices+1), here);
}
@@ -585,7 +582,6 @@
public void decStartedServices(int memFactor, long now, String serviceName) {
if (false) {
RuntimeException here = new RuntimeException("here");
- here.fillInStackTrace();
Slog.d(TAG, "decActiveServices: " + this + " service=" + serviceName
+ " to " + (mNumStartedServices-1), here);
}
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index 44c0bd0..2834e68 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -139,7 +139,7 @@
}
static JankInfo createFromSurfaceControlCallback(SurfaceControl.JankData jankStat) {
- return new JankInfo(jankStat.frameVsyncId).update(jankStat);
+ return new JankInfo(jankStat.getVsyncId()).update(jankStat);
}
private JankInfo(long frameVsyncId) {
@@ -154,10 +154,10 @@
private JankInfo update(SurfaceControl.JankData jankStat) {
this.surfaceControlCallbackFired = true;
- this.jankType = jankStat.jankType;
- this.refreshRate = DisplayRefreshRate.getRefreshRate(jankStat.frameIntervalNs);
+ this.jankType = jankStat.getJankType();
+ this.refreshRate = DisplayRefreshRate.getRefreshRate(jankStat.getFrameIntervalNanos());
if (Flags.useSfFrameDuration()) {
- this.totalDurationNanos = jankStat.actualAppFrameTimeNs;
+ this.totalDurationNanos = jankStat.getActualAppFrameTimeNanos();
}
return this;
}
@@ -458,14 +458,14 @@
}
for (SurfaceControl.JankData jankStat : jankData) {
- if (!isInRange(jankStat.frameVsyncId)) {
+ if (!isInRange(jankStat.getVsyncId())) {
continue;
}
- JankInfo info = findJankInfo(jankStat.frameVsyncId);
+ JankInfo info = findJankInfo(jankStat.getVsyncId());
if (info != null) {
info.update(jankStat);
} else {
- mJankInfos.put((int) jankStat.frameVsyncId,
+ mJankInfos.put((int) jankStat.getVsyncId(),
JankInfo.createFromSurfaceControlCallback(jankStat));
}
}
diff --git a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
index 6b6b81f..48d0d6c 100644
--- a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
+++ b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
@@ -410,6 +410,11 @@
private int mLocaleConfigRes;
private boolean mAllowCrossUidActivitySwitchFromBelow;
+ @Nullable
+ private int[] mAlternateLauncherIconResIds;
+ @Nullable
+ private int[] mAlternateLauncherLabelResIds;
+
private List<AndroidPackageSplit> mSplits;
@NonNull
@@ -874,6 +879,18 @@
return adoptPermissions;
}
+ @Nullable
+ @Override
+ public int[] getAlternateLauncherIconResIds() {
+ return mAlternateLauncherIconResIds;
+ }
+
+ @Nullable
+ @Override
+ public int[] getAlternateLauncherLabelResIds() {
+ return mAlternateLauncherLabelResIds;
+ }
+
@NonNull
@Override
public List<ParsedApexSystemService> getApexSystemServices() {
@@ -1888,6 +1905,19 @@
}
@Override
+ public PackageImpl setAlternateLauncherIconResIds(@Nullable int[] alternateLauncherIconResIds) {
+ this.mAlternateLauncherIconResIds = alternateLauncherIconResIds;
+ return this;
+ }
+
+ @Override
+ public PackageImpl setAlternateLauncherLabelResIds(
+ @Nullable int[] alternateLauncherLabelResIds) {
+ this.mAlternateLauncherLabelResIds = alternateLauncherLabelResIds;
+ return this;
+ }
+
+ @Override
public PackageImpl setTaskReparentingAllowed(boolean value) {
return setBoolean(Booleans.ALLOW_TASK_REPARENTING, value);
}
@@ -3273,6 +3303,8 @@
dest.writeLong(this.mBooleans2);
dest.writeBoolean(this.mAllowCrossUidActivitySwitchFromBelow);
dest.writeInt(this.mIntentMatchingFlags);
+ dest.writeIntArray(this.mAlternateLauncherIconResIds);
+ dest.writeIntArray(this.mAlternateLauncherLabelResIds);
}
private void writeFeatureFlagState(@NonNull Parcel dest) {
@@ -3465,6 +3497,8 @@
this.mBooleans2 = in.readLong();
this.mAllowCrossUidActivitySwitchFromBelow = in.readBoolean();
this.mIntentMatchingFlags = in.readInt();
+ this.mAlternateLauncherIconResIds = in.createIntArray();
+ this.mAlternateLauncherLabelResIds = in.createIntArray();
assignDerivedFields();
assignDerivedFields2();
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
index f4bceb8..67b985a 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
@@ -413,6 +413,18 @@
ParsingPackage setOnBackInvokedCallbackEnabled(boolean enableOnBackInvokedCallback);
+ /**
+ * Set the drawable resources id array of the alternate icons that are parsing from the
+ * AndroidManifest file
+ */
+ ParsingPackage setAlternateLauncherIconResIds(int[] alternateLauncherIconResIds);
+
+ /**
+ * Set the string resources id array of the alternate labels that are parsing from the
+ * AndroidManifest file
+ */
+ ParsingPackage setAlternateLauncherLabelResIds(int[] alternateLauncherLabelResIds);
+
@CallSuper
ParsedPackage hideAsParsed();
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
index 5db7b41..8a6e6be 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -46,6 +46,7 @@
import android.content.pm.ConfigurationInfo;
import android.content.pm.FeatureGroupInfo;
import android.content.pm.FeatureInfo;
+import android.content.pm.Flags;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.Property;
@@ -154,6 +155,13 @@
private static final String TAG = ParsingUtils.TAG;
+ // It is the maximum length of the typedArray of {@link android.R.attr#alternateIcons}
+ // and {@link android.R.attr#alternateLabels}.
+ private static final int MAXIMUM_LAUNCHER_ALTERNATE_IDS_LENGTH = 500;
+
+ private static final String TYPE_STRING = "string";
+ private static final String TYPE_DRAWABLE = "drawable";
+
public static final boolean DEBUG_JAR = false;
public static final boolean DEBUG_BACKUP = false;
public static final float DEFAULT_PRE_O_MAX_ASPECT_RATIO = 1.86f;
@@ -2021,6 +2029,24 @@
pkg.setManageSpaceActivityName(manageSpaceActivityName);
}
+ if (Flags.changeLauncherBadging()) {
+ ParseResult<int[]> result = drawableResIdArray(input, sa, res,
+ R.styleable.AndroidManifestApplication_alternateLauncherIcons,
+ MAXIMUM_LAUNCHER_ALTERNATE_IDS_LENGTH);
+ if (result.isError()) {
+ return input.error(result);
+ }
+ pkg.setAlternateLauncherIconResIds(result.getResult());
+
+ result = stringResIdArray(input, sa, res,
+ R.styleable.AndroidManifestApplication_alternateLauncherLabels,
+ MAXIMUM_LAUNCHER_ALTERNATE_IDS_LENGTH);
+ if (result.isError()) {
+ return input.error(result);
+ }
+ pkg.setAlternateLauncherLabelResIds(result.getResult());
+ }
+
if (pkg.isBackupAllowed()) {
// backupAgent, killAfterRestore, fullBackupContent, backupInForeground,
// and restoreAnyVersion are only relevant if backup is possible for the
@@ -3395,6 +3421,95 @@
return sa.getResourceId(attribute, 0);
}
+ /**
+ * Parse the drawable resource id array in the typed array {@code resourceId}
+ * if available. If {@code maxSize} is not zero, only parse and preserve at most
+ * {@code maxSize} ids.
+ */
+ private static ParseResult<int[]> drawableResIdArray(ParseInput input, @NonNull TypedArray sa,
+ @NonNull Resources res, int resourceId, int maxSize) {
+ return resIdArray(input, sa, res, resourceId, TYPE_DRAWABLE, maxSize);
+ }
+
+ /**
+ * Parse the string resource id array in the typed array {@code resourceId}
+ * if available. If {@code maxSize} is not zero, only parse and preserve at most
+ * {@code maxSize} ids.
+ */
+ private static ParseResult<int[]> stringResIdArray(ParseInput input, @NonNull TypedArray sa,
+ @NonNull Resources res, int resourceId, int maxSize) {
+ return resIdArray(input, sa, res, resourceId, TYPE_STRING, maxSize);
+ }
+
+ /**
+ * Parse the resource id array in the typed array {@code resourceId}
+ * if available. If {@code maxSize} is larger than zero, only parse and preserve
+ * at most {@code maxSize} ids that type is matched to the {@code expectedTypeName}.
+ * Because the TypedArray allows mixed types in an array, if {@code expectedTypeName}
+ * is null, it means don't check the type.
+ */
+ private static ParseResult<int[]> resIdArray(ParseInput input, @NonNull TypedArray sa,
+ @NonNull Resources res, int resourceId, @Nullable String expectedTypeName,
+ int maxSize) {
+ if (!sa.hasValue(resourceId)) {
+ return input.success(null);
+ }
+
+ final int typeArrayResId = sa.getResourceId(resourceId, /* defValue= */ 0);
+ if (typeArrayResId == 0) {
+ return input.success(null);
+ }
+
+ // Parse the typedArray
+ try (TypedArray typedArray = res.obtainTypedArray(typeArrayResId)) {
+ final String typedArrayName = res.getResourceName(typeArrayResId);
+ final int length = typedArray.length();
+ if (maxSize > 0 && length > maxSize) {
+ return input.error(TextUtils.formatSimple(
+ "The length of the typedArray (%s) is larger than %d.",
+ typedArrayName, maxSize));
+ }
+ Set<Integer> resourceIdSet = new ArraySet<>();
+ for (int i = 0; i < length; i++) {
+ final int id = typedArray.getResourceId(i, /* defValue= */ 0);
+ // Add the id when the conditions are all matched:
+ // 1. The resource Id is not 0
+ // 2. The type is the expected type
+ // 3. The id is not duplicated
+ if (id == 0) {
+ return input.error(TextUtils.formatSimple(
+ "There is an item that is not a resource id in the typedArray (%s).",
+ typedArrayName));
+ }
+
+ try {
+ if (resourceIdSet.contains(id)) {
+ return input.error(TextUtils.formatSimple(
+ "There is a duplicated resource (%s) in the typedArray (%s).",
+ res.getResourceName(id), typedArrayName));
+ }
+ final String typeName = res.getResourceTypeName(id);
+ if (expectedTypeName != null
+ && !TextUtils.equals(typeName, expectedTypeName)) {
+ return input.error(TextUtils.formatSimple(
+ "There is a resource (%s) in the typedArray (%s) that is not a"
+ + " %s type.", res.getResourceName(id), typedArrayName,
+ expectedTypeName));
+ }
+ } catch (Resources.NotFoundException e) {
+ return input.error(TextUtils.formatSimple(
+ "There is a resource in the typedArray (%s) that is not found in"
+ + " the app resources.", typedArrayName));
+ }
+ resourceIdSet.add(id);
+ }
+ if (resourceIdSet.isEmpty()) {
+ return input.success(null);
+ }
+ return input.success(resourceIdSet.stream().mapToInt(i -> i).toArray());
+ }
+ }
+
private static String string(@StyleableRes int attribute, TypedArray sa) {
return sa.getString(attribute);
}
diff --git a/core/java/com/android/server/pm/pkg/AndroidPackage.java b/core/java/com/android/server/pm/pkg/AndroidPackage.java
index 5350059..d05f5e3 100644
--- a/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -91,6 +91,28 @@
public interface AndroidPackage {
/**
+ * An array containing the drawable resources that used for the launcher
+ * activity icons.
+ *
+ * @see R.attr#alternateLauncherIcons
+ * @hide
+ */
+ @Immutable.Ignore
+ @Nullable
+ int[] getAlternateLauncherIconResIds();
+
+ /**
+ * An array containing the string resources that used for the launcher
+ * activity labels.
+ *
+ * @see R.attr#alternateLauncherLabels
+ * @hide
+ */
+ @Immutable.Ignore
+ @Nullable
+ int[] getAlternateLauncherLabelResIds();
+
+ /**
* @see ApplicationInfo#className
* @see R.styleable#AndroidManifestApplication_name
*/
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index f007cc5..a09c405 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -67,6 +67,7 @@
jfieldID preferredFrameTimelineIndex;
jfieldID frameTimelinesLength;
jfieldID frameTimelines;
+ jfieldID numberQueuedBuffers;
} vsyncEventDataClassInfo;
} gDisplayEventReceiverClassInfo;
@@ -165,7 +166,8 @@
return env->NewObject(gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz,
gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.init,
frameTimelineObjs.get(), vsyncEventData.preferredFrameTimelineIndex,
- vsyncEventData.frameTimelinesLength, vsyncEventData.frameInterval);
+ vsyncEventData.frameTimelinesLength, vsyncEventData.frameInterval,
+ vsyncEventData.numberQueuedBuffers);
}
void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId,
@@ -188,6 +190,9 @@
env->SetLongField(vsyncEventDataObj.get(),
gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.frameInterval,
vsyncEventData.frameInterval);
+ env->SetIntField(vsyncEventDataObj.get(),
+ gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.numberQueuedBuffers,
+ vsyncEventData.numberQueuedBuffers);
ScopedLocalRef<jobjectArray>
frameTimelinesObj(env,
@@ -441,7 +446,7 @@
GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz,
"<init>",
"([Landroid/view/"
- "DisplayEventReceiver$VsyncEventData$FrameTimeline;IIJ)V");
+ "DisplayEventReceiver$VsyncEventData$FrameTimeline;IIJI)V");
gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.preferredFrameTimelineIndex =
GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz,
@@ -456,6 +461,9 @@
GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz,
"frameTimelines",
"[Landroid/view/DisplayEventReceiver$VsyncEventData$FrameTimeline;");
+ gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.numberQueuedBuffers =
+ GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz,
+ "numberQueuedBuffers", "I");
return res;
}
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index d3bf36e..82b463e 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -2163,7 +2163,7 @@
class JankDataListenerWrapper : public JankDataListener {
public:
- JankDataListenerWrapper(JNIEnv* env, jobject onJankDataListenerObject) {
+ JankDataListenerWrapper(JNIEnv* env, jobject onJankDataListenerObject) : mRemovedVsyncId(-1) {
mOnJankDataListenerWeak = env->NewWeakGlobalRef(onJankDataListenerObject);
env->GetJavaVM(&mVm);
}
@@ -2174,6 +2174,12 @@
}
bool onJankDataAvailable(const std::vector<gui::JankData>& jankData) override {
+ // Don't invoke the listener if we've been force removed and got this
+ // out-of-order callback.
+ if (mRemovedVsyncId == 0) {
+ return false;
+ }
+
JNIEnv* env = getEnv();
jobject target = env->NewLocalRef(mOnJankDataListenerWeak);
@@ -2181,9 +2187,29 @@
return false;
}
- jobjectArray jJankDataArray = env->NewObjectArray(jankData.size(),
- gJankDataClassInfo.clazz, nullptr);
- for (size_t i = 0; i < jankData.size(); i++) {
+ // Compute the count of data items we'll actually forward to Java.
+ size_t count = 0;
+ if (mRemovedVsyncId <= 0) {
+ count = jankData.size();
+ } else {
+ for (const gui::JankData& frame : jankData) {
+ if (frame.frameVsyncId <= mRemovedVsyncId) {
+ count++;
+ }
+ }
+ }
+
+ if (count == 0) {
+ return false;
+ }
+
+ jobjectArray jJankDataArray = env->NewObjectArray(count, gJankDataClassInfo.clazz, nullptr);
+ for (size_t i = 0, j = 0; i < jankData.size() && j < count; i++) {
+ // Filter any data for frames past our removal vsync.
+ if (mRemovedVsyncId > 0 && jankData[i].frameVsyncId > mRemovedVsyncId) {
+ continue;
+ }
+
// The exposed constants in SurfaceControl are simplified, so we need to translate the
// jank type we get from SF to what is exposed in Java.
int sfJankType = jankData[i].jankType;
@@ -2210,7 +2236,7 @@
jankData[i].frameVsyncId, javaJankType,
jankData[i].frameIntervalNs, jankData[i].scheduledAppFrameTimeNs,
jankData[i].actualAppFrameTimeNs);
- env->SetObjectArrayElement(jJankDataArray, i, jJankData);
+ env->SetObjectArrayElement(jJankDataArray, j++, jJankData);
env->DeleteLocalRef(jJankData);
}
@@ -2225,6 +2251,11 @@
return true;
}
+ void removeListener(int64_t afterVsyncId) {
+ mRemovedVsyncId = (afterVsyncId <= 0) ? 0 : afterVsyncId;
+ JankDataListener::removeListener(afterVsyncId);
+ }
+
private:
JNIEnv* getEnv() {
@@ -2235,6 +2266,7 @@
JavaVM* mVm;
jobject mOnJankDataListenerWeak;
+ int64_t mRemovedVsyncId;
};
static jlong nativeCreateJankDataListenerWrapper(JNIEnv* env, jclass clazz,
diff --git a/core/jni/com_android_internal_content_FileSystemUtils.cpp b/core/jni/com_android_internal_content_FileSystemUtils.cpp
index 6c72544..76ead2a 100644
--- a/core/jni/com_android_internal_content_FileSystemUtils.cpp
+++ b/core/jni/com_android_internal_content_FileSystemUtils.cpp
@@ -22,7 +22,6 @@
#include <android-base/hex.h>
#include <android-base/unique_fd.h>
#include <bionic/macros.h>
-#include <elf.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
@@ -204,7 +203,8 @@
return true;
}
-bool punchHolesInElf64(const char *filePath, const uint64_t offset) {
+bool getLoadSegmentPhdrs(const char *filePath, const uint64_t offset,
+ std::vector<Elf64_Phdr> &programHeaders) {
// Open Elf file
Elf64_Ehdr ehdr;
std::ifstream inputStream(filePath, std::ifstream::in);
@@ -227,11 +227,6 @@
uint64_t programHeaderOffset = ehdr.e_phoff;
uint16_t programHeaderNum = ehdr.e_phnum;
- IF_ALOGD() {
- ALOGD("Punching holes in file: %s programHeaderOffset: %" PRIu64 " programHeaderNum: %hu",
- filePath, programHeaderOffset, programHeaderNum);
- }
-
// if this is a zip file, also consider elf offset inside a file
uint64_t phOffset;
if (__builtin_add_overflow(offset, programHeaderOffset, &phOffset)) {
@@ -240,7 +235,6 @@
}
inputStream.seekg(phOffset);
- std::vector<Elf64_Phdr> programHeaders;
for (int headerIndex = 0; headerIndex < programHeaderNum; headerIndex++) {
Elf64_Phdr header;
inputStream.read((char *)&header, sizeof(header));
@@ -254,6 +248,15 @@
programHeaders.push_back(header);
}
+ return true;
+}
+
+bool punchHolesInElf64(const char *filePath, const uint64_t offset) {
+ std::vector<Elf64_Phdr> programHeaders;
+ if (!getLoadSegmentPhdrs(filePath, offset, programHeaders)) {
+ ALOGE("Failed to read program headers from ELF file.");
+ return false;
+ }
return punchHoles(filePath, offset, programHeaders);
}
diff --git a/core/jni/com_android_internal_content_FileSystemUtils.h b/core/jni/com_android_internal_content_FileSystemUtils.h
index 52445e2..4a95686c 100644
--- a/core/jni/com_android_internal_content_FileSystemUtils.h
+++ b/core/jni/com_android_internal_content_FileSystemUtils.h
@@ -15,8 +15,11 @@
*/
#pragma once
+#include <elf.h>
#include <sys/types.h>
+#include <vector>
+
namespace android {
/*
@@ -35,4 +38,11 @@
*/
bool punchHolesInZip(const char* filePath, uint64_t offset, uint16_t extraFieldLen);
+/*
+ * This function reads program headers from ELF file. ELF can be specified with file path directly
+ * or it should be at offset inside Apk. Program headers passed to function is populated.
+ */
+bool getLoadSegmentPhdrs(const char* filePath, const uint64_t offset,
+ std::vector<Elf64_Phdr>& programHeaders);
+
} // namespace android
\ No newline at end of file
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index dc054a4..969ee2e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1849,6 +1849,10 @@
<item>-1</item>
</integer-array>
+ <!-- Specifies the delay in milliseconds after the last user input before turning off the
+ keyboard backlight.
+ -->
+ <integer name="config_keyboardBacklightTimeoutMs">30000</integer>
<!-- An array describing the screen's backlight values corresponding to the brightness
values in the config_screenBrightnessNits array.
diff --git a/core/res/res/values/stoppable_fgs_system_apps.xml b/core/res/res/values/stoppable_fgs_system_apps.xml
index 165ff61..06843f4 100644
--- a/core/res/res/values/stoppable_fgs_system_apps.xml
+++ b/core/res/res/values/stoppable_fgs_system_apps.xml
@@ -19,6 +19,7 @@
<resources>
<!-- A list of system apps whose FGS can be stopped in the task manager. -->
<string-array translatable="false" name="stoppable_fgs_system_apps">
+ <item>com.android.virtualization.terminal</item>
</string-array>
<!-- stoppable_fgs_system_apps which is supposed to be overridden by vendor -->
<string-array translatable="false" name="vendor_stoppable_fgs_system_apps">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index badb986..9dd3027 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2098,6 +2098,7 @@
<java-symbol type="integer" name="config_autoBrightnessDarkeningLightDebounce"/>
<java-symbol type="integer" name="config_autoBrightnessInitialLightSensorRate"/>
<java-symbol type="integer" name="config_autoBrightnessLightSensorRate"/>
+ <java-symbol type="integer" name="config_keyboardBacklightTimeoutMs" />
<java-symbol type="integer" name="config_carDockKeepsScreenOn" />
<java-symbol type="integer" name="config_criticalBatteryWarningLevel" />
<java-symbol type="integer" name="config_datause_notification_type" />
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java
index 1721e1e..9dd196d 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java
@@ -79,7 +79,7 @@
TEST_EDITOR_INFO.label = "testLabel";
TEST_EDITOR_INFO.packageName = "android.view.inputmethod";
TEST_EDITOR_INFO.fieldId = 0;
- TEST_EDITOR_INFO.autofillId = AutofillId.NO_AUTOFILL_ID;
+ TEST_EDITOR_INFO.setAutofillId(AutofillId.NO_AUTOFILL_ID);
TEST_EDITOR_INFO.fieldName = "testField";
TEST_EDITOR_INFO.extras = new Bundle();
TEST_EDITOR_INFO.extras.putString("testKey", "testValue");
@@ -531,7 +531,7 @@
info.setStylusHandwritingEnabled(true);
}
info.packageName = "android.view.inputmethod";
- info.autofillId = new AutofillId(123);
+ info.setAutofillId(new AutofillId(123));
info.fieldId = 456;
info.fieldName = "testField";
info.extras = new Bundle();
@@ -597,7 +597,7 @@
@Test
public void testKindofEqualsComparesAutofillId() {
final EditorInfo infoCopy = TEST_EDITOR_INFO.createCopyInternal();
- infoCopy.autofillId = new AutofillId(42);
+ infoCopy.setAutofillId(new AutofillId(42));
assertFalse(TEST_EDITOR_INFO.kindofEquals(infoCopy));
}
diff --git a/core/tests/coretests/src/android/app/activity/ActivityTestsBase.java b/core/tests/coretests/src/android/app/activity/ActivityTestsBase.java
index 232abe2..7f069ad 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityTestsBase.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityTestsBase.java
@@ -22,7 +22,7 @@
import android.test.AndroidTestCase;
import android.test.PerformanceTestCase;
-public class ActivityTestsBase extends AndroidTestCase
+public class ActivityTestsBase extends AndroidTestCase
implements PerformanceTestCase, LaunchpadActivity.CallingTest {
public static final String PERMISSION_GRANTED =
"com.android.frameworks.coretests.permission.TEST_GRANTED";
@@ -111,7 +111,6 @@
public void finishWithResult(int resultCode, Intent data) {
RuntimeException where = new RuntimeException("Original error was here");
- where.fillInStackTrace();
finishWithResult(resultCode, data, where);
}
@@ -194,15 +193,15 @@
public int getResultCode() {
return mResultCode;
}
-
+
public Intent getResultData() {
return mData;
}
-
+
public RuntimeException getResultStack() {
return mResultStack;
}
-
+
public void onTimeout() {
String msg = mExpecting == null
? "Timeout" : ("Timeout while expecting " + mExpecting);
diff --git a/core/tests/coretests/src/android/app/activity/LaunchpadActivity.java b/core/tests/coretests/src/android/app/activity/LaunchpadActivity.java
index fda249f..9b358e0 100644
--- a/core/tests/coretests/src/android/app/activity/LaunchpadActivity.java
+++ b/core/tests/coretests/src/android/app/activity/LaunchpadActivity.java
@@ -461,7 +461,6 @@
mResultCode = resultCode;
mData = data;
mResultStack = new RuntimeException("Original error was here");
- mResultStack.fillInStackTrace();
}
private void registerMyReceiver(IntentFilter filter) {
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt b/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt
index a6de611..8969b2b 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt
+++ b/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt
@@ -16,7 +16,10 @@
package android.hardware.display
+import android.graphics.PointF
+import android.graphics.RectF
import android.hardware.display.DisplayTopology.TreeNode.POSITION_BOTTOM
+import android.hardware.display.DisplayTopology.TreeNode.POSITION_LEFT
import android.hardware.display.DisplayTopology.TreeNode.POSITION_TOP
import android.hardware.display.DisplayTopology.TreeNode.POSITION_RIGHT
import android.view.Display
@@ -469,4 +472,205 @@
assertThat(actualDisplay4.offset).isEqualTo(-400f)
assertThat(actualDisplay4.children).isEmpty()
}
-}
\ No newline at end of file
+
+ @Test
+ fun rearrange_twoDisplays() {
+ val nodes = rearrangeRects(
+ // Arrange in staggered manner, connected vertically.
+ RectF(100f, 100f, 250f, 200f),
+ RectF(150f, 200f, 300f, 300f),
+ )
+
+ assertThat(nodes[0].children).containsExactly(nodes[1])
+ assertThat(nodes[1].children).isEmpty()
+ assertPositioning(nodes, Pair(POSITION_BOTTOM, 50f))
+ }
+
+ @Test
+ fun rearrange_reverseOrderOfSeveralDisplays() {
+ val nodes = rearrangeRects(
+ RectF(0f, 0f, 150f, 100f),
+ RectF(-150f, 0f, 0f, 100f),
+ RectF(-300f, 0f, -150f, 100f),
+ RectF(-450f, 0f, -300f, 100f),
+ )
+
+ assertPositioning(
+ nodes,
+ Pair(POSITION_LEFT, 0f),
+ Pair(POSITION_LEFT, 0f),
+ Pair(POSITION_LEFT, 0f),
+ )
+
+ assertThat(nodes[0].children).containsExactly(nodes[1])
+ assertThat(nodes[1].children).containsExactly(nodes[2])
+ assertThat(nodes[2].children).containsExactly(nodes[3])
+ assertThat(nodes[3].children).isEmpty()
+ }
+
+ @Test
+ fun rearrange_crossWithRootInCenter() {
+ val nodes = rearrangeRects(
+ RectF(0f, 0f, 150f, 100f),
+ RectF(-150f, 0f, 0f, 100f),
+ RectF(0f,-100f, 150f, 0f),
+ RectF(150f, 0f, 300f, 100f),
+ RectF(0f, 100f, 150f, 200f),
+ )
+
+ assertPositioning(
+ nodes,
+ Pair(POSITION_LEFT, 0f),
+ Pair(POSITION_TOP, 0f),
+ Pair(POSITION_RIGHT, 0f),
+ Pair(POSITION_BOTTOM, 0f),
+ )
+
+ assertThat(nodes[0].children)
+ .containsExactly(nodes[1], nodes[2], nodes[3], nodes[4])
+ }
+
+ @Test
+ fun rearrange_elbowArrangementDoesNotUseCornerAdjacency1() {
+ val nodes = rearrangeRects(
+ // 2
+ // |
+ // 0 - 1
+
+ RectF(0f, 0f, 100f, 100f),
+ RectF(100f, 0f, 200f, 100f),
+ RectF(100f, -100f, 200f, 0f),
+ )
+
+ assertThat(nodes[0].children).containsExactly(nodes[1])
+ assertThat(nodes[1].children).containsExactly(nodes[2])
+ assertThat(nodes[2].children).isEmpty()
+
+ assertPositioning(
+ nodes,
+ Pair(POSITION_RIGHT, 0f),
+ Pair(POSITION_TOP, 0f),
+ )
+ }
+
+ @Test
+ fun rearrange_elbowArrangementDoesNotUseCornerAdjacency2() {
+ val nodes = rearrangeRects(
+ // 0
+ // |
+ // 1
+ // |
+ // 3 - 2
+
+ RectF(0f, 0f, 100f, 100f),
+ RectF(0f, 100f, 100f, 200f),
+ RectF(0f, 200f, 100f, 300f),
+ RectF(-100f, 200f, 0f, 300f),
+ )
+
+ assertThat(nodes[0].children).containsExactly(nodes[1])
+ assertThat(nodes[1].children).containsExactly(nodes[2])
+ assertThat(nodes[2].children).containsExactly(nodes[3])
+ assertThat(nodes[3].children).isEmpty()
+
+ assertPositioning(
+ nodes,
+ Pair(POSITION_BOTTOM, 0f),
+ Pair(POSITION_BOTTOM, 0f),
+ Pair(POSITION_LEFT, 0f),
+ )
+ }
+
+ @Test
+ fun rearrange_useLargerEdge() {
+ val nodes = rearrangeRects(
+ //444111
+ //444111
+ //444111
+ // 000222
+ // 000222
+ // 000222
+ // 333
+ // 333
+ // 333
+ RectF(20f, 30f, 50f, 60f),
+ RectF(30f, 0f, 60f, 30f),
+ RectF(50f, 30f, 80f, 60f),
+ RectF(40f, 60f, 70f, 90f),
+ RectF(0f, 0f, 30f, 30f),
+ )
+
+ assertPositioning(
+ nodes,
+ Pair(POSITION_TOP, 10f),
+ Pair(POSITION_RIGHT, 0f),
+ Pair(POSITION_BOTTOM, -10f),
+ Pair(POSITION_LEFT, 0f),
+ )
+
+ assertThat(nodes[0].children).containsExactly(nodes[1], nodes[2])
+ assertThat(nodes[1].children).containsExactly(nodes[4])
+ assertThat(nodes[2].children).containsExactly(nodes[3])
+ (3..4).forEach { assertThat(nodes[it].children).isEmpty() }
+ }
+
+ @Test
+ fun rearrange_closeGaps() {
+ val nodes = rearrangeRects(
+ //000
+ //000 111
+ //000 111
+ // 111
+ //
+ // 222
+ // 222
+ // 222
+ RectF(0f, 0f, 30f, 30f),
+ RectF(40f, 10f, 70f, 40f),
+ RectF(80.5f, 50f, 110f, 80f), // left+=0.5 to cause a preference for TOP/BOTTOM attach
+ )
+
+ assertPositioning(
+ nodes,
+ // In the case of corner adjacency, we prefer a left/right attachment.
+ Pair(POSITION_RIGHT, 10f),
+ Pair(POSITION_BOTTOM, 40.5f), // TODO: fix implementation to remove this gap
+ )
+
+ assertThat(nodes[0].children).containsExactly(nodes[1])
+ assertThat(nodes[1].children).containsExactly(nodes[2])
+ assertThat(nodes[2].children).isEmpty()
+ }
+
+ /**
+ * Runs the rearrange algorithm and returns the resulting tree as a list of nodes, with the
+ * root at index 0. The number of nodes is inferred from the number of positions passed.
+ */
+ private fun rearrangeRects(vararg pos : RectF) : List<DisplayTopology.TreeNode> {
+ // Generates a linear sequence of nodes in order in the List from root to leaf,
+ // left-to-right. IDs are ascending from 0 to count - 1.
+
+ val nodes = pos.indices.map {
+ DisplayTopology.TreeNode(it, pos[it].width(), pos[it].height(), POSITION_RIGHT, 0f)
+ }
+
+ nodes.forEachIndexed { id, node ->
+ if (id > 0) {
+ nodes[id - 1].addChild(node)
+ }
+ }
+
+ DisplayTopology(nodes[0], 0).rearrange(pos.indices.associateWith {
+ PointF(pos[it].left, pos[it].top)
+ })
+
+ return nodes
+ }
+
+ private fun assertPositioning(
+ nodes : List<DisplayTopology.TreeNode>, vararg positions : Pair<Int, Float>) {
+ assertThat(nodes.drop(1).map { Pair(it.position, it.offset )})
+ .containsExactly(*positions)
+ .inOrder()
+ }
+}
diff --git a/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java b/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java
index ac6c19e..66cf9c7 100644
--- a/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java
+++ b/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java
@@ -17,6 +17,7 @@
package android.view;
import static android.os.vibrator.Flags.FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED;
+import static android.view.flags.Flags.FLAG_DYNAMIC_VIEW_ROTARY_HAPTICS_CONFIGURATION;
import static android.view.HapticFeedbackConstants.SCROLL_ITEM_FOCUS;
import static android.view.HapticFeedbackConstants.SCROLL_LIMIT;
import static android.view.HapticFeedbackConstants.SCROLL_TICK;
@@ -74,12 +75,13 @@
mView = new TestView(InstrumentationRegistry.getContext());
mProvider = new HapticScrollFeedbackProvider(mView, mMockViewConfig,
- /* disabledIfViewPlaysScrollHaptics= */ true);
+ /* isFromView= */ false);
mSetFlagsRule.disableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
}
@Test
public void testRotaryEncoder_noFeedbackWhenViewBasedFeedbackIsEnabled() {
+ mSetFlagsRule.disableFlags(FLAG_DYNAMIC_VIEW_ROTARY_HAPTICS_CONFIGURATION);
when(mMockViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled())
.thenReturn(true);
setHapticScrollTickInterval(5);
@@ -97,7 +99,24 @@
}
@Test
+ public void testRotaryEncoder_dynamicViewRotaryFeedback_enabledEvenWhenViewFeedbackIsEnabled() {
+ mSetFlagsRule.enableFlags(FLAG_DYNAMIC_VIEW_ROTARY_HAPTICS_CONFIGURATION);
+ when(mMockViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled())
+ .thenReturn(true);
+ setHapticScrollTickInterval(5);
+ mProvider = new HapticScrollFeedbackProvider(mView, mMockViewConfig,
+ /* isFromView= */ false);
+
+ mProvider.onScrollProgress(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ 10);
+
+ assertFeedbackCount(mView, SCROLL_TICK, 1);
+ }
+
+ @Test
public void testRotaryEncoder_inputDeviceCustomized_noFeedbackWhenViewBasedFeedbackIsEnabled() {
+ mSetFlagsRule.disableFlags(FLAG_DYNAMIC_VIEW_ROTARY_HAPTICS_CONFIGURATION);
mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
when(mMockViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled())
@@ -119,7 +138,7 @@
@Test
public void testRotaryEncoder_feedbackWhenDisregardingViewBasedScrollHaptics() {
mProvider = new HapticScrollFeedbackProvider(mView, mMockViewConfig,
- /* disabledIfViewPlaysScrollHaptics= */ false);
+ /* isFromView= */ true);
when(mMockViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled())
.thenReturn(true);
setHapticScrollTickInterval(5);
@@ -144,7 +163,7 @@
List<HapticFeedbackRequest> requests = new ArrayList<>();
mProvider = new HapticScrollFeedbackProvider(mView, mMockViewConfig,
- /* disabledIfViewPlaysScrollHaptics= */ false);
+ /* isFromView= */ true);
when(mMockViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled())
.thenReturn(true);
setHapticScrollTickInterval(5);
@@ -917,19 +936,20 @@
@Test
public void testNonRotaryInputFeedbackNotBlockedByRotaryUnavailability() {
+ mSetFlagsRule.disableFlags(FLAG_DYNAMIC_VIEW_ROTARY_HAPTICS_CONFIGURATION);
when(mMockViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled())
.thenReturn(true);
setHapticScrollFeedbackEnabled(true);
setHapticScrollTickInterval(5);
mProvider = new HapticScrollFeedbackProvider(mView, mMockViewConfig,
- /* disabledIfViewPlaysScrollHaptics= */ true);
+ /* isFromView= */ false);
// Expect one feedback here. Touch input should provide feedback since scroll feedback has
// been enabled via `setHapticScrollFeedbackEnabled(true)`.
mProvider.onScrollProgress(
INPUT_DEVICE_1, InputDevice.SOURCE_TOUCHSCREEN, MotionEvent.AXIS_Y,
/* deltaInPixels= */ 10);
- // Because `isViewBasedRotaryEncoderHapticScrollFeedbackEnabled()` is false and
+ // Because `isViewBasedRotaryEncoderHapticScrollFeedbackEnabled()` is true and
// `disabledIfViewPlaysScrollHaptics` is true, the scroll progress from rotary encoders will
// produce no feedback.
mProvider.onScrollProgress(
diff --git a/core/tests/coretests/src/android/view/RotaryScrollHapticsTest.java b/core/tests/coretests/src/android/view/RotaryScrollHapticsTest.java
index 9a5c1c5..b1a5637 100644
--- a/core/tests/coretests/src/android/view/RotaryScrollHapticsTest.java
+++ b/core/tests/coretests/src/android/view/RotaryScrollHapticsTest.java
@@ -30,8 +30,12 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import android.annotation.Nullable;
import android.content.Context;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.view.flags.Flags;
import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -39,6 +43,7 @@
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;
@@ -48,6 +53,8 @@
@RunWith(AndroidJUnit4.class)
@Presubmit
public final class RotaryScrollHapticsTest {
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private static final int TEST_ROTARY_DEVICE_ID = 1;
private static final int TEST_RANDOM_DEVICE_ID = 2;
@@ -167,6 +174,26 @@
}
@Test
+ @EnableFlags(Flags.FLAG_DYNAMIC_VIEW_ROTARY_HAPTICS_CONFIGURATION)
+ public void testChildViewImplementationUsesScrollFeedbackProvider_doesNoScrollFeedback() {
+ mView.configureGenericMotion(/* result= */ false, /* scroll= */ true);
+ mView.mUsesCustomScrollFeedbackProvider = true;
+
+ // Send multiple generic motion events, to catch bugs where the behavior is WAI only for the
+ // first dispatch, but buggy for future calls.
+ mView.dispatchGenericMotionEvent(createRotaryEvent(20));
+ mView.dispatchGenericMotionEvent(createRotaryEvent(10));
+ mView.dispatchGenericMotionEvent(createRotaryEvent(30));
+
+ // Verify that the base View class's ScrollFeedbackProvider produces no scroll progress
+ // or limit events, because there's a custom ScrollFeedbackProvider used by the child
+ // View class implementation, which should hint the base View class to disable its own
+ // ScrollFeedbackProvider usage.
+ verifyNoScrollProgress();
+ verifyNoScrollLimit();
+ }
+
+ @Test
public void testScrollProgress_genericMotionEventCallbackReturningTrue_doesScrollProgress() {
mView.configureGenericMotion(/* result= */ true, /* scroll= */ true);
@@ -208,6 +235,9 @@
private static final class TestGenericMotionEventControllingView extends View {
private boolean mGenericMotionResult;
private boolean mScrollOnGenericMotion;
+ private boolean mUsesCustomScrollFeedbackProvider = false;
+
+ @Nullable private ScrollFeedbackProvider mCustomScrollFeedbackProvider;
TestGenericMotionEventControllingView(Context context) {
super(context);
@@ -222,6 +252,19 @@
public boolean onGenericMotionEvent(MotionEvent event) {
if (mScrollOnGenericMotion) {
scrollTo(100, 200); // scroll values random (not relevant for tests).
+ if (mUsesCustomScrollFeedbackProvider) {
+ // Mimic how a real child class of View would instantiate and use the
+ // ScrollFeedbackProvider API.
+ if (mCustomScrollFeedbackProvider == null) {
+ mCustomScrollFeedbackProvider = ScrollFeedbackProvider.createProvider(this);
+ }
+ float axisScrollValue = event.getAxisValue(AXIS_SCROLL);
+ mCustomScrollFeedbackProvider.onScrollProgress(
+ event.getDeviceId(),
+ event.getSource(),
+ MotionEvent.AXIS_SCROLL,
+ (int) (axisScrollValue * TEST_SCALED_VERTICAL_SCROLL_FACTOR));
+ }
}
return mGenericMotionResult;
}
diff --git a/data/sounds/Android.bp b/data/sounds/Android.bp
new file mode 100644
index 0000000..65d4872
--- /dev/null
+++ b/data/sounds/Android.bp
@@ -0,0 +1,304 @@
+// 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"],
+}
+
+phony {
+ name: "frameworks_sounds",
+ required: [
+ "frameworks_alarm_sounds",
+ "frameworks_notifications_sounds",
+ "frameworks_ringtones_sounds",
+ "frameworks_ui_sounds",
+ "frameworks_ui_48k_sounds",
+ ],
+}
+
+prebuilt_media {
+ name: "frameworks_alarm_sounds",
+ srcs: [
+ "Alarm_Beep_01.ogg",
+ "Alarm_Beep_02.ogg",
+ "Alarm_Beep_03.ogg",
+ "Alarm_Buzzer.ogg",
+ "Alarm_Classic.ogg",
+ "Alarm_Rooster_02.ogg",
+ "alarms/ogg/Argon.ogg",
+ "alarms/ogg/Barium.ogg",
+ "alarms/ogg/Carbon.ogg",
+ "alarms/ogg/Helium.ogg",
+ "alarms/ogg/Krypton.ogg",
+ "alarms/ogg/Neon.ogg",
+ "alarms/ogg/Neptunium.ogg",
+ "alarms/ogg/Osmium.ogg",
+ "alarms/ogg/Oxygen.ogg",
+ "alarms/ogg/Platinum.ogg",
+ "alarms/ogg/Promethium.ogg",
+ "alarms/ogg/Scandium.ogg",
+ ],
+ relative_install_path: "audio/alarms",
+ product_specific: true,
+ no_full_install: true,
+}
+
+prebuilt_media {
+ name: "frameworks_notifications_sounds",
+ srcs: [
+ "notifications/ogg/Adara.ogg",
+ "notifications/Aldebaran.ogg",
+ "notifications/Altair.ogg",
+ "notifications/ogg/Alya.ogg",
+ "notifications/Antares.ogg",
+ "notifications/ogg/Antimony.ogg",
+ "notifications/ogg/Arcturus.ogg",
+ "notifications/ogg/Argon.ogg",
+ "notifications/Beat_Box_Android.ogg",
+ "notifications/ogg/Bellatrix.ogg",
+ "notifications/ogg/Beryllium.ogg",
+ "notifications/Betelgeuse.ogg",
+ "newwavelabs/CaffeineSnake.ogg",
+ "notifications/Canopus.ogg",
+ "notifications/ogg/Capella.ogg",
+ "notifications/Castor.ogg",
+ "notifications/ogg/CetiAlpha.ogg",
+ "notifications/ogg/Cobalt.ogg",
+ "notifications/Cricket.ogg",
+ "newwavelabs/DearDeer.ogg",
+ "notifications/Deneb.ogg",
+ "notifications/Doink.ogg",
+ "newwavelabs/DontPanic.ogg",
+ "notifications/Drip.ogg",
+ "notifications/Electra.ogg",
+ "F1_MissedCall.ogg",
+ "F1_New_MMS.ogg",
+ "F1_New_SMS.ogg",
+ "notifications/ogg/Fluorine.ogg",
+ "notifications/Fomalhaut.ogg",
+ "notifications/ogg/Gallium.ogg",
+ "notifications/Heaven.ogg",
+ "notifications/ogg/Helium.ogg",
+ "newwavelabs/Highwire.ogg",
+ "notifications/ogg/Hojus.ogg",
+ "notifications/ogg/Iridium.ogg",
+ "notifications/ogg/Krypton.ogg",
+ "newwavelabs/KzurbSonar.ogg",
+ "notifications/ogg/Lalande.ogg",
+ "notifications/Merope.ogg",
+ "notifications/ogg/Mira.ogg",
+ "newwavelabs/OnTheHunt.ogg",
+ "notifications/ogg/Palladium.ogg",
+ "notifications/Plastic_Pipe.ogg",
+ "notifications/ogg/Polaris.ogg",
+ "notifications/ogg/Pollux.ogg",
+ "notifications/ogg/Procyon.ogg",
+ "notifications/ogg/Proxima.ogg",
+ "notifications/ogg/Radon.ogg",
+ "notifications/ogg/Rubidium.ogg",
+ "notifications/ogg/Selenium.ogg",
+ "notifications/ogg/Shaula.ogg",
+ "notifications/Sirrah.ogg",
+ "notifications/SpaceSeed.ogg",
+ "notifications/ogg/Spica.ogg",
+ "notifications/ogg/Strontium.ogg",
+ "notifications/ogg/Syrma.ogg",
+ "notifications/TaDa.ogg",
+ "notifications/ogg/Talitha.ogg",
+ "notifications/ogg/Tejat.ogg",
+ "notifications/ogg/Thallium.ogg",
+ "notifications/Tinkerbell.ogg",
+ "notifications/ogg/Upsilon.ogg",
+ "notifications/ogg/Vega.ogg",
+ "newwavelabs/Voila.ogg",
+ "notifications/ogg/Xenon.ogg",
+ "notifications/ogg/Zirconium.ogg",
+ "notifications/arcturus.ogg",
+ "notifications/moonbeam.ogg",
+ "notifications/pixiedust.ogg",
+ "notifications/pizzicato.ogg",
+ "notifications/regulus.ogg",
+ "notifications/sirius.ogg",
+ "notifications/tweeters.ogg",
+ "notifications/vega.ogg",
+ ],
+ relative_install_path: "audio/notifications",
+ product_specific: true,
+ no_full_install: true,
+}
+
+prebuilt_media {
+ name: "frameworks_ringtones_sounds",
+ srcs: [
+ "ringtones/ANDROMEDA.ogg",
+ "ringtones/ogg/Andromeda.ogg",
+ "ringtones/ogg/Aquila.ogg",
+ "ringtones/ogg/ArgoNavis.ogg",
+ "ringtones/ogg/Atria.ogg",
+ "ringtones/BOOTES.ogg",
+ "newwavelabs/Backroad.ogg",
+ "newwavelabs/BeatPlucker.ogg",
+ "newwavelabs/BentleyDubs.ogg",
+ "newwavelabs/Big_Easy.ogg",
+ "newwavelabs/BirdLoop.ogg",
+ "newwavelabs/Bollywood.ogg",
+ "newwavelabs/BussaMove.ogg",
+ "ringtones/CANISMAJOR.ogg",
+ "ringtones/CASSIOPEIA.ogg",
+ "newwavelabs/Cairo.ogg",
+ "newwavelabs/Calypso_Steel.ogg",
+ "ringtones/ogg/CanisMajor.ogg",
+ "newwavelabs/CaribbeanIce.ogg",
+ "ringtones/ogg/Carina.ogg",
+ "ringtones/ogg/Centaurus.ogg",
+ "newwavelabs/Champagne_Edition.ogg",
+ "newwavelabs/Club_Cubano.ogg",
+ "newwavelabs/CrayonRock.ogg",
+ "newwavelabs/CrazyDream.ogg",
+ "newwavelabs/CurveBall.ogg",
+ "ringtones/ogg/Cygnus.ogg",
+ "newwavelabs/DancinFool.ogg",
+ "newwavelabs/Ding.ogg",
+ "newwavelabs/DonMessWivIt.ogg",
+ "ringtones/ogg/Draco.ogg",
+ "newwavelabs/DreamTheme.ogg",
+ "newwavelabs/Eastern_Sky.ogg",
+ "newwavelabs/Enter_the_Nexus.ogg",
+ "ringtones/Eridani.ogg",
+ "newwavelabs/EtherShake.ogg",
+ "ringtones/FreeFlight.ogg",
+ "newwavelabs/FriendlyGhost.ogg",
+ "newwavelabs/Funk_Yall.ogg",
+ "newwavelabs/GameOverGuitar.ogg",
+ "newwavelabs/Gimme_Mo_Town.ogg",
+ "ringtones/ogg/Girtab.ogg",
+ "newwavelabs/Glacial_Groove.ogg",
+ "newwavelabs/Growl.ogg",
+ "newwavelabs/HalfwayHome.ogg",
+ "ringtones/ogg/Hydra.ogg",
+ "newwavelabs/InsertCoin.ogg",
+ "ringtones/ogg/Kuma.ogg",
+ "newwavelabs/LoopyLounge.ogg",
+ "newwavelabs/LoveFlute.ogg",
+ "ringtones/Lyra.ogg",
+ "ringtones/ogg/Machina.ogg",
+ "newwavelabs/MidEvilJaunt.ogg",
+ "newwavelabs/MildlyAlarming.ogg",
+ "newwavelabs/Nairobi.ogg",
+ "newwavelabs/Nassau.ogg",
+ "newwavelabs/NewPlayer.ogg",
+ "newwavelabs/No_Limits.ogg",
+ "newwavelabs/Noises1.ogg",
+ "newwavelabs/Noises2.ogg",
+ "newwavelabs/Noises3.ogg",
+ "newwavelabs/OrganDub.ogg",
+ "ringtones/ogg/Orion.ogg",
+ "ringtones/PERSEUS.ogg",
+ "newwavelabs/Paradise_Island.ogg",
+ "ringtones/ogg/Pegasus.ogg",
+ "ringtones/ogg/Perseus.ogg",
+ "newwavelabs/Playa.ogg",
+ "ringtones/ogg/Pyxis.ogg",
+ "ringtones/ogg/Rasalas.ogg",
+ "newwavelabs/Revelation.ogg",
+ "ringtones/ogg/Rigel.ogg",
+ "Ring_Classic_02.ogg",
+ "Ring_Digital_02.ogg",
+ "Ring_Synth_02.ogg",
+ "Ring_Synth_04.ogg",
+ "newwavelabs/Road_Trip.ogg",
+ "newwavelabs/RomancingTheTone.ogg",
+ "newwavelabs/Safari.ogg",
+ "newwavelabs/Savannah.ogg",
+ "ringtones/ogg/Scarabaeus.ogg",
+ "ringtones/ogg/Sceptrum.ogg",
+ "newwavelabs/Seville.ogg",
+ "newwavelabs/Shes_All_That.ogg",
+ "newwavelabs/SilkyWay.ogg",
+ "newwavelabs/SitarVsSitar.ogg",
+ "ringtones/ogg/Solarium.ogg",
+ "newwavelabs/SpringyJalopy.ogg",
+ "newwavelabs/Steppin_Out.ogg",
+ "newwavelabs/Terminated.ogg",
+ "ringtones/Testudo.ogg",
+ "ringtones/ogg/Themos.ogg",
+ "newwavelabs/Third_Eye.ogg",
+ "newwavelabs/Thunderfoot.ogg",
+ "newwavelabs/TwirlAway.ogg",
+ "ringtones/URSAMINOR.ogg",
+ "ringtones/ogg/UrsaMinor.ogg",
+ "newwavelabs/VeryAlarmed.ogg",
+ "ringtones/Vespa.ogg",
+ "newwavelabs/World.ogg",
+ "ringtones/ogg/Zeta.ogg",
+ "ringtones/hydra.ogg",
+ ],
+ relative_install_path: "audio/ringtones",
+ product_specific: true,
+ no_full_install: true,
+}
+
+prebuilt_media {
+ name: "frameworks_ui_48k_sounds",
+ srcs: [
+ "effects/ogg/Effect_Tick_48k.ogg",
+ "effects/ogg/KeypressDelete_120_48k.ogg",
+ "effects/ogg/KeypressReturn_120_48k.ogg",
+ "effects/ogg/KeypressSpacebar_120_48k.ogg",
+ "effects/ogg/KeypressStandard_120_48k.ogg",
+ "effects/ogg/KeypressInvalid_120_48k.ogg",
+ "effects/ogg/Trusted_48k.ogg",
+ "effects/ogg/VideoRecord_48k.ogg",
+ "effects/ogg/VideoStop_48k.ogg",
+ "effects/ogg/camera_click_48k.ogg",
+ ],
+ dsts: [
+ "Effect_Tick.ogg",
+ "KeypressDelete.ogg",
+ "KeypressReturn.ogg",
+ "KeypressSpacebar.ogg",
+ "KeypressStandard.ogg",
+ "KeypressInvalid.ogg",
+ "Trusted.ogg",
+ "VideoRecord.ogg",
+ "VideoStop.ogg",
+ "camera_click.ogg",
+ ],
+ relative_install_path: "audio/ui",
+ product_specific: true,
+ no_full_install: true,
+}
+
+prebuilt_media {
+ name: "frameworks_ui_sounds",
+ srcs: [
+ "effects/ogg/Dock.ogg",
+ "effects/ogg/Lock.ogg",
+ "effects/ogg/LowBattery.ogg",
+ "effects/ogg/Undock.ogg",
+ "effects/ogg/Unlock.ogg",
+ "effects/ogg/WirelessChargingStarted.ogg",
+ "effects/ogg/camera_focus.ogg",
+ "effects/ogg/ChargingStarted.ogg",
+ "effects/ogg/InCallNotification.ogg",
+ "effects/ogg/NFCFailure.ogg",
+ "effects/ogg/NFCInitiated.ogg",
+ "effects/ogg/NFCSuccess.ogg",
+ "effects/ogg/NFCTransferComplete.ogg",
+ "effects/ogg/NFCTransferInitiated.ogg",
+ ],
+ relative_install_path: "audio/ui",
+ product_specific: true,
+ no_full_install: true,
+}
diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml
index b2ac640..636e3cf 100644
--- a/libs/WindowManager/Shell/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/AndroidManifest.xml
@@ -32,7 +32,6 @@
android:name=".desktopmode.DesktopWallpaperActivity"
android:excludeFromRecents="true"
android:launchMode="singleInstance"
- android:showForAllUsers="true"
android:theme="@style/DesktopWallpaperTheme" />
<activity
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 0fd98ed..39dc267 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
@@ -1295,6 +1295,14 @@
// We still have bubbles, if we dragged an individual bubble to dismiss we were expanded
// so re-expand to whatever is selected.
showExpandedViewForBubbleBar();
+ if (bubbleKey.equals(selectedBubbleKey)) {
+ // We dragged the selected bubble to dismiss, log switch event
+ if (mBubbleData.getSelectedBubble() instanceof Bubble) {
+ // Log only bubbles as overflow can't be dragged
+ mLogger.log((Bubble) mBubbleData.getSelectedBubble(),
+ BubbleLogger.Event.BUBBLE_BAR_BUBBLE_SWITCHED);
+ }
+ }
}
}
@@ -1337,10 +1345,16 @@
public void expandStackAndSelectBubbleFromLauncher(String key, int topOnScreen) {
mBubblePositioner.setBubbleBarTopOnScreen(topOnScreen);
+ boolean wasExpanded = (mLayerView != null && mLayerView.isExpanded());
+
if (BubbleOverflow.KEY.equals(key)) {
mBubbleData.setSelectedBubbleFromLauncher(mBubbleData.getOverflow());
mLayerView.showExpandedView(mBubbleData.getOverflow());
- mLogger.log(BubbleLogger.Event.BUBBLE_BAR_EXPANDED);
+ if (wasExpanded) {
+ mLogger.log(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_SWITCHED);
+ } else {
+ mLogger.log(BubbleLogger.Event.BUBBLE_BAR_EXPANDED);
+ }
return;
}
@@ -1352,7 +1366,11 @@
// already in the stack
mBubbleData.setSelectedBubbleFromLauncher(b);
mLayerView.showExpandedView(b);
- mLogger.log(b, BubbleLogger.Event.BUBBLE_BAR_EXPANDED);
+ if (wasExpanded) {
+ mLogger.log(b, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_SWITCHED);
+ } else {
+ mLogger.log(b, BubbleLogger.Event.BUBBLE_BAR_EXPANDED);
+ }
} else if (mBubbleData.hasOverflowBubbleWithKey(b.getKey())) {
// TODO: (b/271468319) handle overflow
} else {
@@ -2081,6 +2099,9 @@
// Only need to update the layer view if we're currently expanded for selection changes.
if (mLayerView != null && mLayerView.isExpanded()) {
mLayerView.showExpandedView(selectedBubble);
+ if (selectedBubble instanceof Bubble bubble) {
+ mLogger.log(bubble, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_SWITCHED);
+ }
}
}
};
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 0200e18..c99d9ba8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -188,6 +188,8 @@
*/
private boolean mIsFirstReachabilityEducationRunning;
+ private boolean mIsInDesktopMode;
+
@NonNull
private final CompatUIStatusManager mCompatUIStatusManager;
@@ -253,18 +255,19 @@
if (taskInfo != null && !taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat()) {
mSetOfTaskIdsShowingRestartDialog.remove(taskInfo.taskId);
}
-
- if (taskInfo != null && taskListener != null) {
- updateActiveTaskInfo(taskInfo);
- }
-
- // We close all the Compat UI educations in case we're in desktop mode.
- if (taskInfo.configuration == null || taskListener == null
- || isInDesktopMode(taskInfo.displayId)) {
+ mIsInDesktopMode = isInDesktopMode(taskInfo);
+ // We close all the Compat UI educations in case TaskInfo has no configuration or
+ // TaskListener or in desktop mode.
+ if (taskInfo.configuration == null || taskListener == null || mIsInDesktopMode) {
// Null token means the current foreground activity is not in compatibility mode.
removeLayouts(taskInfo.taskId);
return;
}
+ if (taskInfo != null && taskListener != null) {
+ updateActiveTaskInfo(taskInfo);
+ }
+
+
// We're showing the first reachability education so we ignore incoming TaskInfo
// until the education flow has completed or we double tap. The double-tap
// basically cancel all the onboarding flow. We don't have to ignore events in case
@@ -443,7 +446,7 @@
@Nullable ShellTaskOrganizer.TaskListener taskListener) {
CompatUIWindowManager layout = mActiveCompatLayouts.get(taskInfo.taskId);
if (layout != null) {
- if (layout.needsToBeRecreated(taskInfo, taskListener)) {
+ if (layout.needsToBeRecreated(taskInfo, taskListener) || mIsInDesktopMode) {
mActiveCompatLayouts.remove(taskInfo.taskId);
layout.release();
} else {
@@ -456,7 +459,10 @@
return;
}
}
-
+ if (mIsInDesktopMode) {
+ // Return if in desktop mode.
+ return;
+ }
// Create a new UI layout.
final Context context = getOrCreateDisplayContext(taskInfo.displayId);
if (context == null) {
@@ -494,7 +500,8 @@
private void createOrUpdateLetterboxEduLayout(@NonNull TaskInfo taskInfo,
@Nullable ShellTaskOrganizer.TaskListener taskListener) {
if (mActiveLetterboxEduLayout != null) {
- if (mActiveLetterboxEduLayout.needsToBeRecreated(taskInfo, taskListener)) {
+ if (mActiveLetterboxEduLayout.needsToBeRecreated(taskInfo, taskListener)
+ || mIsInDesktopMode) {
mActiveLetterboxEduLayout.release();
mActiveLetterboxEduLayout = null;
} else {
@@ -507,6 +514,10 @@
return;
}
}
+ if (mIsInDesktopMode) {
+ // Return if in desktop mode.
+ return;
+ }
// Create a new UI layout.
final Context context = getOrCreateDisplayContext(taskInfo.displayId);
if (context == null) {
@@ -541,7 +552,7 @@
RestartDialogWindowManager layout =
mTaskIdToRestartDialogWindowManagerMap.get(taskInfo.taskId);
if (layout != null) {
- if (layout.needsToBeRecreated(taskInfo, taskListener)) {
+ if (layout.needsToBeRecreated(taskInfo, taskListener) || mIsInDesktopMode) {
mTaskIdToRestartDialogWindowManagerMap.remove(taskInfo.taskId);
layout.release();
} else {
@@ -556,6 +567,10 @@
return;
}
}
+ if (mIsInDesktopMode) {
+ // Return if in desktop mode.
+ return;
+ }
// Create a new UI layout.
final Context context = getOrCreateDisplayContext(taskInfo.displayId);
if (context == null) {
@@ -594,7 +609,8 @@
private void createOrUpdateReachabilityEduLayout(@NonNull TaskInfo taskInfo,
@Nullable ShellTaskOrganizer.TaskListener taskListener) {
if (mActiveReachabilityEduLayout != null) {
- if (mActiveReachabilityEduLayout.needsToBeRecreated(taskInfo, taskListener)) {
+ if (mActiveReachabilityEduLayout.needsToBeRecreated(taskInfo, taskListener)
+ || mIsInDesktopMode) {
mActiveReachabilityEduLayout.release();
mActiveReachabilityEduLayout = null;
} else {
@@ -608,6 +624,10 @@
return;
}
}
+ if (mIsInDesktopMode) {
+ // Return if in desktop mode.
+ return;
+ }
// Create a new UI layout.
final Context context = getOrCreateDisplayContext(taskInfo.displayId);
if (context == null) {
@@ -647,7 +667,8 @@
private void createOrUpdateUserAspectRatioSettingsLayout(@NonNull TaskInfo taskInfo,
@Nullable ShellTaskOrganizer.TaskListener taskListener) {
if (mUserAspectRatioSettingsLayout != null) {
- if (mUserAspectRatioSettingsLayout.needsToBeRecreated(taskInfo, taskListener)) {
+ if (mUserAspectRatioSettingsLayout.needsToBeRecreated(taskInfo, taskListener)
+ || mIsInDesktopMode) {
mUserAspectRatioSettingsLayout.release();
mUserAspectRatioSettingsLayout = null;
} else {
@@ -660,7 +681,10 @@
return;
}
}
-
+ if (mIsInDesktopMode) {
+ // Return if in desktop mode.
+ return;
+ }
// Create a new UI layout.
final Context context = getOrCreateDisplayContext(taskInfo.displayId);
if (context == null) {
@@ -840,8 +864,8 @@
boolean mHasShownUserAspectRatioSettingsButtonHint;
}
- private boolean isInDesktopMode(int displayId) {
- return Flags.skipCompatUiEducationInDesktopMode()
- && mInDesktopModePredicate.test(displayId);
+ private boolean isInDesktopMode(@Nullable TaskInfo taskInfo) {
+ return taskInfo != null && Flags.skipCompatUiEducationInDesktopMode()
+ && mInDesktopModePredicate.test(taskInfo.displayId);
}
}
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 223038f..6928c25 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
@@ -42,6 +42,7 @@
import android.os.Handler
import android.os.IBinder
import android.os.SystemProperties
+import android.os.UserHandle
import android.util.Size
import android.view.Display.DEFAULT_DISPLAY
import android.view.DragEvent
@@ -1174,7 +1175,11 @@
private fun addWallpaperActivity(wct: WindowContainerTransaction) {
logV("addWallpaperActivity")
- val intent = Intent(context, DesktopWallpaperActivity::class.java)
+ val userHandle = UserHandle.of(userId)
+ val userContext =
+ context.createContextAsUser(userHandle, /* flags= */ 0)
+ val intent = Intent(userContext, DesktopWallpaperActivity::class.java)
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, userId)
val options =
ActivityOptions.makeBasic().apply {
launchWindowingMode = WINDOWING_MODE_FULLSCREEN
@@ -1182,11 +1187,13 @@
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
}
val pendingIntent =
- PendingIntent.getActivity(
- context,
- /* requestCode = */ 0,
+ PendingIntent.getActivityAsUser(
+ userContext,
+ /* requestCode= */ 0,
intent,
- PendingIntent.FLAG_IMMUTABLE
+ PendingIntent.FLAG_IMMUTABLE,
+ /* bundle= */ null,
+ userHandle
)
wct.sendPendingIntent(pendingIntent, intent, options.toBundle())
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt
index 1c2415c..e835b2f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt
@@ -36,7 +36,6 @@
class DesktopWallpaperActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopWallpaperActivity: onCreate")
super.onCreate(savedInstanceState)
window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 5ffc64f..79a9ce5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -47,7 +47,6 @@
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipMenuController;
-import com.android.wm.shell.common.split.SplitScreenUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.DefaultMixedHandler;
@@ -312,10 +311,10 @@
}
/** Whether a particular package is same as current pip package. */
- public boolean isPackageActiveInPip(String packageName) {
- final TaskInfo inPipTask = mPipOrganizer.getTaskInfo();
- return packageName != null && inPipTask != null && mPipOrganizer.isInPip()
- && packageName.equals(SplitScreenUtils.getPackageName(inPipTask.baseIntent));
+ public boolean isPackageActiveInPip(@Nullable String packageName) {
+ return packageName != null
+ && mPipBoundsState.getLastPipComponentName() != null
+ && packageName.equals(mPipBoundsState.getLastPipComponentName().getPackageName());
}
/** Add PiP-related changes to `outWCT` for the given request. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index bc09183..e901c39 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -322,7 +322,7 @@
mPipBoundsAlgorithm.applySnapFraction(toBounds, snapFraction);
mPipBoundsState.setBounds(toBounds);
}
- t.setBounds(mPipTransitionState.mPipTaskToken, mPipBoundsState.getBounds());
+ t.setBounds(mPipTransitionState.getPipTaskToken(), mPipBoundsState.getBounds());
}
private void setDisplayLayout(DisplayLayout layout) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index 607de0e..5438a01 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -25,10 +25,13 @@
import android.graphics.Matrix;
import android.graphics.Rect;
import android.view.SurfaceControl;
+import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipBoundsState;
@@ -48,11 +51,13 @@
private final ShellExecutor mMainExecutor;
private final PipTransitionState mPipTransitionState;
private PipTransitionController mPipTransitionController;
- private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
+ private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
mSurfaceControlTransactionFactory;
@Nullable private Runnable mUpdateMovementBoundsRunnable;
+ private PipAlphaAnimatorSupplier mPipAlphaAnimatorSupplier;
+
public PipScheduler(Context context,
PipBoundsState pipBoundsState,
ShellExecutor mainExecutor,
@@ -64,10 +69,7 @@
mSurfaceControlTransactionFactory =
new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
- }
-
- ShellExecutor getMainExecutor() {
- return mMainExecutor;
+ mPipAlphaAnimatorSupplier = PipAlphaAnimator::new;
}
void setPipTransitionController(PipTransitionController pipTransitionController) {
@@ -76,27 +78,29 @@
@Nullable
private WindowContainerTransaction getExitPipViaExpandTransaction() {
- if (mPipTransitionState.mPipTaskToken == null) {
+ WindowContainerToken pipTaskToken = mPipTransitionState.getPipTaskToken();
+ if (pipTaskToken == null) {
return null;
}
WindowContainerTransaction wct = new WindowContainerTransaction();
// final expanded bounds to be inherited from the parent
- wct.setBounds(mPipTransitionState.mPipTaskToken, null);
+ wct.setBounds(pipTaskToken, null);
// if we are hitting a multi-activity case
// windowing mode change will reparent to original host task
- wct.setWindowingMode(mPipTransitionState.mPipTaskToken, WINDOWING_MODE_UNDEFINED);
+ wct.setWindowingMode(pipTaskToken, WINDOWING_MODE_UNDEFINED);
return wct;
}
@Nullable
private WindowContainerTransaction getRemovePipTransaction() {
- if (mPipTransitionState.mPipTaskToken == null) {
+ WindowContainerToken pipTaskToken = mPipTransitionState.getPipTaskToken();
+ if (pipTaskToken == null) {
return null;
}
WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setBounds(mPipTransitionState.mPipTaskToken, null);
- wct.setWindowingMode(mPipTransitionState.mPipTaskToken, WINDOWING_MODE_UNDEFINED);
- wct.reorder(mPipTransitionState.mPipTaskToken, false);
+ wct.setBounds(pipTaskToken, null);
+ wct.setWindowingMode(pipTaskToken, WINDOWING_MODE_UNDEFINED);
+ wct.reorder(pipTaskToken, false);
return wct;
}
@@ -117,7 +121,7 @@
/** Runs remove PiP animation and schedules remove PiP transition after the animation ends. */
public void removePipAfterAnimation() {
SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
- PipAlphaAnimator animator = new PipAlphaAnimator(mContext,
+ PipAlphaAnimator animator = mPipAlphaAnimatorSupplier.get(mContext,
mPipTransitionState.getPinnedTaskLeash(), tx, PipAlphaAnimator.FADE_OUT);
animator.setAnimationEndCallback(this::scheduleRemovePipImmediately);
animator.start();
@@ -159,13 +163,14 @@
* for running the animator will get this as an extra.
*/
public void scheduleAnimateResizePip(Rect toBounds, boolean configAtEnd, int duration) {
- if (mPipTransitionState.mPipTaskToken == null || !mPipTransitionState.isInPip()) {
+ WindowContainerToken pipTaskToken = mPipTransitionState.getPipTaskToken();
+ if (pipTaskToken == null || !mPipTransitionState.isInPip()) {
return;
}
WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setBounds(mPipTransitionState.mPipTaskToken, toBounds);
+ wct.setBounds(pipTaskToken, toBounds);
if (configAtEnd) {
- wct.deferConfigToTransitionEnd(mPipTransitionState.mPipTaskToken);
+ wct.deferConfigToTransitionEnd(pipTaskToken);
}
mPipTransitionController.startResizeTransition(wct, duration);
}
@@ -204,7 +209,7 @@
return;
}
SurfaceControl leash = mPipTransitionState.getPinnedTaskLeash();
- final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+ final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
Matrix transformTensor = new Matrix();
final float[] mMatrixTmp = new float[9];
@@ -218,7 +223,7 @@
tx.apply();
}
- void setUpdateMovementBoundsRunnable(Runnable updateMovementBoundsRunnable) {
+ void setUpdateMovementBoundsRunnable(@Nullable Runnable updateMovementBoundsRunnable) {
mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
}
@@ -235,4 +240,23 @@
mPipBoundsState.setBounds(newBounds);
maybeUpdateMovementBounds();
}
+
+ @VisibleForTesting
+ void setSurfaceControlTransactionFactory(
+ @NonNull PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
+ mSurfaceControlTransactionFactory = factory;
+ }
+
+ @VisibleForTesting
+ interface PipAlphaAnimatorSupplier {
+ PipAlphaAnimator get(@NonNull Context context,
+ SurfaceControl leash,
+ SurfaceControl.Transaction tx,
+ @PipAlphaAnimator.Fade int direction);
+ }
+
+ @VisibleForTesting
+ void setPipAlphaAnimatorSupplier(@NonNull PipAlphaAnimatorSupplier supplier) {
+ mPipAlphaAnimatorSupplier = supplier;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 3caad09..d415c10 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -420,7 +420,8 @@
}
// Update the src-rect-hint in params in place, to set up initial animator transform.
- params.getSourceRectHint().set(adjustedSourceRectHint);
+ params.copyOnlySet(new PictureInPictureParams.Builder()
+ .setSourceRectHint(adjustedSourceRectHint).build());
// Config-at-end transitions need to have their activities transformed before starting
// the animation; this makes the buffer seem like it's been updated to final size.
@@ -543,7 +544,7 @@
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- WindowContainerToken pipToken = mPipTransitionState.mPipTaskToken;
+ WindowContainerToken pipToken = mPipTransitionState.getPipTaskToken();
TransitionInfo.Change pipChange = getChangeByToken(info, pipToken);
if (pipChange == null) {
@@ -773,11 +774,11 @@
}
private boolean isRemovePipTransition(@NonNull TransitionInfo info) {
- if (mPipTransitionState.mPipTaskToken == null) {
+ if (mPipTransitionState.getPipTaskToken() == null) {
// PiP removal makes sense if enter-PiP has cached a valid pinned task token.
return false;
}
- TransitionInfo.Change pipChange = info.getChange(mPipTransitionState.mPipTaskToken);
+ TransitionInfo.Change pipChange = info.getChange(mPipTransitionState.getPipTaskToken());
if (pipChange == null) {
// Search for the PiP change by token since the windowing mode might be FULLSCREEN now.
return false;
@@ -859,18 +860,18 @@
Preconditions.checkState(extra != null,
"No extra bundle for " + mPipTransitionState);
- mPipTransitionState.mPipTaskToken = extra.getParcelable(
- PIP_TASK_TOKEN, WindowContainerToken.class);
+ mPipTransitionState.setPipTaskToken(extra.getParcelable(
+ PIP_TASK_TOKEN, WindowContainerToken.class));
mPipTransitionState.setPinnedTaskLeash(extra.getParcelable(
PIP_TASK_LEASH, SurfaceControl.class));
- boolean hasValidTokenAndLeash = mPipTransitionState.mPipTaskToken != null
+ boolean hasValidTokenAndLeash = mPipTransitionState.getPipTaskToken() != null
&& mPipTransitionState.getPinnedTaskLeash() != null;
Preconditions.checkState(hasValidTokenAndLeash,
"Unexpected bundle for " + mPipTransitionState);
break;
case PipTransitionState.EXITED_PIP:
- mPipTransitionState.mPipTaskToken = null;
+ mPipTransitionState.setPipTaskToken(null);
mPipTransitionState.setPinnedTaskLeash(null);
break;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
index 03e06f9..8e90bfe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
@@ -138,7 +138,7 @@
// pinned PiP task's WC token
@Nullable
- WindowContainerToken mPipTaskToken;
+ private WindowContainerToken mPipTaskToken;
// pinned PiP task's leash
@Nullable
@@ -304,6 +304,14 @@
mSwipePipToHomeAppBounds.setEmpty();
}
+ @Nullable WindowContainerToken getPipTaskToken() {
+ return mPipTaskToken;
+ }
+
+ public void setPipTaskToken(@Nullable WindowContainerToken token) {
+ mPipTaskToken = token;
+ }
+
@Nullable SurfaceControl getPinnedTaskLeash() {
return mPinnedTaskLeash;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index f739d65..49cf8ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -63,6 +63,8 @@
"Bubbles"),
WM_SHELL_COMPAT_UI(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_COMPAT_UI),
+ WM_SHELL_APP_COMPAT(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ Consts.TAG_WM_APP_COMPAT),
TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
private final boolean mEnabled;
@@ -131,6 +133,7 @@
private static final String TAG_WM_SPLIT_SCREEN = "ShellSplitScreen";
private static final String TAG_WM_DESKTOP_MODE = "ShellDesktopMode";
private static final String TAG_WM_COMPAT_UI = "CompatUi";
+ private static final String TAG_WM_APP_COMPAT = "AppCompat";
private static final boolean ENABLE_DEBUG = true;
private static final boolean ENABLE_LOG_TO_PROTO_DEBUG = true;
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 9016c45..417a655 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
@@ -1286,6 +1286,16 @@
if (mFinishCB == null
|| (Flags.enableShellTopTaskTracking() && mPendingFinishTransition != null)) {
Slog.e(TAG, "Duplicate call to finish");
+ if (runnerFinishCb != null) {
+ try {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.finishInner: calling finish callback",
+ mInstanceId);
+ runnerFinishCb.send(0, null);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to report transition finished", e);
+ }
+ }
return;
}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MaximizeAppWindowWithDragToTopDragZoneTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MaximizeAppWindowWithDragToTopDragZoneTest.kt
new file mode 100644
index 0000000..7e0b81a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MaximizeAppWindowWithDragToTopDragZoneTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.MaximizeAppWindowWithDragToTopDragZone
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/* Functional test for [MaximizeAppWindowWithDragToTopDragZone]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class MaximizeAppWindowWithDragToTopDragZoneTest : MaximizeAppWindowWithDragToTopDragZone()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindowWithDragToTopDragZone.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindowWithDragToTopDragZone.kt
new file mode 100644
index 0000000..a2b88f2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindowWithDragToTopDragZone.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.scenarios
+
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.internal.R
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+/**
+ * Base scenario test for maximizing a desktop app window by dragging it to the top drag zone.
+ */
+@Ignore("Test Base Class")
+abstract class MaximizeAppWindowWithDragToTopDragZone
+constructor(private val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ // Skip the test when the drag-to-maximize is disabled on this device.
+ Assume.assumeTrue(Flags.enableDragToMaximize() &&
+ instrumentation.context.resources.getBoolean(R.bool.config_dragToMaximizeInDesktopMode))
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+ ChangeDisplayOrientationRule.setRotation(rotation)
+ testApp.enterDesktopWithDrag(wmHelper, device)
+ }
+
+ @Test
+ open fun maximizeAppWithDragToTopDragZone() {
+ testApp.maximizeAppWithDragToTopDragZone(wmHelper, device)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
new file mode 100644
index 0000000..cab6252
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2.phone;
+
+import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.when;
+import static org.mockito.kotlin.MatchersKt.eq;
+import static org.mockito.kotlin.VerificationKt.times;
+import static org.mockito.kotlin.VerificationKt.verify;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.SurfaceControl;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
+import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit test against {@link PipScheduler}
+ */
+
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class PipSchedulerTest {
+ private static final int TEST_RESIZE_DURATION = 1;
+ private static final Rect TEST_STARTING_BOUNDS = new Rect(0, 0, 10, 10);
+ private static final Rect TEST_BOUNDS = new Rect(0, 0, 20, 20);
+
+ @Mock private Context mMockContext;
+ @Mock private Resources mMockResources;
+ @Mock private PipBoundsState mMockPipBoundsState;
+ @Mock private ShellExecutor mMockMainExecutor;
+ @Mock private PipTransitionState mMockPipTransitionState;
+ @Mock private PipTransitionController mMockPipTransitionController;
+ @Mock private Runnable mMockUpdateMovementBoundsRunnable;
+ @Mock private WindowContainerToken mMockPipTaskToken;
+ @Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory;
+ @Mock private SurfaceControl.Transaction mMockTransaction;
+ @Mock private PipAlphaAnimator mMockAlphaAnimator;
+
+ @Captor private ArgumentCaptor<Runnable> mRunnableArgumentCaptor;
+ @Captor private ArgumentCaptor<WindowContainerTransaction> mWctArgumentCaptor;
+
+ private PipScheduler mPipScheduler;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mMockContext.getResources()).thenReturn(mMockResources);
+ when(mMockResources.getInteger(anyInt())).thenReturn(0);
+ when(mMockPipBoundsState.getBounds()).thenReturn(TEST_STARTING_BOUNDS);
+ when(mMockFactory.getTransaction()).thenReturn(mMockTransaction);
+ when(mMockTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+ .thenReturn(mMockTransaction);
+
+ mPipScheduler = new PipScheduler(mMockContext, mMockPipBoundsState, mMockMainExecutor,
+ mMockPipTransitionState);
+ mPipScheduler.setPipTransitionController(mMockPipTransitionController);
+ mPipScheduler.setSurfaceControlTransactionFactory(mMockFactory);
+ mPipScheduler.setPipAlphaAnimatorSupplier((context, leash, tx, direction) ->
+ mMockAlphaAnimator);
+
+ SurfaceControl testLeash = new SurfaceControl.Builder()
+ .setContainerLayer()
+ .setName("PipSchedulerTest")
+ .setCallsite("PipSchedulerTest")
+ .build();
+ when(mMockPipTransitionState.getPinnedTaskLeash()).thenReturn(testLeash);
+ }
+
+ @Test
+ public void scheduleExitPipViaExpand_nullTaskToken_noop() {
+ setNullPipTaskToken();
+
+ mPipScheduler.scheduleExitPipViaExpand();
+
+ verify(mMockMainExecutor, never()).execute(any());
+ }
+
+ @Test
+ public void scheduleExitPipViaExpand_exitTransitionCalled() {
+ setMockPipTaskToken();
+
+ mPipScheduler.scheduleExitPipViaExpand();
+
+ verify(mMockMainExecutor, times(1)).execute(mRunnableArgumentCaptor.capture());
+ assertNotNull(mRunnableArgumentCaptor.getValue());
+ mRunnableArgumentCaptor.getValue().run();
+
+ verify(mMockPipTransitionController, times(1))
+ .startExitTransition(eq(TRANSIT_EXIT_PIP), any(), isNull());
+ }
+
+ @Test
+ public void removePipAfterAnimation() {
+ //TODO: Update once this is changed to run animation as part of transition
+ setMockPipTaskToken();
+
+ mPipScheduler.removePipAfterAnimation();
+ verify(mMockAlphaAnimator, times(1))
+ .setAnimationEndCallback(mRunnableArgumentCaptor.capture());
+ assertNotNull(mRunnableArgumentCaptor.getValue());
+ verify(mMockAlphaAnimator, times(1)).start();
+
+ mRunnableArgumentCaptor.getValue().run();
+
+ verify(mMockMainExecutor, times(1)).execute(mRunnableArgumentCaptor.capture());
+ assertNotNull(mRunnableArgumentCaptor.getValue());
+
+ mRunnableArgumentCaptor.getValue().run();
+
+ verify(mMockPipTransitionController, times(1))
+ .startExitTransition(eq(TRANSIT_REMOVE_PIP), any(), isNull());
+ }
+
+ @Test
+ public void scheduleAnimateResizePip_bounds_nullTaskToken_noop() {
+ setNullPipTaskToken();
+
+ mPipScheduler.scheduleAnimateResizePip(TEST_BOUNDS);
+
+ verify(mMockPipTransitionController, never()).startResizeTransition(any(), anyInt());
+ }
+
+ @Test
+ public void scheduleAnimateResizePip_boundsConfig_nullTaskToken_noop() {
+ setNullPipTaskToken();
+
+ mPipScheduler.scheduleAnimateResizePip(TEST_BOUNDS, true);
+
+ verify(mMockPipTransitionController, never()).startResizeTransition(any(), anyInt());
+ }
+
+ @Test
+ public void scheduleAnimateResizePip_boundsConfig_setsConfigAtEnd() {
+ setMockPipTaskToken();
+ when(mMockPipTransitionState.isInPip()).thenReturn(true);
+
+ mPipScheduler.scheduleAnimateResizePip(TEST_BOUNDS, true);
+
+ verify(mMockPipTransitionController, times(1))
+ .startResizeTransition(mWctArgumentCaptor.capture(), anyInt());
+ assertNotNull(mWctArgumentCaptor.getValue());
+ assertNotNull(mWctArgumentCaptor.getValue().getChanges());
+ boolean hasConfigAtEndChange = false;
+ for (WindowContainerTransaction.Change change :
+ mWctArgumentCaptor.getValue().getChanges().values()) {
+ if (change.getConfigAtTransitionEnd()) {
+ hasConfigAtEndChange = true;
+ break;
+ }
+ }
+ assertTrue(hasConfigAtEndChange);
+ }
+
+ @Test
+ public void scheduleAnimateResizePip_boundsConfigDuration_nullTaskToken_noop() {
+ setNullPipTaskToken();
+
+ mPipScheduler.scheduleAnimateResizePip(TEST_BOUNDS, true, TEST_RESIZE_DURATION);
+
+ verify(mMockPipTransitionController, never()).startResizeTransition(any(), anyInt());
+ }
+
+ @Test
+ public void scheduleAnimateResizePip_notInPip_noop() {
+ setMockPipTaskToken();
+ when(mMockPipTransitionState.isInPip()).thenReturn(false);
+
+ mPipScheduler.scheduleAnimateResizePip(TEST_BOUNDS, true, TEST_RESIZE_DURATION);
+
+ verify(mMockPipTransitionController, never()).startResizeTransition(any(), anyInt());
+ }
+
+ @Test
+ public void scheduleAnimateResizePip_resizeTransition() {
+ setMockPipTaskToken();
+ when(mMockPipTransitionState.isInPip()).thenReturn(true);
+
+ mPipScheduler.scheduleAnimateResizePip(TEST_BOUNDS, true, TEST_RESIZE_DURATION);
+
+ verify(mMockPipTransitionController, times(1))
+ .startResizeTransition(any(), eq(TEST_RESIZE_DURATION));
+ }
+
+ @Test
+ public void scheduleUserResizePip_emptyBounds_noop() {
+ setMockPipTaskToken();
+
+ mPipScheduler.scheduleUserResizePip(new Rect());
+
+ verify(mMockTransaction, never()).apply();
+ }
+
+ @Test
+ public void scheduleUserResizePip_rotation_emptyBounds_noop() {
+ setMockPipTaskToken();
+
+ mPipScheduler.scheduleUserResizePip(new Rect(), 90);
+
+ verify(mMockTransaction, never()).apply();
+ }
+
+ @Test
+ public void scheduleUserResizePip_applyTransaction() {
+ setMockPipTaskToken();
+
+ mPipScheduler.scheduleUserResizePip(TEST_BOUNDS, 90);
+
+ verify(mMockTransaction, times(1)).apply();
+ }
+
+ @Test
+ public void finishResize_movementBoundsRunnableCalled() {
+ mPipScheduler.setUpdateMovementBoundsRunnable(mMockUpdateMovementBoundsRunnable);
+ mPipScheduler.scheduleFinishResizePip(TEST_BOUNDS);
+
+ verify(mMockUpdateMovementBoundsRunnable, times(1)).run();
+ }
+
+ private void setNullPipTaskToken() {
+ when(mMockPipTransitionState.getPipTaskToken()).thenReturn(null);
+ }
+
+ private void setMockPipTaskToken() {
+ when(mMockPipTransitionState.getPipTaskToken()).thenReturn(mMockPipTaskToken);
+ }
+}
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 2ae89d3..82e9503 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -16,6 +16,7 @@
package android.media;
+import static android.media.codec.Flags.FLAG_CODEC_AVAILABILITY;
import static android.media.codec.Flags.FLAG_NULL_OUTPUT_SURFACE;
import static android.media.codec.Flags.FLAG_REGION_OF_INTEREST;
import static android.media.codec.Flags.FLAG_SUBSESSION_METRICS;
@@ -29,6 +30,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.ImageFormat;
import android.graphics.Rect;
@@ -1843,6 +1845,12 @@
*/
private static final int CB_METRICS_FLUSHED = 8;
+ /**
+ * Callback ID to notify the change in resource requirement
+ * for the codec component.
+ */
+ private static final int CB_REQUIRED_RESOURCES_CHANGE = 9;
+
private class EventHandler extends Handler {
private MediaCodec mCodec;
@@ -2017,13 +2025,19 @@
case CB_METRICS_FLUSHED:
{
-
if (GetFlag(() -> android.media.codec.Flags.subsessionMetrics())) {
mCallback.onMetricsFlushed(mCodec, (PersistableBundle)msg.obj);
}
break;
}
+ case CB_REQUIRED_RESOURCES_CHANGE: {
+ if (android.media.codec.Flags.codecAvailability()) {
+ mCallback.onRequiredResourcesChanged(mCodec);
+ }
+ break;
+ }
+
default:
{
break;
@@ -2302,6 +2316,70 @@
}
/**
+ * @hide
+ * Abstraction for the Global Codec resources.
+ * This encapsulates all the available codec resources on the device.
+ *
+ * To be able to enforce and test the implementation of codec availability hal APIs,
+ * globally available codec resources are exposed only as TestApi.
+ * This will be tracked and verified through cts.
+ */
+ @FlaggedApi(FLAG_CODEC_AVAILABILITY)
+ @TestApi
+ public static final class GlobalResourceInfo {
+ /**
+ * Identifier for the Resource type.
+ */
+ String mName;
+ /**
+ * Total count/capacity of resources of this type.
+ */
+ long mCapacity;
+ /**
+ * Available count of this resource type.
+ */
+ long mAvailable;
+
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ public long getCapacity() {
+ return mCapacity;
+ }
+
+ public long getAvailable() {
+ return mAvailable;
+ }
+ };
+
+ /**
+ * @hide
+ * Get a list of globally available codec resources.
+ *
+ * To be able to enforce and test the implementation of codec availability hal APIs,
+ * it is exposed only as TestApi.
+ * This will be tracked and verified through cts.
+ *
+ * This returns a {@link java.util.List} list of codec resources.
+ * For every {@link GlobalResourceInfo} in the list, it encapsulates the
+ * information about each resources available globaly on device.
+ *
+ * @return A list of available device codec resources; an empty list if no
+ * device codec resources are available.
+ * @throws UnsupportedOperationException if not implemented.
+ */
+ @FlaggedApi(FLAG_CODEC_AVAILABILITY)
+ @TestApi
+ public static @NonNull List<GlobalResourceInfo> getGloballyAvailableResources() {
+ return native_getGloballyAvailableResources();
+ }
+
+ @NonNull
+ private static native List<GlobalResourceInfo> native_getGloballyAvailableResources();
+
+ /**
* Configures a component.
*
* @param format The format of the input data (decoder) or the desired
@@ -2443,6 +2521,73 @@
}
/**
+ * @hide
+ * Abstraction for the resources associated with a codec instance.
+ * This encapsulates the required codec resources for a configured codec instance.
+ *
+ * To be able to enforce and test the implementation of codec availability hal APIs,
+ * required codec resources are exposed only as TestApi.
+ * This will be tracked and verified through cts.
+ */
+ @FlaggedApi(FLAG_CODEC_AVAILABILITY)
+ @TestApi
+ public static final class InstanceResourceInfo {
+ /**
+ * Identifier for the Resource type.
+ */
+ String mName;
+ /**
+ * Required resource count of this type.
+ */
+ long mStaticCount;
+ /**
+ * Per frame resource requirement of this resource type.
+ */
+ long mPerFrameCount;
+
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ public long getStaticCount() {
+ return mStaticCount;
+ }
+
+ public long getPerFrameCount() {
+ return mPerFrameCount;
+ }
+ };
+
+ /**
+ * @hide
+ * Get a list of required codec resources for this configuration.
+ *
+ * To be able to enforce and test the implementation of codec availability hal APIs,
+ * it is exposed only as TestApi.
+ * This will be tracked and verified through cts.
+ *
+ * This returns a {@link java.util.List} list of codec resources.
+ * For every {@link GlobalResourceInfo} in the list, it encapsulates the
+ * information about each resources required for the current configuration.
+ *
+ * NOTE: This may only be called after {@link #configure}.
+ *
+ * @return A list of required device codec resources; an empty list if no
+ * device codec resources are required.
+ * @throws IllegalStateException if the codec wasn't configured yet.
+ * @throws UnsupportedOperationException if not implemented.
+ */
+ @FlaggedApi(FLAG_CODEC_AVAILABILITY)
+ @TestApi
+ public @NonNull List<InstanceResourceInfo> getRequiredResources() {
+ return native_getRequiredResources();
+ }
+
+ @NonNull
+ private native List<InstanceResourceInfo> native_getRequiredResources();
+
+ /**
* Dynamically sets the output surface of a codec.
* <p>
* This can only be used if the codec was configured with an output surface. The
@@ -5740,6 +5885,25 @@
@NonNull MediaCodec codec, @NonNull PersistableBundle metrics) {
// default implementation ignores this callback.
}
+
+ /**
+ * @hide
+ * Called when there is a change in the required resources for the codec.
+ * <p>
+ * Upon receiving this notification, the updated resource requirement
+ * can be queried through {@link #getRequiredResources}.
+ *
+ * @param codec The MediaCodec object.
+ */
+ @FlaggedApi(FLAG_CODEC_AVAILABILITY)
+ @TestApi
+ public void onRequiredResourcesChanged(@NonNull MediaCodec codec) {
+ /*
+ * A default implementation for backward compatibility.
+ * Since this is a TestApi, we are not enforcing the callback to be
+ * overridden.
+ */
+ }
}
private void postEventFromNative(
diff --git a/media/java/android/media/quality/AmbientBacklightEvent.java b/media/java/android/media/quality/AmbientBacklightEvent.java
index 3bc6b86..5c11def 100644
--- a/media/java/android/media/quality/AmbientBacklightEvent.java
+++ b/media/java/android/media/quality/AmbientBacklightEvent.java
@@ -16,9 +16,11 @@
package android.media.quality;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.media.tv.flags.Flags;
import android.os.Parcel;
import android.os.Parcelable;
@@ -27,8 +29,10 @@
import java.util.Objects;
/**
+ * Ambient backlight event
* @hide
*/
+@FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
public final class AmbientBacklightEvent implements Parcelable {
/** @hide */
@@ -64,6 +68,9 @@
@Nullable
private final AmbientBacklightMetadata mMetadata;
+ /**
+ * Constructor of AmbientBacklightEvent.
+ */
public AmbientBacklightEvent(int eventType,
@Nullable AmbientBacklightMetadata metadata) {
mEventType = eventType;
@@ -75,10 +82,19 @@
mMetadata = in.readParcelable(AmbientBacklightMetadata.class.getClassLoader());
}
+ /**
+ * Gets event type.
+ */
public int getEventType() {
return mEventType;
}
+ /**
+ * Gets ambient backlight metadata.
+ *
+ * @return the metadata of the event. It's non-null only for
+ * {@link #AMBIENT_BACKLIGHT_EVENT_METADATA}.
+ */
@Nullable
public AmbientBacklightMetadata getMetadata() {
return mMetadata;
@@ -95,7 +111,8 @@
return 0;
}
- public static final @NonNull Parcelable.Creator<AmbientBacklightEvent> CREATOR =
+ @NonNull
+ public static final Parcelable.Creator<AmbientBacklightEvent> CREATOR =
new Parcelable.Creator<AmbientBacklightEvent>() {
public AmbientBacklightEvent createFromParcel(Parcel in) {
return new AmbientBacklightEvent(in);
diff --git a/media/java/android/media/quality/AmbientBacklightMetadata.java b/media/java/android/media/quality/AmbientBacklightMetadata.java
index fc77934..9c11f9a 100644
--- a/media/java/android/media/quality/AmbientBacklightMetadata.java
+++ b/media/java/android/media/quality/AmbientBacklightMetadata.java
@@ -16,6 +16,10 @@
package android.media.quality;
+import android.annotation.FlaggedApi;
+import android.annotation.IntRange;
+import android.graphics.PixelFormat;
+import android.media.tv.flags.Flags;
import android.os.Parcel;
import android.os.Parcelable;
@@ -24,9 +28,11 @@
import java.util.Arrays;
/**
+ * Metadata of ambient backlight.
* @hide
*/
-public class AmbientBacklightMetadata implements Parcelable {
+@FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
+public final class AmbientBacklightMetadata implements Parcelable {
@NonNull
private final String mPackageName;
private final int mCompressAlgorithm;
@@ -37,6 +43,9 @@
@NonNull
private final int[] mZonesColors;
+ /**
+ * Constructor of AmbientBacklightMetadata.
+ */
public AmbientBacklightMetadata(@NonNull String packageName, int compressAlgorithm,
int source, int colorFormat, int horizontalZonesNumber, int verticalZonesNumber,
@NonNull int[] zonesColors) {
@@ -59,31 +68,58 @@
mZonesColors = in.createIntArray();
}
+ /**
+ * Gets package name.
+ * @hide
+ */
@NonNull
public String getPackageName() {
return mPackageName;
}
+ /**
+ * Gets compress algorithm.
+ */
+ @AmbientBacklightSettings.CompressAlgorithm
public int getCompressAlgorithm() {
return mCompressAlgorithm;
}
+ /**
+ * Gets source of ambient backlight detection.
+ */
+ @AmbientBacklightSettings.Source
public int getSource() {
return mSource;
}
+ /**
+ * Gets color format.
+ */
+ @PixelFormat.Format
public int getColorFormat() {
return mColorFormat;
}
+ /**
+ * Gets the number of lights in each horizontal zone.
+ */
+ @IntRange(from = 0)
public int getHorizontalZonesNumber() {
return mHorizontalZonesNumber;
}
+ /**
+ * Gets the number of lights in each vertical zone.
+ */
+ @IntRange(from = 0)
public int getVerticalZonesNumber() {
return mVerticalZonesNumber;
}
+ /**
+ * @hide
+ */
@NonNull
public int[] getZonesColors() {
return mZonesColors;
diff --git a/media/java/android/media/quality/AmbientBacklightSettings.java b/media/java/android/media/quality/AmbientBacklightSettings.java
index bb782bf..4ed7bc7 100644
--- a/media/java/android/media/quality/AmbientBacklightSettings.java
+++ b/media/java/android/media/quality/AmbientBacklightSettings.java
@@ -16,7 +16,11 @@
package android.media.quality;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.graphics.PixelFormat;
+import android.media.tv.flags.Flags;
import android.os.Parcel;
import android.os.Parcelable;
@@ -29,7 +33,8 @@
* Settings for ambient backlight.
* @hide
*/
-public class AmbientBacklightSettings implements Parcelable {
+@FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
+public final class AmbientBacklightSettings implements Parcelable {
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef({SOURCE_NONE, SOURCE_AUDIO, SOURCE_VIDEO, SOURCE_AUDIO_VIDEO})
@@ -62,6 +67,7 @@
/**
* The color format is RGB888.
+ * @hide
*/
public static final int COLOR_FORMAT_RGB888 = 1;
@@ -76,7 +82,7 @@
public static final int ALGORITHM_NONE = 0;
/**
- * The compress algorithm is RLE.
+ * The compress algorithm is run length encoding (RLE).
*/
public static final int ALGORITHM_RLE = 1;
@@ -115,6 +121,9 @@
*/
private final int mThreshold;
+ /**
+ * Constructs AmbientBacklightSettings.
+ */
public AmbientBacklightSettings(int source, int maxFps, int colorFormat,
int horizontalZonesNumber, int verticalZonesNumber, boolean isLetterboxOmitted,
int threshold) {
@@ -137,32 +146,57 @@
mThreshold = in.readInt();
}
+ /**
+ * Gets source of ambient backlight detection.
+ */
@Source
public int getSource() {
return mSource;
}
+ /**
+ * Gets max frames per second.
+ */
+ @IntRange(from = 1)
public int getMaxFps() {
return mMaxFps;
}
- @ColorFormat
+ /**
+ * Gets color format.
+ */
+ @PixelFormat.Format
public int getColorFormat() {
return mColorFormat;
}
+ /**
+ * Gets the number of lights in each horizontal zone.
+ */
+ @IntRange(from = 0)
public int getHorizontalZonesNumber() {
return mHorizontalZonesNumber;
}
+ /**
+ * Gets the number of lights in each vertical zone.
+ */
+ @IntRange(from = 0)
public int getVerticalZonesNumber() {
return mVerticalZonesNumber;
}
+ /**
+ * Returns {@code true} if letter box is omitted; {@code false} otherwise.
+ * @hide
+ */
public boolean isLetterboxOmitted() {
return mIsLetterboxOmitted;
}
+ /**
+ * @hide
+ */
public int getThreshold() {
return mThreshold;
}
diff --git a/media/java/android/media/quality/MediaQualityContract.java b/media/java/android/media/quality/MediaQualityContract.java
index 472d798..f07ef87 100644
--- a/media/java/android/media/quality/MediaQualityContract.java
+++ b/media/java/android/media/quality/MediaQualityContract.java
@@ -33,6 +33,7 @@
*/
public interface BaseParameters {
String PARAMETER_ID = "_id";
+ String PARAMETER_TYPE = "_type";
String PARAMETER_NAME = "_name";
String PARAMETER_PACKAGE = "_package";
String PARAMETER_INPUT_ID = "_input_id";
@@ -43,7 +44,7 @@
* Parameters picture quality.
* @hide
*/
- public static final class PictureQuality {
+ public static final class PictureQuality implements BaseParameters {
/**
* The brightness.
*
diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java
index 26d83ac..4d4526c 100644
--- a/media/java/android/media/quality/MediaQualityManager.java
+++ b/media/java/android/media/quality/MediaQualityManager.java
@@ -567,7 +567,6 @@
/**
* Registers a {@link AmbientBacklightCallback}.
- * @hide
*/
public void registerAmbientBacklightCallback(
@NonNull @CallbackExecutor Executor executor,
@@ -581,7 +580,6 @@
/**
* Unregisters the existing {@link AmbientBacklightCallback}.
- * @hide
*/
public void unregisterAmbientBacklightCallback(
@NonNull final AmbientBacklightCallback callback) {
@@ -602,7 +600,6 @@
* Set the ambient backlight settings.
*
* @param settings The settings to use for the backlight detector.
- * @hide
*/
public void setAmbientBacklightSettings(
@NonNull AmbientBacklightSettings settings) {
@@ -618,7 +615,6 @@
* Enables or disables the ambient backlight detection.
*
* @param enabled {@code true} to enable, {@code false} to disable.
- * @hide
*/
public void setAmbientBacklightEnabled(boolean enabled) {
try {
@@ -843,14 +839,12 @@
/**
* Callback used to monitor status of ambient backlight.
- * @hide
*/
public abstract static class AmbientBacklightCallback {
/**
* Called when new ambient backlight event is emitted.
- * @hide
*/
- public void onAmbientBacklightEvent(AmbientBacklightEvent event) {
+ public void onAmbientBacklightEvent(@NonNull AmbientBacklightEvent event) {
}
}
}
diff --git a/media/java/android/media/quality/PictureProfileHandle.aidl b/media/java/android/media/quality/PictureProfileHandle.aidl
new file mode 100644
index 0000000..5d14631
--- /dev/null
+++ b/media/java/android/media/quality/PictureProfileHandle.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.quality;
+
+parcelable PictureProfileHandle;
diff --git a/media/java/android/media/quality/PictureProfileHandle.java b/media/java/android/media/quality/PictureProfileHandle.java
new file mode 100644
index 0000000..2b1cda4
--- /dev/null
+++ b/media/java/android/media/quality/PictureProfileHandle.java
@@ -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 android.media.quality;
+
+import android.annotation.FlaggedApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+// TODO(b/337330263): Expose as public API after API review
+/**
+ * A type-safe handle to a picture profile, which represents a collection of parameters used to
+ * configure picture processing hardware to enhance the quality of graphic buffers.
+ * @hide
+ */
+@FlaggedApi(android.media.tv.flags.Flags.FLAG_MEDIA_QUALITY_FW)
+public final class PictureProfileHandle implements Parcelable {
+ private final long mId;
+
+ @FlaggedApi(android.media.tv.flags.Flags.FLAG_MEDIA_QUALITY_FW)
+ public PictureProfileHandle(long id) {
+ mId = id;
+ }
+
+ @FlaggedApi(android.media.tv.flags.Flags.FLAG_MEDIA_QUALITY_FW)
+ public long getId() {
+ return mId;
+ }
+
+ @FlaggedApi(android.media.tv.flags.Flags.FLAG_MEDIA_QUALITY_FW)
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeLong(mId);
+ }
+
+ @FlaggedApi(android.media.tv.flags.Flags.FLAG_MEDIA_QUALITY_FW)
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @FlaggedApi(android.media.tv.flags.Flags.FLAG_MEDIA_QUALITY_FW)
+ @NonNull
+ public static final Creator<PictureProfileHandle> CREATOR =
+ new Creator<PictureProfileHandle>() {
+ @Override
+ public PictureProfileHandle createFromParcel(Parcel in) {
+ return new PictureProfileHandle(in);
+ }
+
+ @Override
+ public PictureProfileHandle[] newArray(int size) {
+ return new PictureProfileHandle[size];
+ }
+ };
+
+ private PictureProfileHandle(@NonNull Parcel in) {
+ mId = in.readLong();
+ }
+}
diff --git a/media/java/android/media/tv/TvInputServiceExtensionManager.java b/media/java/android/media/tv/TvInputServiceExtensionManager.java
index c514f6e..9442726 100644
--- a/media/java/android/media/tv/TvInputServiceExtensionManager.java
+++ b/media/java/android/media/tv/TvInputServiceExtensionManager.java
@@ -20,7 +20,9 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.StringDef;
+import android.annotation.SystemApi;
import android.media.tv.flags.Flags;
import android.os.IBinder;
import android.os.RemoteException;
@@ -43,6 +45,7 @@
*
* @hide
*/
+@SystemApi
@FlaggedApi(Flags.FLAG_TIF_EXTENSION_STANDARDIZATION)
public final class TvInputServiceExtensionManager {
private static final String TAG = "TvInputServiceExtensionManager";
@@ -186,8 +189,7 @@
@Retention(RetentionPolicy.SOURCE)
public @interface StandardizedExtensionName {}
/**
- * Interface responsible for creating scan session and obtain parameters.
- * @hide
+ * Interface responsible for creating scan session and obtaining related parameters.
*/
public static final String ISCAN_INTERFACE = SCAN_PACKAGE + "IScanInterface";
/**
@@ -286,12 +288,10 @@
public static final String ISCAN_SAT_SEARCH = SCAN_PACKAGE + "IScanSatSearch";
/**
* Interface for Over-the-Air Download.
- * @hide
*/
public static final String IOAD_UPDATE_INTERFACE = OAD_PACKAGE + "IOadUpdateInterface";
/**
* Interface for handling conditional access module app related information.
- * @hide
*/
public static final String ICAM_APP_INFO_SERVICE = CAM_PACKAGE + "ICamAppInfoService";
/**
@@ -406,8 +406,7 @@
public static final String IDOWNLOADABLE_RATING_TABLE_MONITOR = RATING_PACKAGE
+ "IDownloadableRatingTableMonitor";
/**
- * Interface for handling RRT rating related information.
- * @hide
+ * Interface for handling Region Rating Table rating system related information.
*/
public static final String IRATING_INTERFACE = RATING_PACKAGE + "IRatingInterface";
/**
@@ -442,12 +441,10 @@
public static final String IPROGRAM_INFO_LISTENER = RATING_PACKAGE + "IProgramInfoListener";
/**
* Interface for getting broadcast time related information.
- * @hide
*/
public static final String IBROADCAST_TIME = TIME_PACKAGE + "BroadcastTime";
/**
* Interface for handling data service signal information on teletext.
- * @hide
*/
public static final String IDATA_SERVICE_SIGNAL_INFO = TELETEXT_PACKAGE
+ "IDataServiceSignalInfo";
@@ -476,17 +473,14 @@
+ "IScanBackgroundServiceUpdateListener";
/**
* Interface for generating client token.
- * @hide
*/
public static final String ICLIENT_TOKEN = CLIENT_TOKEN_PACKAGE + "IClientToken";
/**
* Interfaces for handling screen mode information.
- * @hide
*/
public static final String ISCREEN_MODE_SETTINGS = SCREEN_MODE_PACKAGE + "IScreenModeSettings";
/**
* Interfaces for handling HDMI signal information update.
- * @hide
*/
public static final String IHDMI_SIGNAL_INTERFACE = SIGNAL_PACKAGE + "IHdmiSignalInterface";
/**
@@ -529,7 +523,6 @@
public static final String ISERVICE_LIST_EDIT = SERVICE_DATABASE_PACKAGE + "IServiceListEdit";
/**
* Interfaces for changes on service database updates.
- * @hide
*/
public static final String ISERVICE_LIST_EDIT_LISTENER = SERVICE_DATABASE_PACKAGE
+ "IServiceListEditListener";
@@ -587,8 +580,7 @@
public static final String ICHANNEL_LIST_TRANSFER = SERVICE_DATABASE_PACKAGE
+ "IChannelListTransfer";
/**
- * Interfaces for record contents updates.
- * @hide
+ * Interface for operations related to recorded contents.
*/
public static final String IRECORDED_CONTENTS = PVR_PACKAGE + "IRecordedContents";
/**
@@ -605,7 +597,6 @@
+ "IGetInfoRecordedContentsCallback";
/**
* Interfaces for monitoring present event information.
- * @hide
*/
public static final String IEVENT_MONITOR = EVENT_PACKAGE + "IEventMonitor";
/**
@@ -784,20 +775,22 @@
}
/**
- * This function should be used by OEM to register IBinder objects that implement
- * standardized AIDL interfaces.
+ * Registers IBinder objects that implement standardized AIDL interfaces.
+ * <p>This function should be used by SoCs/OEMs
*
* @param extensionName Extension Interface Name
* @param binder IBinder object to be registered
- * @return REGISTER_SUCCESS on success of registering IBinder object
- * REGISTER_FAIL_NAME_NOT_STANDARDIZED on failure due to registering extension with
- * non-standardized name
- * REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED on failure due to IBinder not
+ * @return {@link #REGISTER_SUCCESS} on success of registering IBinder object
+ * {@link #REGISTER_FAIL_NAME_NOT_STANDARDIZED} on failure due to registering extension
+ * with non-standardized name
+ * {@link #REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED} on failure due to IBinder not
* implementing standardized AIDL interface
- * REGISTER_FAIL_REMOTE_EXCEPTION on failure due to remote exception
+ * {@link #REGISTER_FAIL_REMOTE_EXCEPTION} on failure due to remote exception
*
* @hide
*/
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
@RegisterResult
public int registerExtensionIBinder(@StandardizedExtensionName @NonNull String extensionName,
@NonNull IBinder binder) {
diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig
index 4de6863..6441652 100644
--- a/media/java/android/media/tv/flags/media_tv.aconfig
+++ b/media/java/android/media/tv/flags/media_tv.aconfig
@@ -80,3 +80,11 @@
description : "Feature flag to add setResourceHolderRetain api to MediaCas and Tuner JAVA."
bug: "372973197"
}
+
+flag {
+ name: "apply_picture_profiles"
+ is_exported: true
+ namespace: "media_tv"
+ description : "Feature flag to enable APIs for applying picture profiles"
+ bug: "337330263"
+}
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index c44e26f..f09dc72 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -104,6 +104,7 @@
"libgrallocusage",
"libmedia_midiiowrapper",
"android.companion.virtualdevice.flags-aconfig-cc",
+ "android.media.codec-aconfig-cc",
"android.media.playback.flags-aconfig-cc",
],
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 4492c85..fc184fe 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -39,6 +39,8 @@
#include <C2Buffer.h>
#include <C2PlatformSupport.h>
+#include <android_media_codec.h>
+
#include <android/hardware/cas/native/1.0/IDescrambler.h>
#include <android_runtime/android_hardware_HardwareBuffer.h>
@@ -189,6 +191,22 @@
jmethodID setId;
} gBufferInfo;
+static struct {
+ jclass clazz;
+ jmethodID ctorId;
+ jfieldID resourceId;
+ jfieldID capacityId;
+ jfieldID availableId;
+} gGlobalResourceInfo;
+
+static struct {
+ jclass clazz;
+ jmethodID ctorId;
+ jfieldID resourceId;
+ jfieldID staticCountId;
+ jfieldID perFrameCountId;
+} gInstanceResourceInfo;
+
struct fields_t {
jmethodID postEventFromNativeID;
jmethodID lockAndGetContextID;
@@ -1129,6 +1147,37 @@
return mCodec->unsubscribeFromVendorParameters(names);
}
+static jobject getJavaResources(
+ JNIEnv *env,
+ const std::vector<MediaCodec::InstanceResourceInfo>& resources) {
+ jobject resourcesObj = env->NewObject(gArrayListInfo.clazz, gArrayListInfo.ctorId);
+ for (const MediaCodec::InstanceResourceInfo& res : resources) {
+ ScopedLocalRef<jobject> object{env, env->NewObject(
+ gInstanceResourceInfo.clazz, gInstanceResourceInfo.ctorId)};
+ ScopedLocalRef<jstring> nameStr{env, env->NewStringUTF(res.mName.c_str())};
+ env->SetObjectField(object.get(), gInstanceResourceInfo.resourceId, nameStr.get());
+ env->SetLongField(object.get(),
+ gInstanceResourceInfo.staticCountId,
+ (jlong)res.mStaticCount);
+ env->SetLongField(object.get(),
+ gInstanceResourceInfo.perFrameCountId,
+ (jlong)res.mPerFrameCount);
+ (void)env->CallBooleanMethod(resourcesObj, gArrayListInfo.addId, object.get());
+ }
+
+ return resourcesObj;
+}
+
+status_t JMediaCodec::getRequiredResources(JNIEnv *env, jobject *resourcesObj) {
+ std::vector<MediaCodec::InstanceResourceInfo> resources;
+ status_t status = mCodec->getRequiredResources(resources);
+ if (status != OK) {
+ return status;
+ }
+ *resourcesObj = getJavaResources(env, resources);
+ return OK;
+}
+
static jthrowable createCodecException(
JNIEnv *env, status_t err, int32_t actionCode, const char *msg = NULL) {
ScopedLocalRef<jclass> clazz(
@@ -1461,6 +1510,25 @@
break;
}
+ case MediaCodec::CB_METRICS_FLUSHED:
+ {
+ sp<WrapperObject<std::unique_ptr<mediametrics::Item>>> metrics;
+ CHECK(msg->findObject("metrics", (sp<RefBase>*)&metrics));
+
+ // metrics should never be null. Not sure if checking it here adds any value.
+ if (metrics == nullptr) {
+ return;
+ }
+
+ mediametrics::Item *item = metrics->value.get();
+ obj = MediaMetricsJNI::writeMetricsToBundle(env, item, NULL);
+ break;
+ }
+ case MediaCodec::CB_REQUIRED_RESOURCES_CHANGED:
+ {
+ break;
+ }
+
default:
TRESPASS();
}
@@ -3545,6 +3613,64 @@
return;
}
+static jobject getJavaResources(
+ JNIEnv *env,
+ const std::vector<MediaCodec::GlobalResourceInfo>& resources) {
+ jobject resourcesObj = env->NewObject(gArrayListInfo.clazz, gArrayListInfo.ctorId);
+ for (const MediaCodec::GlobalResourceInfo& res : resources) {
+ ScopedLocalRef<jobject> object{env, env->NewObject(
+ gGlobalResourceInfo.clazz, gGlobalResourceInfo.ctorId)};
+ ScopedLocalRef<jstring> nameStr{env, env->NewStringUTF(res.mName.c_str())};
+ env->SetObjectField(object.get(), gInstanceResourceInfo.resourceId, nameStr.get());
+ env->SetLongField(object.get(), gGlobalResourceInfo.capacityId, (jlong)res.mCapacity);
+ env->SetLongField(object.get(), gGlobalResourceInfo.availableId, (jlong)res.mAvailable);
+ (void)env->CallBooleanMethod(resourcesObj, gArrayListInfo.addId, object.get());
+ }
+
+ return resourcesObj;
+}
+
+static jobject android_media_MediaCodec_getGloballyAvailableResources(
+ JNIEnv *env, jobject thiz) {
+ (void)thiz;
+ std::vector<MediaCodec::GlobalResourceInfo> resources;
+ status_t status = MediaCodec::getGloballyAvailableResources(resources);
+ if (status != OK) {
+ if (status == ERROR_UNSUPPORTED) {
+ jniThrowException(env, "java/lang/UnsupportedOperationException",
+ "Function Not Implemented");
+ } else {
+ throwExceptionAsNecessary(env, status, nullptr);
+ }
+ return nullptr;
+ }
+
+ return getJavaResources(env, resources);
+}
+
+static jobject android_media_MediaCodec_getRequiredResources(
+ JNIEnv *env, jobject thiz) {
+ sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+ if (codec == nullptr || codec->initCheck() != OK) {
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
+ return nullptr;
+ }
+
+ jobject ret = nullptr;
+ status_t status = codec->getRequiredResources(env, &ret);
+ if (status != OK) {
+ if (status == ERROR_UNSUPPORTED) {
+ jniThrowException(env, "java/lang/UnsupportedOperationException",
+ "Function Not Implemented");
+ } else {
+ throwExceptionAsNecessary(env, status, nullptr);
+ }
+ return nullptr;
+ }
+
+ return ret;
+}
+
static void android_media_MediaCodec_native_init(JNIEnv *env, jclass) {
ScopedLocalRef<jclass> clazz(
env, env->FindClass("android/media/MediaCodec"));
@@ -3890,6 +4016,36 @@
gFields.bufferInfoOffset = env->GetFieldID(clazz.get(), "offset", "I");
gFields.bufferInfoPresentationTimeUs =
env->GetFieldID(clazz.get(), "presentationTimeUs", "J");
+
+ // Since these TestApis are defined under the flag, make sure they are
+ // accessed only when the flag is set.
+ if (android::media::codec::codec_availability()) {
+ clazz.reset(env->FindClass("android/media/MediaCodec$GlobalResourceInfo"));
+ CHECK(clazz.get() != NULL);
+ gGlobalResourceInfo.clazz = (jclass)env->NewGlobalRef(clazz.get());
+ gGlobalResourceInfo.ctorId = env->GetMethodID(clazz.get(), "<init>", "()V");
+ CHECK(gGlobalResourceInfo.ctorId != NULL);
+ gGlobalResourceInfo.resourceId =
+ env->GetFieldID(clazz.get(), "mName", "Ljava/lang/String;");
+ CHECK(gGlobalResourceInfo.resourceId != NULL);
+ gGlobalResourceInfo.capacityId = env->GetFieldID(clazz.get(), "mCapacity", "J");
+ CHECK(gGlobalResourceInfo.capacityId != NULL);
+ gGlobalResourceInfo.availableId = env->GetFieldID(clazz.get(), "mAvailable", "J");
+ CHECK(gGlobalResourceInfo.availableId != NULL);
+
+ clazz.reset(env->FindClass("android/media/MediaCodec$InstanceResourceInfo"));
+ CHECK(clazz.get() != NULL);
+ gInstanceResourceInfo.clazz = (jclass)env->NewGlobalRef(clazz.get());
+ gInstanceResourceInfo.ctorId = env->GetMethodID(clazz.get(), "<init>", "()V");
+ CHECK(gInstanceResourceInfo.ctorId != NULL);
+ gInstanceResourceInfo.resourceId =
+ env->GetFieldID(clazz.get(), "mName", "Ljava/lang/String;");
+ CHECK(gInstanceResourceInfo.resourceId != NULL);
+ gInstanceResourceInfo.staticCountId= env->GetFieldID(clazz.get(), "mStaticCount", "J");
+ CHECK(gInstanceResourceInfo.staticCountId != NULL);
+ gInstanceResourceInfo.perFrameCountId = env->GetFieldID(clazz.get(), "mPerFrameCount", "J");
+ CHECK(gInstanceResourceInfo.perFrameCountId != NULL);
+ }
}
static void android_media_MediaCodec_native_setup(
@@ -4246,6 +4402,12 @@
{ "native_finalize", "()V",
(void *)android_media_MediaCodec_native_finalize },
+
+ { "native_getGloballyAvailableResources", "()Ljava/util/List;",
+ (void *)android_media_MediaCodec_getGloballyAvailableResources},
+
+ { "native_getRequiredResources", "()Ljava/util/List;",
+ (void *)android_media_MediaCodec_getRequiredResources},
};
static const JNINativeMethod gLinearBlockMethods[] = {
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index c9b6b7f6..930dbbe 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -185,6 +185,8 @@
status_t unsubscribeFromVendorParameters(JNIEnv *env, jobject names);
+ status_t getRequiredResources(JNIEnv *env, jobject *resourcesObj);
+
bool hasCryptoOrDescrambler() { return mHasCryptoOrDescrambler; }
const sp<ICrypto> &getCrypto() { return mCrypto; }
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index a23845f..c25f77b 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -107,6 +107,7 @@
method public void onRfDiscoveryStarted(boolean);
method public void onRfFieldActivated(boolean);
method public void onRoutingChanged();
+ method public void onRoutingTableFull();
method public void onStateUpdated(int);
method public void onTagConnected(boolean);
method public void onTagDispatch(@NonNull java.util.function.Consumer<java.lang.Boolean>);
diff --git a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
index b102e87..fb793b0 100644
--- a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
+++ b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
@@ -52,5 +52,6 @@
void onNdefMessage(in Tag tag, in NdefMessage message, in ResultReceiver hasOemExecutableContent);
void onLaunchHceAppChooserActivity(in String selectedAid, in List<ApduServiceInfo> services, in ComponentName failedComponent, in String category);
void onLaunchHceTapAgainActivity(in ApduServiceInfo service, in String category);
+ void onRoutingTableFull();
void onLogEventNotified(in OemLogItems item);
}
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index abd99bc..57ee981 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -394,6 +394,13 @@
void onLaunchHceTapAgainDialog(@NonNull ApduServiceInfo service, @NonNull String category);
/**
+ * Callback to indicate that routing table is full and the OEM can optionally launch a
+ * dialog to request the user to remove some Card Emulation apps from the device to free
+ * routing table space.
+ */
+ void onRoutingTableFull();
+
+ /**
* Callback when OEM specified log event are notified.
* @param item the log items that contains log information of NFC event.
*/
@@ -853,6 +860,12 @@
handleVoidCallback(enabled, cb::onReaderOptionChanged, ex));
}
+ public void onRoutingTableFull() throws RemoteException {
+ mCallbackMap.forEach((cb, ex) ->
+ handleVoidCallback(null,
+ (Object input) -> cb.onRoutingTableFull(), ex));
+ }
+
@Override
public void onGetOemAppSearchIntent(List<String> packages, ResultReceiver intentConsumer)
throws RemoteException {
diff --git a/nfc/java/android/nfc/OemLogItems.java b/nfc/java/android/nfc/OemLogItems.java
index 6671941..4f3e199 100644
--- a/nfc/java/android/nfc/OemLogItems.java
+++ b/nfc/java/android/nfc/OemLogItems.java
@@ -142,8 +142,11 @@
dest.writeByteArray(mCommandApdus);
dest.writeInt(mResponseApdus.length);
dest.writeByteArray(mResponseApdus);
- dest.writeLong(mRfFieldOnTime.getEpochSecond());
- dest.writeInt(mRfFieldOnTime.getNano());
+ dest.writeBoolean(mRfFieldOnTime != null);
+ if (mRfFieldOnTime != null) {
+ dest.writeLong(mRfFieldOnTime.getEpochSecond());
+ dest.writeInt(mRfFieldOnTime.getNano());
+ }
dest.writeParcelable(mTag, 0);
}
@@ -305,7 +308,12 @@
in.readByteArray(this.mCommandApdus);
this.mResponseApdus = new byte[in.readInt()];
in.readByteArray(this.mResponseApdus);
- this.mRfFieldOnTime = Instant.ofEpochSecond(in.readLong(), in.readInt());
+ boolean isRfFieldOnTimeSet = in.readBoolean();
+ if (isRfFieldOnTimeSet) {
+ this.mRfFieldOnTime = Instant.ofEpochSecond(in.readLong(), in.readInt());
+ } else {
+ this.mRfFieldOnTime = null;
+ }
this.mTag = in.readParcelable(Tag.class.getClassLoader(), Tag.class);
}
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index b2dcb7f..a3da93d 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -61,9 +61,11 @@
"SettingsLibUtils",
"SettingsLibZeroStatePreference",
"settingslib_media_flags_lib",
+ ],
+ libs:[
+ // This flag library has been added in frameworks jar
"aconfig_settingslib_flags_java_lib",
],
-
plugins: ["androidx.room_room-compiler-plugin"],
use_resource_processor: true,
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceHierarchy.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceHierarchy.kt
index bde4217..a2b826a 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceHierarchy.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceHierarchy.kt
@@ -166,10 +166,6 @@
}
return null
}
-
- /** Returns all the [PreferenceHierarchyNode]s appear in the hierarchy. */
- fun getAllPreferences(): List<PreferenceHierarchyNode> =
- mutableListOf<PreferenceHierarchyNode>().apply { forEachRecursively { add(it) } }
}
/**
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
index 49acc1d..6b7be91 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
@@ -108,6 +108,9 @@
}
}
+/** Interface indicates that a virtual [Preference] should be created for binding. */
+interface PreferenceBindingPlaceholder
+
/** Abstract preference screen to provide preference hierarchy and binding factory. */
interface PreferenceScreenCreator : PreferenceScreenMetadata, PreferenceScreenProvider {
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
index fbe8927..cfe6089 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
@@ -218,34 +218,47 @@
preferenceScreen: PreferenceScreen,
preferenceBindingFactory: PreferenceBindingFactory,
preferenceHierarchy: PreferenceHierarchy,
- ) =
- preferenceScreen.bindRecursively(
- preferenceBindingFactory,
- preferenceHierarchy.getAllPreferences().associateBy { it.metadata.key },
- )
-
- private fun PreferenceGroup.bindRecursively(
- preferenceBindingFactory: PreferenceBindingFactory,
- preferences: Map<String, PreferenceHierarchyNode>,
- storages: MutableMap<KeyValueStore, PreferenceDataStore> = mutableMapOf(),
) {
- preferences[key]?.let { preferenceBindingFactory.bind(this, it) }
- val count = preferenceCount
- for (index in 0 until count) {
- val preference = getPreference(index)
- if (preference is PreferenceGroup) {
- preference.bindRecursively(preferenceBindingFactory, preferences, storages)
- } else {
- preferences[preference.key]?.let {
- val metadata = it.metadata
- (metadata as? PersistentPreference<*>)?.storage(context)?.let { storage ->
- preference.preferenceDataStore =
- storages.getOrPut(storage) { PreferenceDataStoreAdapter(storage) }
+ val preferences = mutableMapOf<String, PreferenceHierarchyNode>()
+ preferenceHierarchy.forEachRecursively {
+ val metadata = it.metadata
+ preferences[metadata.key] = it
+ }
+ val storages = mutableMapOf<KeyValueStore, PreferenceDataStore>()
+
+ fun Preference.setPreferenceDataStore(metadata: PreferenceMetadata) {
+ (metadata as? PersistentPreference<*>)?.storage(context)?.let { storage ->
+ preferenceDataStore =
+ storages.getOrPut(storage) { PreferenceDataStoreAdapter(storage) }
+ }
+ }
+
+ fun PreferenceGroup.bindRecursively() {
+ preferences.remove(key)?.let { preferenceBindingFactory.bind(this, it) }
+ val count = preferenceCount
+ for (index in 0 until count) {
+ val preference = getPreference(index)
+ if (preference is PreferenceGroup) {
+ preference.bindRecursively()
+ } else {
+ preferences.remove(preference.key)?.let {
+ preference.setPreferenceDataStore(it.metadata)
+ preferenceBindingFactory.bind(preference, it)
}
- preferenceBindingFactory.bind(preference, it)
}
}
}
+
+ preferenceScreen.bindRecursively()
+ for (node in preferences.values) {
+ val metadata = node.metadata
+ val binding = preferenceBindingFactory.getPreferenceBinding(metadata)
+ if (binding !is PreferenceBindingPlaceholder) continue
+ val preference = binding.createWidget(preferenceScreen.context)
+ preference.setPreferenceDataStore(metadata)
+ preferenceBindingFactory.bind(preference, node, binding)
+ preferenceScreen.addPreference(preference)
+ }
}
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPageProvider.kt
index 4d3a78a5..f2bc380 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPageProvider.kt
@@ -17,8 +17,12 @@
package com.android.settingslib.spa.gallery.ui
import android.os.Bundle
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
@@ -30,6 +34,7 @@
import com.android.settingslib.spa.widget.preference.SimplePreferenceMacro
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
import com.android.settingslib.spa.widget.ui.Category
+import com.android.settingslib.spa.widget.ui.LazyCategory
private const val TITLE = "Sample Category"
@@ -65,7 +70,7 @@
)
entryList.add(
SettingsEntryBuilder.create("Preference 3", owner)
- .setMacro { SimplePreferenceMacro(title = "Preference 2", summary = "Summary 3") }
+ .setMacro { SimplePreferenceMacro(title = "Preference 3", summary = "Summary 3") }
.build()
)
entryList.add(
@@ -88,6 +93,13 @@
entries[2].UiLayout()
entries[3].UiLayout()
}
+ Column(Modifier.height(200.dp)) {
+ LazyCategory(
+ list = entries,
+ entry = { index: Int -> @Composable { entries[index].UiLayout() } },
+ title = { index: Int -> if (index == 0 || index == 2) "LazyCategory" else null },
+ ) {}
+ }
}
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt
index 66680fa..28b2b4a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt
@@ -19,8 +19,13 @@
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.TouchApp
import androidx.compose.material3.MaterialTheme
@@ -34,6 +39,7 @@
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsShape
@@ -98,6 +104,57 @@
}
}
+/**
+ * A container that is used to group items with lazy loading.
+ *
+ * @param list The list of items to display.
+ * @param entry The entry for each list item according to its index in list.
+ * @param key Optional. The key for each item in list to provide unique item identifiers, making
+ * the list more efficient.
+ * @param title Optional. Category title for each item or each group of items in the list. It
+ * should be decided by the index.
+ * @param bottomPadding Optional. Bottom outside padding of the category.
+ * @param state Optional. State of LazyList.
+ * @param content Optional. Content to be shown at the top of the category.
+ */
+
+@Composable
+fun LazyCategory(
+ list: List<Any>,
+ entry: (Int) -> @Composable () -> Unit,
+ key: ((Int) -> Any)? = null,
+ title: ((Int) -> String?)? = null,
+ bottomPadding: Dp = SettingsDimension.paddingSmall,
+ state: LazyListState = rememberLazyListState(),
+ content: @Composable () -> Unit,
+) {
+ Column(
+ Modifier.padding(
+ PaddingValues(
+ start = SettingsDimension.paddingLarge,
+ end = SettingsDimension.paddingLarge,
+ top = SettingsDimension.paddingSmall,
+ bottom = bottomPadding,
+ )
+ )
+ .clip(SettingsShape.CornerMedium2)
+ ) {
+ LazyColumn(
+ modifier = Modifier.fillMaxSize(),
+ verticalArrangement = Arrangement.spacedBy(SettingsDimension.paddingTiny),
+ state = state,
+ ) {
+ item { content() }
+
+ items(count = list.size, key = key) {
+ title?.invoke(it)?.let { title -> CategoryTitle(title) }
+ val entryPreference = entry(it)
+ entryPreference()
+ }
+ }
+ }
+}
+
@Preview
@Composable
private fun CategoryPreview() {
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt
index 09a6e6d..4b4a8c2 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt
@@ -16,10 +16,16 @@
package com.android.settingslib.spa.widget.ui
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.height
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
@@ -30,14 +36,11 @@
@RunWith(AndroidJUnit4::class)
class CategoryTest {
- @get:Rule
- val composeTestRule = createComposeRule()
+ @get:Rule val composeTestRule = createComposeRule()
@Test
fun categoryTitle() {
- composeTestRule.setContent {
- CategoryTitle(title = "CategoryTitle")
- }
+ composeTestRule.setContent { CategoryTitle(title = "CategoryTitle") }
composeTestRule.onNodeWithText("CategoryTitle").assertIsDisplayed()
}
@@ -46,12 +49,14 @@
fun category_hasContent_titleDisplayed() {
composeTestRule.setContent {
Category(title = "CategoryTitle") {
- Preference(remember {
- object : PreferenceModel {
- override val title = "Some Preference"
- override val summary = { "Some summary" }
+ Preference(
+ remember {
+ object : PreferenceModel {
+ override val title = "Some Preference"
+ override val summary = { "Some summary" }
+ }
}
- })
+ )
}
}
@@ -60,10 +65,45 @@
@Test
fun category_noContent_titleNotDisplayed() {
- composeTestRule.setContent {
- Category(title = "CategoryTitle") {}
- }
+ composeTestRule.setContent { Category(title = "CategoryTitle") {} }
composeTestRule.onNodeWithText("CategoryTitle").assertDoesNotExist()
}
+
+ @Test
+ fun lazyCategory_content_displayed() {
+ composeTestRule.setContent { TestLazyCategory() }
+
+ composeTestRule.onNodeWithText("text").assertExists()
+ }
+
+ @Test
+ fun lazyCategory_title_displayed() {
+ composeTestRule.setContent { TestLazyCategory() }
+
+ composeTestRule.onNodeWithText("LazyCategory 0").assertExists()
+ composeTestRule.onNodeWithText("LazyCategory 1").assertDoesNotExist()
+ }
+}
+
+@Composable
+private fun TestLazyCategory() {
+ val list: List<PreferenceModel> =
+ listOf(
+ object : PreferenceModel {
+ override val title = "title"
+ },
+ object : PreferenceModel {
+ override val title = "title"
+ },
+ )
+ Column(Modifier.height(200.dp)) {
+ LazyCategory(
+ list = list,
+ entry = { index: Int -> @Composable { Preference(list[index]) } },
+ title = { index: Int -> if (index == 0) "LazyCategory $index" else null },
+ ) {
+ Text("text")
+ }
+ }
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index bededf0..2a214b6 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -37,7 +37,9 @@
import com.android.settingslib.spa.framework.compose.LogCompositions
import com.android.settingslib.spa.framework.compose.TimeMeasurer.Companion.rememberTimeMeasurer
import com.android.settingslib.spa.framework.compose.rememberLazyListStateAndHideKeyboardWhenStartScroll
+import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled
import com.android.settingslib.spa.widget.ui.CategoryTitle
+import com.android.settingslib.spa.widget.ui.LazyCategory
import com.android.settingslib.spa.widget.ui.PlaceholderTitle
import com.android.settingslib.spa.widget.ui.Spinner
import com.android.settingslib.spa.widget.ui.SpinnerOption
@@ -55,19 +57,14 @@
private const val TAG = "AppList"
private const val CONTENT_TYPE_HEADER = "header"
-/**
- * The config used to load the App List.
- */
+/** The config used to load the App List. */
data class AppListConfig(
val userIds: List<Int>,
val showInstantApps: Boolean,
val matchAnyUserForAdmin: Boolean,
)
-data class AppListState(
- val showSystem: () -> Boolean,
- val searchQuery: () -> String,
-)
+data class AppListState(val showSystem: () -> Boolean, val searchQuery: () -> String)
data class AppListInput<T : AppRecord>(
val config: AppListConfig,
@@ -90,7 +87,7 @@
@Composable
internal fun <T : AppRecord> AppListInput<T>.AppListImpl(
- viewModelSupplier: @Composable () -> IAppListViewModel<T>,
+ viewModelSupplier: @Composable () -> IAppListViewModel<T>
) {
LogCompositions(TAG, config.userIds.toString())
val viewModel = viewModelSupplier()
@@ -125,7 +122,7 @@
appListData: State<AppListData<T>?>,
header: @Composable () -> Unit,
bottomPadding: Dp,
- noItemMessage: String?
+ noItemMessage: String?,
) {
val timeMeasurer = rememberTimeMeasurer(TAG)
appListData.value?.let { (list, option) ->
@@ -135,40 +132,61 @@
PlaceholderTitle(noItemMessage ?: stringResource(R.string.no_applications))
return
}
- LazyColumn(
- modifier = Modifier.fillMaxSize(),
- state = rememberLazyListStateAndHideKeyboardWhenStartScroll(),
- contentPadding = PaddingValues(bottom = bottomPadding),
- ) {
- item(contentType = CONTENT_TYPE_HEADER) {
+ if (isSpaExpressiveEnabled) {
+ LazyCategory(
+ list = list,
+ entry = { index: Int ->
+ @Composable {
+ val appEntry = list[index]
+ val summary = getSummary(option, appEntry.record) ?: { "" }
+ remember(appEntry) {
+ AppListItemModel(appEntry.record, appEntry.label, summary)
+ }
+ .AppItem()
+ }
+ },
+ key = { index: Int -> list[index].record.itemKey(option) },
+ title = { index: Int -> getGroupTitle(option, list[index].record) },
+ bottomPadding = bottomPadding,
+ state = rememberLazyListStateAndHideKeyboardWhenStartScroll(),
+ ) {
header()
}
+ } else {
+ LazyColumn(
+ modifier = Modifier.fillMaxSize(),
+ state = rememberLazyListStateAndHideKeyboardWhenStartScroll(),
+ contentPadding = PaddingValues(bottom = bottomPadding),
+ ) {
+ item(contentType = CONTENT_TYPE_HEADER) { header() }
- items(count = list.size, key = { list[it].record.itemKey(option) }) {
- remember(list) { getGroupTitleIfFirst(option, list, it) }
- ?.let { group -> CategoryTitle(title = group) }
+ items(count = list.size, key = { list[it].record.itemKey(option) }) {
+ remember(list) { getGroupTitleIfFirst(option, list, it) }
+ ?.let { group -> CategoryTitle(title = group) }
- val appEntry = list[it]
- val summary = getSummary(option, appEntry.record) ?: { "" }
- remember(appEntry) {
- AppListItemModel(appEntry.record, appEntry.label, summary)
- }.AppItem()
+ val appEntry = list[it]
+ val summary = getSummary(option, appEntry.record) ?: { "" }
+ remember(appEntry) {
+ AppListItemModel(appEntry.record, appEntry.label, summary)
+ }
+ .AppItem()
+ }
}
}
}
}
-private fun <T : AppRecord> T.itemKey(option: Int) =
- listOf(option, app.packageName, app.userId)
+private fun <T : AppRecord> T.itemKey(option: Int) = listOf(option, app.packageName, app.userId)
/** Returns group title if this is the first item of the group. */
private fun <T : AppRecord> AppListModel<T>.getGroupTitleIfFirst(
option: Int,
list: List<AppEntry<T>>,
index: Int,
-): String? = getGroupTitle(option, list[index].record)?.takeIf {
- index == 0 || it != getGroupTitle(option, list[index - 1].record)
-}
+): String? =
+ getGroupTitle(option, list[index].record)?.takeIf {
+ index == 0 || it != getGroupTitle(option, list[index - 1].record)
+ }
@Composable
private fun <T : AppRecord> rememberViewModel(
@@ -183,16 +201,19 @@
viewModel.searchQuery.Sync(state.searchQuery)
LifecycleEffect(onStart = { viewModel.reloadApps() })
- val intentFilter = IntentFilter(Intent.ACTION_PACKAGE_ADDED).apply {
- addAction(Intent.ACTION_PACKAGE_REMOVED)
- addAction(Intent.ACTION_PACKAGE_CHANGED)
- addDataScheme("package")
- }
+ val intentFilter =
+ IntentFilter(Intent.ACTION_PACKAGE_ADDED).apply {
+ addAction(Intent.ACTION_PACKAGE_REMOVED)
+ addAction(Intent.ACTION_PACKAGE_CHANGED)
+ addDataScheme("package")
+ }
for (userId in config.userIds) {
DisposableBroadcastReceiverAsUser(
intentFilter = intentFilter,
userHandle = UserHandle.of(userId),
- ) { viewModel.reloadApps() }
+ ) {
+ viewModel.reloadApps()
+ }
}
return viewModel
}
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index 81a2e6a..bf419cc 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -74,16 +74,6 @@
}
flag {
- name: "volume_panel_broadcast_fix"
- namespace: "systemui"
- description: "Make the volume panel's repository listen for the new ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED broadcast instead of ACTION_NOTIFICATION_POLICY_CHANGED"
- bug: "347707024"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "volume_dialog_audio_sharing_fix"
namespace: "cross_device_experiences"
description: "Gates whether to show separate volume bars during audio sharing"
@@ -111,6 +101,14 @@
}
flag {
+ name: "write_system_preference_permission_enabled"
+ is_fixed_read_only: true
+ namespace: "android_settings"
+ description: "Enable WRITE_SYSTEM_PREFERENCE permission and appop"
+ bug: "375193223"
+}
+
+flag {
name: "asha_profile_access_profile_enabled_true"
namespace: "accessibility"
description: "Changes the return value of HearingAidProfile.accessProfileEnabled() to true"
@@ -166,3 +164,10 @@
description: "Enable the ambient volume control in device details and hearing devices dialog."
bug: "357878944"
}
+
+flag {
+ name: "settings_preference_write_consent_enabled"
+ namespace: "android_settings"
+ description: "Enable the user consent prompt before writing sensitive preferences via service"
+ bug: "378552675"
+}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index bc144d6..f03014c 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -821,8 +821,10 @@
<!-- Title of checkbox setting that enables the Linux terminal app. [CHAR LIMIT=32] -->
<string name="enable_linux_terminal_title">Linux development environment</string>
- <!-- Summary of checkbox setting that enables the Linux terminal app. [CHAR LIMIT=64] -->
- <string name="enable_linux_terminal_summary">Run Linux terminal on Android</string>
+ <!-- Summary of checkbox setting that enables the Linux terminal app. [CHAR LIMIT=none] -->
+ <string name="enable_linux_terminal_summary">(Experimental) Run Linux terminal on Android</string>
+ <!-- Disclaimer below the checkbox that disabling the Linux terminal app would clear its data. [CHAR LIMIT=none] -->
+ <string name="disable_linux_terminal_disclaimer">If you disable, Linux terminal data will be cleared</string>
<!-- HDCP checking title, used for debug purposes only. [CHAR LIMIT=25] -->
<string name="hdcp_checking_title">HDCP checking</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index a87b815..216574a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -1,6 +1,7 @@
package com.android.settingslib.bluetooth;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.UNKNOWN_VALUE_PLACEHOLDER;
+import static com.android.settingslib.flags.Flags.audioSharingHysteresisModeFix;
import static com.android.settingslib.widget.AdaptiveOutlineDrawable.ICON_TYPE_ADVANCED;
import android.annotation.SuppressLint;
@@ -651,6 +652,13 @@
context.getContentResolver()));
}
+ /** Returns if the le audio sharing hysteresis mode fix is available. */
+ @WorkerThread
+ public static boolean isAudioSharingHysteresisModeFixAvailable(@Nullable Context context) {
+ return (audioSharingHysteresisModeFix() && Flags.enableLeAudioSharing())
+ || (context != null && isAudioSharingPreviewEnabled(context.getContentResolver()));
+ }
+
/** Returns if the le audio sharing is enabled. */
public static boolean isAudioSharingEnabled() {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
@@ -733,13 +741,15 @@
@WorkerThread
public static boolean hasConnectedBroadcastSourceForBtDevice(
@Nullable BluetoothDevice device, @Nullable LocalBluetoothManager localBtManager) {
- if (Flags.audioSharingHysteresisModeFix()) {
+ if (localBtManager == null) {
+ Log.d(TAG, "Skip check hasConnectedBroadcastSourceForBtDevice due to arg is null");
+ return false;
+ }
+ if (isAudioSharingHysteresisModeFixAvailable(localBtManager.getContext())) {
return hasActiveLocalBroadcastSourceForBtDevice(device, localBtManager);
}
LocalBluetoothLeBroadcastAssistant assistant =
- localBtManager == null
- ? null
- : localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
+ localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
if (device == null || assistant == null) {
Log.d(TAG, "Skip check hasConnectedBroadcastSourceForBtDevice due to arg is null");
return false;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 8851e14..dc52b4d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -56,7 +56,6 @@
import androidx.annotation.RequiresApi;
import com.android.settingslib.R;
-import com.android.settingslib.flags.Flags;
import com.google.common.collect.ImmutableList;
@@ -1142,7 +1141,7 @@
int targetGroupId = BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
int fallbackActiveGroupId = BluetoothUtils.getPrimaryGroupIdForBroadcast(
mContext.getContentResolver());
- if (Flags.audioSharingHysteresisModeFix()) {
+ if (BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext)) {
int userPreferredPrimaryGroupId = getUserPreferredPrimaryGroupId();
if (userPreferredPrimaryGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID
&& deviceGroupsInBroadcast.containsKey(userPreferredPrimaryGroupId)) {
@@ -1183,7 +1182,8 @@
@NonNull
private Map<Integer, List<BluetoothDevice>> getDeviceGroupsInBroadcast() {
- boolean hysteresisModeFixEnabled = Flags.audioSharingHysteresisModeFix();
+ boolean hysteresisModeFixEnabled =
+ BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext);
List<BluetoothDevice> connectedDevices = mServiceBroadcastAssistant.getConnectedDevices();
return connectedDevices.stream()
.filter(
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
index 7fdbcda..f446bb8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
@@ -93,33 +93,23 @@
IntentFilter().apply {
addAction(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED)
addAction(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED)
- if (Flags.volumePanelBroadcastFix() && android.app.Flags.modesApi())
+ if (android.app.Flags.modesApi())
addAction(
- NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED)
+ NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED
+ )
},
/* broadcastPermission = */ null,
- /* scheduler = */ if (Flags.volumePanelBroadcastFix()) {
- backgroundHandler
- } else {
- null
- },
+ /* scheduler = */ backgroundHandler,
)
awaitClose { context.unregisterReceiver(receiver) }
}
- .let {
- if (Flags.volumePanelBroadcastFix()) {
- // Share the flow to avoid having multiple broadcasts.
- it.flowOn(backgroundCoroutineContext)
- .shareIn(started = SharingStarted.WhileSubscribed(), scope = scope)
- } else {
- it.shareIn(started = SharingStarted.WhileSubscribed(), scope = scope)
- }
- }
+ .flowOn(backgroundCoroutineContext)
+ .shareIn(started = SharingStarted.WhileSubscribed(), scope = scope)
}
override val consolidatedNotificationPolicy: StateFlow<NotificationManager.Policy?> by lazy {
- if (Flags.volumePanelBroadcastFix() && android.app.Flags.modesApi())
+ if (android.app.Flags.modesApi())
flowFromBroadcast(NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED) {
// If available, get the value from extras to avoid a potential binder call.
it?.extras?.getParcelable(EXTRA_NOTIFICATION_POLICY)
@@ -161,11 +151,13 @@
contentResolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.ZEN_MODE),
/* notifyForDescendants= */ false,
- observer)
+ observer,
+ )
contentResolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.ZEN_MODE_CONFIG_ETAG),
/* notifyForDescendants= */ false,
- observer)
+ observer,
+ )
awaitClose { contentResolver.unregisterContentObserver(observer) }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 6d481db..fa5d542 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -1256,4 +1256,40 @@
assertThat(BluetoothUtils.isAudioSharingUIAvailable(mContext)).isTrue();
}
+
+ @Test
+ public void isAudioSharingHysteresisModeFixAvailable_mainAndPreviewFlagOff_returnsFalse() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+ mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION);
+
+ assertThat(BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext)).isFalse();
+ }
+
+ @Test
+ public void isAudioSharingHysteresisModeFixAvailable_hysteresisFixFlagOff_returnsFalse() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+ mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION);
+
+ assertThat(BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext)).isFalse();
+ }
+
+ @Test
+ public void isAudioSharingHysteresisModeFixAvailable_previewFlagOn_returnsTrue() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+ mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION);
+
+ assertThat(BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext)).isTrue();
+ }
+
+ @Test
+ public void isAudioSharingHysteresisModeFixAvailable_mainAndPreviewFlagOn_returnsTrue() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+ mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION);
+
+ assertThat(BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext)).isTrue();
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
index c136644..388af61 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
@@ -23,12 +23,10 @@
import android.content.Intent
import android.database.ContentObserver
import android.os.Parcelable
-import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.provider.Settings.Global
import androidx.test.filters.SmallTest
-import com.android.settingslib.flags.Flags
import com.android.settingslib.notification.modes.TestModeBuilder
import com.android.settingslib.notification.modes.ZenMode
import com.android.settingslib.notification.modes.ZenModesBackend
@@ -93,26 +91,7 @@
)
}
- @DisableFlags(Flags.FLAG_VOLUME_PANEL_BROADCAST_FIX)
- @Test
- fun consolidatedPolicyChanges_repositoryEmits_flagsOff() {
- testScope.runTest {
- val values = mutableListOf<NotificationManager.Policy?>()
- `when`(notificationManager.consolidatedNotificationPolicy).thenReturn(testPolicy1)
- underTest.consolidatedNotificationPolicy
- .onEach { values.add(it) }
- .launchIn(backgroundScope)
- runCurrent()
-
- `when`(notificationManager.consolidatedNotificationPolicy).thenReturn(testPolicy2)
- triggerIntent(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED)
- runCurrent()
-
- assertThat(values).containsExactly(null, testPolicy1, testPolicy2).inOrder()
- }
- }
-
- @EnableFlags(android.app.Flags.FLAG_MODES_API, Flags.FLAG_VOLUME_PANEL_BROADCAST_FIX)
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
@Test
fun consolidatedPolicyChanges_repositoryEmits_flagsOn() {
testScope.runTest {
@@ -131,7 +110,7 @@
}
}
- @EnableFlags(android.app.Flags.FLAG_MODES_API, Flags.FLAG_VOLUME_PANEL_BROADCAST_FIX)
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
@Test
fun consolidatedPolicyChanges_repositoryEmitsFromExtras() {
testScope.runTest {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index fbce6ca..aca2c4e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -16,6 +16,7 @@
package com.android.providers.settings;
+import static android.provider.DeviceConfig.DUMP_ARG_NAMESPACE;
import static android.provider.Settings.Config.SYNC_DISABLED_MODE_NONE;
import static android.provider.Settings.Config.SYNC_DISABLED_MODE_PERSISTENT;
import static android.provider.Settings.Config.SYNC_DISABLED_MODE_UNTIL_REBOOT;
@@ -42,6 +43,7 @@
import android.provider.Settings;
import android.provider.Settings.Config.SyncDisabledMode;
import android.provider.UpdatableDeviceConfigServiceReadiness;
+import android.util.Log;
import android.util.Slog;
import com.android.internal.util.FastPrintWriter;
@@ -55,11 +57,13 @@
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.regex.Pattern;
/**
* Receives shell commands from the command line related to device config flags, and dispatches them
@@ -80,6 +84,7 @@
final SettingsProvider mProvider;
private static final String TAG = "DeviceConfigService";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
public DeviceConfigService(SettingsProvider provider) {
mProvider = provider;
@@ -97,14 +102,35 @@
}
}
+ // TODO(b/364399200): add unit test
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ String filter = null;
if (android.provider.flags.Flags.dumpImprovements()) {
- pw.print("SyncDisabledForTests: ");
- MyShellCommand.getSyncDisabledForTests(pw, pw);
+ if (args.length > 0) {
+ switch (args[0]) {
+ case DUMP_ARG_NAMESPACE:
+ if (args.length < 2) {
+ throw new IllegalArgumentException("argument " + DUMP_ARG_NAMESPACE
+ + " requires an extra argument");
+ }
+ filter = args[1];
+ if (DEBUG) {
+ Slog.d(TAG, "dump(): setting filter as " + filter);
+ }
+ break;
+ default:
+ Slog.w(TAG, "dump(): ignoring invalid arguments: " + Arrays.toString(args));
+ break;
+ }
+ }
+ if (filter == null) {
+ pw.print("SyncDisabledForTests: ");
+ MyShellCommand.getSyncDisabledForTests(pw, pw);
- pw.print("UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService(): ");
- pw.println(UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService());
+ pw.print("UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService(): ");
+ pw.println(UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService());
+ }
pw.println("DeviceConfig provider: ");
try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(fd)) {
@@ -117,8 +143,16 @@
IContentProvider iprovider = mProvider.getIContentProvider();
pw.println("DeviceConfig flags:");
+ Pattern lineFilter = filter == null ? null : Pattern.compile("^.*" + filter + ".*\\/.*$");
for (String line : MyShellCommand.listAll(iprovider)) {
- pw.println(line);
+ if (lineFilter == null || lineFilter.matcher(line).matches()) {
+ pw.println(line);
+ }
+ }
+
+ if (filter != null) {
+ // TODO(b/364399200): use filter to skip instead?
+ return;
}
ArrayList<String> missingFiles = new ArrayList<String>();
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index bffda8b..0948931 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -497,6 +497,9 @@
resource_dirs: [],
static_libs: [
"//frameworks/libs/systemui:compilelib",
+ "//frameworks/base/packages/SystemUI/pods/com/android/systemui/dagger:api",
+ "//frameworks/base/packages/SystemUI/pods/com/android/systemui/util/settings:api",
+ "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail:impl",
"SystemUI-res",
"WifiTrackerLib",
"WindowManager-Shell",
@@ -770,6 +773,9 @@
],
static_libs: [
"//frameworks/libs/systemui:compilelib",
+ "//frameworks/base/packages/SystemUI/pods/com/android/systemui/dagger:api",
+ "//frameworks/base/packages/SystemUI/pods/com/android/systemui/util/settings:api",
+ "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail:impl",
"SystemUI-tests-base",
"androidx.test.uiautomator_uiautomator",
"androidx.core_core-animation-testing",
diff --git a/packages/SystemUI/common/Android.bp b/packages/SystemUI/common/Android.bp
index 91dc3e3..9f15983 100644
--- a/packages/SystemUI/common/Android.bp
+++ b/packages/SystemUI/common/Android.bp
@@ -30,5 +30,9 @@
"src/**/*.kt",
],
+ static_libs: ["SystemUI-shared-utils"],
+
+ libs: ["//frameworks/libs/systemui:tracinglib-platform"],
+
kotlincflags: ["-Xjvm-default=all"],
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt b/packages/SystemUI/common/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt
rename to packages/SystemUI/common/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt
diff --git a/packages/SystemUI/src/com/android/systemui/coroutines/Tracing.kt b/packages/SystemUI/common/src/com/android/systemui/coroutines/Tracing.kt
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/coroutines/Tracing.kt
rename to packages/SystemUI/common/src/com/android/systemui/coroutines/Tracing.kt
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
index 9392b1a..96e99b1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
@@ -66,13 +66,13 @@
interactionHandler = interactionHandler,
dialogFactory = dialogFactory,
widgetSection = widgetSection,
- modifier = Modifier.element(Communal.Elements.Grid)
+ modifier = Modifier.element(Communal.Elements.Grid),
)
}
with(lockSection) {
LockIcon(
overrideColor = MaterialTheme.colorScheme.onPrimaryContainer,
- modifier = Modifier.element(Communal.Elements.LockIcon)
+ modifier = Modifier.element(Communal.Elements.LockIcon),
)
}
with(bottomAreaSection) {
@@ -80,17 +80,13 @@
Modifier.element(Communal.Elements.IndicationArea).fillMaxWidth()
)
}
- }
+ },
) { measurables, constraints ->
val communalGridMeasurable = measurables[0]
val lockIconMeasurable = measurables[1]
val bottomAreaMeasurable = measurables[2]
- val noMinConstraints =
- constraints.copy(
- minWidth = 0,
- minHeight = 0,
- )
+ val noMinConstraints = constraints.copy(minWidth = 0, minHeight = 0)
val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
val lockIconBounds =
@@ -109,14 +105,8 @@
)
layout(constraints.maxWidth, constraints.maxHeight) {
- communalGridPlaceable.place(
- x = 0,
- y = 0,
- )
- lockIconPlaceable.place(
- x = lockIconBounds.left,
- y = lockIconBounds.top,
- )
+ communalGridPlaceable.place(x = 0, y = 0)
+ lockIconPlaceable.place(x = lockIconBounds.left, y = lockIconBounds.top)
bottomAreaPlaceable.place(
x = 0,
y = constraints.maxHeight - bottomAreaPlaceable.height,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 5e1ac1f..df1185c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -807,7 +807,6 @@
) {
ResizeableItemFrameViewModel()
}
-
if (viewModel.isEditMode && dragDropState != null) {
val isItemDragging = dragDropState.draggingItemKey == item.key
val outlineAlpha by
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
index 6e30575..16002bc 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
@@ -59,7 +59,14 @@
private val onAddWidget: (componentName: ComponentName, user: UserHandle, rank: Int) -> Unit,
private val onDeleteWidget: (id: Int, componentName: ComponentName, rank: Int) -> Unit,
private val onReorderWidgets: (widgetIdToRankMap: Map<Int, Int>) -> Unit,
- private val onResizeWidget: (id: Int, spanY: Int, widgetIdToRankMap: Map<Int, Int>) -> Unit,
+ private val onResizeWidget:
+ (
+ id: Int,
+ spanY: Int,
+ widgetIdToRankMap: Map<Int, Int>,
+ componentName: ComponentName,
+ rank: Int,
+ ) -> Unit,
) {
var list = communalContent.toMutableStateList()
private set
@@ -105,7 +112,9 @@
} else {
emptyMap()
}
- onResizeWidget(item.appWidgetId, newSpan, widgetIdToRankMap)
+ val componentName = item.componentName
+ val rank = item.rank
+ onResizeWidget(item.appWidgetId, newSpan, widgetIdToRankMap, componentName, rank)
}
/**
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 041cd15..c33d655 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -25,7 +25,6 @@
import androidx.compose.ui.unit.round
import androidx.compose.ui.util.fastCoerceIn
import com.android.compose.animation.scene.content.Content
-import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.content.state.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
import com.android.compose.nestedscroll.OnStopScope
import com.android.compose.nestedscroll.PriorityNestedScrollConnection
@@ -36,11 +35,11 @@
internal interface DraggableHandler {
/**
- * Start a drag with the given [pointersInfo] and [overSlop].
+ * Start a drag with the given [pointersDown] and [overSlop].
*
* The returned [DragController] should be used to continue or stop the drag.
*/
- fun onDragStarted(pointersInfo: PointersInfo?, overSlop: Float): DragController
+ fun onDragStarted(pointersDown: PointersInfo.PointersDown?, overSlop: Float): DragController
}
/**
@@ -95,7 +94,7 @@
* Note: if this returns true, then [onDragStarted] will be called with overSlop equal to 0f,
* indicating that the transition should be intercepted.
*/
- internal fun shouldImmediatelyIntercept(pointersInfo: PointersInfo?): Boolean {
+ internal fun shouldImmediatelyIntercept(pointersDown: PointersInfo.PointersDown?): Boolean {
// We don't intercept the touch if we are not currently driving the transition.
val dragController = dragController
if (dragController?.isDrivingTransition != true) {
@@ -106,7 +105,7 @@
// Only intercept the current transition if one of the 2 swipes results is also a transition
// between the same pair of contents.
- val swipes = computeSwipes(pointersInfo)
+ val swipes = computeSwipes(pointersDown)
val fromContent = layoutImpl.content(swipeAnimation.currentContent)
val (upOrLeft, downOrRight) = swipes.computeSwipesResults(fromContent)
val currentScene = layoutImpl.state.currentScene
@@ -123,7 +122,10 @@
))
}
- override fun onDragStarted(pointersInfo: PointersInfo?, overSlop: Float): DragController {
+ override fun onDragStarted(
+ pointersDown: PointersInfo.PointersDown?,
+ overSlop: Float,
+ ): DragController {
if (overSlop == 0f) {
val oldDragController = dragController
check(oldDragController != null && oldDragController.isDrivingTransition) {
@@ -148,7 +150,7 @@
return updateDragController(swipes, swipeAnimation)
}
- val swipes = computeSwipes(pointersInfo)
+ val swipes = computeSwipes(pointersDown)
val fromContent = layoutImpl.contentForUserActions()
swipes.updateSwipesResults(fromContent)
@@ -194,11 +196,11 @@
)
}
- private fun computeSwipes(pointersInfo: PointersInfo?): Swipes {
- val fromSource = pointersInfo?.let { resolveSwipeSource(it.startedPosition) }
+ private fun computeSwipes(pointersDown: PointersInfo.PointersDown?): Swipes {
+ val fromSource = pointersDown?.let { resolveSwipeSource(it.startedPosition) }
return Swipes(
- upOrLeft = resolveSwipe(orientation, isUpOrLeft = true, pointersInfo, fromSource),
- downOrRight = resolveSwipe(orientation, isUpOrLeft = false, pointersInfo, fromSource),
+ upOrLeft = resolveSwipe(orientation, isUpOrLeft = true, pointersDown, fromSource),
+ downOrRight = resolveSwipe(orientation, isUpOrLeft = false, pointersDown, fromSource),
)
}
}
@@ -206,7 +208,7 @@
private fun resolveSwipe(
orientation: Orientation,
isUpOrLeft: Boolean,
- pointersInfo: PointersInfo?,
+ pointersDown: PointersInfo.PointersDown?,
fromSource: SwipeSource.Resolved?,
): Swipe.Resolved {
return Swipe.Resolved(
@@ -227,9 +229,9 @@
}
},
// If the number of pointers is not specified, 1 is assumed.
- pointerCount = pointersInfo?.pointersDown ?: 1,
+ pointerCount = pointersDown?.count ?: 1,
// Resolves the pointer type only if all pointers are of the same type.
- pointersType = pointersInfo?.pointersDownByType?.keys?.singleOrNull(),
+ pointersType = pointersDown?.countByType?.keys?.singleOrNull(),
fromSource = fromSource,
)
}
@@ -540,40 +542,15 @@
val connection: PriorityNestedScrollConnection = nestedScrollConnection()
- private fun resolveSwipe(isUpOrLeft: Boolean, pointersInfo: PointersInfo?): Swipe.Resolved {
- return resolveSwipe(
- orientation = draggableHandler.orientation,
- isUpOrLeft = isUpOrLeft,
- pointersInfo = pointersInfo,
- fromSource =
- pointersInfo?.let { draggableHandler.resolveSwipeSource(it.startedPosition) },
- )
- }
-
private fun nestedScrollConnection(): PriorityNestedScrollConnection {
// If we performed a long gesture before entering priority mode, we would have to avoid
// moving on to the next scene.
var canChangeScene = false
- var lastPointersInfo: PointersInfo? = null
+ var lastPointersDown: PointersInfo.PointersDown? = null
- fun hasNextScene(amount: Float): Boolean {
- val transitionState = layoutState.transitionState
- val scene = transitionState.currentScene
- val fromScene = layoutImpl.scene(scene)
- val resolvedSwipe =
- when {
- amount < 0f -> resolveSwipe(isUpOrLeft = true, lastPointersInfo)
- amount > 0f -> resolveSwipe(isUpOrLeft = false, lastPointersInfo)
- else -> null
- }
- val nextScene = resolvedSwipe?.let { fromScene.findActionResultBestMatch(it) }
- if (nextScene != null) return true
-
- if (transitionState !is TransitionState.Idle) return false
-
- val overscrollSpec = layoutImpl.state.transitions.overscrollSpec(scene, orientation)
- return overscrollSpec != null
+ fun shouldEnableSwipes(): Boolean {
+ return layoutImpl.contentForUserActions().shouldEnableSwipes(orientation)
}
var isIntercepting = false
@@ -581,14 +558,24 @@
return PriorityNestedScrollConnection(
orientation = orientation,
canStartPreScroll = { offsetAvailable, offsetBeforeStart, _ ->
- val pointersInfo = pointersInfoOwner.pointersInfo()
+ val pointersDown: PointersInfo.PointersDown? =
+ when (val info = pointersInfoOwner.pointersInfo()) {
+ PointersInfo.MouseWheel -> {
+ // Do not support mouse wheel interactions
+ return@PriorityNestedScrollConnection false
+ }
+
+ is PointersInfo.PointersDown -> info
+ null -> null
+ }
+
canChangeScene =
if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f
val canInterceptSwipeTransition =
canChangeScene &&
offsetAvailable != 0f &&
- draggableHandler.shouldImmediatelyIntercept(pointersInfo)
+ draggableHandler.shouldImmediatelyIntercept(pointersDown)
if (!canInterceptSwipeTransition) return@PriorityNestedScrollConnection false
val threshold = layoutImpl.transitionInterceptionThreshold
@@ -599,11 +586,7 @@
return@PriorityNestedScrollConnection false
}
- if (pointersInfo?.isMouseWheel == true) {
- // Do not support mouse wheel interactions
- return@PriorityNestedScrollConnection false
- }
- lastPointersInfo = pointersInfo
+ lastPointersDown = pointersDown
// If the current swipe transition is *not* closed to 0f or 1f, then we want the
// scroll events to intercept the current transition to continue the scene
@@ -622,28 +605,33 @@
val isZeroOffset =
if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f
- val pointersInfo = pointersInfoOwner.pointersInfo()
- if (pointersInfo?.isMouseWheel == true) {
- // Do not support mouse wheel interactions
- return@PriorityNestedScrollConnection false
- }
- lastPointersInfo = pointersInfo
+ val pointersDown: PointersInfo.PointersDown? =
+ when (val info = pointersInfoOwner.pointersInfo()) {
+ PointersInfo.MouseWheel -> {
+ // Do not support mouse wheel interactions
+ return@PriorityNestedScrollConnection false
+ }
+
+ is PointersInfo.PointersDown -> info
+ null -> null
+ }
+ lastPointersDown = pointersDown
val canStart =
when (behavior) {
NestedScrollBehavior.EdgeNoPreview -> {
canChangeScene = isZeroOffset
- isZeroOffset && hasNextScene(offsetAvailable)
+ isZeroOffset && shouldEnableSwipes()
}
NestedScrollBehavior.EdgeWithPreview -> {
canChangeScene = isZeroOffset
- hasNextScene(offsetAvailable)
+ shouldEnableSwipes()
}
NestedScrollBehavior.EdgeAlways -> {
canChangeScene = true
- hasNextScene(offsetAvailable)
+ shouldEnableSwipes()
}
}
@@ -664,14 +652,19 @@
// We could start an overscroll animation
canChangeScene = false
- val pointersInfo = pointersInfoOwner.pointersInfo()
- if (pointersInfo?.isMouseWheel == true) {
- // Do not support mouse wheel interactions
- return@PriorityNestedScrollConnection false
- }
- lastPointersInfo = pointersInfo
+ val pointersDown: PointersInfo.PointersDown? =
+ when (val info = pointersInfoOwner.pointersInfo()) {
+ PointersInfo.MouseWheel -> {
+ // Do not support mouse wheel interactions
+ return@PriorityNestedScrollConnection false
+ }
- val canStart = behavior.canStartOnPostFling && hasNextScene(velocityAvailable)
+ is PointersInfo.PointersDown -> info
+ null -> null
+ }
+ lastPointersDown = pointersDown
+
+ val canStart = behavior.canStartOnPostFling && shouldEnableSwipes()
if (canStart) {
isIntercepting = false
}
@@ -679,11 +672,10 @@
canStart
},
onStart = { firstScroll ->
- val pointersInfo = lastPointersInfo
scrollController(
dragController =
draggableHandler.onDragStarted(
- pointersInfo = pointersInfo,
+ pointersDown = lastPointersDown,
overSlop = if (isIntercepting) 0f else firstScroll,
),
canChangeScene = canChangeScene,
@@ -701,8 +693,7 @@
): ScrollController {
return object : ScrollController {
override fun onScroll(deltaScroll: Float, source: NestedScrollSource): Float {
- val pointersInfo = pointersInfoOwner.pointersInfo()
- if (pointersInfo?.isMouseWheel == true) {
+ if (pointersInfoOwner.pointersInfo() == PointersInfo.MouseWheel) {
// Do not support mouse wheel interactions
return 0f
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index ab2324a..1603267 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
@@ -80,8 +80,8 @@
@Stable
internal fun Modifier.multiPointerDraggable(
orientation: Orientation,
- startDragImmediately: (pointersInfo: PointersInfo) -> Boolean,
- onDragStarted: (pointersInfo: PointersInfo, overSlop: Float) -> DragController,
+ startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean,
+ onDragStarted: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController,
onFirstPointerDown: () -> Unit = {},
swipeDetector: SwipeDetector = DefaultSwipeDetector,
dispatcher: NestedScrollDispatcher,
@@ -99,8 +99,9 @@
private data class MultiPointerDraggableElement(
private val orientation: Orientation,
- private val startDragImmediately: (pointersInfo: PointersInfo) -> Boolean,
- private val onDragStarted: (pointersInfo: PointersInfo, overSlop: Float) -> DragController,
+ private val startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean,
+ private val onDragStarted:
+ (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController,
private val onFirstPointerDown: () -> Unit,
private val swipeDetector: SwipeDetector,
private val dispatcher: NestedScrollDispatcher,
@@ -126,8 +127,8 @@
internal class MultiPointerDraggableNode(
orientation: Orientation,
- var startDragImmediately: (pointersInfo: PointersInfo) -> Boolean,
- var onDragStarted: (pointersInfo: PointersInfo, overSlop: Float) -> DragController,
+ var startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean,
+ var onDragStarted: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController,
var onFirstPointerDown: () -> Unit,
swipeDetector: SwipeDetector = DefaultSwipeDetector,
private val dispatcher: NestedScrollDispatcher,
@@ -185,20 +186,27 @@
private var lastPointerEvent: PointerEvent? = null
private var startedPosition: Offset? = null
- private var pointersDown: Int = 0
+ private var countPointersDown: Int = 0
internal fun pointersInfo(): PointersInfo? {
- val startedPosition = startedPosition
- val lastPointerEvent = lastPointerEvent
- if (startedPosition == null || lastPointerEvent == null) {
- // This may be null, i.e. when the user uses TalkBack
- return null
- }
+ // This may be null, i.e. when the user uses TalkBack
+ val lastPointerEvent = lastPointerEvent ?: return null
- return PointersInfo(
+ if (lastPointerEvent.type == PointerEventType.Scroll) return PointersInfo.MouseWheel
+
+ val startedPosition = startedPosition ?: return null
+
+ return PointersInfo.PointersDown(
startedPosition = startedPosition,
- pointersDown = pointersDown,
- lastPointerEvent = lastPointerEvent,
+ count = countPointersDown,
+ countByType =
+ buildMap {
+ lastPointerEvent.changes.fastForEach { change ->
+ if (!change.pressed) return@fastForEach
+ val newValue = (get(change.type) ?: 0) + 1
+ put(change.type, newValue)
+ }
+ },
)
}
@@ -218,11 +226,11 @@
val changes = pointerEvent.changes
lastPointerEvent = pointerEvent
- pointersDown = changes.countDown()
+ countPointersDown = changes.countDown()
when {
// There are no more pointers down.
- pointersDown == 0 -> {
+ countPointersDown == 0 -> {
startedPosition = null
// In case of multiple events with 0 pointers down (not pressed) we may have
@@ -290,8 +298,8 @@
detectDragGestures(
orientation = orientation,
startDragImmediately = startDragImmediately,
- onDragStart = { pointersInfo, overSlop ->
- onDragStarted(pointersInfo, overSlop)
+ onDragStart = { pointersDown, overSlop ->
+ onDragStarted(pointersDown, overSlop)
},
onDrag = { controller, amount ->
dispatchScrollEvents(
@@ -440,8 +448,8 @@
*/
private suspend fun AwaitPointerEventScope.detectDragGestures(
orientation: Orientation,
- startDragImmediately: (pointersInfo: PointersInfo) -> Boolean,
- onDragStart: (pointersInfo: PointersInfo, overSlop: Float) -> DragController,
+ startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean,
+ onDragStart: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController,
onDrag: (controller: DragController, dragAmount: Float) -> Unit,
onDragEnd: (controller: DragController) -> Unit,
onDragCancel: (controller: DragController) -> Unit,
@@ -466,13 +474,14 @@
.first()
var overSlop = 0f
- var lastPointersInfo =
+ var lastPointersDown: PointersInfo.PointersDown =
checkNotNull(pointersInfo()) {
"We should have pointers down, last event: $currentEvent"
}
+ as PointersInfo.PointersDown
val drag =
- if (startDragImmediately(lastPointersInfo)) {
+ if (startDragImmediately(lastPointersDown)) {
consumablePointer.consume()
consumablePointer
} else {
@@ -499,10 +508,11 @@
)
} ?: return
- lastPointersInfo =
+ lastPointersDown =
checkNotNull(pointersInfo()) {
"We should have pointers down, last event: $currentEvent"
}
+ as PointersInfo.PointersDown
// Make sure that overSlop is not 0f. This can happen when the user drags by exactly
// the touch slop. However, the overSlop we pass to onDragStarted() is used to
// compute the direction we are dragging in, so overSlop should never be 0f unless
@@ -516,7 +526,7 @@
drag
}
- val controller = onDragStart(lastPointersInfo, overSlop)
+ val controller = onDragStart(lastPointersDown, overSlop)
val successful: Boolean
try {
@@ -666,48 +676,31 @@
fun pointersInfo(): PointersInfo?
}
-/**
- * Holds information about pointer interactions within a composable.
- *
- * This class stores details such as the starting position of a gesture, the number of pointers
- * down, and whether the last pointer event was a mouse wheel scroll.
- *
- * @param startedPosition The starting position of the gesture. This is the position where the first
- * pointer touched the screen, not necessarily the point where dragging begins. This may be
- * different from the initial touch position if a child composable intercepts the gesture before
- * this one.
- * @param pointersDown The number of pointers currently down.
- * @param isMouseWheel Indicates whether the last pointer event was a mouse wheel scroll.
- * @param pointersDownByType Provide a map of pointer types to the count of pointers of that type
- * currently down/pressed.
- */
-internal data class PointersInfo(
- val startedPosition: Offset,
- val pointersDown: Int,
- val isMouseWheel: Boolean,
- val pointersDownByType: Map<PointerType, Int>,
-) {
- init {
- check(pointersDown > 0) { "We should have at least 1 pointer down, $pointersDown instead" }
+internal sealed interface PointersInfo {
+ /**
+ * Holds information about pointer interactions within a composable.
+ *
+ * This class stores details such as the starting position of a gesture, the number of pointers
+ * down, and whether the last pointer event was a mouse wheel scroll.
+ *
+ * @param startedPosition The starting position of the gesture. This is the position where the
+ * first pointer touched the screen, not necessarily the point where dragging begins. This may
+ * be different from the initial touch position if a child composable intercepts the gesture
+ * before this one.
+ * @param count The number of pointers currently down.
+ * @param countByType Provide a map of pointer types to the count of pointers of that type
+ * currently down/pressed.
+ */
+ data class PointersDown(
+ val startedPosition: Offset,
+ val count: Int,
+ val countByType: Map<PointerType, Int>,
+ ) : PointersInfo {
+ init {
+ check(count > 0) { "We should have at least 1 pointer down, $count instead" }
+ }
}
-}
-private fun PointersInfo(
- startedPosition: Offset,
- pointersDown: Int,
- lastPointerEvent: PointerEvent,
-): PointersInfo {
- return PointersInfo(
- startedPosition = startedPosition,
- pointersDown = pointersDown,
- isMouseWheel = lastPointerEvent.type == PointerEventType.Scroll,
- pointersDownByType =
- buildMap {
- lastPointerEvent.changes.fastForEach { change ->
- if (!change.pressed) return@fastForEach
- val newValue = (get(change.type) ?: 0) + 1
- put(change.type, newValue)
- }
- },
- )
+ /** Indicates whether the last pointer event was a mouse wheel scroll. */
+ data object MouseWheel : PointersInfo
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 61332b6..72b29ee 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -25,17 +25,13 @@
import androidx.compose.runtime.setValue
import androidx.compose.ui.util.fastAll
import androidx.compose.ui.util.fastAny
-import androidx.compose.ui.util.fastFilter
import androidx.compose.ui.util.fastForEach
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.transformation.SharedElementTransformation
-import com.android.compose.animation.scene.transition.link.LinkedTransition
-import com.android.compose.animation.scene.transition.link.StateLink
import kotlin.math.absoluteValue
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Job
-import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
/**
@@ -236,7 +232,6 @@
canShowOverlay: (OverlayKey) -> Boolean = { true },
canHideOverlay: (OverlayKey) -> Boolean = { true },
canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ -> true },
- stateLinks: List<StateLink> = emptyList(),
): MutableSceneTransitionLayoutState {
return MutableSceneTransitionLayoutStateImpl(
initialScene,
@@ -246,7 +241,6 @@
canShowOverlay,
canHideOverlay,
canReplaceOverlay,
- stateLinks,
)
}
@@ -258,10 +252,7 @@
internal val canChangeScene: (SceneKey) -> Boolean = { true },
internal val canShowOverlay: (OverlayKey) -> Boolean = { true },
internal val canHideOverlay: (OverlayKey) -> Boolean = { true },
- internal val canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ ->
- true
- },
- private val stateLinks: List<StateLink> = emptyList(),
+ internal val canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ -> true },
) : MutableSceneTransitionLayoutState {
private val creationThread: Thread = Thread.currentThread()
@@ -364,20 +355,11 @@
checkThread()
try {
- // Keep a reference to the previous transition (if any).
- val previousTransition = currentTransition
-
// Start the transition.
startTransitionInternal(transition, chain)
- // Handle transition links.
- previousTransition?.let { cancelActiveTransitionLinks(it) }
- if (stateLinks.isNotEmpty()) {
- coroutineScope { setupTransitionLinks(transition) }
- }
-
// Run the transition until it is finished.
- transition.run()
+ transition.runInternal()
} finally {
finishTransition(transition)
}
@@ -471,42 +453,6 @@
)
}
- private fun cancelActiveTransitionLinks(transition: TransitionState.Transition) {
- transition.activeTransitionLinks.forEach { (link, linkedTransition) ->
- link.target.finishTransition(linkedTransition)
- }
- transition.activeTransitionLinks.clear()
- }
-
- private fun CoroutineScope.setupTransitionLinks(transition: TransitionState.Transition) {
- stateLinks.fastForEach { stateLink ->
- val matchingLinks =
- stateLink.transitionLinks.fastFilter { it.isMatchingLink(transition) }
- if (matchingLinks.isEmpty()) return@fastForEach
- if (matchingLinks.size > 1) error("More than one link matched.")
-
- val targetCurrentScene = stateLink.target.transitionState.currentScene
- val matchingLink = matchingLinks[0]
-
- if (!matchingLink.targetIsInValidState(targetCurrentScene)) return@fastForEach
-
- val linkedTransition =
- LinkedTransition(
- originalTransition = transition,
- fromScene = targetCurrentScene,
- toScene = matchingLink.targetTo,
- key = matchingLink.targetTransitionKey,
- )
-
- // Start with UNDISPATCHED so that startTransition is called directly and the new linked
- // transition is observable directly.
- launch(start = CoroutineStart.UNDISPATCHED) {
- stateLink.target.startTransition(linkedTransition)
- }
- transition.activeTransitionLinks[stateLink] = linkedTransition
- }
- }
-
/**
* Notify that [transition] was finished and that it settled to its
* [currentScene][TransitionState.currentScene]. This will do nothing if [transition] was
@@ -535,9 +481,6 @@
// Mark this transition as finished.
finishedTransitions.add(transition)
- // Finish all linked transitions.
- finishActiveTransitionLinks(transition)
-
// Keep a reference to the last transition, in case we remove all transitions and should
// settle to Idle.
val lastTransition = transitionStates.last()
@@ -584,13 +527,6 @@
transitionStates = listOf(TransitionState.Idle(scene, currentOverlays))
}
- private fun finishActiveTransitionLinks(transition: TransitionState.Transition) {
- for ((link, linkedTransition) in transition.activeTransitionLinks) {
- link.target.finishTransition(linkedTransition)
- }
- transition.activeTransitionLinks.clear()
- }
-
/**
* Check if a transition is in progress. If the progress value is near 0 or 1, immediately snap
* to the closest scene.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index ba5f414..a448ee4 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -56,7 +56,7 @@
}
/** Whether swipe should be enabled in the given [orientation]. */
-private fun Content.shouldEnableSwipes(orientation: Orientation): Boolean {
+internal fun Content.shouldEnableSwipes(orientation: Orientation): Boolean {
if (userActions.isEmpty()) {
return false
}
@@ -200,10 +200,10 @@
override fun onCancelPointerInput() = multiPointerDraggableNode.onCancelPointerInput()
- private fun startDragImmediately(pointersInfo: PointersInfo): Boolean {
+ private fun startDragImmediately(pointersDown: PointersInfo.PointersDown): Boolean {
// Immediately start the drag if the user can't swipe in the other direction and the gesture
// handler can intercept it.
- return !canOppositeSwipe() && draggableHandler.shouldImmediatelyIntercept(pointersInfo)
+ return !canOppositeSwipe() && draggableHandler.shouldImmediatelyIntercept(pointersDown)
}
private fun canOppositeSwipe(): Boolean {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
index 7d29a68..e3118d67 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
@@ -18,6 +18,7 @@
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationVector1D
+import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.runtime.Stable
@@ -34,8 +35,6 @@
import com.android.compose.animation.scene.TransformationSpec
import com.android.compose.animation.scene.TransformationSpecImpl
import com.android.compose.animation.scene.TransitionKey
-import com.android.compose.animation.scene.transition.link.LinkedTransition
-import com.android.compose.animation.scene.transition.link.StateLink
import kotlinx.coroutines.launch
/** The state associated to a [SceneTransitionLayout] at some specific point in time. */
@@ -280,8 +279,8 @@
*/
private var interruptionDecay: Animatable<Float, AnimationVector1D>? = null
- /** The map of active links that connects this transition to other transitions. */
- internal val activeTransitionLinks = mutableMapOf<StateLink, LinkedTransition>()
+ /** Whether this transition was already started. */
+ private var wasStarted = false
init {
check(fromContent != toContent)
@@ -328,7 +327,7 @@
}
/** Run this transition and return once it is finished. */
- abstract suspend fun run()
+ protected abstract suspend fun run()
/**
* Freeze this transition state so that neither [currentScene] nor [currentOverlays] will
@@ -341,6 +340,13 @@
*/
abstract fun freezeAndAnimateToCurrentState()
+ internal suspend fun runInternal() {
+ check(!wasStarted) { "A Transition can be started only once." }
+ wasStarted = true
+
+ run()
+ }
+
internal fun updateOverscrollSpecs(
fromSpec: OverscrollSpecImpl?,
toSpec: OverscrollSpecImpl?,
@@ -376,7 +382,7 @@
val progressSpec =
spring(
stiffness = swipeSpec.stiffness,
- dampingRatio = swipeSpec.dampingRatio,
+ dampingRatio = Spring.DampingRatioNoBouncy,
visibilityThreshold = ProgressVisibilityThreshold,
)
animatable.animateTo(0f, progressSpec)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
deleted file mode 100644
index 42ba9ba..0000000
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
+++ /dev/null
@@ -1,59 +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.compose.animation.scene.transition.link
-
-import com.android.compose.animation.scene.SceneKey
-import com.android.compose.animation.scene.TransitionKey
-import com.android.compose.animation.scene.content.state.TransitionState
-
-/** A linked transition which is driven by a [originalTransition]. */
-internal class LinkedTransition(
- private val originalTransition: TransitionState.Transition,
- fromScene: SceneKey,
- toScene: SceneKey,
- override val key: TransitionKey? = null,
-) : TransitionState.Transition.ChangeScene(fromScene, toScene) {
-
- override val currentScene: SceneKey
- get() {
- return when (originalTransition.currentScene) {
- originalTransition.fromContent -> fromScene
- originalTransition.toContent -> toScene
- else -> error("Original currentScene is neither FromScene nor ToScene")
- }
- }
-
- override val isInitiatedByUserInput: Boolean
- get() = originalTransition.isInitiatedByUserInput
-
- override val isUserInputOngoing: Boolean
- get() = originalTransition.isUserInputOngoing
-
- override val progress: Float
- get() = originalTransition.progress
-
- override val progressVelocity: Float
- get() = originalTransition.progressVelocity
-
- override suspend fun run() {
- originalTransition.run()
- }
-
- override fun freezeAndAnimateToCurrentState() {
- originalTransition.freezeAndAnimateToCurrentState()
- }
-}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
deleted file mode 100644
index 2aec509..0000000
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compose.animation.scene.transition.link
-
-import com.android.compose.animation.scene.ContentKey
-import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateImpl
-import com.android.compose.animation.scene.SceneKey
-import com.android.compose.animation.scene.SceneTransitionLayoutState
-import com.android.compose.animation.scene.TransitionKey
-import com.android.compose.animation.scene.content.state.TransitionState
-
-/** A link between a source (implicit) and [target] `SceneTransitionLayoutState`. */
-class StateLink(target: SceneTransitionLayoutState, val transitionLinks: List<TransitionLink>) {
-
- internal val target = target as MutableSceneTransitionLayoutStateImpl
-
- /**
- * Links two transitions (source and target) together.
- *
- * `null` can be passed to indicate that any SceneKey should match. e.g. passing `null`, `null`,
- * `null`, `SceneA` means that any transition at the source will trigger a transition in the
- * target to `SceneA` from any current scene.
- */
- class TransitionLink(
- val sourceFrom: ContentKey?,
- val sourceTo: ContentKey?,
- val targetFrom: SceneKey?,
- val targetTo: SceneKey,
- val targetTransitionKey: TransitionKey? = null,
- ) {
- init {
- if (
- (sourceFrom != null && sourceFrom == sourceTo) ||
- (targetFrom != null && targetFrom == targetTo)
- )
- error("From and To can't be the same")
- }
-
- internal fun isMatchingLink(transition: TransitionState.Transition): Boolean {
- return (sourceFrom == null || sourceFrom == transition.fromContent) &&
- (sourceTo == null || sourceTo == transition.toContent)
- }
-
- internal fun targetIsInValidState(targetCurrentContent: ContentKey): Boolean {
- return (targetFrom == null || targetFrom == targetCurrentContent) &&
- targetTo != targetCurrentContent
- }
- }
-}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index 098673e..7e6f3a8 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -52,17 +52,15 @@
private const val SCREEN_SIZE = 100f
private val LAYOUT_SIZE = IntSize(SCREEN_SIZE.toInt(), SCREEN_SIZE.toInt())
-private fun pointersInfo(
+private fun pointersDown(
startedPosition: Offset = Offset.Zero,
pointersDown: Int = 1,
- isMouseWheel: Boolean = false,
pointersDownByType: Map<PointerType, Int> = mapOf(PointerType.Touch to pointersDown),
-): PointersInfo {
- return PointersInfo(
+): PointersInfo.PointersDown {
+ return PointersInfo.PointersDown(
startedPosition = startedPosition,
- pointersDown = pointersDown,
- isMouseWheel = isMouseWheel,
- pointersDownByType = pointersDownByType,
+ count = pointersDown,
+ countByType = pointersDownByType,
)
}
@@ -138,7 +136,7 @@
val draggableHandler = layoutImpl.draggableHandler(Orientation.Vertical)
val horizontalDraggableHandler = layoutImpl.draggableHandler(Orientation.Horizontal)
- var pointerInfoOwner: () -> PointersInfo = { pointersInfo() }
+ var pointerInfoOwner: () -> PointersInfo = { pointersDown() }
fun nestedScrollConnection(
nestedScrollBehavior: NestedScrollBehavior,
@@ -221,7 +219,7 @@
}
fun onDragStarted(
- pointersInfo: PointersInfo = pointersInfo(),
+ pointersInfo: PointersInfo.PointersDown = pointersDown(),
overSlop: Float,
expectedConsumedOverSlop: Float = overSlop,
): DragController {
@@ -235,18 +233,20 @@
)
}
- fun onDragStartedImmediately(pointersInfo: PointersInfo = pointersInfo()): DragController {
+ fun onDragStartedImmediately(
+ pointersInfo: PointersInfo.PointersDown = pointersDown()
+ ): DragController {
return onDragStarted(draggableHandler, pointersInfo, overSlop = 0f)
}
fun onDragStarted(
draggableHandler: DraggableHandler,
- pointersInfo: PointersInfo = pointersInfo(),
+ pointersInfo: PointersInfo.PointersDown = pointersDown(),
overSlop: Float = 0f,
expectedConsumedOverSlop: Float = overSlop,
): DragController {
val dragController =
- draggableHandler.onDragStarted(pointersInfo = pointersInfo, overSlop = overSlop)
+ draggableHandler.onDragStarted(pointersDown = pointersInfo, overSlop = overSlop)
// MultiPointerDraggable will always call onDelta with the initial overSlop right after
dragController.onDragDelta(pixels = overSlop, expectedConsumedOverSlop)
@@ -529,7 +529,7 @@
val dragController =
onDragStarted(
pointersInfo =
- pointersInfo(startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE * 0.5f)),
+ pointersDown(startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE * 0.5f)),
overSlop = up(fractionOfScreen = 0.2f),
)
assertTransition(
@@ -555,7 +555,7 @@
// Start dragging from the bottom
onDragStarted(
- pointersInfo = pointersInfo(startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE)),
+ pointersInfo = pointersDown(startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE)),
overSlop = up(fractionOfScreen = 0.1f),
)
assertTransition(
@@ -1052,7 +1052,7 @@
navigateToSceneC()
// Swipe up from the middle to transition to scene B.
- val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+ val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
onDragStarted(pointersInfo = middle, overSlop = up(0.1f))
assertTransition(
currentScene = SceneC,
@@ -1084,7 +1084,7 @@
// Start a new gesture from the bottom of the screen. Because swiping up from the bottom of
// C leads to scene A (and not B), the previous transitions is *not* intercepted and we
// instead animate from C to A.
- val bottom = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2, SCREEN_SIZE))
+ val bottom = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2, SCREEN_SIZE))
assertThat(draggableHandler.shouldImmediatelyIntercept(bottom)).isFalse()
onDragStarted(pointersInfo = bottom, overSlop = up(0.1f))
@@ -1103,7 +1103,7 @@
navigateToSceneC()
// Swipe up from the middle to transition to scene B.
- val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+ val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
onDragStarted(pointersInfo = middle, overSlop = up(0.1f))
assertTransition(fromScene = SceneC, toScene = SceneB, isUserInputOngoing = true)
@@ -1120,15 +1120,15 @@
@Test
fun interruptedTransitionCanNotBeImmediatelyIntercepted() = runGestureTest {
- assertThat(draggableHandler.shouldImmediatelyIntercept(pointersInfo = null)).isFalse()
+ assertThat(draggableHandler.shouldImmediatelyIntercept(pointersDown = null)).isFalse()
onDragStarted(overSlop = up(0.1f))
- assertThat(draggableHandler.shouldImmediatelyIntercept(pointersInfo = null)).isTrue()
+ assertThat(draggableHandler.shouldImmediatelyIntercept(pointersDown = null)).isTrue()
layoutState.startTransitionImmediately(
animationScope = testScope.backgroundScope,
transition(SceneA, SceneB),
)
- assertThat(draggableHandler.shouldImmediatelyIntercept(pointersInfo = null)).isFalse()
+ assertThat(draggableHandler.shouldImmediatelyIntercept(pointersDown = null)).isFalse()
}
@Test
@@ -1160,7 +1160,7 @@
assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
// Intercept the transition and swipe down back to scene A.
- assertThat(draggableHandler.shouldImmediatelyIntercept(pointersInfo = null)).isTrue()
+ assertThat(draggableHandler.shouldImmediatelyIntercept(pointersDown = null)).isTrue()
val dragController2 = onDragStartedImmediately()
// Block the transition when the user release their finger.
@@ -1204,7 +1204,7 @@
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways)
// Drag from the **top** of the screen
- pointerInfoOwner = { pointersInfo() }
+ pointerInfoOwner = { pointersDown() }
assertIdle(currentScene = SceneC)
nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f))
@@ -1221,7 +1221,7 @@
advanceUntilIdle()
// Drag from the **bottom** of the screen
- pointerInfoOwner = { pointersInfo(startedPosition = Offset(0f, SCREEN_SIZE)) }
+ pointerInfoOwner = { pointersDown(startedPosition = Offset(0f, SCREEN_SIZE)) }
assertIdle(currentScene = SceneC)
nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f))
@@ -1241,7 +1241,7 @@
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways)
// Use mouse wheel
- pointerInfoOwner = { pointersInfo(isMouseWheel = true) }
+ pointerInfoOwner = { PointersInfo.MouseWheel }
assertIdle(currentScene = SceneC)
nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f))
@@ -1251,7 +1251,7 @@
@Test
fun transitionIsImmediatelyUpdatedWhenReleasingFinger() = runGestureTest {
// Swipe up from the middle to transition to scene B.
- val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+ val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
val dragController = onDragStarted(pointersInfo = middle, overSlop = up(0.1f))
assertTransition(fromScene = SceneA, toScene = SceneB, isUserInputOngoing = true)
@@ -1265,7 +1265,7 @@
layoutState.transitions = transitions { overscrollDisabled(SceneB, Orientation.Vertical) }
// Swipe up to scene B at progress = 200%.
- val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+ val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
val dragController =
onDragStarted(
pointersInfo = middle,
@@ -1296,7 +1296,7 @@
layoutState.transitions = transitions { overscrollDisabled(SceneB, Orientation.Vertical) }
// Swipe up to scene B at progress = 200%.
- val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+ val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
val dragController = onDragStarted(pointersInfo = middle, overSlop = up(0.99f))
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.99f)
@@ -1342,7 +1342,7 @@
overscroll(SceneB, Orientation.Vertical) { fade(TestElements.Foo) }
}
- val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+ val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
val dragController = onDragStarted(pointersInfo = middle, overSlop = up(0.5f))
val transition = assertThat(transitionState).isSceneTransition()
@@ -1374,7 +1374,7 @@
overscroll(SceneC, Orientation.Vertical) { fade(TestElements.Foo) }
}
- val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+ val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
val dragController = onDragStarted(pointersInfo = middle, overSlop = down(0.5f))
val transition = assertThat(transitionState).isSceneTransition()
@@ -1405,7 +1405,7 @@
overscroll(SceneB, Orientation.Vertical) { fade(TestElements.Foo) }
}
- val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+ val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
val dragController = onDragStarted(pointersInfo = middle, overSlop = up(1.5f))
val transition = assertThat(transitionState).isSceneTransition()
@@ -1437,7 +1437,7 @@
overscroll(SceneC, Orientation.Vertical) { fade(TestElements.Foo) }
}
- val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+ val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
val dragController = onDragStarted(pointersInfo = middle, overSlop = down(1.5f))
val transition = assertThat(transitionState).isSceneTransition()
@@ -1471,7 +1471,7 @@
mutableUserActionsA = mapOf(Swipe.Up to UserActionResult(SceneB))
- val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+ val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
val dragController = onDragStarted(pointersInfo = middle, overSlop = down(1f))
val transition = assertThat(transitionState).isSceneTransition()
assertThat(transition).hasFromScene(SceneA)
@@ -1504,7 +1504,7 @@
mutableUserActionsA = mapOf(Swipe.Down to UserActionResult(SceneC))
- val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+ val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
val dragController = onDragStarted(pointersInfo = middle, overSlop = up(1f))
val transition = assertThat(transitionState).isSceneTransition()
assertThat(transition).hasFromScene(SceneA)
@@ -1676,4 +1676,33 @@
assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
assertThat(layoutState.transitionState).hasCurrentOverlays(OverlayB)
}
+
+ @Test
+ fun replaceOverlayNestedScroll() = runGestureTest {
+ layoutState.showOverlay(OverlayA, animationScope = testScope)
+ advanceUntilIdle()
+
+ // Initial state.
+ assertThat(layoutState.transitionState).isIdle()
+ assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
+ assertThat(layoutState.transitionState).hasCurrentOverlays(OverlayA)
+
+ // Swipe down to replace overlay A by overlay B.
+
+ val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
+ nestedScroll.scroll(downOffset(0.1f))
+ val transition = assertThat(layoutState.transitionState).isReplaceOverlayTransition()
+ assertThat(transition).hasCurrentScene(SceneA)
+ assertThat(transition).hasFromOverlay(OverlayA)
+ assertThat(transition).hasToOverlay(OverlayB)
+ assertThat(transition).hasCurrentOverlays(OverlayA)
+ assertThat(transition).hasProgress(0.1f)
+
+ nestedScroll.preFling(Velocity(0f, velocityThreshold))
+ advanceUntilIdle()
+ // Commit the gesture. The overlays are instantly swapped in the set of current overlays.
+ assertThat(layoutState.transitionState).isIdle()
+ assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
+ assertThat(layoutState.transitionState).hasCurrentOverlays(OverlayB)
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index a2b263b..2b70908 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -26,10 +26,8 @@
import com.android.compose.animation.scene.TestScenes.SceneA
import com.android.compose.animation.scene.TestScenes.SceneB
import com.android.compose.animation.scene.TestScenes.SceneC
-import com.android.compose.animation.scene.TestScenes.SceneD
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.subjects.assertThat
-import com.android.compose.animation.scene.transition.link.StateLink
import com.android.compose.animation.scene.transition.seekToScene
import com.android.compose.test.MonotonicClockTestScope
import com.android.compose.test.TestSceneTransition
@@ -42,6 +40,7 @@
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertThrows
import org.junit.Rule
@@ -132,147 +131,6 @@
assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
}
- private fun setupLinkedStates(
- parentInitialScene: SceneKey = SceneC,
- childInitialScene: SceneKey = SceneA,
- sourceFrom: SceneKey? = SceneA,
- sourceTo: SceneKey? = SceneB,
- targetFrom: SceneKey? = SceneC,
- targetTo: SceneKey = SceneD,
- ): Pair<MutableSceneTransitionLayoutStateImpl, MutableSceneTransitionLayoutStateImpl> {
- val parentState = MutableSceneTransitionLayoutState(parentInitialScene)
- val link =
- listOf(
- StateLink(
- parentState,
- listOf(StateLink.TransitionLink(sourceFrom, sourceTo, targetFrom, targetTo)),
- )
- )
- val childState = MutableSceneTransitionLayoutState(childInitialScene, stateLinks = link)
- return Pair(
- parentState as MutableSceneTransitionLayoutStateImpl,
- childState as MutableSceneTransitionLayoutStateImpl,
- )
- }
-
- @Test
- fun linkedTransition_startsLinkAndFinishesLinkInToState() = runTest {
- val (parentState, childState) = setupLinkedStates()
-
- val childTransition = transition(SceneA, SceneB)
-
- val job =
- childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
- assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
- assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
-
- childTransition.finish()
- job.join()
- assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
- assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
- }
-
- @Test
- fun linkedTransition_transitiveLink() = runTest {
- val parentParentState =
- MutableSceneTransitionLayoutState(SceneB) as MutableSceneTransitionLayoutStateImpl
- val parentLink =
- listOf(
- StateLink(
- parentParentState,
- listOf(StateLink.TransitionLink(SceneC, SceneD, SceneB, SceneC)),
- )
- )
- val parentState =
- MutableSceneTransitionLayoutState(SceneC, stateLinks = parentLink)
- as MutableSceneTransitionLayoutStateImpl
- val link =
- listOf(
- StateLink(
- parentState,
- listOf(StateLink.TransitionLink(SceneA, SceneB, SceneC, SceneD)),
- )
- )
- val childState =
- MutableSceneTransitionLayoutState(SceneA, stateLinks = link)
- as MutableSceneTransitionLayoutStateImpl
-
- val childTransition = transition(SceneA, SceneB)
-
- val job =
- childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
- assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
- assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
- assertThat(parentParentState.isTransitioning(SceneB, SceneC)).isTrue()
-
- childTransition.finish()
- job.join()
- assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
- assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
- assertThat(parentParentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
- }
-
- @Test
- fun linkedTransition_linkProgressIsEqual() = runTest {
- val (parentState, childState) = setupLinkedStates()
-
- var progress = 0f
- val childTransition = transition(SceneA, SceneB, progress = { progress })
-
- childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
- assertThat(parentState.currentTransition?.progress).isEqualTo(0f)
-
- progress = .5f
- assertThat(parentState.currentTransition?.progress).isEqualTo(.5f)
- }
-
- @Test
- fun linkedTransition_reverseTransitionIsNotLinked() = runTest {
- val (parentState, childState) = setupLinkedStates()
-
- val childTransition = transition(SceneB, SceneA, current = { SceneB })
-
- val job =
- childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
- assertThat(childState.isTransitioning(SceneB, SceneA)).isTrue()
- assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
-
- childTransition.finish()
- job.join()
- assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
- assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
- }
-
- @Test
- fun linkedTransition_startsLinkAndFinishesLinkInFromState() = runTest {
- val (parentState, childState) = setupLinkedStates()
-
- val childTransition = transition(SceneA, SceneB, current = { SceneA })
- val job =
- childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
-
- childTransition.finish()
- job.join()
- assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneA))
- assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
- }
-
- @Test
- fun linkedTransition_startsLinkButLinkedStateIsTakenOver() = runTest {
- val (parentState, childState) = setupLinkedStates()
-
- val childTransition = transition(SceneA, SceneB)
- val parentTransition = transition(SceneC, SceneA)
- val job =
- childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
- parentState.startTransitionImmediately(animationScope = backgroundScope, parentTransition)
-
- childTransition.finish()
- job.join()
- assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
- assertThat(parentState.transitionState).isEqualTo(parentTransition)
- }
-
@Test
fun setTargetScene_withTransitionKey() = runMonotonicClockTest {
val transitionkey = TransitionKey(debugName = "foo")
@@ -393,51 +251,6 @@
assertThat(state.isTransitioning()).isTrue()
}
- @Test
- fun linkedTransition_fuzzyLinksAreMatchedAndStarted() = runTest {
- val (parentState, childState) = setupLinkedStates(SceneC, SceneA, null, null, null, SceneD)
- val childTransition = transition(SceneA, SceneB)
-
- val job =
- childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
- assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
- assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
-
- childTransition.finish()
- job.join()
- assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
- assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
- }
-
- @Test
- fun linkedTransition_fuzzyLinksAreMatchedAndResetToProperPreviousScene() = runTest {
- val (parentState, childState) =
- setupLinkedStates(SceneC, SceneA, SceneA, null, null, SceneD)
-
- val childTransition = transition(SceneA, SceneB, current = { SceneA })
-
- val job =
- childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
- assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
- assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
-
- childTransition.finish()
- job.join()
- assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneA))
- assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
- }
-
- @Test
- fun linkedTransition_fuzzyLinksAreNotMatched() = runTest {
- val (parentState, childState) =
- setupLinkedStates(SceneC, SceneA, SceneB, null, SceneC, SceneD)
- val childTransition = transition(SceneA, SceneB)
-
- childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
- assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
- assertThat(parentState.isTransitioning(SceneC, SceneD)).isFalse()
- }
-
private fun MonotonicClockTestScope.startOverscrollableTransistionFromAtoB(
progress: () -> Float,
sceneTransitions: SceneTransitions,
@@ -777,4 +590,15 @@
assertThat(transition.progressTo(SceneA)).isEqualTo(1f - 0.2f)
assertThrows(IllegalArgumentException::class.java) { transition.progressTo(SceneC) }
}
+
+ @Test
+ fun transitionCanBeStartedOnlyOnce() = runTest {
+ val state = MutableSceneTransitionLayoutState(SceneA)
+ val transition = transition(from = SceneA, to = SceneB)
+
+ state.startTransitionImmediately(backgroundScope, transition)
+ assertThrows(IllegalStateException::class.java) {
+ runBlocking { state.startTransition(transition) }
+ }
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index 3b2ee98..97a96a4 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -574,7 +574,7 @@
rule.setContent {
touchSlop = LocalViewConfiguration.current.touchSlop
SceneTransitionLayout(layoutState, Modifier.size(LayoutWidth, LayoutHeight)) {
- scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
+ scene(SceneA, userActions = mapOf(Swipe.Up to SceneB, Swipe.Down to SceneB)) {
Box(
Modifier.fillMaxSize()
// A scrollable that does not consume the scroll gesture
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
deleted file mode 100644
index dd58ea7..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.keyguard;
-
-import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.hardware.display.DisplayManagerGlobal;
-import android.testing.TestableLooper;
-import android.view.Display;
-import android.view.DisplayInfo;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.navigationbar.NavigationBarController;
-import com.android.systemui.settings.FakeDisplayTracker;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.concurrent.Executor;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-@TestableLooper.RunWithLooper
-public class KeyguardDisplayManagerTest extends SysuiTestCase {
-
- @Mock
- private NavigationBarController mNavigationBarController;
- @Mock
- private ConnectedDisplayKeyguardPresentation.Factory
- mConnectedDisplayKeyguardPresentationFactory;
- @Mock
- private ConnectedDisplayKeyguardPresentation mConnectedDisplayKeyguardPresentation;
- @Mock
- private KeyguardDisplayManager.DeviceStateHelper mDeviceStateHelper;
- @Mock
- private KeyguardStateController mKeyguardStateController;
-
- private Executor mMainExecutor = Runnable::run;
- private Executor mBackgroundExecutor = Runnable::run;
- private KeyguardDisplayManager mManager;
- private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
- // The default and secondary displays are both in the default group
- private Display mDefaultDisplay;
- private Display mSecondaryDisplay;
-
- // This display is in a different group from the default and secondary displays.
- private Display mAlwaysUnlockedDisplay;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mManager = spy(new KeyguardDisplayManager(mContext, () -> mNavigationBarController,
- mDisplayTracker, mMainExecutor, mBackgroundExecutor, mDeviceStateHelper,
- mKeyguardStateController, mConnectedDisplayKeyguardPresentationFactory));
- doReturn(mConnectedDisplayKeyguardPresentation).when(
- mConnectedDisplayKeyguardPresentationFactory).create(any());
- doReturn(mConnectedDisplayKeyguardPresentation).when(mManager)
- .createPresentation(any());
- mDefaultDisplay = new Display(DisplayManagerGlobal.getInstance(), Display.DEFAULT_DISPLAY,
- new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS);
- mSecondaryDisplay = new Display(DisplayManagerGlobal.getInstance(),
- Display.DEFAULT_DISPLAY + 1,
- new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS);
-
- DisplayInfo alwaysUnlockedDisplayInfo = new DisplayInfo();
- alwaysUnlockedDisplayInfo.displayId = Display.DEFAULT_DISPLAY + 2;
- alwaysUnlockedDisplayInfo.flags = Display.FLAG_ALWAYS_UNLOCKED;
- mAlwaysUnlockedDisplay = new Display(DisplayManagerGlobal.getInstance(),
- Display.DEFAULT_DISPLAY,
- alwaysUnlockedDisplayInfo, DEFAULT_DISPLAY_ADJUSTMENTS);
- }
-
- @Test
- public void testShow_defaultDisplayOnly() {
- mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay});
- mManager.show();
- verify(mManager, never()).createPresentation(any());
- }
-
- @Test
- public void testShow_includeSecondaryDisplay() {
- mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay, mSecondaryDisplay});
- mManager.show();
- verify(mManager, times(1)).createPresentation(eq(mSecondaryDisplay));
- }
-
- @Test
- public void testShow_includeAlwaysUnlockedDisplay() {
- mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay, mAlwaysUnlockedDisplay});
-
- mManager.show();
- verify(mManager, never()).createPresentation(any());
- }
-
- @Test
- public void testShow_includeSecondaryAndAlwaysUnlockedDisplays() {
- mDisplayTracker.setAllDisplays(
- new Display[]{mDefaultDisplay, mSecondaryDisplay, mAlwaysUnlockedDisplay});
-
- mManager.show();
- verify(mManager, times(1)).createPresentation(eq(mSecondaryDisplay));
- }
-
- @Test
- public void testShow_concurrentDisplayActive_occluded() {
- mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay, mSecondaryDisplay});
-
- when(mDeviceStateHelper.isConcurrentDisplayActive(mSecondaryDisplay)).thenReturn(true);
- when(mKeyguardStateController.isOccluded()).thenReturn(true);
- verify(mManager, never()).createPresentation(eq(mSecondaryDisplay));
- }
-
- @Test
- public void testShow_presentationCreated() {
- when(mManager.createPresentation(any())).thenCallRealMethod();
- mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay, mSecondaryDisplay});
-
- mManager.show();
-
- verify(mConnectedDisplayKeyguardPresentationFactory).create(eq(mSecondaryDisplay));
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt
new file mode 100644
index 0000000..57a6797
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt
@@ -0,0 +1,225 @@
+/*
+ * 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.keyguard
+
+import android.hardware.display.DisplayManagerGlobal
+import android.platform.test.annotations.EnableFlags
+import android.testing.TestableLooper.RunWithLooper
+import android.view.Display
+import android.view.DisplayAdjustments
+import android.view.DisplayInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardDisplayManager.DeviceStateHelper
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.navigationbar.NavigationBarController
+import com.android.systemui.settings.FakeDisplayTracker
+import com.android.systemui.shade.data.repository.FakeShadePositionRepository
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import java.util.concurrent.Executor
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.advanceUntilIdle
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.reset
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@RunWithLooper
+@OptIn(ExperimentalCoroutinesApi::class)
+class KeyguardDisplayManagerTest : SysuiTestCase() {
+ @Mock private val navigationBarController = mock(NavigationBarController::class.java)
+ @Mock
+ private val presentationFactory = mock(ConnectedDisplayKeyguardPresentation.Factory::class.java)
+ @Mock
+ private val connectedDisplayKeyguardPresentation =
+ mock(ConnectedDisplayKeyguardPresentation::class.java)
+ @Mock private val deviceStateHelper = mock(DeviceStateHelper::class.java)
+ @Mock private val keyguardStateController = mock(KeyguardStateController::class.java)
+ private val shadePositionRepository = FakeShadePositionRepository()
+
+ private val mainExecutor = Executor { it.run() }
+ private val backgroundExecutor = Executor { it.run() }
+ private lateinit var manager: KeyguardDisplayManager
+ private val displayTracker = FakeDisplayTracker(mContext)
+ // The default and secondary displays are both in the default group
+ private lateinit var defaultDisplay: Display
+ private lateinit var secondaryDisplay: Display
+
+ private val testScope = TestScope(UnconfinedTestDispatcher())
+
+ // This display is in a different group from the default and secondary displays.
+ private lateinit var alwaysUnlockedDisplay: Display
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ manager =
+ KeyguardDisplayManager(
+ mContext,
+ { navigationBarController },
+ displayTracker,
+ mainExecutor,
+ backgroundExecutor,
+ deviceStateHelper,
+ keyguardStateController,
+ presentationFactory,
+ { shadePositionRepository },
+ testScope.backgroundScope,
+ )
+ whenever(presentationFactory.create(any())).doReturn(connectedDisplayKeyguardPresentation)
+
+ defaultDisplay =
+ Display(
+ DisplayManagerGlobal.getInstance(),
+ Display.DEFAULT_DISPLAY,
+ DisplayInfo(),
+ DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS,
+ )
+ secondaryDisplay =
+ Display(
+ DisplayManagerGlobal.getInstance(),
+ Display.DEFAULT_DISPLAY + 1,
+ DisplayInfo(),
+ DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS,
+ )
+
+ val alwaysUnlockedDisplayInfo = DisplayInfo()
+ alwaysUnlockedDisplayInfo.displayId = Display.DEFAULT_DISPLAY + 2
+ alwaysUnlockedDisplayInfo.flags = Display.FLAG_ALWAYS_UNLOCKED
+ alwaysUnlockedDisplay =
+ Display(
+ DisplayManagerGlobal.getInstance(),
+ Display.DEFAULT_DISPLAY,
+ alwaysUnlockedDisplayInfo,
+ DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS,
+ )
+ }
+
+ @Test
+ fun testShow_defaultDisplayOnly() {
+ displayTracker.allDisplays = arrayOf(defaultDisplay)
+ manager.show()
+ verify(presentationFactory, never()).create(any())
+ }
+
+ @Test
+ fun testShow_includeSecondaryDisplay() {
+ displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay)
+ manager.show()
+ verify(presentationFactory).create(eq(secondaryDisplay))
+ }
+
+ @Test
+ fun testShow_includeAlwaysUnlockedDisplay() {
+ displayTracker.allDisplays = arrayOf(defaultDisplay, alwaysUnlockedDisplay)
+
+ manager.show()
+ verify(presentationFactory, never()).create(any())
+ }
+
+ @Test
+ fun testShow_includeSecondaryAndAlwaysUnlockedDisplays() {
+ displayTracker.allDisplays =
+ arrayOf(defaultDisplay, secondaryDisplay, alwaysUnlockedDisplay)
+
+ manager.show()
+ verify(presentationFactory).create(eq(secondaryDisplay))
+ }
+
+ @Test
+ fun testShow_concurrentDisplayActive_occluded() {
+ displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay)
+
+ whenever(deviceStateHelper.isConcurrentDisplayActive(secondaryDisplay)).thenReturn(true)
+ whenever(keyguardStateController.isOccluded).thenReturn(true)
+ verify(presentationFactory, never()).create(eq(secondaryDisplay))
+ }
+
+ @Test
+ fun testShow_presentationCreated() {
+ displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay)
+
+ manager.show()
+
+ verify(presentationFactory).create(eq(secondaryDisplay))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun show_shadeMovesDisplay_newPresentationCreated() {
+ displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay)
+ // Shade in the default display, we expect the presentation to be in the secondary only
+ shadePositionRepository.setDisplayId(defaultDisplay.displayId)
+
+ manager.show()
+
+ verify(presentationFactory).create(eq(secondaryDisplay))
+ verify(presentationFactory, never()).create(eq(defaultDisplay))
+ reset(presentationFactory)
+ whenever(presentationFactory.create(any())).thenReturn(connectedDisplayKeyguardPresentation)
+
+ // Let's move it to the secondary display. We expect it will be added in the default
+ // one.
+ shadePositionRepository.setDisplayId(secondaryDisplay.displayId)
+ testScope.advanceUntilIdle()
+
+ verify(presentationFactory).create(eq(defaultDisplay))
+ reset(presentationFactory)
+ whenever(presentationFactory.create(any())).thenReturn(connectedDisplayKeyguardPresentation)
+
+ // Let's move it back! it should be re-created (it means it was removed before)
+ shadePositionRepository.setDisplayId(defaultDisplay.displayId)
+ testScope.advanceUntilIdle()
+
+ verify(presentationFactory).create(eq(secondaryDisplay))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun show_shadeInSecondaryDisplay_defaultOneHasPresentation() {
+ displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay)
+ shadePositionRepository.setDisplayId(secondaryDisplay.displayId)
+
+ manager.show()
+
+ verify(presentationFactory).create(eq(defaultDisplay))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun show_shadeInDefaultDisplay_secondaryOneHasPresentation() {
+ displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay)
+ shadePositionRepository.setDisplayId(defaultDisplay.displayId)
+
+ manager.show()
+
+ verify(presentationFactory).create(eq(secondaryDisplay))
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
index fcb433b..9d471f4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
@@ -45,9 +45,9 @@
import android.platform.test.annotations.EnableFlags;
import android.provider.Settings;
import android.testing.TestableLooper;
+import android.util.Pair;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.LinearLayout;
import android.widget.Space;
import android.widget.Spinner;
@@ -166,6 +166,7 @@
when(mCachedDevice.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(true);
when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(true);
when(mCachedDevice.isConnectedHapClientDevice()).thenReturn(true);
+ when(mCachedDevice.getDrawableWithDescription()).thenReturn(new Pair<>(mDrawable, ""));
when(mHearingDeviceItem.getCachedBluetoothDevice()).thenReturn(mCachedDevice);
mContext.setMockPackageManager(mPackageManager);
@@ -217,6 +218,18 @@
@Test
@EnableFlags(Flags.FLAG_HEARING_DEVICES_DIALOG_RELATED_TOOLS)
+ public void showDialog_noLiveCaption_noRelatedToolsInConfig_relatedToolLayoutGone() {
+ mContext.getOrCreateTestableResources().addOverride(
+ R.array.config_quickSettingsHearingDevicesRelatedToolName, new String[]{});
+
+ setUpPairNewDeviceDialog();
+ mDialog.show();
+
+ assertToolsUi(0);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HEARING_DEVICES_DIALOG_RELATED_TOOLS)
public void showDialog_hasLiveCaption_noRelatedToolsInConfig_showOneRelatedTool() {
when(mPackageManager.queryIntentActivities(
eq(LIVE_CAPTION_INTENT), anyInt())).thenReturn(
@@ -227,8 +240,7 @@
setUpPairNewDeviceDialog();
mDialog.show();
- LinearLayout relatedToolsView = (LinearLayout) getRelatedToolsView(mDialog);
- assertThat(countChildWithoutSpace(relatedToolsView)).isEqualTo(1);
+ assertToolsUi(1);
}
@Test
@@ -251,8 +263,7 @@
setUpPairNewDeviceDialog();
mDialog.show();
- LinearLayout relatedToolsView = (LinearLayout) getRelatedToolsView(mDialog);
- assertThat(countChildWithoutSpace(relatedToolsView)).isEqualTo(2);
+ assertToolsUi(2);
}
@Test
@@ -263,8 +274,8 @@
setUpDeviceListDialog();
mDialog.show();
- Spinner spinner = (Spinner) getPresetSpinner(mDialog);
- assertThat(spinner.getVisibility()).isEqualTo(View.GONE);
+ ViewGroup presetLayout = getPresetLayout(mDialog);
+ assertThat(presetLayout.getVisibility()).isEqualTo(View.GONE);
}
@Test
@@ -276,8 +287,9 @@
setUpDeviceListDialog();
mDialog.show();
- Spinner spinner = (Spinner) getPresetSpinner(mDialog);
- assertThat(spinner.getVisibility()).isEqualTo(View.VISIBLE);
+ ViewGroup presetLayout = getPresetLayout(mDialog);
+ assertThat(presetLayout.getVisibility()).isEqualTo(View.VISIBLE);
+ Spinner spinner = getPresetSpinner(mDialog);
assertThat(spinner.getSelectedItemPosition()).isEqualTo(0);
}
@@ -292,8 +304,9 @@
mDialogDelegate.onActiveDeviceChanged(mCachedDevice, BluetoothProfile.LE_AUDIO);
mTestableLooper.processAllMessages();
- Spinner spinner = (Spinner) getPresetSpinner(mDialog);
- assertThat(spinner.getVisibility()).isEqualTo(View.VISIBLE);
+ ViewGroup presetLayout = getPresetLayout(mDialog);
+ assertThat(presetLayout.getVisibility()).isEqualTo(View.VISIBLE);
+ Spinner spinner = getPresetSpinner(mDialog);
assertThat(spinner.getSelectedItemPosition()).isEqualTo(0);
}
@@ -359,14 +372,23 @@
return dialog.requireViewById(R.id.pair_new_device_button);
}
- private View getRelatedToolsView(SystemUIDialog dialog) {
- return dialog.requireViewById(R.id.related_tools_container);
+ private ViewGroup getToolsContainer(SystemUIDialog dialog) {
+ return dialog.requireViewById(R.id.tools_container);
}
- private View getPresetSpinner(SystemUIDialog dialog) {
+ private ViewGroup getToolsLayout(SystemUIDialog dialog) {
+ return dialog.requireViewById(R.id.tools_layout);
+ }
+
+ private Spinner getPresetSpinner(SystemUIDialog dialog) {
return dialog.requireViewById(R.id.preset_spinner);
}
+ private ViewGroup getPresetLayout(SystemUIDialog dialog) {
+ return dialog.requireViewById(R.id.preset_layout);
+ }
+
+
private int countChildWithoutSpace(ViewGroup viewGroup) {
int spaceCount = 0;
for (int i = 0; i < viewGroup.getChildCount(); i++) {
@@ -377,6 +399,15 @@
return viewGroup.getChildCount() - spaceCount;
}
+ private void assertToolsUi(int childCount) {
+ ViewGroup toolsContainer = getToolsContainer(mDialog);
+ assertThat(countChildWithoutSpace(toolsContainer)).isEqualTo(childCount);
+
+ int targetVisibility = childCount == 0 ? View.GONE : View.VISIBLE;
+ ViewGroup toolsLayout = getToolsLayout(mDialog);
+ assertThat(toolsLayout.getVisibility()).isEqualTo(targetVisibility);
+ }
+
@After
public void reset() {
if (mDialogDelegate != null) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt
index f06b105..cee17c3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt
@@ -18,7 +18,9 @@
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
+import android.graphics.drawable.Drawable
import android.testing.TestableLooper
+import android.util.Pair
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.bluetooth.CachedBluetoothDevice
@@ -64,6 +66,7 @@
@Mock private lateinit var bluetoothDevice1: BluetoothDevice
@Mock private lateinit var cachedDevice2: CachedBluetoothDevice
@Mock private lateinit var bluetoothDevice2: BluetoothDevice
+ @Mock private lateinit var drawable: Drawable
@Captor
private lateinit var argumentCaptor: ArgumentCaptor<BluetoothAdapter.OnMetadataChangedListener>
private lateinit var interactor: BluetoothDeviceMetadataInteractor
@@ -77,12 +80,14 @@
whenever(cachedDevice1.name).thenReturn(DEVICE_NAME)
whenever(cachedDevice1.address).thenReturn(DEVICE_ADDRESS)
whenever(cachedDevice1.connectionSummary).thenReturn(CONNECTION_SUMMARY)
+ whenever(cachedDevice1.drawableWithDescription).thenReturn(Pair.create(drawable, ""))
whenever(bluetoothDevice1.address).thenReturn(DEVICE_ADDRESS)
whenever(cachedDevice2.device).thenReturn(bluetoothDevice2)
whenever(cachedDevice2.name).thenReturn(DEVICE_NAME)
whenever(cachedDevice2.address).thenReturn(DEVICE_ADDRESS)
whenever(cachedDevice2.connectionSummary).thenReturn(CONNECTION_SUMMARY)
+ whenever(cachedDevice2.drawableWithDescription).thenReturn(Pair.create(drawable, ""))
whenever(bluetoothDevice2.address).thenReturn(DEVICE_ADDRESS)
interactor = bluetoothDeviceMetadataInteractor
@@ -175,14 +180,14 @@
.addOnMetadataChangedListener(
eq(bluetoothDevice1),
any(),
- argumentCaptor.capture()
+ argumentCaptor.capture(),
)
val listener = argumentCaptor.value
listener.onMetadataChanged(
bluetoothDevice1,
BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY,
- ByteArray(0)
+ ByteArray(0),
)
assertThat(update).isEqualTo(Unit)
}
@@ -203,14 +208,14 @@
.addOnMetadataChangedListener(
eq(bluetoothDevice1),
any(),
- argumentCaptor.capture()
+ argumentCaptor.capture(),
)
val listener = argumentCaptor.value
listener.onMetadataChanged(
bluetoothDevice1,
BluetoothDevice.METADATA_MODEL_NAME,
- ByteArray(0)
+ ByteArray(0),
)
assertThat(update).isNull()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt
index 82918a5..af2d7e4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt
@@ -47,20 +47,14 @@
@Test
fun logAddWidget_componentNotLoggable_doNotLog() {
- underTest.logAddWidget(
- componentName = "com.green.package/my_test_widget",
- rank = 1,
- )
+ underTest.logAddWidget(componentName = "com.green.package/my_test_widget", rank = 1)
verify(statsLogProxy, never())
- .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt())
+ .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt(), anyInt())
}
@Test
fun logAddWidget_componentLoggable_logAddEvent() {
- underTest.logAddWidget(
- componentName = "com.blue.package/my_test_widget",
- rank = 1,
- )
+ underTest.logAddWidget(componentName = "com.blue.package/my_test_widget", rank = 1)
verify(statsLogProxy)
.writeCommunalHubWidgetEventReported(
SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__ADD,
@@ -71,20 +65,14 @@
@Test
fun logRemoveWidget_componentNotLoggable_doNotLog() {
- underTest.logRemoveWidget(
- componentName = "com.yellow.package/my_test_widget",
- rank = 2,
- )
+ underTest.logRemoveWidget(componentName = "com.yellow.package/my_test_widget", rank = 2)
verify(statsLogProxy, never())
- .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt())
+ .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt(), anyInt())
}
@Test
fun logRemoveWidget_componentLoggable_logRemoveEvent() {
- underTest.logRemoveWidget(
- componentName = "com.red.package/my_test_widget",
- rank = 2,
- )
+ underTest.logRemoveWidget(componentName = "com.red.package/my_test_widget", rank = 2)
verify(statsLogProxy)
.writeCommunalHubWidgetEventReported(
SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__REMOVE,
@@ -95,20 +83,14 @@
@Test
fun logTapWidget_componentNotLoggable_doNotLog() {
- underTest.logTapWidget(
- componentName = "com.yellow.package/my_test_widget",
- rank = 2,
- )
+ underTest.logTapWidget(componentName = "com.yellow.package/my_test_widget", rank = 2)
verify(statsLogProxy, never())
- .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt())
+ .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt(), anyInt())
}
@Test
fun logTapWidget_componentLoggable_logRemoveEvent() {
- underTest.logTapWidget(
- componentName = "com.red.package/my_test_widget",
- rank = 2,
- )
+ underTest.logTapWidget(componentName = "com.red.package/my_test_widget", rank = 2)
verify(statsLogProxy)
.writeCommunalHubWidgetEventReported(
SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__TAP,
@@ -140,4 +122,43 @@
)
assertThat(statsEvents).hasSize(1)
}
+
+ @Test
+ fun logResizeWidget_componentNotLoggable_doNotLog() {
+ underTest.logResizeWidget(
+ componentName = "com.green.package/my_test_widget",
+ rank = 1,
+ spanY = 2,
+ )
+ verify(statsLogProxy, never())
+ .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt(), anyInt())
+ }
+
+ @Test
+ fun logResizeWidget_componentLoggable_logResizeEvent() {
+ underTest.logResizeWidget(
+ componentName = "com.blue.package/my_test_widget",
+ rank = 1,
+ spanY = 2,
+ )
+ verify(statsLogProxy)
+ .writeCommunalHubWidgetEventReported(
+ SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__RESIZE,
+ "com.blue.package/my_test_widget",
+ rank = 1,
+ spanY = 2,
+ )
+ }
+
+ @Test
+ fun logResizeWidget_defaultSpanY_usesDefaultValue() {
+ underTest.logResizeWidget(componentName = "com.blue.package/my_test_widget", rank = 1)
+ verify(statsLogProxy)
+ .writeCommunalHubWidgetEventReported(
+ SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__RESIZE,
+ "com.blue.package/my_test_widget",
+ rank = 1,
+ spanY = 0,
+ )
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index cecc11e..6b851cb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -353,6 +353,32 @@
assertThat(event.contentDescription).isEqualTo("Test Clock widget added to lock screen")
}
+ @Test
+ fun onResizeWidget_logsMetrics() =
+ testScope.runTest {
+ val appWidgetId = 123
+ val spanY = 2
+ val widgetIdToRankMap = mapOf(appWidgetId to 1)
+ val componentName = ComponentName("test.package", "TestWidget")
+ val rank = 1
+
+ underTest.onResizeWidget(
+ appWidgetId = appWidgetId,
+ spanY = spanY,
+ widgetIdToRankMap = widgetIdToRankMap,
+ componentName = componentName,
+ rank = rank,
+ )
+
+ verify(communalInteractor).resizeWidget(appWidgetId, spanY, widgetIdToRankMap)
+ verify(metricsLogger)
+ .logResizeWidget(
+ componentName = componentName.flattenToString(),
+ rank = rank,
+ spanY = spanY,
+ )
+ }
+
private companion object {
val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
const val WIDGET_PICKER_PACKAGE_NAME = "widget_picker_package_name"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt
index 5df9b7b..2efa2f3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt
@@ -85,63 +85,62 @@
@Test
fun connectKeyboard_delayElapse_launchForKeyboard() =
testScope.runTest {
- launchAndAssert(TutorialType.KEYBOARD)
-
keyboardRepository.setIsAnyKeyboardConnected(true)
advanceTimeBy(LAUNCH_DELAY)
+
+ launchAndAssert(TutorialType.KEYBOARD)
}
@Test
fun connectBothDevices_delayElapse_launchForBoth() =
testScope.runTest {
- launchAndAssert(TutorialType.BOTH)
-
keyboardRepository.setIsAnyKeyboardConnected(true)
touchpadRepository.setIsAnyTouchpadConnected(true)
advanceTimeBy(LAUNCH_DELAY)
+
+ launchAndAssert(TutorialType.BOTH)
}
@Test
fun connectBothDevice_delayNotElapse_launchNothing() =
testScope.runTest {
- launchAndAssert(TutorialType.NONE)
-
keyboardRepository.setIsAnyKeyboardConnected(true)
touchpadRepository.setIsAnyTouchpadConnected(true)
advanceTimeBy(A_SHORT_PERIOD_OF_TIME)
+
+ launchAndAssert(TutorialType.NONE)
}
@Test
fun nothingConnect_delayElapse_launchNothing() =
testScope.runTest {
- launchAndAssert(TutorialType.NONE)
-
keyboardRepository.setIsAnyKeyboardConnected(false)
touchpadRepository.setIsAnyTouchpadConnected(false)
advanceTimeBy(LAUNCH_DELAY)
+
+ launchAndAssert(TutorialType.NONE)
}
@Test
fun connectKeyboard_thenTouchpad_delayElapse_launchForBoth() =
testScope.runTest {
- launchAndAssert(TutorialType.BOTH)
-
keyboardRepository.setIsAnyKeyboardConnected(true)
advanceTimeBy(A_SHORT_PERIOD_OF_TIME)
touchpadRepository.setIsAnyTouchpadConnected(true)
advanceTimeBy(REMAINING_TIME)
+
+ launchAndAssert(TutorialType.BOTH)
}
@Test
fun connectKeyboard_thenTouchpad_removeKeyboard_delayElapse_launchNothing() =
testScope.runTest {
- launchAndAssert(TutorialType.NONE)
-
keyboardRepository.setIsAnyKeyboardConnected(true)
advanceTimeBy(A_SHORT_PERIOD_OF_TIME)
touchpadRepository.setIsAnyTouchpadConnected(true)
keyboardRepository.setIsAnyKeyboardConnected(false)
advanceTimeBy(REMAINING_TIME)
+ launchAndAssert(TutorialType.NONE)
}
private suspend fun launchAndAssert(expectedTutorial: TutorialType) =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
new file mode 100644
index 0000000..c646d8f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
@@ -0,0 +1,291 @@
+/*
+ * 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.keyboard.shortcut.data.repository
+
+import android.content.Context
+import android.content.Context.INPUT_SERVICE
+import android.hardware.input.InputGestureData
+import android.hardware.input.InputGestureData.createKeyTrigger
+import android.hardware.input.KeyGestureEvent
+import android.hardware.input.fakeInputManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.view.KeyEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.hardware.input.Flags.FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES
+import com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyboard.shortcut.customShortcutCategoriesRepository
+import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.AppCategories
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
+import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.userTracker
+import com.android.systemui.testKosmos
+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.kotlin.anyOrNull
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() {
+
+ private val mockUserContext: Context = mock()
+ private val kosmos =
+ testKosmos().also {
+ it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext })
+ }
+
+ private val fakeInputManager = kosmos.fakeInputManager
+ private val testScope = kosmos.testScope
+ private val helper = kosmos.shortcutHelperTestHelper
+ private val repo = kosmos.customShortcutCategoriesRepository
+
+ @Before
+ fun setup() {
+ whenever(mockUserContext.getSystemService(INPUT_SERVICE))
+ .thenReturn(fakeInputManager.inputManager)
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+ fun categories_emitsCorrectlyConvertedShortcutCategories() {
+ testScope.runTest {
+ whenever(
+ fakeInputManager.inputManager.getCustomInputGestures(/* filter= */ anyOrNull())
+ )
+ .thenReturn(customizableInputGesturesWithSimpleShortcutCombinations)
+
+ helper.toggle(deviceId = 123)
+ val categories by collectLastValue(repo.categories)
+
+ assertThat(categories)
+ .containsExactlyElementsIn(expectedShortcutCategoriesWithSimpleShortcutCombination)
+ }
+ }
+
+ @Test
+ @DisableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+ fun categories_emitsEmptyListWhenFlagIsDisabled() {
+ testScope.runTest {
+ whenever(
+ fakeInputManager.inputManager.getCustomInputGestures(/* filter= */ anyOrNull())
+ )
+ .thenReturn(customizableInputGesturesWithSimpleShortcutCombinations)
+
+ helper.toggle(deviceId = 123)
+ val categories by collectLastValue(repo.categories)
+
+ assertThat(categories).isEmpty()
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+ fun categories_ignoresUnknownKeyGestureTypes() {
+ testScope.runTest {
+ whenever(
+ fakeInputManager.inputManager.getCustomInputGestures(/* filter= */ anyOrNull())
+ )
+ .thenReturn(customizableInputGestureWithUnknownKeyGestureType)
+
+ helper.toggle(deviceId = 123)
+ val categories by collectLastValue(repo.categories)
+
+ assertThat(categories).isEmpty()
+ }
+ }
+
+ private fun simpleInputGestureData(
+ keyCode: Int = KeyEvent.KEYCODE_A,
+ modifiers: Int = KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON,
+ keyGestureType: Int,
+ ): InputGestureData {
+ val builder = InputGestureData.Builder()
+ builder.setKeyGestureType(keyGestureType)
+ builder.setTrigger(createKeyTrigger(keyCode, modifiers))
+ return builder.build()
+ }
+
+ private fun simpleShortcutCategory(
+ category: ShortcutCategoryType,
+ subcategoryLabel: String,
+ shortcutLabel: String,
+ ): ShortcutCategory {
+ return ShortcutCategory(
+ type = category,
+ subCategories =
+ listOf(
+ ShortcutSubCategory(
+ label = subcategoryLabel,
+ shortcuts = listOf(simpleShortcut(shortcutLabel)),
+ )
+ ),
+ )
+ }
+
+ private fun simpleShortcut(label: String) =
+ Shortcut(
+ label = label,
+ commands =
+ listOf(
+ ShortcutCommand(
+ isCustom = true,
+ keys =
+ listOf(
+ ShortcutKey.Text("Ctrl"),
+ ShortcutKey.Text("Alt"),
+ ShortcutKey.Text("A"),
+ ),
+ )
+ ),
+ )
+
+ private val customizableInputGestureWithUnknownKeyGestureType =
+ // These key gesture events are currently not supported by shortcut helper customizer
+ listOf(
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS
+ ),
+ simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY),
+ )
+
+ private val expectedShortcutCategoriesWithSimpleShortcutCombination =
+ listOf(
+ simpleShortcutCategory(System, "System apps", "Open assistant"),
+ simpleShortcutCategory(System, "System controls", "Go to home screen"),
+ simpleShortcutCategory(System, "System apps", "Open settings"),
+ simpleShortcutCategory(System, "System controls", "Lock screen"),
+ simpleShortcutCategory(System, "System controls", "View notifications"),
+ simpleShortcutCategory(System, "System apps", "Take a note"),
+ simpleShortcutCategory(System, "System controls", "Take screenshot"),
+ simpleShortcutCategory(System, "System controls", "Go back"),
+ simpleShortcutCategory(
+ MultiTasking,
+ "Split screen",
+ "Switch from split screen to full screen",
+ ),
+ simpleShortcutCategory(
+ MultiTasking,
+ "Split screen",
+ "Use split screen with current app on the left",
+ ),
+ simpleShortcutCategory(
+ MultiTasking,
+ "Split screen",
+ "Switch to app on left or above while using split screen",
+ ),
+ simpleShortcutCategory(
+ MultiTasking,
+ "Split screen",
+ "Use split screen with current app on the right",
+ ),
+ simpleShortcutCategory(
+ MultiTasking,
+ "Split screen",
+ "Switch to app on right or below while using split screen",
+ ),
+ simpleShortcutCategory(System, "System controls", "Show shortcuts"),
+ simpleShortcutCategory(System, "System controls", "View recent apps"),
+ simpleShortcutCategory(AppCategories, "Applications", "Calculator"),
+ simpleShortcutCategory(AppCategories, "Applications", "Calendar"),
+ simpleShortcutCategory(AppCategories, "Applications", "Chrome"),
+ simpleShortcutCategory(AppCategories, "Applications", "Contacts"),
+ simpleShortcutCategory(AppCategories, "Applications", "Gmail"),
+ simpleShortcutCategory(AppCategories, "Applications", "Maps"),
+ simpleShortcutCategory(AppCategories, "Applications", "Messages"),
+ simpleShortcutCategory(MultiTasking, "Recent apps", "Cycle forward through recent apps"),
+ )
+
+ private val customizableInputGesturesWithSimpleShortcutCombinations =
+ listOf(
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT
+ ),
+ simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_HOME),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS
+ ),
+ simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL
+ ),
+ simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT
+ ),
+ simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_BACK),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION
+ ),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT
+ ),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT
+ ),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT
+ ),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT
+ ),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER
+ ),
+ simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR
+ ),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR
+ ),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER
+ ),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS
+ ),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL
+ ),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS
+ ),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING
+ ),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER
+ ),
+ )
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepositoryTest.kt
similarity index 98%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepositoryTest.kt
index 620b8b6..f90ab1f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepositoryTest.kt
@@ -40,6 +40,7 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts
+import com.android.systemui.keyboard.shortcut.defaultShortcutCategoriesRepository
import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
@@ -47,7 +48,6 @@
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource
-import com.android.systemui.keyboard.shortcut.shortcutHelperCategoriesRepository
import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperInputShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperMultiTaskingShortcutsSource
@@ -71,7 +71,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
-class ShortcutHelperCategoriesRepositoryTest : SysuiTestCase() {
+class DefaultShortcutCategoriesRepositoryTest : SysuiTestCase() {
private val fakeSystemSource = FakeKeyboardShortcutGroupsSource()
private val fakeMultiTaskingSource = FakeKeyboardShortcutGroupsSource()
@@ -87,7 +87,7 @@
it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource()
}
- private val repo = kosmos.shortcutHelperCategoriesRepository
+ private val repo = kosmos.defaultShortcutCategoriesRepository
private val helper = kosmos.shortcutHelperTestHelper
private val testScope = kosmos.testScope
private val fakeInputManager = kosmos.fakeInputManager
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSFooterViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
index 93dede5..f1f6c61 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
@@ -38,7 +38,7 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.res.R;
import com.android.systemui.retail.data.repository.FakeRetailModeRepository;
-import com.android.systemui.retail.domain.interactor.RetailModeInteractorImpl;
+import com.android.systemui.retail.domain.interactor.impl.RetailModeInteractorImpl;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.utils.leaks.LeakCheckedTest;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
index 03feceb..b72668b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
@@ -18,6 +18,7 @@
import android.app.StatusBarManager
import android.content.testableContext
+import android.graphics.Rect
import android.testing.TestableLooper.RunWithLooper
import androidx.compose.runtime.snapshots.Snapshot
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -43,6 +44,7 @@
import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
import com.android.systemui.statusbar.sysuiStatusBarStateController
+import com.android.systemui.util.animation.DisappearParameters
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
@@ -375,6 +377,92 @@
}
}
+ @Test
+ fun applyQsScrollPositionForClipping() =
+ with(kosmos) {
+ testScope.testWithinLifecycle {
+ val left = 1f
+ val top = 3f
+ val right = 5f
+ val bottom = 7f
+
+ underTest.applyNewQsScrollerBounds(left, top, right, bottom)
+
+ assertThat(qsMediaHost.currentClipping)
+ .isEqualTo(Rect(left.toInt(), top.toInt(), right.toInt(), bottom.toInt()))
+ }
+ }
+
+ @Test
+ fun shouldUpdateMediaSquishiness_inSplitShadeFalse_mediaSquishinessSet() =
+ with(kosmos) {
+ testScope.testWithinLifecycle {
+ underTest.isInSplitShade = false
+ underTest.squishinessFraction = 0.3f
+
+ underTest.shouldUpdateSquishinessOnMedia = true
+ Snapshot.sendApplyNotifications()
+ runCurrent()
+
+ assertThat(underTest.qsMediaHost.squishFraction).isWithin(0.01f).of(0.3f)
+
+ underTest.shouldUpdateSquishinessOnMedia = false
+ Snapshot.sendApplyNotifications()
+ runCurrent()
+ assertThat(underTest.qsMediaHost.squishFraction).isWithin(0.01f).of(1f)
+ }
+ }
+
+ @Test
+ fun inSplitShade_differentStatusBarState_mediaSquishinessSet() =
+ with(kosmos) {
+ testScope.testWithinLifecycle {
+ underTest.isInSplitShade = true
+ underTest.squishinessFraction = 0.3f
+
+ sysuiStatusBarStateController.setState(StatusBarState.SHADE)
+ Snapshot.sendApplyNotifications()
+ runCurrent()
+ assertThat(underTest.qsMediaHost.squishFraction).isWithin(epsilon).of(0.3f)
+
+ sysuiStatusBarStateController.setState(StatusBarState.KEYGUARD)
+ runCurrent()
+ Snapshot.sendApplyNotifications()
+ runCurrent()
+ assertThat(underTest.qsMediaHost.squishFraction).isWithin(epsilon).of(1f)
+
+ sysuiStatusBarStateController.setState(StatusBarState.SHADE_LOCKED)
+ runCurrent()
+ Snapshot.sendApplyNotifications()
+ runCurrent()
+ assertThat(underTest.qsMediaHost.squishFraction).isWithin(epsilon).of(1f)
+ }
+ }
+
+ @Test
+ fun disappearParams() =
+ with(kosmos) {
+ testScope.testWithinLifecycle {
+ setMediaState(ACTIVE_MEDIA)
+
+ setConfigurationForMediaInRow(false)
+ Snapshot.sendApplyNotifications()
+ runCurrent()
+
+ assertThat(underTest.qqsMediaHost.disappearParameters)
+ .isEqualTo(disappearParamsColumn)
+ assertThat(underTest.qsMediaHost.disappearParameters)
+ .isEqualTo(disappearParamsColumn)
+
+ setConfigurationForMediaInRow(true)
+ Snapshot.sendApplyNotifications()
+ runCurrent()
+
+ assertThat(underTest.qqsMediaHost.disappearParameters).isEqualTo(disappearParamsRow)
+ assertThat(underTest.qsMediaHost.disappearParameters).isEqualTo(disappearParamsRow)
+ }
+ }
+
private fun TestScope.setMediaState(state: MediaState) {
with(kosmos) {
val activeMedia = state == ACTIVE_MEDIA
@@ -404,6 +492,26 @@
}
private const val epsilon = 0.001f
+
+ private val disappearParamsColumn =
+ DisappearParameters().apply {
+ fadeStartPosition = 0.95f
+ disappearStart = 0f
+ disappearEnd = 0.95f
+ disappearSize.set(1f, 0f)
+ gonePivot.set(0f, 0f)
+ contentTranslationFraction.set(0f, 1f)
+ }
+
+ private val disappearParamsRow =
+ DisappearParameters().apply {
+ fadeStartPosition = 0.95f
+ disappearStart = 0f
+ disappearEnd = 0.6f
+ disappearSize.set(0f, 0.4f)
+ gonePivot.set(1f, 0f)
+ contentTranslationFraction.set(0.25f, 1f)
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt
index ba7a65d..47bfda4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt
@@ -21,6 +21,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.retail.data.repository.impl.RetailModeSettingsRepository
import com.android.systemui.util.settings.FakeGlobalSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt
index b536520..b47dcb5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt
@@ -20,6 +20,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.retail.data.repository.FakeRetailModeRepository
+import com.android.systemui.retail.domain.interactor.impl.RetailModeInteractorImpl
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
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 152911a..af0274b 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
@@ -44,6 +44,7 @@
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.bouncer.shared.logging.BouncerUiEvent
import com.android.systemui.classifier.FalsingCollector
@@ -54,6 +55,7 @@
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryHapticsInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
import com.android.systemui.flags.EnableSceneContainer
@@ -114,6 +116,7 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
@@ -2356,6 +2359,58 @@
assertThat(isLockscreenEnabled).isTrue()
}
+ @Test
+ fun replacesLockscreenSceneOnBackStack_whenUnlockdViaAlternateBouncer_fromShade() =
+ testScope.runTest {
+ val transitionState =
+ prepareState(
+ isDeviceUnlocked = false,
+ initialSceneKey = Scenes.Lockscreen,
+ authenticationMethod = AuthenticationMethodModel.Pin,
+ )
+ underTest.start()
+
+ val isUnlocked by
+ collectLastValue(
+ kosmos.deviceUnlockedInteractor.deviceUnlockStatus.map { it.isUnlocked }
+ )
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val backStack by collectLastValue(sceneBackInteractor.backStack)
+ val isAlternateBouncerVisible by
+ collectLastValue(kosmos.alternateBouncerInteractor.isVisible)
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ assertThat(isAlternateBouncerVisible).isFalse()
+
+ // Change to shade.
+ sceneInteractor.changeScene(Scenes.Shade, "")
+ transitionState.value = ObservableTransitionState.Idle(Scenes.Shade)
+ runCurrent()
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+ assertThat(backStack?.asIterable()?.first()).isEqualTo(Scenes.Lockscreen)
+ assertThat(isAlternateBouncerVisible).isFalse()
+
+ // Show the alternate bouncer.
+ kosmos.alternateBouncerInteractor.forceShow()
+ kosmos.sysuiStatusBarStateController.leaveOpen = true // leave shade open
+ runCurrent()
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+ assertThat(backStack?.asIterable()?.first()).isEqualTo(Scenes.Lockscreen)
+ assertThat(isAlternateBouncerVisible).isTrue()
+
+ // Trigger a fingerprint unlock.
+ kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+ assertThat(isUnlocked).isTrue()
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+ assertThat(backStack?.asIterable()?.first()).isEqualTo(Scenes.Gone)
+ assertThat(isAlternateBouncerVisible).isFalse()
+ }
+
private fun TestScope.emulateSceneTransition(
transitionStateFlow: MutableStateFlow<ObservableTransitionState>,
toScene: SceneKey,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
index a862fdf..778e822 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
@@ -19,6 +19,8 @@
import android.content.Context
import android.content.res.Configuration
import android.graphics.Rect
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.view.Display
import android.view.DisplayCutout
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -29,6 +31,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.leak.RotationUtils
import com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE
@@ -1051,7 +1054,8 @@
}
@Test
- fun onMaxBoundsChanged_beforeStart_listenerNotNotified() {
+ @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ fun onMaxBoundsChanged_beforeStart_flagEnabled_listenerNotNotified() {
// Start out with an existing configuration with bounds
configuration.windowConfiguration.setMaxBounds(0, 0, 100, 100)
configurationController.onConfigurationChanged(configuration)
@@ -1083,7 +1087,41 @@
}
@Test
- fun onDensityOrFontScaleChanged_beforeStart_listenerNotNotified() {
+ @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ fun onMaxBoundsChanged_beforeStart_flagDisabled_listenerNotNotified() {
+ // Start out with an existing configuration with bounds
+ configuration.windowConfiguration.setMaxBounds(0, 0, 100, 100)
+ configurationController.onConfigurationChanged(configuration)
+ val provider =
+ StatusBarContentInsetsProviderImpl(
+ contextMock,
+ configurationController,
+ mock<DumpManager>(),
+ mock<CommandRegistry>(),
+ mock<SysUICutoutProvider>(),
+ )
+ val listener =
+ object : StatusBarContentInsetsChangedListener {
+ var triggered = false
+
+ override fun onStatusBarContentInsetsChanged() {
+ triggered = true
+ }
+ }
+ provider.addCallback(listener)
+
+ // WHEN the config is updated with new bounds
+ // but provider is not started
+ configuration.windowConfiguration.setMaxBounds(0, 0, 456, 789)
+ configurationController.onConfigurationChanged(configuration)
+
+ // THEN the listener is notified
+ assertThat(listener.triggered).isTrue()
+ }
+
+ @Test
+ @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ fun onDensityOrFontScaleChanged_beforeStart_flagEnabled_listenerNotNotified() {
configuration.densityDpi = 12
val provider =
StatusBarContentInsetsProviderImpl(
@@ -1112,6 +1150,36 @@
}
@Test
+ @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ fun onDensityOrFontScaleChanged_beforeStart_flagDisabled_listenerNotified() {
+ configuration.densityDpi = 12
+ val provider =
+ StatusBarContentInsetsProviderImpl(
+ contextMock,
+ configurationController,
+ mock<DumpManager>(),
+ mock<CommandRegistry>(),
+ mock<SysUICutoutProvider>(),
+ )
+ val listener =
+ object : StatusBarContentInsetsChangedListener {
+ var triggered = false
+
+ override fun onStatusBarContentInsetsChanged() {
+ triggered = true
+ }
+ }
+ provider.addCallback(listener)
+
+ // WHEN the config is updated, but the provider is not started
+ configuration.densityDpi = 20
+ configurationController.onConfigurationChanged(configuration)
+
+ // THEN the listener is notified
+ assertThat(listener.triggered).isTrue()
+ }
+
+ @Test
fun onDensityOrFontScaleChanged_afterStart_listenerNotified() {
configuration.densityDpi = 12
val provider =
@@ -1169,7 +1237,8 @@
}
@Test
- fun onThemeChanged_beforeStart_listenerNotNotified() {
+ @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ fun onThemeChanged_beforeStart_flagEnabled_listenerNotNotified() {
val provider =
StatusBarContentInsetsProviderImpl(
contextMock,
@@ -1193,6 +1262,32 @@
assertThat(listener.triggered).isFalse()
}
+ @Test
+ @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ fun onThemeChanged_beforeStart_flagDisabled_listenerNotified() {
+ val provider =
+ StatusBarContentInsetsProviderImpl(
+ contextMock,
+ configurationController,
+ mock<DumpManager>(),
+ mock<CommandRegistry>(),
+ mock<SysUICutoutProvider>(),
+ )
+ val listener =
+ object : StatusBarContentInsetsChangedListener {
+ var triggered = false
+
+ override fun onStatusBarContentInsetsChanged() {
+ triggered = true
+ }
+ }
+ provider.addCallback(listener)
+
+ configurationController.notifyThemeChanged()
+
+ assertThat(listener.triggered).isTrue()
+ }
+
private fun assertRects(
expected: Rect,
actual: Rect,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewBinder.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewBinder.kt
index cdc7aa2..9888574 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewBinder.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewBinder.kt
@@ -32,6 +32,8 @@
override fun bind(
view: View,
viewModel: HomeStatusBarViewModel,
+ systemEventChipAnimateIn: ((View) -> Unit)?,
+ systemEventChipAnimateOut: ((View) -> Unit)?,
listener: StatusBarVisibilityChangeListener,
) {
this.listener = listener
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
index 02c1540..eef5753 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
@@ -19,6 +19,7 @@
import android.view.View
import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -53,11 +54,14 @@
)
)
- override val isSystemInfoVisible =
+ override val systemInfoCombinedVis =
MutableStateFlow(
- HomeStatusBarViewModel.VisibilityModel(
- visibility = View.GONE,
- shouldAnimateChange = false,
+ HomeStatusBarViewModel.SystemInfoCombinedVisibilityModel(
+ HomeStatusBarViewModel.VisibilityModel(
+ visibility = View.GONE,
+ shouldAnimateChange = false,
+ ),
+ Idle,
)
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
index b3a73d8..c4d2569 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
@@ -60,11 +60,16 @@
import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
+import com.android.systemui.statusbar.events.data.repository.systemStatusEventAnimationRepository
+import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.AnimatingIn
+import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.AnimatingOut
+import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle
import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.VisibilityModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.emptyFlow
@@ -90,6 +95,7 @@
private val activeNotificationListRepository = kosmos.activeNotificationListRepository
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
private val disableFlagsRepository = kosmos.fakeDisableFlagsRepository
+ private val systemStatusEventAnimationRepository = kosmos.systemStatusEventAnimationRepository
private lateinit var underTest: HomeStatusBarViewModel
@@ -546,25 +552,50 @@
@Test
fun isSystemInfoVisible_allowedByDisableFlags_visible() =
testScope.runTest {
- val latest by collectLastValue(underTest.isSystemInfoVisible)
+ val latest by collectLastValue(underTest.systemInfoCombinedVis)
transitionKeyguardToGone()
disableFlagsRepository.disableFlags.value =
DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE)
- assertThat(latest!!.visibility).isEqualTo(View.VISIBLE)
+ assertThat(latest!!.baseVisibility.visibility).isEqualTo(View.VISIBLE)
}
@Test
fun isSystemInfoVisible_notAllowedByDisableFlags_gone() =
testScope.runTest {
- val latest by collectLastValue(underTest.isSystemInfoVisible)
+ val latest by collectLastValue(underTest.systemInfoCombinedVis)
transitionKeyguardToGone()
disableFlagsRepository.disableFlags.value =
DisableFlagsModel(DISABLE_SYSTEM_INFO, DISABLE2_NONE)
- assertThat(latest!!.visibility).isEqualTo(View.GONE)
+ assertThat(latest!!.baseVisibility.visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun systemInfoCombineVis_animationsPassThrough() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.systemInfoCombinedVis)
+ transitionKeyguardToGone()
+
+ assertThat(latest!!.baseVisibility)
+ .isEqualTo(VisibilityModel(visibility = View.VISIBLE, shouldAnimateChange = false))
+ assertThat(latest!!.animationState).isEqualTo(Idle)
+
+ // WHEN the animation state changes, but the visibility state doesn't change
+ systemStatusEventAnimationRepository.animationState.value = AnimatingIn
+
+ // THEN the visibility is the same
+ assertThat(latest!!.baseVisibility)
+ .isEqualTo(VisibilityModel(visibility = View.VISIBLE, shouldAnimateChange = false))
+ // THEN the animation state updates
+ assertThat(latest!!.animationState).isEqualTo(AnimatingIn)
+
+ systemStatusEventAnimationRepository.animationState.value = AnimatingOut
+ assertThat(latest!!.baseVisibility)
+ .isEqualTo(VisibilityModel(visibility = View.VISIBLE, shouldAnimateChange = false))
+ assertThat(latest!!.animationState).isEqualTo(AnimatingOut)
}
@Test
@@ -573,7 +604,7 @@
testScope.runTest {
val clockVisible by collectLastValue(underTest.isClockVisible)
val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
- val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+ val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
keyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.GONE,
@@ -583,7 +614,7 @@
assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
- assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
+ assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
}
@Test
@@ -592,13 +623,13 @@
testScope.runTest {
val clockVisible by collectLastValue(underTest.isClockVisible)
val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
- val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+ val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
- assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
+ assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
}
@Test
@@ -607,7 +638,7 @@
testScope.runTest {
val clockVisible by collectLastValue(underTest.isClockVisible)
val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
- val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+ val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
keyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
@@ -617,7 +648,7 @@
assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
- assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
+ assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
}
@Test
@@ -626,13 +657,13 @@
testScope.runTest {
val clockVisible by collectLastValue(underTest.isClockVisible)
val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
- val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+ val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
kosmos.sceneContainerRepository.snapToScene(Scenes.Bouncer)
assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
- assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
+ assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
}
@Test
@@ -641,7 +672,7 @@
testScope.runTest {
val clockVisible by collectLastValue(underTest.isClockVisible)
val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
- val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+ val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
keyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
@@ -651,7 +682,7 @@
assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE)
assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE)
- assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE)
+ assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.VISIBLE)
}
@Test
@@ -660,14 +691,14 @@
testScope.runTest {
val clockVisible by collectLastValue(underTest.isClockVisible)
val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
- val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+ val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, taskInfo = null)
assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE)
assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE)
- assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE)
+ assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.VISIBLE)
}
@Test
@@ -676,13 +707,13 @@
testScope.runTest {
val clockVisible by collectLastValue(underTest.isClockVisible)
val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
- val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+ val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
transitionKeyguardToGone()
assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE)
assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE)
- assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE)
+ assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.VISIBLE)
}
@Test
@@ -691,14 +722,14 @@
testScope.runTest {
val clockVisible by collectLastValue(underTest.isClockVisible)
val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
- val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+ val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
transitionKeyguardToGone()
kosmos.shadeTestUtil.setShadeExpansion(0f)
assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE)
assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE)
- assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE)
+ assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.VISIBLE)
}
@Test
@@ -707,13 +738,13 @@
testScope.runTest {
val clockVisible by collectLastValue(underTest.isClockVisible)
val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
- val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+ val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
kosmos.sceneContainerRepository.snapToScene(Scenes.Gone)
assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE)
assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE)
- assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE)
+ assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.VISIBLE)
}
@Test
@@ -722,14 +753,14 @@
testScope.runTest {
val clockVisible by collectLastValue(underTest.isClockVisible)
val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
- val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+ val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
transitionKeyguardToGone()
kosmos.shadeTestUtil.setShadeExpansion(1f)
assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
- assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
+ assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
}
@Test
@@ -738,14 +769,14 @@
testScope.runTest {
val clockVisible by collectLastValue(underTest.isClockVisible)
val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
- val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+ val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
transitionKeyguardToGone()
kosmos.sceneContainerRepository.snapToScene(Scenes.Shade)
assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
- assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
+ assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
}
@Test
@@ -754,7 +785,7 @@
testScope.runTest {
val clockVisible by collectLastValue(underTest.isClockVisible)
val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
- val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+ val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
// Secure camera is an occluding activity
keyguardTransitionRepository.sendTransitionSteps(
@@ -766,7 +797,7 @@
assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
- assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
+ assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
}
@Test
@@ -775,7 +806,7 @@
testScope.runTest {
val clockVisible by collectLastValue(underTest.isClockVisible)
val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
- val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+ val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
// Secure camera is an occluding activity
@@ -784,7 +815,7 @@
assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
- assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
+ assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
}
private fun activeNotificationsStore(notifications: List<ActiveNotificationModel>) =
diff --git a/packages/SystemUI/pods/Android.bp b/packages/SystemUI/pods/Android.bp
new file mode 100644
index 0000000..e45f317
--- /dev/null
+++ b/packages/SystemUI/pods/Android.bp
@@ -0,0 +1,22 @@
+//
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+ // The package default_visibility specified here is inherited to subpackages that do not
+ // specify default_visibility:
+ default_visibility: ["//visibility:private"],
+}
diff --git a/packages/SystemUI/pods/com/android/systemui/dagger/Android.bp b/packages/SystemUI/pods/com/android/systemui/dagger/Android.bp
new file mode 100644
index 0000000..df90be8
--- /dev/null
+++ b/packages/SystemUI/pods/com/android/systemui/dagger/Android.bp
@@ -0,0 +1,34 @@
+//
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+soong_namespace {
+}
+
+package {
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+java_library {
+ name: "api",
+ srcs: [
+ "**/*.java",
+ "**/*.kt",
+ ],
+ libs: [
+ "jsr330",
+ ],
+ visibility: ["//frameworks/base/packages/SystemUI:__subpackages__"],
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUISingleton.java b/packages/SystemUI/pods/com/android/systemui/dagger/SysUISingleton.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/dagger/SysUISingleton.java
rename to packages/SystemUI/pods/com/android/systemui/dagger/SysUISingleton.java
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Application.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/Application.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Application.java
rename to packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/Application.java
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Background.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/Background.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Background.java
rename to packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/Background.java
diff --git a/packages/SystemUI/pods/com/android/systemui/retail/Android.bp b/packages/SystemUI/pods/com/android/systemui/retail/Android.bp
new file mode 100644
index 0000000..f047848
--- /dev/null
+++ b/packages/SystemUI/pods/com/android/systemui/retail/Android.bp
@@ -0,0 +1,38 @@
+//
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+soong_namespace {
+}
+
+package {
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+java_library {
+ name: "impl",
+ srcs: ["*.kt"],
+ libs: [
+ "jsr330",
+ "dagger2",
+ "SystemUICommon",
+ "kotlinx_coroutines",
+ ],
+ static_libs: [
+ "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail/data:impl",
+ "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail/domain:impl",
+ ],
+ visibility: ["//frameworks/base/packages/SystemUI"],
+}
diff --git a/packages/SystemUI/src/com/android/systemui/retail/dagger/RetailModeModule.kt b/packages/SystemUI/pods/com/android/systemui/retail/RetailModeModule.kt
similarity index 83%
rename from packages/SystemUI/src/com/android/systemui/retail/dagger/RetailModeModule.kt
rename to packages/SystemUI/pods/com/android/systemui/retail/RetailModeModule.kt
index e863949..c20e368 100644
--- a/packages/SystemUI/src/com/android/systemui/retail/dagger/RetailModeModule.kt
+++ b/packages/SystemUI/pods/com/android/systemui/retail/RetailModeModule.kt
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.retail.dagger
+package com.android.systemui.retail
import com.android.systemui.retail.data.repository.RetailModeRepository
-import com.android.systemui.retail.data.repository.RetailModeSettingsRepository
+import com.android.systemui.retail.data.repository.impl.RetailModeSettingsRepository
import com.android.systemui.retail.domain.interactor.RetailModeInteractor
-import com.android.systemui.retail.domain.interactor.RetailModeInteractorImpl
+import com.android.systemui.retail.domain.interactor.impl.RetailModeInteractorImpl
import dagger.Binds
import dagger.Module
diff --git a/packages/SystemUI/pods/com/android/systemui/retail/data/Android.bp b/packages/SystemUI/pods/com/android/systemui/retail/data/Android.bp
new file mode 100644
index 0000000..f148a7c
--- /dev/null
+++ b/packages/SystemUI/pods/com/android/systemui/retail/data/Android.bp
@@ -0,0 +1,55 @@
+//
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+soong_namespace {
+}
+
+package {
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+java_library {
+ name: "api",
+ srcs: ["repository/*.kt"],
+ libs: [
+ "kotlinx_coroutines",
+ ],
+ visibility: [
+ "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail",
+ "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail/dagger",
+ "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail/domain",
+ ],
+}
+
+java_library {
+ name: "impl",
+ srcs: ["repository/impl/*.kt"],
+ libs: [
+ "jsr330",
+ "kotlinx_coroutines",
+ "//frameworks/base/packages/SystemUI/pods/com/android/systemui/dagger:api",
+ "//frameworks/base/packages/SystemUI/pods/com/android/systemui/util/settings:api",
+ "SystemUICommon",
+ ],
+ static_libs: [
+ "api",
+ ],
+ visibility: [
+ "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail",
+ "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail/dagger",
+ "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail/domain",
+ ],
+}
diff --git a/packages/SystemUI/pods/com/android/systemui/retail/data/repository/RetailModeRepository.kt b/packages/SystemUI/pods/com/android/systemui/retail/data/repository/RetailModeRepository.kt
new file mode 100644
index 0000000..c9eac25
--- /dev/null
+++ b/packages/SystemUI/pods/com/android/systemui/retail/data/repository/RetailModeRepository.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.retail.data.repository
+
+import kotlinx.coroutines.flow.StateFlow
+
+/** Repository to track if the device is in Retail mode */
+interface RetailModeRepository {
+ /** Flow of whether the device is currently in retail mode. */
+ val retailMode: StateFlow<Boolean>
+
+ /** Last value of whether the device is in retail mode. */
+ val inRetailMode: Boolean
+ get() = retailMode.value
+}
diff --git a/packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt b/packages/SystemUI/pods/com/android/systemui/retail/data/repository/impl/RetailModeSettingsRepository.kt
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt
rename to packages/SystemUI/pods/com/android/systemui/retail/data/repository/impl/RetailModeSettingsRepository.kt
index 09fd7df..8955263 100644
--- a/packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt
+++ b/packages/SystemUI/pods/com/android/systemui/retail/data/repository/impl/RetailModeSettingsRepository.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.retail.data.repository
+package com.android.systemui.retail.data.repository.impl
import android.database.ContentObserver
import android.provider.Settings
@@ -22,6 +22,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.retail.data.repository.RetailModeRepository
import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -34,16 +35,6 @@
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
-/** Repository to track if the device is in Retail mode */
-interface RetailModeRepository {
- /** Flow of whether the device is currently in retail mode. */
- val retailMode: StateFlow<Boolean>
-
- /** Last value of whether the device is in retail mode. */
- val inRetailMode: Boolean
- get() = retailMode.value
-}
-
/**
* Tracks [Settings.Global.DEVICE_DEMO_MODE].
*
diff --git a/packages/SystemUI/pods/com/android/systemui/retail/domain/Android.bp b/packages/SystemUI/pods/com/android/systemui/retail/domain/Android.bp
new file mode 100644
index 0000000..787861c
--- /dev/null
+++ b/packages/SystemUI/pods/com/android/systemui/retail/domain/Android.bp
@@ -0,0 +1,46 @@
+//
+// 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.
+//
+
+soong_namespace {
+}
+
+package {
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+java_library {
+ name: "api",
+ srcs: ["interactor/*.kt"],
+ visibility: [
+ "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail",
+ ],
+}
+
+java_library {
+ name: "impl",
+ srcs: ["interactor/impl/*.kt"],
+ libs: [
+ "jsr330",
+ "//frameworks/base/packages/SystemUI/pods/com/android/systemui/dagger:api",
+ "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail/data:api",
+ ],
+ static_libs: [
+ "api",
+ ],
+ visibility: [
+ "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail",
+ ],
+}
diff --git a/packages/SystemUI/src/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt b/packages/SystemUI/pods/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt
similarity index 65%
copy from packages/SystemUI/src/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt
copy to packages/SystemUI/pods/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt
index eea452c..748e34d 100644
--- a/packages/SystemUI/src/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt
+++ b/packages/SystemUI/pods/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,22 +16,8 @@
package com.android.systemui.retail.domain.interactor
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.retail.data.repository.RetailModeRepository
-import javax.inject.Inject
-
/** Interactor to determine if the device is currently in retail mode */
interface RetailModeInteractor {
/** Whether the device is currently in retail mode */
val isInRetailMode: Boolean
}
-
-@SysUISingleton
-class RetailModeInteractorImpl
-@Inject
-constructor(
- private val repository: RetailModeRepository,
-) : RetailModeInteractor {
- override val isInRetailMode: Boolean
- get() = repository.inRetailMode
-}
diff --git a/packages/SystemUI/src/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt b/packages/SystemUI/pods/com/android/systemui/retail/domain/interactor/impl/RetailModeInteractorImpl.kt
similarity index 79%
rename from packages/SystemUI/src/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt
rename to packages/SystemUI/pods/com/android/systemui/retail/domain/interactor/impl/RetailModeInteractorImpl.kt
index eea452c..8dbe562 100644
--- a/packages/SystemUI/src/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt
+++ b/packages/SystemUI/pods/com/android/systemui/retail/domain/interactor/impl/RetailModeInteractorImpl.kt
@@ -14,18 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.retail.domain.interactor
+package com.android.systemui.retail.domain.interactor.impl
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.retail.data.repository.RetailModeRepository
+import com.android.systemui.retail.domain.interactor.RetailModeInteractor
import javax.inject.Inject
-/** Interactor to determine if the device is currently in retail mode */
-interface RetailModeInteractor {
- /** Whether the device is currently in retail mode */
- val isInRetailMode: Boolean
-}
-
@SysUISingleton
class RetailModeInteractorImpl
@Inject
diff --git a/packages/SystemUI/pods/com/android/systemui/util/settings/Android.bp b/packages/SystemUI/pods/com/android/systemui/util/settings/Android.bp
new file mode 100644
index 0000000..1aa7729
--- /dev/null
+++ b/packages/SystemUI/pods/com/android/systemui/util/settings/Android.bp
@@ -0,0 +1,40 @@
+//
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+soong_namespace {
+}
+
+package {
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+java_library {
+ name: "api",
+ srcs: [
+ "*.java",
+ "*.kt",
+ ],
+ libs: [
+ "//frameworks/libs/systemui:tracinglib-platform",
+ "//frameworks/base/packages/SystemUI/pods/com/android/systemui/dagger:api",
+ "SystemUICommon",
+ "androidx.annotation_annotation",
+ "kotlinx_coroutines_android",
+ "jsr330",
+ ],
+ kotlincflags: ["-Xjvm-default=all"],
+ visibility: ["//frameworks/base/packages/SystemUI:__subpackages__"],
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettings.java b/packages/SystemUI/pods/com/android/systemui/util/settings/GlobalSettings.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettings.java
rename to packages/SystemUI/pods/com/android/systemui/util/settings/GlobalSettings.java
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java b/packages/SystemUI/pods/com/android/systemui/util/settings/GlobalSettingsImpl.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
rename to packages/SystemUI/pods/com/android/systemui/util/settings/GlobalSettingsImpl.java
index 7fcabe4..d68501f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
+++ b/packages/SystemUI/pods/com/android/systemui/util/settings/GlobalSettingsImpl.java
@@ -23,7 +23,7 @@
import android.net.Uri;
import android.provider.Settings;
-import com.android.systemui.util.kotlin.SettingsSingleThreadBackground;
+import com.android.systemui.util.settings.SettingsSingleThreadBackground;
import kotlinx.coroutines.CoroutineDispatcher;
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettings.java b/packages/SystemUI/pods/com/android/systemui/util/settings/SecureSettings.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/util/settings/SecureSettings.java
rename to packages/SystemUI/pods/com/android/systemui/util/settings/SecureSettings.java
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java b/packages/SystemUI/pods/com/android/systemui/util/settings/SecureSettingsImpl.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
rename to packages/SystemUI/pods/com/android/systemui/util/settings/SecureSettingsImpl.java
index c296481..211a6f4 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
+++ b/packages/SystemUI/pods/com/android/systemui/util/settings/SecureSettingsImpl.java
@@ -21,7 +21,7 @@
import android.net.Uri;
import android.provider.Settings;
-import com.android.systemui.util.kotlin.SettingsSingleThreadBackground;
+import com.android.systemui.util.settings.SettingsSingleThreadBackground;
import kotlinx.coroutines.CoroutineDispatcher;
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt b/packages/SystemUI/pods/com/android/systemui/util/settings/SettingsProxy.kt
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
rename to packages/SystemUI/pods/com/android/systemui/util/settings/SettingsProxy.kt
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt b/packages/SystemUI/pods/com/android/systemui/util/settings/SettingsProxyExt.kt
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
rename to packages/SystemUI/pods/com/android/systemui/util/settings/SettingsProxyExt.kt
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SettingsSingleThreadBackground.java b/packages/SystemUI/pods/com/android/systemui/util/settings/SettingsSingleThreadBackground.java
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/util/kotlin/SettingsSingleThreadBackground.java
rename to packages/SystemUI/pods/com/android/systemui/util/settings/SettingsSingleThreadBackground.java
index e13981d..5632a36 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/SettingsSingleThreadBackground.java
+++ b/packages/SystemUI/pods/com/android/systemui/util/settings/SettingsSingleThreadBackground.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.util.kotlin;
+package com.android.systemui.util.settings;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettings.java b/packages/SystemUI/pods/com/android/systemui/util/settings/SystemSettings.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/util/settings/SystemSettings.java
rename to packages/SystemUI/pods/com/android/systemui/util/settings/SystemSettings.java
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java b/packages/SystemUI/pods/com/android/systemui/util/settings/SystemSettingsImpl.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
rename to packages/SystemUI/pods/com/android/systemui/util/settings/SystemSettingsImpl.java
index e670b2c..1b3f74e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
+++ b/packages/SystemUI/pods/com/android/systemui/util/settings/SystemSettingsImpl.java
@@ -21,7 +21,7 @@
import android.net.Uri;
import android.provider.Settings;
-import com.android.systemui.util.kotlin.SettingsSingleThreadBackground;
+import com.android.systemui.util.settings.SettingsSingleThreadBackground;
import kotlinx.coroutines.CoroutineDispatcher;
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt b/packages/SystemUI/pods/com/android/systemui/util/settings/UserSettingsProxy.kt
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
rename to packages/SystemUI/pods/com/android/systemui/util/settings/UserSettingsProxy.kt
diff --git a/packages/SystemUI/res/drawable/hearing_devices_preset_spinner_background.xml b/packages/SystemUI/res/drawable/hearing_devices_spinner_background.xml
similarity index 94%
rename from packages/SystemUI/res/drawable/hearing_devices_preset_spinner_background.xml
rename to packages/SystemUI/res/drawable/hearing_devices_spinner_background.xml
index c83b6d3..dfefb9d 100644
--- a/packages/SystemUI/res/drawable/hearing_devices_preset_spinner_background.xml
+++ b/packages/SystemUI/res/drawable/hearing_devices_spinner_background.xml
@@ -14,7 +14,8 @@
limitations under the License.
-->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+<layer-list
+ xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:paddingMode="stack">
<item>
@@ -30,8 +31,8 @@
android:end="20dp"
android:gravity="end|center_vertical">
<vector
- android:width="@dimen/hearing_devices_preset_spinner_arrow_size"
- android:height="@dimen/hearing_devices_preset_spinner_arrow_size"
+ android:width="@dimen/hearing_devices_preset_spinner_icon_size"
+ android:height="@dimen/hearing_devices_preset_spinner_icon_size"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?androidprv:attr/colorControlNormal">
diff --git a/packages/SystemUI/res/drawable/hearing_devices_preset_spinner_popup_background.xml b/packages/SystemUI/res/drawable/hearing_devices_spinner_popup_background.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/hearing_devices_preset_spinner_popup_background.xml
rename to packages/SystemUI/res/drawable/hearing_devices_spinner_popup_background.xml
diff --git a/packages/SystemUI/res/drawable/hearing_devices_spinner_selected_background.xml b/packages/SystemUI/res/drawable/hearing_devices_spinner_selected_background.xml
new file mode 100644
index 0000000..c708d22
--- /dev/null
+++ b/packages/SystemUI/res/drawable/hearing_devices_spinner_selected_background.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/settingslib_switch_bar_bg_on"
+ android:insetTop="8dp"
+ android:insetBottom="8dp"
+ android:insetLeft="11dp"
+ android:insetRight="11dp" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_check.xml b/packages/SystemUI/res/drawable/ic_check.xml
new file mode 100644
index 0000000..80707d8
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_check.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+<vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <path
+ android:pathData="M0 0h24v24H0z"/>
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/bluetooth_device_item.xml b/packages/SystemUI/res/layout/bluetooth_device_item.xml
index b9314c7..124aec6a 100644
--- a/packages/SystemUI/res/layout/bluetooth_device_item.xml
+++ b/packages/SystemUI/res/layout/bluetooth_device_item.xml
@@ -27,8 +27,8 @@
<ImageView
android:id="@+id/bluetooth_device_icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
+ android:layout_width="36dp"
+ android:layout_height="36dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
@@ -36,8 +36,8 @@
<TextView
android:layout_width="0dp"
+ android:layout_height="wrap_content"
android:id="@+id/bluetooth_device_name"
- style="@style/BluetoothTileDialog.DeviceName"
android:textDirection="locale"
android:textAlignment="gravity"
android:paddingStart="20dp"
@@ -54,8 +54,8 @@
<TextView
android:layout_width="0dp"
+ android:layout_height="wrap_content"
android:id="@+id/bluetooth_device_summary"
- style="@style/BluetoothTileDialog.DeviceSummary"
android:paddingStart="20dp"
android:paddingEnd="10dp"
android:paddingBottom="15dp"
@@ -65,7 +65,8 @@
app:layout_constraintStart_toEndOf="@+id/bluetooth_device_icon"
app:layout_constraintEnd_toStartOf="@+id/guideline"
app:layout_constraintBottom_toBottomOf="parent"
- android:gravity="center_vertical" />
+ android:gravity="center_vertical"
+ android:textSize="14sp" />
<androidx.constraintlayout.widget.Guideline
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
index b4eaa40..1f93717 100644
--- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
@@ -32,7 +32,7 @@
android:ellipsize="end"
android:gravity="center_vertical|center_horizontal"
android:text="@string/quick_settings_bluetooth_label"
- android:textAppearance="@style/TextAppearance.Dialog.Title"
+ android:textAppearance="@style/TextAppearance.BluetoothTileDialog"
android:textSize="24sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -49,7 +49,8 @@
android:gravity="center_vertical|center_horizontal"
android:maxLines="2"
android:text="@string/quick_settings_bluetooth_tile_subtitle"
- android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+ android:textSize="14sp"
+ android:textAppearance="@style/TextAppearance.BluetoothTileDialog"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_title" />
@@ -105,7 +106,7 @@
android:paddingStart="36dp"
android:text="@string/turn_on_bluetooth"
android:clickable="false"
- android:textAppearance="@style/TextAppearance.Dialog.Title"
+ android:textAppearance="@style/TextAppearance.BluetoothTileDialog"
android:textSize="16sp"
app:layout_constraintEnd_toStartOf="@+id/bluetooth_toggle"
app:layout_constraintStart_toStartOf="parent"
@@ -146,7 +147,7 @@
android:paddingEnd="15dp"
android:paddingStart="36dp"
android:clickable="false"
- android:textAppearance="@style/TextAppearance.Dialog.Title"
+ android:textAppearance="@style/TextAppearance.BluetoothTileDialog"
android:textSize="16sp"
app:layout_constraintEnd_toStartOf="@+id/bluetooth_auto_on_toggle"
app:layout_constraintStart_toStartOf="parent"
@@ -187,7 +188,8 @@
android:layout_marginTop="20dp"
android:paddingStart="36dp"
android:paddingEnd="40dp"
- android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+ android:textSize="14sp"
+ android:textAppearance="@style/TextAppearance.BluetoothTileDialog"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/bluetooth_auto_on_toggle_info_icon" />
@@ -204,7 +206,7 @@
android:id="@+id/see_all_button"
style="@style/BluetoothTileDialog.Device"
android:paddingEnd="0dp"
- android:paddingStart="20dp"
+ android:paddingStart="26dp"
android:background="@drawable/bluetooth_tile_dialog_bg_off"
android:layout_width="0dp"
android:layout_height="64dp"
@@ -214,11 +216,11 @@
app:layout_constraintTop_toBottomOf="@+id/device_list"
app:layout_constraintBottom_toTopOf="@+id/pair_new_device_button"
android:drawableStart="@drawable/ic_arrow_forward"
- android:drawablePadding="20dp"
+ android:drawablePadding="26dp"
android:drawableTint="?android:attr/textColorPrimary"
android:text="@string/see_all_bluetooth_devices"
android:textSize="14sp"
- android:textAppearance="@style/TextAppearance.Dialog.Title"
+ android:textAppearance="@style/TextAppearance.BluetoothTileDialog"
android:textDirection="locale"
android:textAlignment="viewStart"
android:maxLines="1"
@@ -229,7 +231,7 @@
android:id="@+id/pair_new_device_button"
style="@style/BluetoothTileDialog.Device"
android:paddingEnd="0dp"
- android:paddingStart="20dp"
+ android:paddingStart="26dp"
android:background="@drawable/bluetooth_tile_dialog_bg_off"
android:layout_width="0dp"
android:layout_height="64dp"
@@ -238,11 +240,11 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/see_all_button"
android:drawableStart="@drawable/ic_add"
- android:drawablePadding="20dp"
+ android:drawablePadding="26dp"
android:drawableTint="?android:attr/textColorPrimary"
android:text="@string/pair_new_bluetooth_devices"
android:textSize="14sp"
- android:textAppearance="@style/TextAppearance.Dialog.Title"
+ android:textAppearance="@style/TextAppearance.BluetoothTileDialog"
android:textDirection="locale"
android:textAlignment="viewStart"
android:maxLines="1"
@@ -259,6 +261,7 @@
<Button
android:id="@+id/audio_sharing_button"
style="@style/BluetoothTileDialog.AudioSharingButton"
+ android:textAppearance="@style/TextAppearance.BluetoothTileDialog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="9dp"
@@ -282,6 +285,7 @@
<Button
android:id="@+id/done_button"
style="@style/Widget.Dialog.Button"
+ android:textAppearance="@style/TextAppearance.BluetoothTileDialog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="9dp"
diff --git a/packages/SystemUI/res/layout/hearing_devices_preset_dropdown_item.xml b/packages/SystemUI/res/layout/hearing_devices_preset_dropdown_item.xml
deleted file mode 100644
index 17c0222..0000000
--- a/packages/SystemUI/res/layout/hearing_devices_preset_dropdown_item.xml
+++ /dev/null
@@ -1,26 +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.
--->
-
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/hearing_devices_preset_option_text"
- style="?android:attr/spinnerDropDownItemStyle"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="@dimen/hearing_devices_preset_spinner_height"
- android:paddingStart="@dimen/hearing_devices_preset_spinner_text_padding_start"
- android:gravity="center_vertical"
- android:textDirection="locale"
- android:ellipsize="end" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/hearing_devices_preset_spinner_selected.xml b/packages/SystemUI/res/layout/hearing_devices_preset_spinner_selected.xml
deleted file mode 100644
index d512e7c..0000000
--- a/packages/SystemUI/res/layout/hearing_devices_preset_spinner_selected.xml
+++ /dev/null
@@ -1,48 +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.
--->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="@dimen/hearing_devices_preset_spinner_height"
- android:paddingStart="@dimen/hearing_devices_preset_spinner_text_padding_start"
- android:paddingTop="@dimen/hearing_devices_preset_spinner_text_padding_vertical"
- android:paddingBottom="@dimen/hearing_devices_preset_spinner_text_padding_vertical"
- android:orientation="vertical">
- <TextView
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:textAppearance="@style/TextAppearance.Dialog.Title"
- android:lineSpacingExtra="6dp"
- android:text="@string/hearing_devices_preset_label"
- android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
- android:textSize="14sp"
- android:gravity="center_vertical"
- android:textDirection="locale"
- android:layout_weight="1" />
- <TextView
- android:id="@+id/hearing_devices_preset_option_text"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:textAppearance="@style/TextAppearance.Dialog.Body"
- android:lineSpacingExtra="6dp"
- android:gravity="center_vertical"
- android:ellipsize="end"
- android:maxLines="1"
- android:textDirection="locale"
- android:layout_weight="1" />
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/hearing_devices_spinner_dropdown_view.xml b/packages/SystemUI/res/layout/hearing_devices_spinner_dropdown_view.xml
new file mode 100644
index 0000000..70f2cd5
--- /dev/null
+++ b/packages/SystemUI/res/layout/hearing_devices_spinner_dropdown_view.xml
@@ -0,0 +1,45 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/bluetooth_dialog_device_height"
+ android:paddingStart="@dimen/hearing_devices_preset_spinner_padding"
+ android:paddingEnd="@dimen/hearing_devices_preset_spinner_padding"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/hearing_devices_spinner_check_icon"
+ android:layout_width="@dimen/hearing_devices_preset_spinner_icon_size"
+ android:layout_height="@dimen/hearing_devices_preset_spinner_icon_size"
+ android:layout_gravity="center_vertical"
+ android:layout_marginEnd="@dimen/hearing_devices_layout_margin"
+ android:tint="?androidprv:attr/materialColorOnPrimaryContainer"
+ android:src="@drawable/ic_check"
+ android:contentDescription="@string/hearing_devices_spinner_item_selected"/>
+ <TextView
+ android:id="@+id/hearing_devices_spinner_text"
+ style="?android:attr/spinnerDropDownItemStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:textDirection="locale"
+ android:paddingStart="0dp"
+ android:maxLines="1"
+ android:ellipsize="end" />
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/hearing_devices_spinner_view.xml b/packages/SystemUI/res/layout/hearing_devices_spinner_view.xml
new file mode 100644
index 0000000..7574244
--- /dev/null
+++ b/packages/SystemUI/res/layout/hearing_devices_spinner_view.xml
@@ -0,0 +1,32 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/bluetooth_dialog_device_height"
+ android:paddingStart="@dimen/hearing_devices_preset_spinner_padding"
+ android:paddingEnd="@dimen/hearing_devices_preset_spinner_padding">
+ <TextView
+ android:id="@+id/hearing_devices_spinner_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.Dialog.Body"
+ android:layout_gravity="center_vertical"
+ android:ellipsize="end"
+ android:maxLines="1"
+ android:textDirection="locale" />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml b/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml
index 80692f9..bf04a6f 100644
--- a/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml
@@ -28,50 +28,16 @@
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintBottom_toTopOf="@id/preset_spinner" />
-
- <Spinner
- android:id="@+id/preset_spinner"
- style="@style/BluetoothTileDialog.Device"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/hearing_devices_layout_margin"
- android:minHeight="@dimen/hearing_devices_preset_spinner_height"
- android:gravity="center_vertical"
- android:background="@drawable/hearing_devices_preset_spinner_background"
- android:popupBackground="@drawable/hearing_devices_preset_spinner_popup_background"
- android:dropDownVerticalOffset="@dimen/hearing_devices_preset_spinner_height"
- android:dropDownWidth="match_parent"
- android:paddingStart="0dp"
- android:paddingEnd="0dp"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toBottomOf="@id/device_list"
- app:layout_constraintBottom_toTopOf="@id/pair_new_device_button"
- android:longClickable="false"
- android:visibility="gone"/>
-
- <androidx.constraintlayout.widget.Barrier
- android:id="@+id/device_function_barrier"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- app:barrierAllowsGoneWidgets="false"
- app:barrierDirection="bottom"
- app:constraint_referenced_ids="device_list,preset_spinner" />
+ app:layout_constraintEnd_toEndOf="parent" />
<Button
android:id="@+id/pair_new_device_button"
style="@style/BluetoothTileDialog.Device"
- android:paddingEnd="0dp"
- android:paddingStart="20dp"
- android:background="@drawable/bluetooth_tile_dialog_bg_off"
- android:layout_width="0dp"
- android:layout_height="64dp"
- android:contentDescription="@string/accessibility_hearing_device_pair_new_device"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toBottomOf="@id/device_function_barrier"
+ app:layout_constraintTop_toBottomOf="@id/device_list"
+ android:layout_height="@dimen/bluetooth_dialog_device_height"
+ android:contentDescription="@string/accessibility_hearing_device_pair_new_device"
android:drawableStart="@drawable/ic_add"
android:drawablePadding="20dp"
android:drawableTint="?android:attr/textColorPrimary"
@@ -81,26 +47,75 @@
android:textDirection="locale"
android:textAlignment="viewStart"
android:maxLines="1"
- android:ellipsize="end" />
-
- <androidx.constraintlayout.widget.Barrier
- android:id="@+id/device_barrier"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- app:barrierAllowsGoneWidgets="false"
- app:barrierDirection="bottom"
- app:constraint_referenced_ids="device_function_barrier, pair_new_device_button" />
+ android:ellipsize="end"
+ android:background="@drawable/bluetooth_tile_dialog_bg_off" />
<LinearLayout
- android:id="@+id/related_tools_container"
+ android:id="@+id/preset_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/bluetooth_dialog_layout_margin"
- android:layout_marginEnd="@dimen/bluetooth_dialog_layout_margin"
- android:layout_marginTop="@dimen/hearing_devices_layout_margin"
- android:orientation="horizontal"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toBottomOf="@id/device_barrier" />
+ app:layout_constraintTop_toBottomOf="@id/pair_new_device_button"
+ android:layout_marginTop="@dimen/hearing_devices_layout_margin"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/preset_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/bluetooth_dialog_layout_margin"
+ android:layout_marginEnd="@dimen/bluetooth_dialog_layout_margin"
+ android:paddingStart="@dimen/hearing_devices_small_title_padding_horizontal"
+ android:text="@string/hearing_devices_preset_label"
+ android:textAppearance="@style/TextAppearance.Dialog.Title"
+ android:textSize="14sp"
+ android:gravity="center_vertical"
+ android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+ android:textDirection="locale"/>
+ <Spinner
+ android:id="@+id/preset_spinner"
+ style="@style/BluetoothTileDialog.Device"
+ android:layout_height="@dimen/bluetooth_dialog_device_height"
+ android:layout_marginTop="4dp"
+ android:paddingStart="0dp"
+ android:paddingEnd="0dp"
+ android:background="@drawable/hearing_devices_spinner_background"
+ android:popupBackground="@drawable/hearing_devices_spinner_popup_background"
+ android:dropDownWidth="match_parent"
+ android:longClickable="false"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/tools_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/preset_layout"
+ android:layout_marginTop="@dimen/hearing_devices_layout_margin"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/tools_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/bluetooth_dialog_layout_margin"
+ android:layout_marginEnd="@dimen/bluetooth_dialog_layout_margin"
+ android:paddingStart="@dimen/hearing_devices_small_title_padding_horizontal"
+ android:text="@string/hearing_devices_tools_label"
+ android:textAppearance="@style/TextAppearance.Dialog.Title"
+ android:textSize="14sp"
+ android:gravity="center_vertical"
+ android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+ android:textDirection="locale"/>
+ <LinearLayout
+ android:id="@+id/tools_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/bluetooth_dialog_layout_margin"
+ android:layout_marginEnd="@dimen/bluetooth_dialog_layout_margin"
+ android:layout_marginTop="4dp"
+ android:orientation="horizontal"/>
+ </LinearLayout>
+
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/hearing_tool_item.xml b/packages/SystemUI/res/layout/hearing_tool_item.xml
index f5baf2a..da9178b 100644
--- a/packages/SystemUI/res/layout/hearing_tool_item.xml
+++ b/packages/SystemUI/res/layout/hearing_tool_item.xml
@@ -46,7 +46,7 @@
android:textAlignment="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/hearing_devices_layout_margin"
+ android:layout_marginTop="4dp"
android:ellipsize="end"
android:textSize="12sp"
android:maxLines="3"
diff --git a/packages/SystemUI/res/layout/split_clock_view.xml b/packages/SystemUI/res/layout/split_clock_view.xml
deleted file mode 100644
index 8198f03..0000000
--- a/packages/SystemUI/res/layout/split_clock_view.xml
+++ /dev/null
@@ -1,54 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2014 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
- -->
-
-<!-- Extends LinearLayout -->
-<com.android.systemui.statusbar.policy.SplitClockView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- >
- <TextClock
- android:id="@+id/time_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
- android:textSize="@dimen/qs_time_collapsed_size"
- android:textColor="?android:attr/textColorPrimary"
- />
- <TextClock
- android:id="@+id/am_pm_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
- android:textSize="@dimen/qs_time_collapsed_size"
- android:textColor="?android:attr/textColorPrimary"
- android:importantForAccessibility="no"
- />
-
- <!-- Empty text view so we have the same height when expanded/collapsed-->
- <TextView
- android:id="@+id/empty_time_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:visibility="gone"
- android:singleLine="true"
- android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
- android:textColor="?android:attr/textColorPrimary"
- />
-</com.android.systemui.statusbar.policy.SplitClockView>
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index e90f055f..f187ce6 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -13,50 +13,58 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:id="@+id/volume_dialog_container"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="right"
- android:divider="@drawable/volume_dialog_floating_sliders_spacer"
- android:orientation="horizontal"
- android:showDividers="middle|end|beginning"
- android:theme="@style/volume_dialog_theme">
+ android:id="@+id/volume_dialog_root"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
<LinearLayout
- android:id="@+id/volume_dialog_floating_sliders_container"
+ android:id="@+id/volume_dialog_container"
android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:divider="@drawable/volume_dialog_floating_sliders_spacer"
- android:gravity="bottom"
- android:orientation="horizontal"
- android:paddingBottom="@dimen/volume_dialog_floating_sliders_bottom_padding"
- android:showDividers="middle" />
-
- <LinearLayout
- android:id="@+id/volume_dialog"
- android:layout_width="@dimen/volume_dialog_width"
android:layout_height="wrap_content"
- android:background="@drawable/volume_dialog_background"
- android:divider="@drawable/volume_dialog_spacer"
- android:paddingVertical="@dimen/volume_dialog_vertical_padding"
- android:gravity="center_horizontal"
- android:orientation="vertical"
- android:showDividers="middle">
+ android:layout_gravity="center_vertical|end"
+ android:divider="@drawable/volume_dialog_floating_sliders_spacer"
+ android:orientation="horizontal"
+ android:showDividers="middle|end|beginning">
- <include layout="@layout/volume_ringer_drawer" />
+ <LinearLayout
+ android:id="@+id/volume_dialog_floating_sliders_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:divider="@drawable/volume_dialog_floating_sliders_spacer"
+ android:gravity="bottom"
+ android:orientation="horizontal"
+ android:paddingBottom="@dimen/volume_dialog_floating_sliders_bottom_padding"
+ android:showDividers="middle" />
- <include layout="@layout/volume_dialog_slider" />
+ <LinearLayout
+ android:id="@+id/volume_dialog"
+ android:layout_width="@dimen/volume_dialog_width"
+ android:layout_height="wrap_content"
+ android:background="@drawable/volume_dialog_background"
+ android:clipChildren="false"
+ android:clipToOutline="false"
+ android:clipToPadding="false"
+ android:divider="@drawable/volume_dialog_spacer"
+ android:gravity="center_horizontal"
+ android:orientation="vertical"
+ android:paddingVertical="@dimen/volume_dialog_vertical_padding"
+ android:showDividers="middle">
- <ImageButton
- android:id="@+id/volume_dialog_settings"
- android:layout_width="@dimen/volume_dialog_button_size"
- android:layout_height="@dimen/volume_dialog_button_size"
- android:background="@drawable/ripple_drawable_20dp"
- android:contentDescription="@string/accessibility_volume_settings"
- android:soundEffectsEnabled="false"
- android:src="@drawable/horizontal_ellipsis"
- android:tint="?androidprv:attr/materialColorPrimary" />
+ <include layout="@layout/volume_ringer_drawer" />
+
+ <include layout="@layout/volume_dialog_slider" />
+
+ <ImageButton
+ android:id="@+id/volume_dialog_settings"
+ android:layout_width="@dimen/volume_dialog_button_size"
+ android:layout_height="@dimen/volume_dialog_button_size"
+ android:background="@drawable/ripple_drawable_20dp"
+ android:contentDescription="@string/accessibility_volume_settings"
+ android:soundEffectsEnabled="false"
+ android:src="@drawable/horizontal_ellipsis"
+ android:tint="?androidprv:attr/materialColorPrimary" />
+ </LinearLayout>
</LinearLayout>
-</LinearLayout>
\ No newline at end of file
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 67eb5b0..7af0057 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1805,10 +1805,9 @@
<!-- Hearing devices dialog related dimensions -->
<dimen name="hearing_devices_layout_margin">12dp</dimen>
- <dimen name="hearing_devices_preset_spinner_height">72dp</dimen>
- <dimen name="hearing_devices_preset_spinner_text_padding_start">20dp</dimen>
- <dimen name="hearing_devices_preset_spinner_text_padding_vertical">15dp</dimen>
- <dimen name="hearing_devices_preset_spinner_arrow_size">24dp</dimen>
+ <dimen name="hearing_devices_small_title_padding_horizontal">16dp</dimen>
+ <dimen name="hearing_devices_preset_spinner_padding">22dp</dimen>
+ <dimen name="hearing_devices_preset_spinner_icon_size">24dp</dimen>
<dimen name="hearing_devices_preset_spinner_background_radius">28dp</dimen>
<dimen name="hearing_devices_tool_icon_size">28dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 53ab686..b45aadd 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1002,6 +1002,10 @@
<string name="hearing_devices_presets_error">Couldn\'t update preset</string>
<!-- QuickSettings: Title for hearing aids presets. Preset is a set of hearing aid settings. User can apply different settings in different environments (e.g. Outdoor, Restaurant, Home) [CHAR LIMIT=40]-->
<string name="hearing_devices_preset_label">Preset</string>
+ <!-- QuickSettings: Content description for the icon that indicates the item is selected [CHAR LIMIT=NONE]-->
+ <string name="hearing_devices_spinner_item_selected">Selected</string>
+ <!-- QuickSettings: Title for related tools of hearing. [CHAR LIMIT=40]-->
+ <string name="hearing_devices_tools_label">Tools</string>
<!-- QuickSettings: Tool name for hearing devices dialog related tools [CHAR LIMIT=40] [BACKUP_MESSAGE_ID=8916875614623730005]-->
<string name="quick_settings_hearing_devices_live_caption_title">Live Caption</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 7d071cd..e14008a 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -557,6 +557,16 @@
<item name="android:showWhenLocked">true</item>
</style>
+ <style name="Theme.SystemUI.Dialog.Volume">
+ <item name="android:backgroundDimEnabled">false</item>
+ <item name="android:showWhenLocked">true</item>
+ <item name="android:windowBackground">@color/transparent</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:windowFullscreen">true</item>
+ <item name="android:windowIsFloating">false</item>
+ <item name="android:windowNoTitle">true</item>
+ </style>
+
<style name="SystemUI.Material3.Slider.Volume">
<item name="trackHeight">40dp</item>
<item name="thumbHeight">52dp</item>
@@ -1471,28 +1481,14 @@
<item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
</style>
- <style name="BluetoothTileDialog.Device.Active">
- <item name="android:textColor">?androidprv:attr/materialColorOnPrimaryContainer</item>
- </style>
-
- <style name="BluetoothTileDialog.DeviceName">
- <item name="android:textSize">14sp</item>
- <item name="android:textAppearance">@style/TextAppearance.Dialog.Title</item>
+ <style name="TextAppearance.BluetoothTileDialog">
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ <item name="android:textDirection">locale</item>
+ <item name="android:textAlignment">gravity</item>
<item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
</style>
- <style name="BluetoothTileDialog.DeviceSummary">
- <item name="android:ellipsize">end</item>
- <item name="android:maxLines">2</item>
- <item name="android:textAppearance">@style/TextAppearance.Dialog.Body.Message</item>
- <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item>
- </style>
-
- <style name="BluetoothTileDialog.DeviceName.Active">
- <item name="android:textColor">?androidprv:attr/materialColorOnPrimaryContainer</item>
- </style>
-
- <style name="BluetoothTileDialog.DeviceSummary.Active">
+ <style name="TextAppearance.BluetoothTileDialog.Active">
<item name="android:textColor">?androidprv:attr/materialColorOnPrimaryContainer</item>
</style>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 1342dd0..95830b5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -15,6 +15,8 @@
*/
package com.android.keyguard;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+
import android.annotation.NonNull;
import android.app.Presentation;
import android.content.Context;
@@ -36,18 +38,24 @@
import androidx.annotation.Nullable;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.views.NavigationBarView;
import com.android.systemui.settings.DisplayTracker;
+import com.android.systemui.shade.data.repository.ShadePositionRepository;
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import dagger.Lazy;
+import kotlinx.coroutines.CoroutineScope;
+
import java.util.concurrent.Executor;
import javax.inject.Inject;
+import javax.inject.Provider;
@SysUISingleton
public class KeyguardDisplayManager {
@@ -58,6 +66,7 @@
private final DisplayManager mDisplayService;
private final DisplayTracker mDisplayTracker;
private final Lazy<NavigationBarController> mNavigationBarControllerLazy;
+ private final Provider<ShadePositionRepository> mShadePositionRepositoryProvider;
private final ConnectedDisplayKeyguardPresentation.Factory
mConnectedDisplayKeyguardPresentationFactory;
private final Context mContext;
@@ -102,9 +111,12 @@
DeviceStateHelper deviceStateHelper,
KeyguardStateController keyguardStateController,
ConnectedDisplayKeyguardPresentation.Factory
- connectedDisplayKeyguardPresentationFactory) {
+ connectedDisplayKeyguardPresentationFactory,
+ Provider<ShadePositionRepository> shadePositionRepositoryProvider,
+ @Application CoroutineScope appScope) {
mContext = context;
mNavigationBarControllerLazy = navigationBarControllerLazy;
+ mShadePositionRepositoryProvider = shadePositionRepositoryProvider;
uiBgExecutor.execute(() -> mMediaRouter = mContext.getSystemService(MediaRouter.class));
mDisplayService = mContext.getSystemService(DisplayManager.class);
mDisplayTracker = displayTracker;
@@ -112,6 +124,17 @@
mDeviceStateHelper = deviceStateHelper;
mKeyguardStateController = keyguardStateController;
mConnectedDisplayKeyguardPresentationFactory = connectedDisplayKeyguardPresentationFactory;
+ if (ShadeWindowGoesAround.isEnabled()) {
+ collectFlow(appScope, shadePositionRepositoryProvider.get().getDisplayId(),
+ (id) -> onShadeWindowMovedToDisplayId(id));
+ }
+ }
+
+ private void onShadeWindowMovedToDisplayId(int shadeDisplayId) {
+ if (mShowing) {
+ hidePresentation(shadeDisplayId);
+ updateDisplays(/* showing= */ true);
+ }
}
private boolean isKeyguardShowable(Display display) {
@@ -119,9 +142,20 @@
if (DEBUG) Log.i(TAG, "Cannot show Keyguard on null display");
return false;
}
- if (display.getDisplayId() == mDisplayTracker.getDefaultDisplayId()) {
- if (DEBUG) Log.i(TAG, "Do not show KeyguardPresentation on the default display");
- return false;
+ if (ShadeWindowGoesAround.isEnabled()) {
+ int shadeDisplayId = mShadePositionRepositoryProvider.get().getDisplayId().getValue();
+ if (display.getDisplayId() == shadeDisplayId) {
+ if (DEBUG) {
+ Log.i(TAG,
+ "Do not show KeyguardPresentation on the shade window display");
+ }
+ return false;
+ }
+ } else {
+ if (display.getDisplayId() == mDisplayTracker.getDefaultDisplayId()) {
+ if (DEBUG) Log.i(TAG, "Do not show KeyguardPresentation on the default display");
+ return false;
+ }
}
display.getDisplayInfo(mTmpDisplayInfo);
if ((mTmpDisplayInfo.flags & Display.FLAG_PRIVATE) != 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index 1978bb8..1f21af8 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -35,10 +35,9 @@
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.View.Visibility;
+import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -114,10 +113,9 @@
private SystemUIDialog mDialog;
private RecyclerView mDeviceList;
private List<DeviceItem> mHearingDeviceItemList;
+ private View mPresetLayout;
private Spinner mPresetSpinner;
- private ArrayAdapter<String> mPresetInfoAdapter;
- private Button mPairButton;
- private LinearLayout mRelatedToolsContainer;
+ private HearingDevicesSpinnerAdapter mPresetInfoAdapter;
private final HearingDevicesPresetsController.PresetCallback mPresetCallback =
new HearingDevicesPresetsController.PresetCallback() {
@Override
@@ -245,7 +243,7 @@
mPresetsController.getAllPresetInfo();
final int activePresetIndex = mPresetsController.getActivePresetIndex();
refreshPresetInfoAdapter(presetInfos, activePresetIndex);
- mPresetSpinner.setVisibility(
+ mPresetLayout.setVisibility(
(activeHearingDevice != null && activeHearingDevice.isConnectedHapClientDevice()
&& !mPresetInfoAdapter.isEmpty()) ? VISIBLE : GONE);
});
@@ -291,14 +289,13 @@
}
mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_DIALOG_SHOW, mLaunchSourceId);
- mPairButton = dialog.requireViewById(R.id.pair_new_device_button);
mDeviceList = dialog.requireViewById(R.id.device_list);
+ mPresetLayout = dialog.requireViewById(R.id.preset_layout);
mPresetSpinner = dialog.requireViewById(R.id.preset_spinner);
- mRelatedToolsContainer = dialog.requireViewById(R.id.related_tools_container);
setupDeviceListView(dialog);
setupPresetSpinner(dialog);
- setupPairNewDeviceButton(dialog, mShowPairNewDevice ? VISIBLE : GONE);
+ setupPairNewDeviceButton(dialog);
if (com.android.systemui.Flags.hearingDevicesDialogRelatedTools()) {
setupRelatedToolsView(dialog);
}
@@ -353,11 +350,7 @@
mHearingDeviceItemList);
mPresetsController.setHearingDeviceIfSupportHap(activeHearingDevice);
- mPresetInfoAdapter = new ArrayAdapter<>(dialog.getContext(),
- R.layout.hearing_devices_preset_spinner_selected,
- R.id.hearing_devices_preset_option_text);
- mPresetInfoAdapter.setDropDownViewResource(
- R.layout.hearing_devices_preset_dropdown_item);
+ mPresetInfoAdapter = new HearingDevicesSpinnerAdapter(dialog.getContext());
mPresetSpinner.setAdapter(mPresetInfoAdapter);
// disable redundant Touch & Hold accessibility action for Switch Access
@@ -378,6 +371,7 @@
mPresetSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ mPresetInfoAdapter.setSelected(position);
mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_PRESET_SELECT,
mLaunchSourceId);
mPresetsController.selectPreset(
@@ -389,14 +383,17 @@
// Do nothing
}
});
- mPresetSpinner.setVisibility(
+ mPresetLayout.setVisibility(
(activeHearingDevice != null && activeHearingDevice.isConnectedHapClientDevice()
&& !mPresetInfoAdapter.isEmpty()) ? VISIBLE : GONE);
}
- private void setupPairNewDeviceButton(SystemUIDialog dialog, @Visibility int visibility) {
- if (visibility == VISIBLE) {
- mPairButton.setOnClickListener(v -> {
+ private void setupPairNewDeviceButton(SystemUIDialog dialog) {
+ final Button pairButton = dialog.requireViewById(R.id.pair_new_device_button);
+
+ pairButton.setVisibility(mShowPairNewDevice ? VISIBLE : GONE);
+ if (mShowPairNewDevice) {
+ pairButton.setOnClickListener(v -> {
mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_PAIR, mLaunchSourceId);
dismissDialogIfExists();
final Intent intent = new Intent(Settings.ACTION_HEARING_DEVICE_PAIRING_SETTINGS);
@@ -404,12 +401,11 @@
mActivityStarter.postStartActivityDismissingKeyguard(intent, /* delay= */ 0,
mDialogTransitionAnimator.createActivityTransitionController(dialog));
});
- } else {
- mPairButton.setVisibility(GONE);
}
}
private void setupRelatedToolsView(SystemUIDialog dialog) {
+
final Context context = dialog.getContext();
final List<ToolItem> toolItemList = new ArrayList<>();
final String[] toolNameArray;
@@ -430,15 +426,20 @@
} catch (Resources.NotFoundException e) {
Log.i(TAG, "No hearing devices related tool config resource");
}
+
+ final View toolsLayout = dialog.requireViewById(R.id.tools_layout);
+ toolsLayout.setVisibility(toolItemList.isEmpty() ? GONE : VISIBLE);
+
+ final LinearLayout toolsContainer = dialog.requireViewById(R.id.tools_container);
for (int i = 0; i < toolItemList.size(); i++) {
- View view = createHearingToolView(context, toolItemList.get(i));
- mRelatedToolsContainer.addView(view);
+ View view = createHearingToolView(context, toolItemList.get(i), toolsContainer);
+ toolsContainer.addView(view);
if (i != toolItemList.size() - 1) {
final int spaceSize = context.getResources().getDimensionPixelSize(
R.dimen.hearing_devices_layout_margin);
Space space = new Space(context);
space.setLayoutParams(new LinearLayout.LayoutParams(spaceSize, 0));
- mRelatedToolsContainer.addView(space);
+ toolsContainer.addView(space);
}
}
}
@@ -453,6 +454,7 @@
for (int position = 0; position < size; position++) {
if (presetInfos.get(position).getIndex() == activePresetIndex) {
mPresetSpinner.setSelection(position, /* animate= */ false);
+ mPresetInfoAdapter.setSelected(position);
}
}
}
@@ -493,9 +495,9 @@
}
@NonNull
- private View createHearingToolView(Context context, ToolItem item) {
- View view = LayoutInflater.from(context).inflate(R.layout.hearing_tool_item,
- mRelatedToolsContainer, false);
+ private View createHearingToolView(Context context, ToolItem item, ViewGroup container) {
+ View view = LayoutInflater.from(context).inflate(R.layout.hearing_tool_item, container,
+ false);
ImageView icon = view.requireViewById(R.id.tool_icon);
TextView text = view.requireViewById(R.id.tool_name);
view.setContentDescription(item.getToolName());
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
index 9367cb5..e47e4b2 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
@@ -138,19 +138,18 @@
Pair<Drawable, String> iconPair = item.getIconWithDescription();
if (iconPair != null) {
- Drawable drawable = iconPair.getFirst().mutate();
- drawable.setTint(tintColor);
+ Drawable drawable = iconPair.getFirst();
mIconView.setImageDrawable(drawable);
mIconView.setContentDescription(iconPair.getSecond());
}
mNameView.setTextAppearance(
- item.isActive() ? R.style.BluetoothTileDialog_DeviceName_Active
- : R.style.BluetoothTileDialog_DeviceName);
+ item.isActive() ? R.style.TextAppearance_BluetoothTileDialog_Active
+ : R.style.TextAppearance_BluetoothTileDialog);
mNameView.setText(item.getDeviceName());
mSummaryView.setTextAppearance(
- item.isActive() ? R.style.BluetoothTileDialog_DeviceSummary_Active
- : R.style.BluetoothTileDialog_DeviceSummary);
+ item.isActive() ? R.style.TextAppearance_BluetoothTileDialog_Active
+ : R.style.TextAppearance_BluetoothTileDialog);
mSummaryView.setText(item.getConnectionSummary());
mGearIcon.getDrawable().mutate().setTint(tintColor);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesSpinnerAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesSpinnerAdapter.java
new file mode 100644
index 0000000..28d742c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesSpinnerAdapter.java
@@ -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.systemui.accessibility.hearingaid;
+
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settingslib.Utils;
+import com.android.systemui.res.R;
+
+/**
+ * An ArrayAdapter which was used by Spinner in hearing devices dialog.
+ */
+public class HearingDevicesSpinnerAdapter extends ArrayAdapter<String> {
+
+ private final Context mContext;
+ private int mSelectedPosition;
+
+ public HearingDevicesSpinnerAdapter(@NonNull Context context) {
+ super(context, R.layout.hearing_devices_spinner_view,
+ R.id.hearing_devices_spinner_text);
+ setDropDownViewResource(R.layout.hearing_devices_spinner_dropdown_view);
+ mContext = context;
+ }
+
+ @Override
+ public View getDropDownView(int position, @Nullable View convertView,
+ @NonNull ViewGroup parent) {
+
+ View view = super.getDropDownView(position, convertView, parent);
+
+ final boolean isSelected = position == mSelectedPosition;
+ view.setBackgroundResource(isSelected
+ ? R.drawable.hearing_devices_spinner_selected_background
+ : R.drawable.bluetooth_tile_dialog_bg_off);
+
+ View checkIcon = view.findViewById(R.id.hearing_devices_spinner_check_icon);
+ if (checkIcon != null) {
+ checkIcon.setVisibility(isSelected ? VISIBLE : GONE);
+ }
+
+ TextView text = view.findViewById(R.id.hearing_devices_spinner_text);
+ if (text != null) {
+ int tintColor = Utils.getColorAttr(mContext,
+ isSelected ? com.android.internal.R.attr.materialColorOnPrimaryContainer
+ : com.android.internal.R.attr.materialColorOnSurface).getDefaultColor();
+ text.setTextColor(tintColor);
+ }
+ return view;
+ }
+
+ /**
+ * Sets the selected position into this adapter. The selected item will have different UI in
+ * the dropdown view.
+ *
+ * @param position the selected position
+ */
+ public void setSelected(int position) {
+ mSelectedPosition = position;
+ notifyDataSetChanged();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
index a9c5c69..d7a0fc9 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
@@ -100,7 +100,7 @@
initialUiProperties: BluetoothTileDialogViewModel.UiProperties,
cachedContentHeight: Int,
dialogCallback: BluetoothTileDialogCallback,
- dimissListener: Runnable
+ dimissListener: Runnable,
): BluetoothTileDialogDelegate
}
@@ -135,7 +135,7 @@
object : AccessibilityDelegate() {
override fun onInitializeAccessibilityNodeInfo(
host: View,
- info: AccessibilityNodeInfo
+ info: AccessibilityNodeInfo,
) {
super.onInitializeAccessibilityNodeInfo(host, info)
info.addAction(
@@ -144,7 +144,7 @@
context.getString(
R.string
.quick_settings_bluetooth_audio_sharing_button_accessibility
- )
+ ),
)
)
}
@@ -180,7 +180,7 @@
dialog: SystemUIDialog,
deviceItem: List<DeviceItem>,
showSeeAll: Boolean,
- showPairNewDevice: Boolean
+ showPairNewDevice: Boolean,
) {
withContext(mainDispatcher) {
val start = systemClock.elapsedRealtime()
@@ -207,7 +207,7 @@
internal fun onBluetoothStateUpdated(
dialog: SystemUIDialog,
isEnabled: Boolean,
- uiProperties: BluetoothTileDialogViewModel.UiProperties
+ uiProperties: BluetoothTileDialogViewModel.UiProperties,
) {
getToggleView(dialog).apply {
isChecked = isEnabled
@@ -221,7 +221,7 @@
internal fun onBluetoothAutoOnUpdated(
dialog: SystemUIDialog,
isEnabled: Boolean,
- @StringRes infoResId: Int
+ @StringRes infoResId: Int,
) {
getAutoOnToggle(dialog).isChecked = isEnabled
getAutoOnToggleInfoTextView(dialog).text = dialog.context.getString(infoResId)
@@ -231,7 +231,7 @@
dialog: SystemUIDialog,
visibility: Int,
label: String?,
- isActive: Boolean
+ isActive: Boolean,
) {
getAudioSharingButtonView(dialog).apply {
this.visibility = visibility
@@ -339,14 +339,14 @@
object : DiffUtil.ItemCallback<DeviceItem>() {
override fun areItemsTheSame(
deviceItem1: DeviceItem,
- deviceItem2: DeviceItem
+ deviceItem2: DeviceItem,
): Boolean {
return deviceItem1.cachedBluetoothDevice == deviceItem2.cachedBluetoothDevice
}
override fun areContentsTheSame(
deviceItem1: DeviceItem,
- deviceItem2: DeviceItem
+ deviceItem2: DeviceItem,
): Boolean {
return deviceItem1.type == deviceItem2.type &&
deviceItem1.cachedBluetoothDevice == deviceItem2.cachedBluetoothDevice &&
@@ -394,7 +394,7 @@
internal fun bind(
item: DeviceItem,
- deviceItemOnClickCallback: BluetoothTileDialogCallback
+ deviceItemOnClickCallback: BluetoothTileDialogCallback,
) {
container.apply {
isEnabled = item.isEnabled
@@ -409,14 +409,14 @@
com.android.settingslib.Utils.getColorAttr(
context,
if (item.isActive) InternalR.attr.materialColorOnPrimaryContainer
- else InternalR.attr.materialColorOnSurface
+ else InternalR.attr.materialColorOnSurface,
)
.defaultColor
// update icons
iconView.apply {
item.iconWithDescription?.let {
- setImageDrawable(it.first.apply { mutate()?.setTint(tintColor) })
+ setImageDrawable(it.first)
contentDescription = it.second
}
}
@@ -427,25 +427,25 @@
// update text styles
nameView.setTextAppearance(
- if (item.isActive) R.style.BluetoothTileDialog_DeviceName_Active
- else R.style.BluetoothTileDialog_DeviceName
+ if (item.isActive) R.style.TextAppearance_BluetoothTileDialog_Active
+ else R.style.TextAppearance_BluetoothTileDialog
)
summaryView.setTextAppearance(
- if (item.isActive) R.style.BluetoothTileDialog_DeviceSummary_Active
- else R.style.BluetoothTileDialog_DeviceSummary
+ if (item.isActive) R.style.TextAppearance_BluetoothTileDialog_Active
+ else R.style.TextAppearance_BluetoothTileDialog
)
accessibilityDelegate =
object : AccessibilityDelegate() {
override fun onInitializeAccessibilityNodeInfo(
host: View,
- info: AccessibilityNodeInfo
+ info: AccessibilityNodeInfo,
) {
super.onInitializeAccessibilityNodeInfo(host, info)
info.addAction(
AccessibilityAction(
AccessibilityAction.ACTION_CLICK.id,
- item.actionAccessibilityLabel
+ item.actionAccessibilityLabel,
)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
index 2921373..92f0580 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
@@ -57,7 +57,6 @@
companion object {
@JvmStatic
fun createDeviceItem(
- context: Context,
cachedDevice: CachedBluetoothDevice,
type: DeviceItemType,
connectionSummary: String,
@@ -71,9 +70,7 @@
deviceName = cachedDevice.name,
connectionSummary = connectionSummary,
iconWithDescription =
- BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let {
- Pair(it.first, it.second)
- },
+ cachedDevice.drawableWithDescription.let { Pair(it.first, it.second) },
background = background,
isEnabled = !cachedDevice.isBusy,
actionAccessibilityLabel = actionAccessibilityLabel,
@@ -96,7 +93,6 @@
override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
return createDeviceItem(
- context,
cachedDevice,
DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE,
cachedDevice.connectionSummary ?: "",
@@ -122,7 +118,6 @@
override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
return createDeviceItem(
- context,
cachedDevice,
DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
@@ -153,7 +148,6 @@
override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
return createDeviceItem(
- context,
cachedDevice,
DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
context.getString(
@@ -191,7 +185,6 @@
override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
return createDeviceItem(
- context,
cachedDevice,
DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
@@ -232,7 +225,6 @@
override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
return createDeviceItem(
- context,
cachedDevice,
DeviceItemType.CONNECTED_BLUETOOTH_DEVICE,
cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
@@ -262,7 +254,6 @@
override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
return createDeviceItem(
- context,
cachedDevice,
DeviceItemType.SAVED_BLUETOOTH_DEVICE,
cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
index 7cfad60..6423f8f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
@@ -30,6 +30,7 @@
@Named(LOGGABLE_PREFIXES) private val loggablePrefixes: List<String>,
private val statsLogProxy: StatsLogProxy,
) {
+
/** Logs an add widget event for metrics. No-op if widget is not loggable. */
fun logAddWidget(componentName: String, rank: Int?) {
if (!componentName.isLoggable()) {
@@ -69,11 +70,21 @@
)
}
+ fun logResizeWidget(componentName: String, rank: Int, spanY: Int = 0) {
+ if (!componentName.isLoggable()) {
+ return
+ }
+
+ statsLogProxy.writeCommunalHubWidgetEventReported(
+ SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__RESIZE,
+ componentName,
+ rank = rank,
+ spanY = spanY,
+ )
+ }
+
/** Logs loggable widgets and the total widget count as a [StatsEvent]. */
- fun logWidgetsSnapshot(
- statsEvents: MutableList<StatsEvent>,
- componentNames: List<String>,
- ) {
+ fun logWidgetsSnapshot(statsEvents: MutableList<StatsEvent>, componentNames: List<String>) {
val loggableComponentNames = componentNames.filter { it.isLoggable() }.toTypedArray()
statsEvents.add(
statsLogProxy.buildCommunalHubSnapshotStatsEvent(
@@ -95,6 +106,7 @@
action: Int,
componentName: String,
rank: Int,
+ spanY: Int = 0,
)
/** Builds a [SysUiStatsLog.COMMUNAL_HUB_SNAPSHOT] stats event. */
@@ -112,12 +124,14 @@
action: Int,
componentName: String,
rank: Int,
+ spanY: Int,
) {
SysUiStatsLog.write(
SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED,
action,
componentName,
rank,
+ spanY,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index e25ea4c..4e0e112 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -178,7 +178,13 @@
* alongside the resize, in case resizing also requires re-ordering. This ensures the
* re-ordering is done in the same database transaction as the resize.
*/
- open fun onResizeWidget(appWidgetId: Int, spanY: Int, widgetIdToRankMap: Map<Int, Int>) {}
+ open fun onResizeWidget(
+ appWidgetId: Int,
+ spanY: Int,
+ widgetIdToRankMap: Map<Int, Int>,
+ componentName: ComponentName,
+ rank: Int,
+ ) {}
/** Called as the UI requests opening the widget editor with an optional preselected widget. */
open fun onOpenWidgetEditor(shouldOpenWidgetPickerOnStart: Boolean = false) {}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 6508e4b5..ccff230 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -141,8 +141,19 @@
override fun onReorderWidgets(widgetIdToRankMap: Map<Int, Int>) =
communalInteractor.updateWidgetOrder(widgetIdToRankMap)
- override fun onResizeWidget(appWidgetId: Int, spanY: Int, widgetIdToRankMap: Map<Int, Int>) {
+ override fun onResizeWidget(
+ appWidgetId: Int,
+ spanY: Int,
+ widgetIdToRankMap: Map<Int, Int>,
+ componentName: ComponentName,
+ rank: Int,
+ ) {
communalInteractor.resizeWidget(appWidgetId, spanY, widgetIdToRankMap)
+ metricsLogger.logResizeWidget(
+ componentName = componentName.flattenToString(),
+ rank = rank,
+ spanY = spanY,
+ )
}
override fun onReorderWidgetStart() {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 4447dff..b7d3c92 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -100,7 +100,7 @@
import com.android.systemui.qs.footer.dagger.FooterActionsModule;
import com.android.systemui.recents.Recents;
import com.android.systemui.recordissue.RecordIssueModule;
-import com.android.systemui.retail.dagger.RetailModeModule;
+import com.android.systemui.retail.RetailModeModule;
import com.android.systemui.scene.shared.model.SceneContainerConfig;
import com.android.systemui.scene.shared.model.SceneDataSource;
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator;
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
index 4a369e7..7758dcc 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
@@ -17,12 +17,14 @@
package com.android.systemui.inputdevice.tutorial.domain.interactor
import android.os.SystemProperties
+import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger
import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType
import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.KEYBOARD
import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.TOUCHPAD
import com.android.systemui.inputdevice.tutorial.data.repository.TutorialSchedulerRepository
+import com.android.systemui.inputdevice.tutorial.domain.interactor.TutorialSchedulerInteractor.Companion.LAUNCH_DELAY
import com.android.systemui.keyboard.data.repository.KeyboardRepository
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
@@ -35,6 +37,7 @@
import kotlin.time.toKotlinDuration
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
@@ -96,6 +99,9 @@
private suspend fun waitForDeviceConnection(deviceType: DeviceType) =
isAnyDeviceConnected[deviceType]!!.filter { it }.first()
+ // Only for testing notifications. This should behave independently from scheduling
+ @VisibleForTesting val commandTutorials = MutableStateFlow(TutorialType.NONE)
+
// Merging two flows ensures that tutorial is launched consecutively to avoid race condition
val tutorials: Flow<TutorialType> =
merge(touchpadScheduleFlow, keyboardScheduleFlow).map {
@@ -146,6 +152,15 @@
pw.println("Touchpad connect time = ${repo.firstConnectionTime(TOUCHPAD)}")
pw.println(" launch time = ${repo.launchTime(TOUCHPAD)}")
}
+ "notify" -> {
+ if (args.size != 2) help(pw)
+ when (args[1]) {
+ "keyboard" -> commandTutorials.value = TutorialType.KEYBOARD
+ "touchpad" -> commandTutorials.value = TutorialType.TOUCHPAD
+ "both" -> commandTutorials.value = TutorialType.BOTH
+ else -> help(pw)
+ }
+ }
else -> help(pw)
}
}
@@ -155,6 +170,7 @@
pw.println("Available commands:")
pw.println(" clear")
pw.println(" info")
+ pw.println(" notify [keyboard|touchpad|both]")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/TutorialNotificationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/TutorialNotificationCoordinator.kt
index 3b26f2f..9dae649 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/TutorialNotificationCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/TutorialNotificationCoordinator.kt
@@ -24,6 +24,7 @@
import android.content.Intent
import android.os.Bundle
import androidx.core.app.NotificationCompat
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -41,7 +42,7 @@
import com.android.systemui.settings.UserTracker
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
-import com.android.app.tracing.coroutines.launchTraced as launch
+import kotlinx.coroutines.flow.merge
/** When the scheduler is due, show a notification to launch tutorial */
@SysUISingleton
@@ -56,7 +57,11 @@
) {
fun start() {
backgroundScope.launch {
- tutorialSchedulerInteractor.tutorials.collect { showNotification(it) }
+ merge(
+ tutorialSchedulerInteractor.tutorials,
+ tutorialSchedulerInteractor.commandTutorials,
+ )
+ .collect { showNotification(it) }
}
}
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 fa49415..fee08b3 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
@@ -27,6 +27,7 @@
import androidx.lifecycle.Lifecycle.State.STARTED
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.compose.theme.PlatformTheme
import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger
import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger.TutorialContext
@@ -40,7 +41,6 @@
import com.android.systemui.inputdevice.tutorial.ui.viewmodel.Screen.HOME_GESTURE
import java.util.Optional
import javax.inject.Inject
-import com.android.app.tracing.coroutines.launchTraced as launch
/**
* Activity for out of the box experience for keyboard and touchpad. Note that it's possible that
@@ -90,6 +90,7 @@
setContent {
PlatformTheme { KeyboardTouchpadTutorialContainer(vm, touchpadTutorialScreensProvider) }
}
+ // TODO(b/376692701): Update launchTime when the activity is launched by Companion App
if (savedInstanceState == null) {
metricsLogger.logPeripheralTutorialLaunched(
intent.getStringExtra(INTENT_TUTORIAL_ENTRY_POINT_KEY),
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/model/InternalShortcutModels.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/model/InternalShortcutModels.kt
new file mode 100644
index 0000000..8c393e2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/model/InternalShortcutModels.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.keyboard.shortcut.data.model
+
+import android.graphics.drawable.Icon
+import android.hardware.input.InputGestureData
+import android.view.KeyboardShortcutGroup
+import com.android.systemui.keyboard.shortcut.data.repository.ShortcutCategoriesUtils
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
+
+/**
+ * Internal Keyboard Shortcut models to use with [ShortcutCategoriesUtils.fetchShortcutCategory]
+ * when converting API models to Shortcut Helper Model [ShortcutCategory]. These Internal Models
+ * bridge the Gap between [InputGestureData] from custom shortcuts API and [KeyboardShortcutGroup]
+ * from default shortcuts API allowing us to have a single Utility Class that converts API models to
+ * Shortcut Helper models
+ *
+ * @param label Equivalent to shortcut helper's subcategory label
+ * @param items Keyboard Shortcuts received from API
+ * @param packageName package name of current app shortcut if available.
+ */
+data class InternalKeyboardShortcutGroup(
+ val label: String,
+ val items: List<InternalKeyboardShortcutInfo>,
+ val packageName: String? = null,
+)
+
+/**
+ * @param label Shortcut label
+ * @param keycode Key to trigger shortcut
+ * @param modifiers Mask of shortcut modifiers
+ * @param baseCharacter Key to trigger shortcut if is a character
+ * @param icon Shortcut icon if available - often used for app launch shortcuts
+ * @param isCustomShortcut If Shortcut is user customized or system defined.
+ */
+data class InternalKeyboardShortcutInfo(
+ val label: String,
+ val keycode: Int,
+ val modifiers: Int,
+ val baseCharacter: Char = Char.MIN_VALUE,
+ val icon: Icon? = null,
+ val isCustomShortcut: Boolean = false,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
new file mode 100644
index 0000000..7f8fbb5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
@@ -0,0 +1,175 @@
+/*
+ * 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.keyboard.shortcut.data.repository
+
+import android.content.Context
+import android.content.Context.INPUT_SERVICE
+import android.hardware.input.InputGestureData
+import android.hardware.input.InputGestureData.KeyTrigger
+import android.hardware.input.InputManager
+import android.hardware.input.InputSettings
+import android.hardware.input.KeyGestureEvent
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutGroup
+import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutInfo
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active
+import com.android.systemui.settings.UserTracker
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+@SysUISingleton
+class CustomShortcutCategoriesRepository
+@Inject
+constructor(
+ stateRepository: ShortcutHelperStateRepository,
+ private val userTracker: UserTracker,
+ @Background private val backgroundScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val shortcutCategoriesUtils: ShortcutCategoriesUtils,
+) : ShortcutCategoriesRepository {
+
+ private val userContext: Context
+ get() = userTracker.createCurrentUserContext(userTracker.userContext)
+
+ // Input manager created with user context to provide correct user id when requesting custom
+ // shortcut
+ private val inputManager: InputManager
+ get() = userContext.getSystemService(INPUT_SERVICE) as InputManager
+
+ private val activeInputDevice =
+ stateRepository.state.map {
+ if (it is Active) {
+ withContext(backgroundDispatcher) { inputManager.getInputDevice(it.deviceId) }
+ } else {
+ null
+ }
+ }
+
+ override val categories: Flow<List<ShortcutCategory>> =
+ activeInputDevice
+ .map { inputDevice ->
+ if (inputDevice == null) {
+ emptyList()
+ } else {
+ val customInputGesturesForUser: List<InputGestureData> =
+ if (InputSettings.isCustomizableInputGesturesFeatureFlagEnabled()) {
+ inputManager.getCustomInputGestures(/* filter= */ null)
+ } else emptyList()
+ val sources = toInternalGroupSources(customInputGesturesForUser)
+ val supportedKeyCodes =
+ shortcutCategoriesUtils.fetchSupportedKeyCodes(
+ inputDevice.id,
+ sources.map { it.groups },
+ )
+
+ val result =
+ sources.mapNotNull { source ->
+ shortcutCategoriesUtils.fetchShortcutCategory(
+ type = source.type,
+ groups = source.groups,
+ inputDevice = inputDevice,
+ supportedKeyCodes = supportedKeyCodes,
+ )
+ }
+ result
+ }
+ }
+ .stateIn(
+ scope = backgroundScope,
+ initialValue = emptyList(),
+ started = SharingStarted.Lazily,
+ )
+
+ private fun toInternalGroupSources(
+ inputGestures: List<InputGestureData>
+ ): List<InternalGroupsSource> {
+ val ungroupedInternalGroupSources =
+ inputGestures.mapNotNull { gestureData ->
+ val keyTrigger = gestureData.trigger as KeyTrigger
+ val keyGestureType = gestureData.action.keyGestureType()
+ fetchGroupLabelByGestureType(keyGestureType)?.let { groupLabel ->
+ toInternalKeyboardShortcutInfo(keyGestureType, keyTrigger)?.let {
+ internalKeyboardShortcutInfo ->
+ val group =
+ InternalKeyboardShortcutGroup(
+ label = groupLabel,
+ items = listOf(internalKeyboardShortcutInfo),
+ )
+
+ fetchShortcutCategoryTypeByGestureType(keyGestureType)?.let {
+ InternalGroupsSource(groups = listOf(group), type = it)
+ }
+ }
+ }
+ }
+
+ return ungroupedInternalGroupSources
+ }
+
+ private fun toInternalKeyboardShortcutInfo(
+ keyGestureType: Int,
+ keyTrigger: KeyTrigger,
+ ): InternalKeyboardShortcutInfo? {
+ fetchShortcutInfoLabelByGestureType(keyGestureType)?.let {
+ return InternalKeyboardShortcutInfo(
+ label = it,
+ keycode = keyTrigger.keycode,
+ modifiers = keyTrigger.modifierState,
+ isCustomShortcut = true,
+ )
+ }
+ return null
+ }
+
+ private fun fetchGroupLabelByGestureType(
+ @KeyGestureEvent.KeyGestureType keyGestureType: Int
+ ): String? {
+ return InputGestures.gestureToInternalKeyboardShortcutGroupLabelMap.getOrDefault(
+ keyGestureType,
+ null,
+ )
+ }
+
+ private fun fetchShortcutInfoLabelByGestureType(
+ @KeyGestureEvent.KeyGestureType keyGestureType: Int
+ ): String? {
+ return InputGestures.gestureToInternalKeyboardShortcutInfoLabelMap.getOrDefault(
+ keyGestureType,
+ null,
+ )
+ }
+
+ private fun fetchShortcutCategoryTypeByGestureType(
+ @KeyGestureEvent.KeyGestureType keyGestureType: Int
+ ): ShortcutCategoryType? {
+ return InputGestures.gestureToShortcutCategoryTypeMap.getOrDefault(keyGestureType, null)
+ }
+
+ private data class InternalGroupsSource(
+ val groups: List<InternalKeyboardShortcutGroup>,
+ val type: ShortcutCategoryType,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepository.kt
new file mode 100644
index 0000000..5bb7cdd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepository.kt
@@ -0,0 +1,160 @@
+/*
+ * 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.keyboard.shortcut.data.repository
+
+import android.hardware.input.InputManager
+import android.view.KeyboardShortcutGroup
+import android.view.KeyboardShortcutInfo
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutGroup
+import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutInfo
+import com.android.systemui.keyboard.shortcut.data.source.KeyboardShortcutGroupsSource
+import com.android.systemui.keyboard.shortcut.qualifiers.AppCategoriesShortcuts
+import com.android.systemui.keyboard.shortcut.qualifiers.CurrentAppShortcuts
+import com.android.systemui.keyboard.shortcut.qualifiers.InputShortcuts
+import com.android.systemui.keyboard.shortcut.qualifiers.MultitaskingShortcuts
+import com.android.systemui.keyboard.shortcut.qualifiers.SystemShortcuts
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.AppCategories
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.CurrentApp
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.InputMethodEditor
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+@SysUISingleton
+class DefaultShortcutCategoriesRepository
+@Inject
+constructor(
+ @Background private val backgroundScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ @SystemShortcuts private val systemShortcutsSource: KeyboardShortcutGroupsSource,
+ @MultitaskingShortcuts private val multitaskingShortcutsSource: KeyboardShortcutGroupsSource,
+ @AppCategoriesShortcuts private val appCategoriesShortcutsSource: KeyboardShortcutGroupsSource,
+ @InputShortcuts private val inputShortcutsSource: KeyboardShortcutGroupsSource,
+ @CurrentAppShortcuts private val currentAppShortcutsSource: KeyboardShortcutGroupsSource,
+ private val inputManager: InputManager,
+ stateRepository: ShortcutHelperStateRepository,
+ shortcutCategoriesUtils: ShortcutCategoriesUtils,
+) : ShortcutCategoriesRepository {
+
+ private val sources =
+ listOf(
+ InternalGroupsSource(source = systemShortcutsSource, typeProvider = { System }),
+ InternalGroupsSource(
+ source = multitaskingShortcutsSource,
+ typeProvider = { MultiTasking },
+ ),
+ InternalGroupsSource(
+ source = appCategoriesShortcutsSource,
+ typeProvider = { AppCategories },
+ ),
+ InternalGroupsSource(
+ source = inputShortcutsSource,
+ typeProvider = { InputMethodEditor },
+ ),
+ InternalGroupsSource(
+ source = currentAppShortcutsSource,
+ typeProvider = { groups -> getCurrentAppShortcutCategoryType(groups) },
+ ),
+ )
+
+ private val activeInputDevice =
+ stateRepository.state.map {
+ if (it is Active) {
+ withContext(backgroundDispatcher) { inputManager.getInputDevice(it.deviceId) }
+ } else {
+ null
+ }
+ }
+
+ override val categories: Flow<List<ShortcutCategory>> =
+ activeInputDevice
+ .map { inputDevice ->
+ if (inputDevice == null) {
+ return@map emptyList()
+ }
+ val groupsFromAllSources =
+ sources.map {
+ toInternalKeyboardShortcutGroups(it.source.shortcutGroups(inputDevice.id))
+ }
+ val supportedKeyCodes =
+ shortcutCategoriesUtils.fetchSupportedKeyCodes(
+ inputDevice.id,
+ groupsFromAllSources,
+ )
+ return@map sources.mapIndexedNotNull { index, internalGroupsSource ->
+ shortcutCategoriesUtils.fetchShortcutCategory(
+ internalGroupsSource.typeProvider(groupsFromAllSources[index]),
+ groupsFromAllSources[index],
+ inputDevice,
+ supportedKeyCodes,
+ )
+ }
+ }
+ .stateIn(
+ scope = backgroundScope,
+ started = SharingStarted.Lazily,
+ initialValue = emptyList(),
+ )
+
+ private fun toInternalKeyboardShortcutGroups(
+ keyboardShortcutGroups: List<KeyboardShortcutGroup>
+ ): List<InternalKeyboardShortcutGroup> {
+ return keyboardShortcutGroups.map { group ->
+ InternalKeyboardShortcutGroup(
+ label = group.label.toString(),
+ items = group.items.map { toInternalKeyboardShortcutInfo(it) },
+ packageName = group.packageName?.toString(),
+ )
+ }
+ }
+
+ private fun toInternalKeyboardShortcutInfo(
+ keyboardShortcutInfo: KeyboardShortcutInfo
+ ): InternalKeyboardShortcutInfo {
+ return InternalKeyboardShortcutInfo(
+ label = keyboardShortcutInfo.label!!.toString(),
+ keycode = keyboardShortcutInfo.keycode,
+ modifiers = keyboardShortcutInfo.modifiers,
+ baseCharacter = keyboardShortcutInfo.baseCharacter,
+ icon = keyboardShortcutInfo.icon,
+ )
+ }
+
+ private fun getCurrentAppShortcutCategoryType(
+ shortcutGroups: List<InternalKeyboardShortcutGroup>
+ ): ShortcutCategoryType? {
+ val packageName = shortcutGroups.firstOrNull()?.packageName ?: return null
+ return CurrentApp(packageName)
+ }
+
+ private class InternalGroupsSource(
+ val source: KeyboardShortcutGroupsSource,
+ val typeProvider: (groups: List<InternalKeyboardShortcutGroup>) -> ShortcutCategoryType?,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestures.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestures.kt
new file mode 100644
index 0000000..28134db
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestures.kt
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyboard.shortcut.data.repository
+
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_BACK
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_HOME
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.AppCategories
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System
+
+object InputGestures {
+ val gestureToShortcutCategoryTypeMap =
+ mapOf(
+ // System Category
+ KEY_GESTURE_TYPE_HOME to System,
+ KEY_GESTURE_TYPE_RECENT_APPS to System,
+ KEY_GESTURE_TYPE_BACK to System,
+ KEY_GESTURE_TYPE_TAKE_SCREENSHOT to System,
+ KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER to System,
+ KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL to System,
+ KEY_GESTURE_TYPE_LOCK_SCREEN to System,
+ KEY_GESTURE_TYPE_OPEN_NOTES to System,
+ KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS to System,
+ KEY_GESTURE_TYPE_LAUNCH_ASSISTANT to System,
+ KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT to System,
+ KEY_GESTURE_TYPE_ALL_APPS to System,
+
+ // Multitasking Category
+ KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER to MultiTasking,
+ KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT to MultiTasking,
+ KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT to MultiTasking,
+ KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION to MultiTasking,
+ KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT to MultiTasking,
+ KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT to MultiTasking,
+
+ // App Category
+ KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR to AppCategories,
+ KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR to AppCategories,
+ KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER to AppCategories,
+ KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS to AppCategories,
+ KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL to AppCategories,
+ KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS to AppCategories,
+ KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING to AppCategories,
+ )
+
+ // TODO move all string to to resources use the same resources as the original shortcuts
+ // - that way when the strings are translated there are no discrepancies
+ val gestureToInternalKeyboardShortcutGroupLabelMap =
+ mapOf(
+ // System Category
+ KEY_GESTURE_TYPE_HOME to "System controls",
+ KEY_GESTURE_TYPE_RECENT_APPS to "System controls",
+ KEY_GESTURE_TYPE_BACK to "System controls",
+ KEY_GESTURE_TYPE_TAKE_SCREENSHOT to "System controls",
+ KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER to "System controls",
+ KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL to "System controls",
+ KEY_GESTURE_TYPE_LOCK_SCREEN to "System controls",
+ KEY_GESTURE_TYPE_ALL_APPS to "System controls",
+ KEY_GESTURE_TYPE_OPEN_NOTES to "System apps",
+ KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS to "System apps",
+ KEY_GESTURE_TYPE_LAUNCH_ASSISTANT to "System apps",
+ KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT to "System apps",
+
+ // Multitasking Category
+ KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER to "Recent apps",
+ KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT to "Split screen",
+ KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT to "Split screen",
+ KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION to "Split screen",
+ KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT to "Split screen",
+ KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT to "Split screen",
+
+ // App Category
+ KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR to "Applications",
+ KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR to "Applications",
+ KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER to "Applications",
+ KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS to "Applications",
+ KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL to "Applications",
+ KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS to "Applications",
+ KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING to "Applications",
+ )
+
+ val gestureToInternalKeyboardShortcutInfoLabelMap =
+ mapOf(
+ // System Category
+ KEY_GESTURE_TYPE_HOME to "Go to home screen",
+ KEY_GESTURE_TYPE_RECENT_APPS to "View recent apps",
+ KEY_GESTURE_TYPE_BACK to "Go back",
+ KEY_GESTURE_TYPE_TAKE_SCREENSHOT to "Take screenshot",
+ KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER to "Show shortcuts",
+ KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL to "View notifications",
+ KEY_GESTURE_TYPE_LOCK_SCREEN to "Lock screen",
+ KEY_GESTURE_TYPE_ALL_APPS to "Open apps list",
+ KEY_GESTURE_TYPE_OPEN_NOTES to "Take a note",
+ KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS to "Open settings",
+ KEY_GESTURE_TYPE_LAUNCH_ASSISTANT to "Open assistant",
+ KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT to "Open assistant",
+
+ // Multitasking Category
+ KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER to "Cycle forward through recent apps",
+ KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT to
+ "Use split screen with current app on the left",
+ KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT to
+ "Use split screen with current app on the right",
+ KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION to "Switch from split screen to full screen",
+ KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT to
+ "Switch to app on left or above while using split screen",
+ KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT to
+ "Switch to app on right or below while using split screen",
+
+ // App Category
+ KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR to "Calculator",
+ KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR to "Calendar",
+ KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER to "Chrome",
+ KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS to "Contacts",
+ KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL to "Gmail",
+ KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS to "Maps",
+ KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING to "Messages",
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesRepository.kt
new file mode 100644
index 0000000..2e8cc00
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesRepository.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyboard.shortcut.data.repository
+
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
+import kotlinx.coroutines.flow.Flow
+
+interface ShortcutCategoriesRepository {
+ val categories: Flow<List<ShortcutCategory>>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt
similarity index 60%
rename from packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
rename to packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt
index 12dd581..899fd15 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt
@@ -24,123 +24,34 @@
import android.view.InputDevice
import android.view.KeyCharacterMap
import android.view.KeyEvent
-import android.view.KeyboardShortcutGroup
-import android.view.KeyboardShortcutInfo
import com.android.systemui.Flags.shortcutHelperKeyGlyph
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.keyboard.shortcut.data.source.KeyboardShortcutGroupsSource
-import com.android.systemui.keyboard.shortcut.qualifiers.AppCategoriesShortcuts
-import com.android.systemui.keyboard.shortcut.qualifiers.CurrentAppShortcuts
-import com.android.systemui.keyboard.shortcut.qualifiers.InputShortcuts
-import com.android.systemui.keyboard.shortcut.qualifiers.MultitaskingShortcuts
-import com.android.systemui.keyboard.shortcut.qualifiers.SystemShortcuts
+import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutGroup
+import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutInfo
import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.AppCategories
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.CurrentApp
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.InputMethodEditor
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutIcon
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
+import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.withContext
-@SysUISingleton
-class ShortcutHelperCategoriesRepository
+class ShortcutCategoriesUtils
@Inject
constructor(
private val context: Context,
- @Background private val backgroundScope: CoroutineScope,
- @Background private val backgroundDispatcher: CoroutineDispatcher,
- @SystemShortcuts private val systemShortcutsSource: KeyboardShortcutGroupsSource,
- @MultitaskingShortcuts private val multitaskingShortcutsSource: KeyboardShortcutGroupsSource,
- @AppCategoriesShortcuts private val appCategoriesShortcutsSource: KeyboardShortcutGroupsSource,
- @InputShortcuts private val inputShortcutsSource: KeyboardShortcutGroupsSource,
- @CurrentAppShortcuts private val currentAppShortcutsSource: KeyboardShortcutGroupsSource,
+ @Background private val backgroundCoroutineContext: CoroutineContext,
private val inputManager: InputManager,
- stateRepository: ShortcutHelperStateRepository,
) {
-
- private val sources =
- listOf(
- InternalGroupsSource(
- source = systemShortcutsSource,
- isTrusted = true,
- typeProvider = { System },
- ),
- InternalGroupsSource(
- source = multitaskingShortcutsSource,
- isTrusted = true,
- typeProvider = { MultiTasking },
- ),
- InternalGroupsSource(
- source = appCategoriesShortcutsSource,
- isTrusted = true,
- typeProvider = { AppCategories },
- ),
- InternalGroupsSource(
- source = inputShortcutsSource,
- isTrusted = false,
- typeProvider = { InputMethodEditor },
- ),
- InternalGroupsSource(
- source = currentAppShortcutsSource,
- isTrusted = false,
- typeProvider = { groups -> getCurrentAppShortcutCategoryType(groups) },
- ),
- )
-
- private val activeInputDevice =
- stateRepository.state.map {
- if (it is Active) {
- withContext(backgroundDispatcher) { inputManager.getInputDevice(it.deviceId) }
- } else {
- null
- }
- }
-
- val categories: Flow<List<ShortcutCategory>> =
- activeInputDevice
- .map { inputDevice ->
- if (inputDevice == null) {
- return@map emptyList()
- }
- val groupsFromAllSources = sources.map { it.source.shortcutGroups(inputDevice.id) }
- val supportedKeyCodes = fetchSupportedKeyCodes(inputDevice.id, groupsFromAllSources)
- return@map sources.mapIndexedNotNull { index, internalGroupsSource ->
- fetchShortcutCategory(
- internalGroupsSource,
- groupsFromAllSources[index],
- inputDevice,
- supportedKeyCodes,
- )
- }
- }
- .stateIn(
- scope = backgroundScope,
- started = SharingStarted.Lazily,
- initialValue = emptyList(),
- )
-
- private fun fetchShortcutCategory(
- internalGroupsSource: InternalGroupsSource,
- groups: List<KeyboardShortcutGroup>,
+ fun fetchShortcutCategory(
+ type: ShortcutCategoryType?,
+ groups: List<InternalKeyboardShortcutGroup>,
inputDevice: InputDevice,
supportedKeyCodes: Set<Int>,
): ShortcutCategory? {
- val type = internalGroupsSource.typeProvider(groups)
return if (type == null) {
null
} else {
@@ -151,27 +62,17 @@
inputDevice.keyCharacterMap,
type,
groups,
- internalGroupsSource.isTrusted,
+ type.isTrusted,
supportedKeyCodes,
)
}
}
- private fun getCurrentAppShortcutCategoryType(
- shortcutGroups: List<KeyboardShortcutGroup>
- ): ShortcutCategoryType? {
- return if (shortcutGroups.isEmpty()) {
- null
- } else {
- CurrentApp(packageName = shortcutGroups[0].packageName.toString())
- }
- }
-
private fun toShortcutCategory(
keyGlyphMap: KeyGlyphMap?,
keyCharacterMap: KeyCharacterMap,
type: ShortcutCategoryType,
- shortcutGroups: List<KeyboardShortcutGroup>,
+ shortcutGroups: List<InternalKeyboardShortcutGroup>,
keepIcons: Boolean,
supportedKeyCodes: Set<Int>,
): ShortcutCategory? {
@@ -179,7 +80,7 @@
shortcutGroups
.map { shortcutGroup ->
ShortcutSubCategory(
- shortcutGroup.label.toString(),
+ shortcutGroup.label,
toShortcuts(
keyGlyphMap,
keyCharacterMap,
@@ -201,7 +102,7 @@
private fun toShortcuts(
keyGlyphMap: KeyGlyphMap?,
keyCharacterMap: KeyCharacterMap,
- infoList: List<KeyboardShortcutInfo>,
+ infoList: List<InternalKeyboardShortcutInfo>,
keepIcons: Boolean,
supportedKeyCodes: Set<Int>,
) =
@@ -216,13 +117,13 @@
private fun toShortcut(
keyGlyphMap: KeyGlyphMap?,
keyCharacterMap: KeyCharacterMap,
- shortcutInfo: KeyboardShortcutInfo,
+ shortcutInfo: InternalKeyboardShortcutInfo,
keepIcon: Boolean,
): Shortcut? {
val shortcutCommand =
toShortcutCommand(keyGlyphMap, keyCharacterMap, shortcutInfo) ?: return null
return Shortcut(
- label = shortcutInfo.label!!.toString(),
+ label = shortcutInfo.label,
icon = toShortcutIcon(keepIcon, shortcutInfo),
commands = listOf(shortcutCommand),
)
@@ -230,7 +131,7 @@
private fun toShortcutIcon(
keepIcon: Boolean,
- shortcutInfo: KeyboardShortcutInfo,
+ shortcutInfo: InternalKeyboardShortcutInfo,
): ShortcutIcon? {
if (!keepIcon) {
return null
@@ -247,7 +148,7 @@
private fun toShortcutCommand(
keyGlyphMap: KeyGlyphMap?,
keyCharacterMap: KeyCharacterMap,
- info: KeyboardShortcutInfo,
+ info: InternalKeyboardShortcutInfo,
): ShortcutCommand? {
val keys = mutableListOf<ShortcutKey>()
var remainingModifiers = info.modifiers
@@ -272,7 +173,7 @@
Log.wtf(TAG, "No keys for $info")
return null
}
- return ShortcutCommand(keys)
+ return ShortcutCommand(keys = keys, isCustom = info.isCustomShortcut)
}
private fun toShortcutModifierKey(keyGlyphMap: KeyGlyphMap?, modifierMask: Int): ShortcutKey? {
@@ -325,11 +226,11 @@
return null
}
- private suspend fun fetchSupportedKeyCodes(
+ suspend fun fetchSupportedKeyCodes(
deviceId: Int,
- groupsFromAllSources: List<List<KeyboardShortcutGroup>>,
+ groupsFromAllSources: List<List<InternalKeyboardShortcutGroup>>,
): Set<Int> =
- withContext(backgroundDispatcher) {
+ withContext(backgroundCoroutineContext) {
val allUsedKeyCodes =
groupsFromAllSources
.flatMap { groups -> groups.flatMap { group -> group.items } }
@@ -342,14 +243,8 @@
.toSet()
}
- private class InternalGroupsSource(
- val source: KeyboardShortcutGroupsSource,
- val isTrusted: Boolean,
- val typeProvider: (groups: List<KeyboardShortcutGroup>) -> ShortcutCategoryType?,
- )
-
companion object {
- private const val TAG = "SHCategoriesRepo"
+ private const val TAG = "ShortcutCategoriesUtils"
private val SUPPORTED_MODIFIERS =
listOf(
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
index 6f19561..39fc27d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
@@ -17,7 +17,7 @@
package com.android.systemui.keyboard.shortcut.domain.interactor
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperCategoriesRepository
+import com.android.systemui.keyboard.shortcut.data.repository.DefaultShortcutCategoriesRepository
import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
@@ -28,9 +28,7 @@
@SysUISingleton
class ShortcutHelperCategoriesInteractor
@Inject
-constructor(
- categoriesRepository: ShortcutHelperCategoriesRepository,
-) {
+constructor(categoriesRepository: DefaultShortcutCategoriesRepository) {
val shortcutCategories: Flow<List<ShortcutCategory>> =
categoriesRepository.categories.map { categories ->
@@ -42,12 +40,12 @@
shortcutCategory.subCategories.map {
ShortcutSubCategory(
label = it.label,
- shortcuts = groupShortcutsInSubcategory(it.shortcuts)
+ shortcuts = groupShortcutsInSubcategory(it.shortcuts),
)
}
return ShortcutCategory(
type = shortcutCategory.type,
- subCategories = subCategoriesWithGroupedShortcuts
+ subCategories = subCategoriesWithGroupedShortcuts,
)
}
@@ -59,7 +57,7 @@
Shortcut(
label = commonLabel,
icon = groupedShortcuts.firstOrNull()?.icon,
- commands = groupedShortcuts.flatMap { it.commands }
+ commands = groupedShortcuts.flatMap { it.commands },
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
index c89ef15..813a1fca 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
@@ -17,24 +17,36 @@
package com.android.systemui.keyboard.shortcut.shared.model
sealed interface ShortcutCategoryType {
- data object System : ShortcutCategoryType
+ val isTrusted: Boolean
- data object MultiTasking : ShortcutCategoryType
+ data object System : ShortcutCategoryType {
+ override val isTrusted: Boolean = true
+ }
- data object InputMethodEditor : ShortcutCategoryType
+ data object MultiTasking : ShortcutCategoryType {
+ override val isTrusted: Boolean = true
+ }
- data object AppCategories : ShortcutCategoryType
+ data object InputMethodEditor : ShortcutCategoryType {
+ override val isTrusted: Boolean = false
+ }
- data class CurrentApp(val packageName: String) : ShortcutCategoryType
+ data object AppCategories : ShortcutCategoryType {
+ override val isTrusted: Boolean = true
+ }
+
+ data class CurrentApp(val packageName: String) : ShortcutCategoryType {
+ override val isTrusted: Boolean = false
+ }
}
data class ShortcutCategory(
val type: ShortcutCategoryType,
- val subCategories: List<ShortcutSubCategory>
+ val subCategories: List<ShortcutSubCategory>,
) {
constructor(
type: ShortcutCategoryType,
- vararg subCategories: ShortcutSubCategory
+ vararg subCategories: ShortcutSubCategory,
) : this(type, subCategories.asList())
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt
index 28451ae..c7e6b43 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt
@@ -18,10 +18,11 @@
import androidx.annotation.DrawableRes
-data class ShortcutCommand(val keys: List<ShortcutKey>)
+data class ShortcutCommand(val keys: List<ShortcutKey>, val isCustom: Boolean = false)
class ShortcutCommandBuilder {
private val keys = mutableListOf<ShortcutKey>()
+ private var isCustom = false
fun key(text: String) {
keys += ShortcutKey.Text(text)
@@ -31,7 +32,11 @@
keys += ShortcutKey.Icon.ResIdIcon(drawableResId)
}
- fun build() = ShortcutCommand(keys)
+ fun isCustom(isCustom: Boolean) {
+ this.isCustom = isCustom
+ }
+
+ fun build() = ShortcutCommand(keys, isCustom)
}
fun shortcutCommand(block: ShortcutCommandBuilder.() -> Unit) =
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt
index 807c70b..10a201e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt
@@ -27,6 +27,7 @@
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
@@ -36,6 +37,7 @@
import com.android.systemui.keyboard.shortcut.ui.composable.getWidth
import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutHelperViewModel
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import com.android.systemui.statusbar.phone.createBottomSheet
import javax.inject.Inject
@@ -86,6 +88,7 @@
},
)
dialog.setOnDismissListener { shortcutHelperViewModel.onViewClosed() }
+ dialog.setTitle(stringResource(R.string.shortcut_helper_title))
},
maxWidth = ShortcutHelperBottomSheet.LargeScreenWidthLandscape,
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
index 7292dda..b30e1e9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
@@ -26,6 +26,7 @@
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
@@ -44,7 +45,6 @@
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
-import com.android.app.tracing.coroutines.launchTraced as launch
object KeyguardClockViewBinder {
private val TAG = KeyguardClockViewBinder::class.simpleName!!
@@ -133,15 +133,26 @@
launch {
if (!MigrateClocksToBlueprint.isEnabled) return@launch
aodBurnInViewModel.movement.collect { burnInModel ->
- viewModel.currentClock.value?.let {
- it.largeClock.layout.applyAodBurnIn(
+ viewModel.currentClock.value
+ ?.largeClock
+ ?.layout
+ ?.applyAodBurnIn(
AodClockBurnInModel(
translationX = burnInModel.translationX.toFloat(),
translationY = burnInModel.translationY.toFloat(),
scale = burnInModel.scale,
)
)
- }
+ }
+ }
+
+ launch {
+ if (!MigrateClocksToBlueprint.isEnabled) return@launch
+ viewModel.largeClockTextSize.collect { fontSizePx ->
+ viewModel.currentClock.value
+ ?.largeClock
+ ?.events
+ ?.onFontSettingChanged(fontSizePx = fontSizePx.toFloat())
}
}
}
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 82adced..3a7a640 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
@@ -187,6 +187,9 @@
val largeClockTopMargin: Flow<Int> =
configurationInteractor.onAnyConfigurationChange.map { getLargeClockTopMargin() }
+ val largeClockTextSize: Flow<Int> =
+ configurationInteractor.dimensionPixelSize(customR.dimen.large_clock_text_size)
+
enum class ClockLayout {
LARGE_CLOCK,
SMALL_CLOCK,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index 51ff598..ec6a17b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -70,6 +70,7 @@
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInRoot
+import androidx.compose.ui.layout.positionOnScreen
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.stringResource
@@ -124,6 +125,7 @@
import com.android.systemui.qs.ui.composable.QuickSettingsTheme
import com.android.systemui.res.R
import com.android.systemui.util.LifecycleFragment
+import com.android.systemui.util.animation.UniqueObjectHostView
import com.android.systemui.util.asIndenting
import com.android.systemui.util.printSection
import com.android.systemui.util.println
@@ -447,8 +449,7 @@
}
override fun setShouldUpdateSquishinessOnMedia(shouldUpdate: Boolean) {
- super.setShouldUpdateSquishinessOnMedia(shouldUpdate)
- // TODO (b/353253280)
+ viewModel.shouldUpdateSquishinessOnMedia = shouldUpdate
}
override fun setInSplitShade(isInSplitShade: Boolean) {
@@ -660,7 +661,20 @@
Column(
modifier =
- Modifier.offset {
+ Modifier.onPlaced { coordinates ->
+ val positionOnScreen = coordinates.positionOnScreen()
+ val left = positionOnScreen.x
+ val right = left + coordinates.size.width
+ val top = positionOnScreen.y
+ val bottom = top + coordinates.size.height
+ viewModel.applyNewQsScrollerBounds(
+ left = left,
+ top = top,
+ right = right,
+ bottom = bottom,
+ )
+ }
+ .offset {
IntOffset(
x = 0,
y = viewModel.qsScrollTranslationY.fastRoundToInt(),
@@ -704,7 +718,10 @@
val Media =
@Composable {
if (viewModel.qsMediaVisible) {
- MediaObject(mediaHost = viewModel.qsMediaHost)
+ MediaObject(
+ mediaHost = viewModel.qsMediaHost,
+ update = { translationY = viewModel.qsMediaTranslationY },
+ )
}
}
Box(
@@ -987,7 +1004,11 @@
}
@Composable
-private fun MediaObject(mediaHost: MediaHost, modifier: Modifier = Modifier) {
+private fun MediaObject(
+ mediaHost: MediaHost,
+ modifier: Modifier = Modifier,
+ update: UniqueObjectHostView.() -> Unit = {},
+) {
Box {
AndroidView(
modifier = modifier,
@@ -1000,6 +1021,7 @@
)
}
},
+ update = { view -> view.update() },
onReset = {},
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
index e912a0c..9029563 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -26,6 +26,7 @@
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.lifecycle.LifecycleCoroutineScope
+import com.android.app.animation.Interpolators
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.keyguard.BouncerPanelExpansionCalculator
import com.android.systemui.Dumpable
@@ -270,6 +271,23 @@
val qsMediaInRow: Boolean
get() = qsMediaInRowViewModel.shouldMediaShowInRow
+ var shouldUpdateSquishinessOnMedia by mutableStateOf(false)
+
+ val qsMediaTranslationY by derivedStateOf {
+ if (
+ qsExpansion > 0f &&
+ !isKeyguardState &&
+ !qqsMediaVisible &&
+ !qsMediaInRow &&
+ !isInSplitShade
+ ) {
+ val interpolation = Interpolators.ACCELERATE.getInterpolation(1f - qsExpansion)
+ -qsMediaHost.hostView.height * 1.3f * interpolation
+ } else {
+ 0f
+ }
+ }
+
val animateTilesExpansion: Boolean
get() = inFirstPage && !mediaSuddenlyAppearingInLandscape
@@ -297,6 +315,18 @@
MediaHostState.EXPANDED
}
+ private val shouldApplySquishinessToMedia by derivedStateOf {
+ shouldUpdateSquishinessOnMedia || (isInSplitShade && statusBarState == StatusBarState.SHADE)
+ }
+
+ private val mediaSquishiness by derivedStateOf {
+ if (shouldApplySquishinessToMedia) {
+ squishinessFraction
+ } else {
+ 1f
+ }
+ }
+
private var qsBounds by mutableStateOf(Rect())
private val constrainedSquishinessFraction: Float
@@ -355,8 +385,6 @@
private val isOverscrolling: Boolean
get() = overScrollAmount != 0
- private var shouldUpdateMediaSquishiness by mutableStateOf(false)
-
private val forceQs by derivedStateOf {
(isQsExpanded || isStackScrollerOverscrolling) &&
(isKeyguardState && !showCollapsedOnKeyguard)
@@ -394,11 +422,26 @@
),
)
+ fun applyNewQsScrollerBounds(left: Float, top: Float, right: Float, bottom: Float) {
+ if (usingMedia) {
+ qsMediaHost.currentClipping.set(
+ left.toInt(),
+ top.toInt(),
+ right.toInt(),
+ bottom.toInt(),
+ )
+ }
+ }
+
override suspend fun onActivated(): Nothing {
initMediaHosts() // init regardless of using media (same as current QS).
coroutineScope {
launch { hydrateSquishinessInteractor() }
- launch { hydrateQqsMediaExpansion() }
+ if (usingMedia) {
+ launch { hydrateQqsMediaExpansion() }
+ launch { hydrateMediaSquishiness() }
+ launch { hydrateMediaDisappearParameters() }
+ }
launch { hydrator.activate() }
launch { containerViewModel.activate() }
launch { qqsMediaInRowViewModel.activate() }
@@ -429,6 +472,21 @@
snapshotFlow { qqsMediaExpansion }.collect { qqsMediaHost.expansion = it }
}
+ private suspend fun hydrateMediaSquishiness() {
+ snapshotFlow { mediaSquishiness }.collect { qsMediaHost.squishFraction = it }
+ }
+
+ private suspend fun hydrateMediaDisappearParameters() {
+ coroutineScope {
+ launch {
+ snapshotFlow { qqsMediaInRow }.collect { qqsMediaHost.applyDisappearParameters(it) }
+ }
+ launch {
+ snapshotFlow { qsMediaInRow }.collect { qsMediaHost.applyDisappearParameters(it) }
+ }
+ }
+ }
+
override fun dump(pw: PrintWriter, args: Array<out String>) {
pw.asIndenting().run {
printSection("Quick Settings state") {
@@ -474,6 +532,9 @@
println("qsMediaInRow", qsMediaInRow)
println("collapsedLandscapeMedia", collapsedLandscapeMedia)
println("qqsMediaExpansion", qqsMediaExpansion)
+ println("shouldUpdateSquishinessOnMedia", shouldUpdateSquishinessOnMedia)
+ println("mediaSquishiness", mediaSquishiness)
+ println("qsMediaTranslationY", qsMediaTranslationY)
}
}
}
@@ -510,3 +571,22 @@
// lazily.
.onStart { emit(mediaHost.visible) }
}
+
+// Taken from QSPanelControllerBase
+private fun MediaHost.applyDisappearParameters(inRow: Boolean) {
+ disappearParameters.apply {
+ fadeStartPosition = 0.95f
+ disappearStart = 0f
+ if (inRow) {
+ disappearSize.set(0f, 0.4f)
+ gonePivot.set(1f, 0f)
+ contentTranslationFraction.set(0.25f, 1f)
+ disappearEnd = 0.6f
+ } else {
+ disappearSize.set(1f, 0f)
+ gonePivot.set(0f, 0f)
+ contentTranslationFraction.set(0f, 1f)
+ disappearEnd = 0.95f
+ }
+ }
+}
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
index 4a51bf0..177a5be 100644
--- 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
@@ -17,6 +17,7 @@
package com.android.systemui.qs.panels.ui.compose.infinitegrid
import android.graphics.drawable.Animatable
+import android.graphics.drawable.Drawable
import android.text.TextUtils
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
@@ -30,7 +31,9 @@
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicText
import androidx.compose.material3.MaterialTheme
@@ -68,6 +71,8 @@
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.SideIconHeight
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.SideIconWidth
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
@@ -79,6 +84,7 @@
label: String,
secondaryLabel: String?,
icon: Icon,
+ sideDrawable: Drawable?,
colors: TileColors,
squishiness: () -> Float,
accessibilityUiState: AccessibilityUiState? = null,
@@ -135,6 +141,14 @@
colors = colors,
accessibilityUiState = accessibilityUiState,
)
+
+ if (sideDrawable != null) {
+ Image(
+ painter = rememberDrawablePainter(sideDrawable),
+ contentDescription = null,
+ modifier = Modifier.width(SideIconWidth).height(SideIconHeight),
+ )
+ }
}
}
@@ -229,6 +243,8 @@
object CommonTileDefaults {
val IconSize = 32.dp
val LargeTileIconSize = 28.dp
+ val SideIconWidth = 32.dp
+ val SideIconHeight = 20.dp
val ToggleTargetSize = 56.dp
val TileHeight = 72.dp
val TilePadding = 8.dp
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
index 9bbf290..fe59c4d3 100644
--- 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
@@ -130,15 +130,7 @@
// TODO(b/361789146): Draw the shapes instead of clipping
val tileShape = TileDefaults.animateTileShape(uiState.state)
- val animatedColor by
- animateColorAsState(
- if (iconOnly || !uiState.handlesSecondaryClick) {
- colors.iconBackground
- } else {
- colors.background
- },
- label = "QSTileBackgroundColor",
- )
+ val animatedColor by animateColorAsState(colors.background, label = "QSTileBackgroundColor")
TileExpandable(
color = { animatedColor },
@@ -199,6 +191,7 @@
label = uiState.label,
secondaryLabel = uiState.secondaryLabel,
icon = icon,
+ sideDrawable = uiState.sideDrawable,
colors = colors,
iconShape = iconShape,
toggleClick = secondaryClick,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
index 56675e4..2fc7f0f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.panels.ui.viewmodel
import android.content.res.Resources
+import android.graphics.drawable.Drawable
import android.service.quicksettings.Tile
import android.text.TextUtils
import android.widget.Switch
@@ -36,6 +37,7 @@
val handlesLongClick: Boolean,
val handlesSecondaryClick: Boolean,
val icon: Supplier<QSTile.Icon?>,
+ val sideDrawable: Drawable?,
val accessibilityUiState: AccessibilityUiState,
)
@@ -90,6 +92,7 @@
handlesLongClick = handlesLongClick,
handlesSecondaryClick = handlesSecondaryClick,
icon = icon?.let { Supplier { icon } } ?: iconSupplier ?: Supplier { null },
+ sideDrawable = sideViewCustomDrawable,
AccessibilityUiState(
contentDescription?.toString() ?: "",
stateDescription.toString(),
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/UserAwareConnection.kt b/packages/SystemUI/src/com/android/systemui/recordissue/UserAwareConnection.kt
index 6aaa27d..eca4051 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/UserAwareConnection.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/UserAwareConnection.kt
@@ -69,7 +69,11 @@
@WorkerThread
fun doUnBind() {
if (shouldUnBind) {
- userContextProvider.userContext.unbindService(this)
+ try {
+ userContextProvider.userContext.unbindService(this)
+ } catch (e: IllegalArgumentException) {
+ Log.e(TAG, "Can't disconnect because service wasn't connected anyways.", e)
+ }
shouldUnBind = false
}
}
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 580a51a..daeaaa5 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
@@ -373,6 +373,7 @@
"device was unlocked with alternate bouncer showing" +
" and shade didn't need to be left open"
} else {
+ replaceLockscreenSceneOnBackStack()
null
}
}
@@ -391,16 +392,7 @@
val prevScene = previousScene.value
val targetScene = prevScene ?: Scenes.Gone
if (targetScene != Scenes.Gone) {
- sceneBackInteractor.updateBackStack { stack ->
- val list = stack.asIterable().toMutableList()
- check(list.last() == Scenes.Lockscreen) {
- "The bottommost/last SceneKey of the back stack isn't" +
- " the Lockscreen scene like expected. The back" +
- " stack is $stack."
- }
- list[list.size - 1] = Scenes.Gone
- sceneStackOf(*list.toTypedArray())
- }
+ replaceLockscreenSceneOnBackStack()
}
targetScene to
"device was unlocked with primary bouncer showing," +
@@ -435,6 +427,20 @@
}
}
+ /** If the [Scenes.Lockscreen] is on the backstack, replaces it with [Scenes.Gone]. */
+ private fun replaceLockscreenSceneOnBackStack() {
+ sceneBackInteractor.updateBackStack { stack ->
+ val list = stack.asIterable().toMutableList()
+ check(list.last() == Scenes.Lockscreen) {
+ "The bottommost/last SceneKey of the back stack isn't" +
+ " the Lockscreen scene like expected. The back" +
+ " stack is $stack."
+ }
+ list[list.size - 1] = Scenes.Gone
+ sceneStackOf(*list.toTypedArray())
+ }
+ }
+
private fun handlePowerState() {
applicationScope.launch {
powerInteractor.detailedWakefulness.collect { wakefulness ->
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 24dba59..4d77e3e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -17,8 +17,6 @@
package com.android.systemui.shade;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
-import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
@@ -27,16 +25,13 @@
import android.content.Context;
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;
import android.os.RemoteException;
import android.os.Trace;
import android.util.Log;
import android.view.Display;
-import android.view.Gravity;
import android.view.IWindow;
import android.view.IWindowSession;
import android.view.View;
@@ -271,33 +266,7 @@
// Now that the notification shade encompasses the sliding panel and its
// translucent backdrop, the entire thing is made TRANSLUCENT and is
// hardware-accelerated.
- mLp = new LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT,
- LayoutParams.TYPE_NOTIFICATION_SHADE,
- LayoutParams.FLAG_NOT_FOCUSABLE
- | LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
- | LayoutParams.FLAG_SPLIT_TOUCH
- | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
- | LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
- PixelFormat.TRANSLUCENT);
- mLp.token = new Binder();
- mLp.gravity = Gravity.TOP;
- mLp.setFitInsetsTypes(0 /* types */);
- mLp.setTitle("NotificationShade");
- mLp.packageName = mContext.getPackageName();
- mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- mLp.privateFlags |= PRIVATE_FLAG_OPTIMIZE_MEASURE;
-
- if (SceneContainerFlag.isEnabled()) {
- // This prevents the appearance and disappearance of the software keyboard (also known
- // as the "IME") from scrolling/panning the window to make room for the keyboard.
- //
- // The scene container logic does its own adjustment and animation when the IME appears
- // or disappears.
- mLp.softInputMode = LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
- }
-
+ mLp = ShadeWindowLayoutParams.INSTANCE.create(mContext);
mWindowManager.addView(mWindowRootView, mLp);
// We use BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE here, however, there is special logic in
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
index 63510b8..e15830e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
@@ -157,7 +157,6 @@
@SysUISingleton
@Provides
- @ShadeDisplayAware
fun provideShadePositionRepository(impl: ShadePositionRepositoryImpl): ShadePositionRepository {
ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
return impl
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLayoutParams.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLayoutParams.kt
new file mode 100644
index 0000000..6bb50f9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLayoutParams.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.content.Context
+import android.graphics.PixelFormat
+import android.os.Binder
+import android.view.Gravity
+import android.view.ViewGroup
+import android.view.WindowManager.LayoutParams
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+
+object ShadeWindowLayoutParams {
+ /**
+ * Creates [LayoutParams] for the shade window.
+ *
+ * This is extracted to a single place as those layout params will be used by several places:
+ * - When sysui starts, and the shade is added the first time
+ * - When the shade moves to a different window (e.g. while an external display is connected)
+ */
+ fun create(context: Context): LayoutParams {
+ return LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ LayoutParams.TYPE_NOTIFICATION_SHADE,
+ LayoutParams.FLAG_NOT_FOCUSABLE or
+ LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING or
+ LayoutParams.FLAG_SPLIT_TOUCH or
+ LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
+ LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
+ // Now that the notification shade encompasses the sliding panel and its
+ // translucent backdrop, the entire thing is made TRANSLUCENT and is
+ // hardware-accelerated.
+ PixelFormat.TRANSLUCENT,
+ )
+ .apply {
+ token = Binder()
+ gravity = Gravity.TOP
+ fitInsetsTypes = 0
+ title = "NotificationShade"
+ packageName = context.packageName
+ layoutInDisplayCutoutMode = LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+ privateFlags = privateFlags or LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE
+ if (SceneContainerFlag.isEnabled) {
+ // This prevents the appearance and disappearance of the software keyboard (also
+ // known as the "IME") from scrolling/panning the window to make room for the
+ // keyboard.
+ //
+ // The scene container logic does its own adjustment and animation when the IME
+ // appears or disappears.
+ softInputMode = LayoutParams.SOFT_INPUT_ADJUST_NOTHING
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadePositionRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadePositionRepository.kt
new file mode 100644
index 0000000..37210b9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadePositionRepository.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.data.repository
+
+import android.view.Display
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+class FakeShadePositionRepository : ShadePositionRepository {
+ private val _displayId = MutableStateFlow(Display.DEFAULT_DISPLAY)
+
+ override fun setDisplayId(displayId: Int) {
+ _displayId.value = displayId
+ }
+
+ override val displayId: StateFlow<Int>
+ get() = _displayId
+
+ override fun resetDisplayId() {
+ _displayId.value = Display.DEFAULT_DISPLAY
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt b/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt
index 6f492cf..c23ff53 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt
@@ -32,7 +32,7 @@
/** Is the refactor enabled */
@JvmStatic
- inline val isEnabled
+ inline val isEnabled: Boolean
get() = Flags.shadeWindowGoesAround()
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
index 564d52a..1cb4c44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
@@ -27,7 +27,10 @@
interface SystemStatusAnimationScheduler :
CallbackController<SystemStatusAnimationCallback>, Dumpable {
- /** StateFlow holding the current [SystemEventAnimationState] at any time. */
+ /**
+ * The current state of the animation. This can be used from compose functions to coordinate
+ * their animations with the chip
+ */
val animationState: StateFlow<SystemEventAnimationState>
fun onStatusEvent(event: StatusEvent)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/data/repository/SystemStatusEventAnimationRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/data/repository/SystemStatusEventAnimationRepository.kt
new file mode 100644
index 0000000..971f5d1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/data/repository/SystemStatusEventAnimationRepository.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.events.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler
+import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState
+import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
+
+/** Repository to expose the [SystemStatusAnimationScheduler] state via flows */
+interface SystemStatusEventAnimationRepository {
+ val animationState: StateFlow<SystemEventAnimationState>
+}
+
+@SysUISingleton
+class SystemStatusEventAnimationRepositoryImpl
+@Inject
+constructor(scheduler: SystemStatusAnimationScheduler) : SystemStatusEventAnimationRepository {
+ override val animationState = scheduler.animationState
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/domain/interactor/SystemStatusEventAnimationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/domain/interactor/SystemStatusEventAnimationInteractor.kt
new file mode 100644
index 0000000..3e30642
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/domain/interactor/SystemStatusEventAnimationInteractor.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.events.domain.interactor
+
+import android.view.View
+import androidx.core.animation.Animator
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.events.data.repository.SystemStatusEventAnimationRepository
+import com.android.systemui.statusbar.phone.fragment.StatusBarSystemEventDefaultAnimator
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Interactor for dealing with system status event animations. This class can be used to monitor the
+ * current [animationState], and defines some common animation functions that an handle hiding
+ * system chrome in order to make space for the event chips
+ */
+@SysUISingleton
+class SystemStatusEventAnimationInteractor
+@Inject
+constructor(
+ repo: SystemStatusEventAnimationRepository,
+ configurationInteractor: ConfigurationInteractor,
+ @Application scope: CoroutineScope,
+) {
+ private val chipAnimateInTranslationX =
+ configurationInteractor
+ .dimensionPixelSize(R.dimen.ongoing_appops_chip_animation_in_status_bar_translation_x)
+ .stateIn(scope, SharingStarted.Eagerly, 0)
+
+ private val chipAnimateOutTranslationX =
+ configurationInteractor
+ .dimensionPixelSize(R.dimen.ongoing_appops_chip_animation_out_status_bar_translation_x)
+ .stateIn(scope, SharingStarted.Eagerly, 0)
+
+ val animationState = repo.animationState
+
+ private fun getDefaultStatusBarAnimationForChipEnter(
+ setX: (Float) -> Unit,
+ setAlpha: (Float) -> Unit,
+ ): Animator {
+ return StatusBarSystemEventDefaultAnimator.getDefaultStatusBarAnimationForChipEnter(
+ chipAnimateInTranslationX.value,
+ setX,
+ setAlpha,
+ )
+ }
+
+ private fun getDefaultStatusBarAnimationForChipExit(
+ setX: (Float) -> Unit,
+ setAlpha: (Float) -> Unit,
+ ): Animator {
+ return StatusBarSystemEventDefaultAnimator.getDefaultStatusBarAnimationForChipExit(
+ chipAnimateOutTranslationX.value,
+ setX,
+ setAlpha,
+ )
+ }
+
+ fun animateStatusBarContentForChipEnter(v: View) {
+ getDefaultStatusBarAnimationForChipEnter(setX = v::setTranslationX, setAlpha = v::setAlpha)
+ .start()
+ }
+
+ fun animateStatusBarContentForChipExit(v: View) {
+ v.translationX = chipAnimateOutTranslationX.value.toFloat()
+ getDefaultStatusBarAnimationForChipExit(setX = v::setTranslationX, setAlpha = v::setAlpha)
+ .start()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index e74ed8d..c487ff5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -49,6 +49,7 @@
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
+import android.util.Log;
import android.view.ContentInfo;
import androidx.annotation.NonNull;
@@ -65,6 +66,7 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.icon.IconPack;
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
import com.android.systemui.statusbar.notification.row.NotificationGuts;
@@ -194,6 +196,10 @@
*/
private boolean mIsDemoted = false;
+ // TODO(b/377565433): Move into NotificationContentModel during/after
+ // NotificationRowContentBinderRefactor.
+ private PromotedNotificationContentModel mPromotedNotificationContentModel;
+
/**
* True if both
* 1) app provided full screen intent but does not have the permission to send it
@@ -1061,6 +1067,32 @@
this.mHeadsUpStatusBarTextPublic.setValue(headsUpStatusBarModel.getPublicText());
}
+ /**
+ * Gets the content needed to render this notification as a promoted notification on various
+ * surfaces (like status bar chips and AOD).
+ */
+ public PromotedNotificationContentModel getPromotedNotificationContentModel() {
+ if (PromotedNotificationContentModel.featureFlagEnabled()) {
+ return mPromotedNotificationContentModel;
+ } else {
+ Log.wtf(TAG, "getting promoted content without feature flag enabled");
+ return null;
+ }
+ }
+
+ /**
+ * Sets the content needed to render this notification as a promoted notification on various
+ * surfaces (like status bar chips and AOD).
+ */
+ public void setPromotedNotificationContentModel(
+ @Nullable PromotedNotificationContentModel promotedNotificationContentModel) {
+ if (PromotedNotificationContentModel.featureFlagEnabled()) {
+ this.mPromotedNotificationContentModel = promotedNotificationContentModel;
+ } else {
+ Log.wtf(TAG, "setting promoted content without feature flag enabled");
+ }
+ }
+
/** Information about a suggestion that is being edited. */
public static class EditedSuggestionInfo {
@@ -1101,4 +1133,6 @@
private static final long INITIALIZATION_DELAY = 400;
private static final long NOT_LAUNCHED_YET = -LAUNCH_COOLDOWN;
private static final int COLOR_INVALID = 1;
+
+ private static final String TAG = "NotificationEntry";
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
index 23da90d..8edbc5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
@@ -34,6 +34,7 @@
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationsProvider
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationEntryModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
@@ -173,6 +174,7 @@
isGroupSummary = sbn.notification.isGroupSummary,
bucket = bucket,
callType = sbn.toCallType(),
+ promotedContent = promotedNotificationContentModel,
)
}
}
@@ -199,6 +201,7 @@
isGroupSummary: Boolean,
bucket: Int,
callType: CallType,
+ promotedContent: PromotedNotificationContentModel?,
): ActiveNotificationModel {
return individuals[key]?.takeIf {
it.isCurrent(
@@ -223,6 +226,7 @@
contentIntent = contentIntent,
bucket = bucket,
callType = callType,
+ promotedContent = promotedContent,
)
}
?: ActiveNotificationModel(
@@ -247,6 +251,7 @@
contentIntent = contentIntent,
bucket = bucket,
callType = callType,
+ promotedContent = promotedContent,
)
}
@@ -272,6 +277,7 @@
isGroupSummary: Boolean,
bucket: Int,
callType: CallType,
+ promotedContent: PromotedNotificationContentModel?,
): Boolean {
return when {
key != this.key -> false
@@ -295,6 +301,9 @@
contentIntent != this.contentIntent -> false
bucket != this.bucket -> false
callType != this.callType -> false
+ // QQQ: Do we need to do the same `isCurrent` thing within the content model to avoid
+ // recreating the active notification model constantly?
+ promotedContent != this.promotedContent -> false
else -> true
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
new file mode 100644
index 0000000..41ee3b9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.promoted.shared.model
+
+import android.annotation.DrawableRes
+import android.graphics.drawable.Icon
+import com.android.internal.widget.NotificationProgressModel
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
+
+/**
+ * The content needed to render a promoted notification to surfaces besides the notification stack,
+ * like the skeleton view on AOD or the status bar chip.
+ */
+data class PromotedNotificationContentModel(
+ val key: String,
+
+ // for all styles:
+ val skeletonSmallIcon: Icon?, // TODO(b/377568176): Make into an IconModel.
+ val appName: CharSequence?,
+ val subText: CharSequence?,
+ val time: When?,
+ val lastAudiblyAlertedMs: Long,
+ @DrawableRes val profileBadgeResId: Int?,
+ val title: CharSequence?,
+ val text: CharSequence?,
+ val skeletonLargeIcon: Icon?, // TODO(b/377568176): Make into an IconModel.
+ val style: Style,
+
+ // for CallStyle:
+ val personIcon: Icon?, // TODO(b/377568176): Make into an IconModel.
+ val personName: CharSequence?,
+ val verificationIcon: Icon?, // TODO(b/377568176): Make into an IconModel.
+ val verificationText: CharSequence?,
+
+ // for ProgressStyle:
+ val progress: NotificationProgressModel?,
+) {
+ class Builder(val key: String) {
+ var skeletonSmallIcon: Icon? = null
+ var appName: CharSequence? = null
+ var subText: CharSequence? = null
+ var time: When? = null
+ var lastAudiblyAlertedMs: Long = 0L
+ @DrawableRes var profileBadgeResId: Int? = null
+ var title: CharSequence? = null
+ var text: CharSequence? = null
+ var skeletonLargeIcon: Icon? = null
+ var style: Style = Style.Ineligible
+
+ // for CallStyle:
+ var personIcon: Icon? = null
+ var personName: CharSequence? = null
+ var verificationIcon: Icon? = null
+ var verificationText: CharSequence? = null
+
+ // for ProgressStyle:
+ var progress: NotificationProgressModel? = null
+
+ fun build() =
+ PromotedNotificationContentModel(
+ key = key,
+ skeletonSmallIcon = skeletonSmallIcon,
+ appName = appName,
+ subText = subText,
+ time = time,
+ lastAudiblyAlertedMs = lastAudiblyAlertedMs,
+ profileBadgeResId = profileBadgeResId,
+ title = title,
+ text = text,
+ skeletonLargeIcon = skeletonLargeIcon,
+ style = style,
+ personIcon = personIcon,
+ personName = personName,
+ verificationIcon = verificationIcon,
+ verificationText = verificationText,
+ progress = progress,
+ )
+ }
+
+ /** The timestamp associated with a notification, along with the mode used to display it. */
+ data class When(val time: Long, val mode: Mode) {
+ /** The mode used to display a notification's `when` value. */
+ enum class Mode {
+ Absolute,
+ CountDown,
+ CountUp,
+ }
+ }
+
+ /** The promotion-eligible style of a notification, or [Style.Ineligible] if not. */
+ enum class Style {
+ BigPicture,
+ BigText,
+ Call,
+ Progress,
+ Ineligible,
+ }
+
+ companion object {
+ @JvmStatic
+ fun featureFlagEnabled(): Boolean =
+ PromotedNotificationUi.isEnabled || StatusBarNotifChips.isEnabled
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
index 19a92a2..a2b7155 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
@@ -17,7 +17,9 @@
import android.app.PendingIntent
import android.graphics.drawable.Icon
+import android.util.Log
import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.notification.stack.PriorityBucket
/**
@@ -36,6 +38,7 @@
val groupKey: String?,
/** When this notification was posted. */
val whenTime: Long,
+ // TODO(b/377566661): Make isPromoted just check if promotedContent != null.
/** True if this notification should be promoted and false otherwise. */
val isPromoted: Boolean,
/** Is this entry in the ambient / minimized section (lowest priority)? */
@@ -78,7 +81,24 @@
@PriorityBucket val bucket: Int,
/** The call type set on the notification. */
val callType: CallType,
-) : ActiveNotificationEntryModel()
+ /**
+ * The content needed to render this as a promoted notification on various surfaces, or null if
+ * this notification cannot be rendered as a promoted notification.
+ */
+ val promotedContent: PromotedNotificationContentModel?,
+) : ActiveNotificationEntryModel() {
+ init {
+ if (!PromotedNotificationContentModel.featureFlagEnabled()) {
+ if (promotedContent != null) {
+ Log.wtf(TAG, "passing non-null promoted content without feature flag enabled")
+ }
+ }
+ }
+
+ companion object {
+ private const val TAG = "ActiveNotificationEntryModel"
+ }
+}
/** Model for a group of notifications. */
data class ActiveNotificationGroupModel(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index d991b1d..41db5f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -174,6 +174,14 @@
private val dumpableName = TAG + nameSuffix
private val commandName = StatusBarInsetsCommand.NAME + nameSuffix
+ init {
+ if (!StatusBarConnectedDisplays.isEnabled) {
+ // Call start(), since it is not called when the flag is disabled, to keep the old
+ // behavior as it was.
+ start()
+ }
+ }
+
override fun start() {
configurationController.addCallback(this)
dumpManager.registerDumpable(dumpableName, this)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 0c511aea..aef26de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -520,6 +520,7 @@
mListenForCanShowAlternateBouncer.cancel(null);
}
mListenForCanShowAlternateBouncer = null;
+
// Collector that keeps the AlternateBouncerInteractor#canShowAlternateBouncer flow hot.
mListenForCanShowAlternateBouncer = mJavaAdapter.alwaysCollectFlow(
mAlternateBouncerInteractor.getCanShowAlternateBouncer(),
@@ -568,6 +569,12 @@
}
private void consumeCanShowAlternateBouncer(boolean canShow) {
+ if (SceneContainerFlag.isEnabled()) {
+ // When the scene framework is enabled, the alternative bouncer is hidden from the scene
+ // framework logic so there's no need for this logic here.
+ return;
+ }
+
// Hack: this is required to fix issues where
// KeyguardBouncerRepository#alternateBouncerVisible state is incorrectly set and then never
// reset. This is caused by usages of show()/forceShow() that only read this flow to set the
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 c55a63c..23b4b65 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
@@ -376,7 +376,11 @@
mCarrierConfigTracker.addDefaultDataSubscriptionChangedListener(mDefaultDataListener);
mHomeStatusBarViewBinder.bind(
- mStatusBar, mHomeStatusBarViewModel, mStatusBarVisibilityChangeListener);
+ mStatusBar,
+ mHomeStatusBarViewModel,
+ /* systemEventChipAnimateIn */ null,
+ /* systemEventChipAnimateOut */ null,
+ mStatusBarVisibilityChangeListener);
}
private String getDumpableName() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt
index 1f9ea08..fd7bce0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt
@@ -71,49 +71,87 @@
override fun onSystemEventAnimationBegin(): Animator {
isAnimationRunning = true
- val moveOut =
- ValueAnimator.ofFloat(0f, 1f).apply {
- duration = 23.frames
- interpolator = STATUS_BAR_X_MOVE_OUT
- addUpdateListener {
- onTranslationXChanged(-(translationXIn * animatedValue as Float))
- }
- }
- val alphaOut =
- ValueAnimator.ofFloat(1f, 0f).apply {
- duration = 8.frames
- interpolator = null
- addUpdateListener { onAlphaChanged(animatedValue as Float) }
- }
-
- val animSet = AnimatorSet()
- animSet.playTogether(moveOut, alphaOut)
- return animSet
+ return getDefaultStatusBarAnimationForChipEnter(
+ translationXIn,
+ onTranslationXChanged,
+ onAlphaChanged,
+ )
}
override fun onSystemEventAnimationFinish(hasPersistentDot: Boolean): Animator {
onTranslationXChanged(translationXOut.toFloat())
- val moveIn =
- ValueAnimator.ofFloat(1f, 0f).apply {
- duration = 23.frames
- startDelay = 7.frames
- interpolator = STATUS_BAR_X_MOVE_IN
- addUpdateListener {
- onTranslationXChanged(translationXOut * animatedValue as Float)
- }
- }
- val alphaIn =
- ValueAnimator.ofFloat(0f, 1f).apply {
- duration = 5.frames
- startDelay = 11.frames
- interpolator = null
- addUpdateListener { onAlphaChanged(animatedValue as Float) }
- }
+ val anim =
+ getDefaultStatusBarAnimationForChipExit(
+ translationXOut,
+ onTranslationXChanged,
+ onAlphaChanged,
+ )
+ anim.doOnEnd { isAnimationRunning = false }
+ anim.doOnCancel { isAnimationRunning = false }
- val animatorSet = AnimatorSet()
- animatorSet.playTogether(moveIn, alphaIn)
- animatorSet.doOnEnd { isAnimationRunning = false }
- animatorSet.doOnCancel { isAnimationRunning = false }
- return animatorSet
+ return anim
+ }
+
+ /** Static definition of these animations so we can use them more easily from view binders */
+ companion object {
+ /**
+ * Chip: coming in. Animated view: going out.
+ *
+ * Implements the exact spec for animating any status bar elements OUT to make space for the
+ * chip IN animation.
+ */
+ fun getDefaultStatusBarAnimationForChipEnter(
+ targetTranslation: Int,
+ setX: (Float) -> Unit,
+ setAlpha: (Float) -> Unit,
+ ): Animator {
+ val moveOut =
+ ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = 23.frames
+ interpolator = STATUS_BAR_X_MOVE_OUT
+ addUpdateListener { setX(-(targetTranslation * animatedValue as Float)) }
+ }
+ val alphaOut =
+ ValueAnimator.ofFloat(1f, 0f).apply {
+ duration = 8.frames
+ interpolator = null
+ addUpdateListener { setAlpha(animatedValue as Float) }
+ }
+
+ val animSet = AnimatorSet()
+ animSet.playTogether(moveOut, alphaOut)
+ return animSet
+ }
+
+ /**
+ * Chip: going out. Animated view: coming in.
+ *
+ * Implements the exact spec for animating any status bar elements IN as the chip is
+ * animating OUT
+ */
+ fun getDefaultStatusBarAnimationForChipExit(
+ targetTranslation: Int,
+ setX: (Float) -> Unit,
+ setAlpha: (Float) -> Unit,
+ ): Animator {
+ val moveIn =
+ ValueAnimator.ofFloat(1f, 0f).apply {
+ duration = 23.frames
+ startDelay = 7.frames
+ interpolator = STATUS_BAR_X_MOVE_IN
+ addUpdateListener { setX(targetTranslation * animatedValue as Float) }
+ }
+ val alphaIn =
+ ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = 5.frames
+ startDelay = 11.frames
+ interpolator = null
+ addUpdateListener { setAlpha(animatedValue as Float) }
+ }
+
+ val animatorSet = AnimatorSet()
+ animatorSet.playTogether(moveIn, alphaIn)
+ return animatorSet
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 935b101..bfdc8bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -23,6 +23,8 @@
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.statusbar.events.data.repository.SystemStatusEventAnimationRepository
+import com.android.systemui.statusbar.events.data.repository.SystemStatusEventAnimationRepositoryImpl
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl
import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
@@ -85,6 +87,11 @@
abstract fun connectivityRepository(impl: ConnectivityRepositoryImpl): ConnectivityRepository
@Binds
+ abstract fun systemStatusEventAnimationRepository(
+ impl: SystemStatusEventAnimationRepositoryImpl
+ ): SystemStatusEventAnimationRepository
+
+ @Binds
abstract fun realDeviceBasedSatelliteRepository(
impl: DeviceBasedSatelliteRepositoryImpl
): RealDeviceBasedSatelliteRepository
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
index 2177e02..72df027 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
@@ -32,6 +32,10 @@
import com.android.systemui.statusbar.chips.ui.binder.OngoingActivityChipBinder
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.core.StatusBarRootModernization
+import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState
+import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.AnimatingIn
+import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.AnimatingOut
+import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.RunningChipAnim
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel
@@ -46,10 +50,16 @@
/**
* Binds the view to the view-model. [listener] will be notified whenever an event that may
* change the status bar visibility occurs.
+ *
+ * Null chip animations are used when [StatusBarRootModernization] is off (i.e., when we are
+ * binding from the fragment). If non-null, they control the animation of the system icon area
+ * to support the chip animations.
*/
fun bind(
view: View,
viewModel: HomeStatusBarViewModel,
+ systemEventChipAnimateIn: ((View) -> Unit)?,
+ systemEventChipAnimateOut: ((View) -> Unit)?,
listener: StatusBarVisibilityChangeListener,
)
}
@@ -59,6 +69,8 @@
override fun bind(
view: View,
viewModel: HomeStatusBarViewModel,
+ systemEventChipAnimateIn: ((View) -> Unit)?,
+ systemEventChipAnimateOut: ((View) -> Unit)?,
listener: StatusBarVisibilityChangeListener,
) {
view.repeatWhenAttached {
@@ -95,6 +107,7 @@
when (primaryChipModel) {
is OngoingActivityChipModel.Shown ->
primaryChipView.show(shouldAnimateChange = true)
+
is OngoingActivityChipModel.Hidden ->
primaryChipView.hide(
state = View.GONE,
@@ -109,6 +122,7 @@
hasSecondaryOngoingActivity = false,
shouldAnimate = true,
)
+
is OngoingActivityChipModel.Hidden ->
listener.onOngoingActivityStatusChanged(
hasPrimaryOngoingActivity = false,
@@ -175,10 +189,30 @@
view.requireViewById<View>(R.id.status_bar_end_side_content)
// TODO(b/364360986): Also handle operator name view.
launch {
- viewModel.isSystemInfoVisible.collect {
- systemInfoView.adjustVisibility(it)
- // TODO(b/364360986): The system info view has a custom alpha controller
- // in CollapsedStatusBarFragment.
+ viewModel.systemInfoCombinedVis.collect { (baseVis, animState) ->
+ // Broadly speaking, the baseVis controls the view.visibility, and
+ // the animation state uses only alpha to achieve its effect. This
+ // means that we can always modify the visibility, and if we're
+ // animating we can use the animState to handle it. If we are not
+ // animating, then we can use the baseVis default animation
+ if (animState.isAnimatingChip()) {
+ // Just apply the visibility of the view, but don't animate
+ systemInfoView.visibility = baseVis.visibility
+ // Now apply the animation state, with its animator
+ when (animState) {
+ AnimatingIn -> {
+ systemEventChipAnimateIn?.invoke(systemInfoView)
+ }
+ AnimatingOut -> {
+ systemEventChipAnimateOut?.invoke(systemInfoView)
+ }
+ else -> {
+ // Nothing to do here
+ }
+ }
+ } else {
+ systemInfoView.adjustVisibility(baseVis)
+ }
}
}
}
@@ -186,6 +220,14 @@
}
}
+ private fun SystemEventAnimationState.isAnimatingChip() =
+ when (this) {
+ AnimatingIn,
+ AnimatingOut,
+ RunningChipAnim -> true
+ else -> false
+ }
+
private fun OngoingActivityChipModel.toVisibilityModel(): VisibilityModel {
return VisibilityModel(
visibility = if (this is OngoingActivityChipModel.Shown) View.VISIBLE else View.GONE,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
index a9c2f72..1faa9f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
@@ -35,6 +35,7 @@
import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.res.R
import com.android.systemui.statusbar.data.repository.DarkIconDispatcherStore
+import com.android.systemui.statusbar.events.domain.interactor.SystemStatusEventAnimationInteractor
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder
import com.android.systemui.statusbar.phone.NotificationIconContainer
import com.android.systemui.statusbar.phone.PhoneStatusBarView
@@ -59,6 +60,7 @@
private val iconController: StatusBarIconController,
private val ongoingCallController: OngoingCallController,
private val darkIconDispatcherStore: DarkIconDispatcherStore,
+ private val eventAnimationInteractor: SystemStatusEventAnimationInteractor,
) {
fun create(root: ViewGroup, andThen: (ViewGroup) -> Unit): ComposeView {
val composeView = ComposeView(root.context)
@@ -73,6 +75,7 @@
iconController = iconController,
ongoingCallController = ongoingCallController,
darkIconDispatcher = darkIconDispatcherStore.forDisplay(root.context.displayId),
+ eventAnimationInteractor = eventAnimationInteractor,
onViewCreated = andThen,
)
}
@@ -102,6 +105,7 @@
iconController: StatusBarIconController,
ongoingCallController: OngoingCallController,
darkIconDispatcher: DarkIconDispatcher,
+ eventAnimationInteractor: SystemStatusEventAnimationInteractor,
onViewCreated: (ViewGroup) -> Unit,
) {
// None of these methods are used when [StatusBarRootModernization] is on.
@@ -181,6 +185,8 @@
statusBarViewBinder.bind(
phoneStatusBarView,
statusBarViewModel,
+ eventAnimationInteractor::animateStatusBarContentForChipEnter,
+ eventAnimationInteractor::animateStatusBarContentForChipExit,
nopVisibilityChangeListener,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
index 4277a8b..6a9b43c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
@@ -35,6 +35,9 @@
import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModel
+import com.android.systemui.statusbar.events.domain.interactor.SystemStatusEventAnimationInteractor
+import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState
+import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import com.android.systemui.statusbar.phone.domain.interactor.LightsOutInteractor
@@ -95,7 +98,12 @@
val isClockVisible: Flow<VisibilityModel>
val isNotificationIconContainerVisible: Flow<VisibilityModel>
- val isSystemInfoVisible: Flow<VisibilityModel>
+ /**
+ * Pair of (system info visibility, event animation state). The animation state can be used to
+ * respond to the system event chip animations. In all cases, system info visibility correctly
+ * models the View.visibility for the system info area
+ */
+ val systemInfoCombinedVis: StateFlow<SystemInfoCombinedVisibilityModel>
/**
* Apps can request a low profile mode [android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE] where
@@ -114,6 +122,12 @@
/** True if a visibility change should be animated. */
val shouldAnimateChange: Boolean,
)
+
+ /** The combined visibility + animation state for the system info status bar area */
+ data class SystemInfoCombinedVisibilityModel(
+ val baseVisibility: VisibilityModel,
+ val animationState: SystemEventAnimationState,
+ )
}
@SysUISingleton
@@ -129,6 +143,7 @@
sceneContainerOcclusionInteractor: SceneContainerOcclusionInteractor,
shadeInteractor: ShadeInteractor,
ongoingActivityChipsViewModel: OngoingActivityChipsViewModel,
+ animations: SystemStatusEventAnimationInteractor,
@Application coroutineScope: CoroutineScope,
) : HomeStatusBarViewModel {
override val isTransitioningFromLockscreenToOccluded: StateFlow<Boolean> =
@@ -228,7 +243,7 @@
visibilityViaDisableFlags.animate,
)
}
- override val isSystemInfoVisible: Flow<VisibilityModel> =
+ private val isSystemInfoVisible =
combine(
shouldHomeStatusBarBeVisible,
collapsedStatusBarInteractor.visibilityViaDisableFlags,
@@ -238,6 +253,22 @@
VisibilityModel(showSystemInfo.toVisibilityInt(), visibilityViaDisableFlags.animate)
}
+ override val systemInfoCombinedVis =
+ combine(isSystemInfoVisible, animations.animationState) { sysInfoVisible, animationState ->
+ HomeStatusBarViewModel.SystemInfoCombinedVisibilityModel(
+ sysInfoVisible,
+ animationState,
+ )
+ }
+ .stateIn(
+ coroutineScope,
+ SharingStarted.WhileSubscribed(),
+ HomeStatusBarViewModel.SystemInfoCombinedVisibilityModel(
+ VisibilityModel(View.VISIBLE, false),
+ Idle,
+ ),
+ )
+
@View.Visibility
private fun Boolean.toVisibilityInt(): Int {
return if (this) View.VISIBLE else View.GONE
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitClockView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitClockView.java
deleted file mode 100644
index 0d36b48..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitClockView.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.policy;
-
-import android.app.ActivityManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.UserHandle;
-import android.text.format.DateFormat;
-import android.util.AttributeSet;
-import android.widget.LinearLayout;
-import android.widget.TextClock;
-
-import com.android.systemui.res.R;
-
-/**
- * Container for a clock which has two separate views for the clock itself and AM/PM indicator. This
- * is used to scale the clock independently of AM/PM.
- */
-public class SplitClockView extends LinearLayout {
-
- private TextClock mTimeView;
- private TextClock mAmPmView;
-
- private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (Intent.ACTION_TIME_CHANGED.equals(action)
- || Intent.ACTION_TIMEZONE_CHANGED.equals(action)
- || Intent.ACTION_LOCALE_CHANGED.equals(action)
- || Intent.ACTION_CONFIGURATION_CHANGED.equals(action)
- || Intent.ACTION_USER_SWITCHED.equals(action)) {
- updatePatterns();
- }
- }
- };
-
- public SplitClockView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mTimeView = findViewById(R.id.time_view);
- mAmPmView = findViewById(R.id.am_pm_view);
- mTimeView.setShowCurrentUserTime(true);
- mAmPmView.setShowCurrentUserTime(true);
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_TIME_CHANGED);
- filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
- filter.addAction(Intent.ACTION_LOCALE_CHANGED);
- filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
- filter.addAction(Intent.ACTION_USER_SWITCHED);
- getContext().registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter, null, null);
-
- updatePatterns();
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- getContext().unregisterReceiver(mIntentReceiver);
- }
-
- private void updatePatterns() {
- String formatString = DateFormat.getTimeFormatString(getContext(),
- ActivityManager.getCurrentUser());
- int index = getAmPmPartEndIndex(formatString);
- String timeString;
- String amPmString;
- if (index == -1) {
- timeString = formatString;
- amPmString = "";
- } else {
- timeString = formatString.substring(0, index);
- amPmString = formatString.substring(index);
- }
- mTimeView.setFormat12Hour(timeString);
- mTimeView.setFormat24Hour(timeString);
- mTimeView.setContentDescriptionFormat12Hour(formatString);
- mTimeView.setContentDescriptionFormat24Hour(formatString);
- mAmPmView.setFormat12Hour(amPmString);
- mAmPmView.setFormat24Hour(amPmString);
- }
-
- /**
- * @return the index where the AM/PM part starts at the end in {@code formatString} including
- * leading white spaces or {@code -1} if no AM/PM part is found or {@code formatString}
- * doesn't end with AM/PM part
- */
- private static int getAmPmPartEndIndex(String formatString) {
- boolean hasAmPm = false;
- int length = formatString.length();
- for (int i = length - 1; i >= 0; i--) {
- char c = formatString.charAt(i);
- boolean isAmPm = c == 'a';
- boolean isWhitespace = Character.isWhitespace(c);
- if (isAmPm) {
- hasAmPm = true;
- }
- if (isAmPm || isWhitespace) {
- continue;
- }
- if (i == length - 1) {
-
- // First character was not AM/PM and not whitespace, so it's not ending with AM/PM.
- return -1;
- } else {
-
- // If we have AM/PM at all, return last index, or -1 to indicate that it's not
- // ending with AM/PM.
- return hasAmPm ? i + 1 : -1;
- }
- }
-
- // Only AM/PM and whitespaces? The whole string is AM/PM. Else: Only whitespaces in the
- // string.
- return hasAmPm ? 0 : -1;
- }
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java
index b79d39e..36d64a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java
@@ -39,7 +39,7 @@
import com.android.systemui.statusbar.data.repository.StatusBarConfigurationController;
/**
- * Status bar view.
+ * Status bar view
* We now extend WindowRootView so that we can host Compose views
*/
public class StatusBarWindowView extends FrameLayout {
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
index 3c06828..2a9b1b9 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
@@ -22,6 +22,7 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Tracing
import com.android.systemui.dagger.qualifiers.UiBackground
+import com.android.systemui.util.settings.SettingsSingleThreadBackground
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.CoroutineDispatcher
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
index 4fc9a7c..5c0cc81 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
@@ -18,9 +18,13 @@
import android.app.Dialog
import android.content.Context
+import android.graphics.PixelFormat
import android.os.Bundle
import android.view.MotionEvent
+import android.view.ViewGroup
+import android.view.WindowManager
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.res.R
import com.android.systemui.volume.Events
import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
import com.android.systemui.volume.dialog.ui.binder.VolumeDialogViewBinder
@@ -32,10 +36,34 @@
@Application context: Context,
private val viewBinder: VolumeDialogViewBinder,
private val visibilityInteractor: VolumeDialogVisibilityInteractor,
-) : Dialog(context) {
+) : Dialog(context, R.style.Theme_SystemUI_Dialog_Volume) {
+
+ init {
+ with(window!!) {
+ addFlags(
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
+ WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
+ WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
+ )
+ addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
+
+ setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)
+ setWindowAnimations(-1)
+ setFormat(PixelFormat.TRANSLUCENT)
+
+ attributes =
+ attributes.apply {
+ title = "VolumeDialog" // Not the same as Window#setTitle
+ }
+ setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
+ }
+ setCanceledOnTouchOutside(true)
+ }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ setContentView(R.layout.volume_dialog)
viewBinder.bind(this)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
index 78eabb2..d9a945c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
@@ -17,21 +17,22 @@
package com.android.systemui.volume.dialog.ui.binder
import android.app.Dialog
-import android.graphics.Color
-import android.graphics.PixelFormat
-import android.graphics.drawable.ColorDrawable
+import android.graphics.Rect
+import android.graphics.Region
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
-import android.view.Window
-import android.view.WindowManager
+import android.view.ViewTreeObserver
+import android.view.ViewTreeObserver.InternalInsetsInfo
+import android.widget.FrameLayout
+import androidx.annotation.GravityInt
import com.android.internal.view.RotationPolicy
import com.android.systemui.lifecycle.WindowLifecycleState
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.lifecycle.viewModel
import com.android.systemui.res.R
+import com.android.systemui.util.children
import com.android.systemui.volume.SystemUIInterpolators
-import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
import com.android.systemui.volume.dialog.ringer.ui.binder.VolumeDialogRingerViewBinder
import com.android.systemui.volume.dialog.settings.ui.binder.VolumeDialogSettingsButtonViewBinder
@@ -52,6 +53,8 @@
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
/** Binds the root view of the Volume Dialog. */
@OptIn(ExperimentalCoroutinesApi::class)
@@ -61,65 +64,41 @@
constructor(
private val volumeResources: VolumeDialogResources,
private val gravityViewModel: VolumeDialogGravityViewModel,
- private val viewModelFactory: VolumeDialogViewModel.Factory,
+ private val dialogViewModelFactory: VolumeDialogViewModel.Factory,
private val jankListenerFactory: JankListenerFactory,
private val tracer: VolumeTracer,
- @VolumeDialog private val coroutineScope: CoroutineScope,
private val volumeDialogRingerViewBinder: VolumeDialogRingerViewBinder,
private val slidersViewBinder: VolumeDialogSlidersViewBinder,
private val settingsButtonViewBinder: VolumeDialogSettingsButtonViewBinder,
) {
fun bind(dialog: Dialog) {
- setupDialog(dialog)
- val view: View = dialog.requireViewById(R.id.volume_dialog_container)
- view.alpha = 0f
- view.repeatWhenAttached {
- view.viewModel(
+ // Root view of the Volume Dialog.
+ val root: ViewGroup = dialog.requireViewById(R.id.volume_dialog_root)
+ // Volume Dialog container view that contains the dialog itself without the floating sliders
+ val container: View = root.requireViewById(R.id.volume_dialog_container)
+ container.alpha = 0f
+ container.repeatWhenAttached {
+ root.viewModel(
traceName = "VolumeDialogViewBinder",
minWindowLifecycleState = WindowLifecycleState.ATTACHED,
- factory = { viewModelFactory.create() },
+ factory = { dialogViewModelFactory.create() },
) { viewModel ->
- viewModel.dialogTitle.onEach { dialog.window?.setTitle(it) }.launchIn(this)
+ animateVisibility(container, dialog, viewModel.dialogVisibilityModel)
- animateVisibility(view, dialog, viewModel.dialogVisibilityModel)
+ viewModel.dialogTitle.onEach { dialog.window?.setTitle(it) }.launchIn(this)
+ gravityViewModel.dialogGravity
+ .onEach { container.setLayoutGravity(it) }
+ .launchIn(this)
+
+ launch { root.viewTreeObserver.computeInternalInsetsListener(root) }
awaitCancellation()
}
}
- volumeDialogRingerViewBinder.bind(view)
- slidersViewBinder.bind(view)
- settingsButtonViewBinder.bind(view)
- }
-
- /** Configures [Window] for the [Dialog]. */
- private fun setupDialog(dialog: Dialog) {
- with(dialog.window!!) {
- clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
- addFlags(
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
- WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
- WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
- WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
- )
- addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
-
- requestFeature(Window.FEATURE_NO_TITLE)
- setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
- setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)
- setWindowAnimations(-1)
- setFormat(PixelFormat.TRANSLUCENT)
-
- attributes =
- attributes.apply {
- title = "VolumeDialog" // Not the same as Window#setTitle
- }
- setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
-
- gravityViewModel.dialogGravity.onEach { setGravity(it) }.launchIn(coroutineScope)
- }
- dialog.setContentView(R.layout.volume_dialog)
- dialog.setCanceledOnTouchOutside(true)
+ volumeDialogRingerViewBinder.bind(root)
+ slidersViewBinder.bind(root)
+ settingsButtonViewBinder.bind(root)
}
private fun CoroutineScope.animateVisibility(
@@ -209,4 +188,33 @@
}
animator.suspendAnimate(jankListenerFactory.dismiss(this, duration))
}
+
+ private suspend fun ViewTreeObserver.computeInternalInsetsListener(viewGroup: ViewGroup) =
+ suspendCancellableCoroutine<Unit> { continuation ->
+ val listener =
+ ViewTreeObserver.OnComputeInternalInsetsListener { inoutInfo ->
+ viewGroup.fillTouchableBounds(inoutInfo)
+ }
+ addOnComputeInternalInsetsListener(listener)
+ continuation.invokeOnCancellation { removeOnComputeInternalInsetsListener(listener) }
+ }
+
+ private fun ViewGroup.fillTouchableBounds(internalInsetsInfo: InternalInsetsInfo) {
+ for (child in children) {
+ val boundsRect = Rect()
+ internalInsetsInfo.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION)
+
+ child.getBoundsInWindow(boundsRect, false)
+ internalInsetsInfo.touchableRegion.op(boundsRect, Region.Op.UNION)
+ }
+ val boundsRect = Rect()
+ getBoundsInWindow(boundsRect, false)
+ }
+
+ private fun View.setLayoutGravity(@GravityInt newGravity: Int) {
+ val frameLayoutParams =
+ layoutParams as? FrameLayout.LayoutParams
+ ?: error("View must be a child of a FrameLayout")
+ layoutParams = frameLayoutParams.apply { gravity = newGravity }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
index 2ff8cbc..5bf1513 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
@@ -78,8 +78,7 @@
fun testAvailableMediaDeviceItemFactory_createFromCachedDevice() {
`when`(cachedDevice.name).thenReturn(DEVICE_NAME)
`when`(cachedDevice.connectionSummary).thenReturn(CONNECTION_SUMMARY)
- `when`(BluetoothUtils.getBtClassDrawableWithDescription(any(), any()))
- .thenReturn(Pair.create(drawable, ""))
+ `when`(cachedDevice.drawableWithDescription).thenReturn(Pair.create(drawable, ""))
val deviceItem = availableMediaDeviceItemFactory.create(context, cachedDevice)
assertDeviceItem(deviceItem, DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE)
@@ -89,8 +88,7 @@
fun testConnectedDeviceItemFactory_createFromCachedDevice() {
`when`(cachedDevice.name).thenReturn(DEVICE_NAME)
`when`(cachedDevice.connectionSummary).thenReturn(CONNECTION_SUMMARY)
- `when`(BluetoothUtils.getBtClassDrawableWithDescription(any(), any()))
- .thenReturn(Pair.create(drawable, ""))
+ `when`(cachedDevice.drawableWithDescription).thenReturn(Pair.create(drawable, ""))
val deviceItem = connectedDeviceItemFactory.create(context, cachedDevice)
assertDeviceItem(deviceItem, DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
@@ -100,8 +98,7 @@
fun testSavedDeviceItemFactory_createFromCachedDevice() {
`when`(cachedDevice.name).thenReturn(DEVICE_NAME)
`when`(cachedDevice.connectionSummary).thenReturn(CONNECTION_SUMMARY)
- `when`(BluetoothUtils.getBtClassDrawableWithDescription(any(), any()))
- .thenReturn(Pair.create(drawable, ""))
+ `when`(cachedDevice.drawableWithDescription).thenReturn(Pair.create(drawable, ""))
val deviceItem = savedDeviceItemFactory.create(context, cachedDevice)
assertDeviceItem(deviceItem, DeviceItemType.SAVED_BLUETOOTH_DEVICE)
@@ -111,8 +108,7 @@
@Test
fun testAvailableAudioSharingMediaDeviceItemFactory_createFromCachedDevice() {
`when`(cachedDevice.name).thenReturn(DEVICE_NAME)
- `when`(BluetoothUtils.getBtClassDrawableWithDescription(any(), any()))
- .thenReturn(Pair.create(drawable, ""))
+ `when`(cachedDevice.drawableWithDescription).thenReturn(Pair.create(drawable, ""))
val deviceItem =
AvailableAudioSharingMediaDeviceItemFactory(localBluetoothManager)
.create(context, cachedDevice)
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 fc318d5..856333e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -49,6 +49,7 @@
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -2507,17 +2508,54 @@
@EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
@Test
- public void testEventLogging_bubbleBar_dragBubbleToDismiss() {
+ public void testEventLogging_bubbleBar_dragSelectedBubbleToDismiss() {
mBubbleProperties.mIsBubbleBarEnabled = true;
mPositioner.setIsLargeScreen(true);
FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
mBubbleController.registerBubbleStateListener(bubbleStateListener);
mEntryListener.onEntryAdded(mRow);
- mBubbleController.dragBubbleToDismiss(mRow.getKey(), 1L);
+ mEntryListener.onEntryAdded(mRow2);
+ mBubbleController.expandStackAndSelectBubbleFromLauncher(mRow2.getKey(), 0);
+ clearInvocations(mBubbleLogger);
+
+ // Dismiss selected bubble
+ mBubbleController.startBubbleDrag(mRow2.getKey());
+ mBubbleController.dragBubbleToDismiss(mRow2.getKey(), System.currentTimeMillis());
+
+ // Log bubble dismissed via drag and new bubble selected
+ verify(mBubbleLogger).log(eqBubbleWithKey(mRow2.getKey()),
+ eq(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_DRAG_BUBBLE));
+ verify(mBubbleLogger).log(eqBubbleWithKey(mRow.getKey()),
+ eq(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_SWITCHED));
+
+ verifyNoMoreInteractions(mBubbleLogger);
+ }
+
+ @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+ @Test
+ public void testEventLogging_bubbleBar_dragOtherBubbleToDismiss() {
+ mBubbleProperties.mIsBubbleBarEnabled = true;
+ mPositioner.setIsLargeScreen(true);
+ FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+ mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+ mEntryListener.onEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow2);
+ mBubbleController.expandStackAndSelectBubbleFromLauncher(mRow2.getKey(), 0);
+
+ clearInvocations(mBubbleLogger);
+
+ // Dismiss other bubble
+ mBubbleController.startBubbleDrag(mRow.getKey());
+ mBubbleController.dragBubbleToDismiss(mRow.getKey(), System.currentTimeMillis());
+
+ // Log bubble dismissed via drag, but no switch event
verify(mBubbleLogger).log(eqBubbleWithKey(mRow.getKey()),
eq(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_DRAG_BUBBLE));
+
+ verifyNoMoreInteractions(mBubbleLogger);
}
@EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
@@ -2643,6 +2681,32 @@
eq(BubbleLogger.Event.BUBBLE_BAR_EXPANDED));
}
+ @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+ @Test
+ public void testEventLogging_bubbleBar_switchBubble() {
+ mBubbleProperties.mIsBubbleBarEnabled = true;
+ mPositioner.setIsLargeScreen(true);
+ FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+ mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+ mEntryListener.onEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow2);
+ mBubbleController.expandStackAndSelectBubbleFromLauncher(mRow.getKey(), 0);
+
+ // First select is expand
+ verify(mBubbleLogger).log(eqBubbleWithKey(mRow.getKey()),
+ eq(BubbleLogger.Event.BUBBLE_BAR_EXPANDED));
+ verify(mBubbleLogger, never()).log(eqBubbleWithKey(mRow.getKey()),
+ eq(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_SWITCHED));
+
+ // Second select is switch
+ mBubbleController.expandStackAndSelectBubbleFromLauncher(mRow2.getKey(), 0);
+ verify(mBubbleLogger).log(eqBubbleWithKey(mRow2.getKey()),
+ eq(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_SWITCHED));
+ verify(mBubbleLogger, never()).log(eqBubbleWithKey(mRow2.getKey()),
+ eq(BubbleLogger.Event.BUBBLE_BAR_EXPANDED));
+ }
+
/** Creates a bubble using the userId and package. */
private Bubble createBubble(int userId, String pkg) {
final UserHandle userHandle = new UserHandle(userId);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
index 8022e6e..f52f039 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
@@ -22,7 +22,9 @@
import android.hardware.input.fakeInputManager
import android.view.windowManager
import com.android.systemui.broadcast.broadcastDispatcher
-import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperCategoriesRepository
+import com.android.systemui.keyboard.shortcut.data.repository.CustomShortcutCategoriesRepository
+import com.android.systemui.keyboard.shortcut.data.repository.DefaultShortcutCategoriesRepository
+import com.android.systemui.keyboard.shortcut.data.repository.ShortcutCategoriesUtils
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperStateRepository
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperTestHelper
import com.android.systemui.keyboard.shortcut.data.source.AppCategoriesShortcutsSource
@@ -40,6 +42,7 @@
import com.android.systemui.keyguard.data.repository.fakeCommandQueue
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.backgroundCoroutineContext
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.model.sysUiState
@@ -73,10 +76,18 @@
var Kosmos.shortcutHelperCurrentAppShortcutsSource: KeyboardShortcutGroupsSource by
Kosmos.Fixture { CurrentAppShortcutsSource(windowManager) }
-val Kosmos.shortcutHelperCategoriesRepository by
+val Kosmos.shortcutCategoriesUtils by
Kosmos.Fixture {
- ShortcutHelperCategoriesRepository(
+ ShortcutCategoriesUtils(
applicationContext,
+ backgroundCoroutineContext,
+ fakeInputManager.inputManager,
+ )
+ }
+
+val Kosmos.defaultShortcutCategoriesRepository by
+ Kosmos.Fixture {
+ DefaultShortcutCategoriesRepository(
applicationCoroutineScope,
testDispatcher,
shortcutHelperSystemShortcutsSource,
@@ -86,6 +97,18 @@
shortcutHelperCurrentAppShortcutsSource,
fakeInputManager.inputManager,
shortcutHelperStateRepository,
+ shortcutCategoriesUtils,
+ )
+ }
+
+val Kosmos.customShortcutCategoriesRepository by
+ Kosmos.Fixture {
+ CustomShortcutCategoriesRepository(
+ shortcutHelperStateRepository,
+ userTracker,
+ applicationCoroutineScope,
+ testDispatcher,
+ shortcutCategoriesUtils,
)
}
@@ -112,7 +135,7 @@
}
val Kosmos.shortcutHelperCategoriesInteractor by
- Kosmos.Fixture { ShortcutHelperCategoriesInteractor(shortcutHelperCategoriesRepository) }
+ Kosmos.Fixture { ShortcutHelperCategoriesInteractor(defaultShortcutCategoriesRepository) }
val Kosmos.shortcutHelperViewModel by
Kosmos.Fixture {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/data/repository/SystemStatusEventAnimationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/data/repository/SystemStatusEventAnimationRepositoryKosmos.kt
new file mode 100644
index 0000000..92eeef9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/data/repository/SystemStatusEventAnimationRepositoryKosmos.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.events.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState
+import kotlinx.coroutines.flow.MutableStateFlow
+
+val Kosmos.systemStatusEventAnimationRepository: FakeSystemStatusEventAnimationRepository by
+ Kosmos.Fixture { FakeSystemStatusEventAnimationRepository() }
+
+class FakeSystemStatusEventAnimationRepository : SystemStatusEventAnimationRepository {
+ override val animationState = MutableStateFlow(SystemEventAnimationState.Idle)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/domain/interactor/SystemStatusEventAnimationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/domain/interactor/SystemStatusEventAnimationInteractorKosmos.kt
new file mode 100644
index 0000000..7513fea
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/domain/interactor/SystemStatusEventAnimationInteractorKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.events.domain.interactor
+
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.events.data.repository.systemStatusEventAnimationRepository
+
+val Kosmos.systemStatusEventAnimationInteractor by
+ Kosmos.Fixture {
+ SystemStatusEventAnimationInteractor(
+ repo = systemStatusEventAnimationRepository,
+ configurationInteractor = configurationInteractor,
+ scope = applicationCoroutineScope,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
index 32c582f..2ec8016 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
@@ -19,6 +19,7 @@
import android.app.PendingIntent
import android.graphics.drawable.Icon
import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.statusbar.notification.shared.CallType
import com.android.systemui.statusbar.notification.stack.BUCKET_UNKNOWN
@@ -46,6 +47,7 @@
contentIntent: PendingIntent? = null,
bucket: Int = BUCKET_UNKNOWN,
callType: CallType = CallType.None,
+ promotedContent: PromotedNotificationContentModel? = null,
) =
ActiveNotificationModel(
key = key,
@@ -69,4 +71,5 @@
isGroupSummary = isGroupSummary,
bucket = bucket,
callType = callType,
+ promotedContent = promotedContent,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
index 3a7ada2..03e4c89 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
@@ -24,6 +24,7 @@
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
+import com.android.systemui.statusbar.events.domain.interactor.systemStatusEventAnimationInteractor
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.phone.domain.interactor.lightsOutInteractor
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.collapsedStatusBarInteractor
@@ -40,6 +41,7 @@
sceneContainerOcclusionInteractor,
shadeInteractor,
ongoingActivityChipsViewModel,
+ systemStatusEventAnimationInteractor,
applicationCoroutineScope,
)
}
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/diff-and-update-golden.sh b/ravenwood/tools/hoststubgen/test-tiny-framework/diff-and-update-golden.sh
index 3726ca9..b389a67 100755
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/diff-and-update-golden.sh
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/diff-and-update-golden.sh
@@ -30,7 +30,7 @@
EOF
}
-source "${0%/*}"/../../common.sh
+source "${0%/*}"/../common.sh
SCRIPT_NAME="${0##*/}"
@@ -61,7 +61,6 @@
done
shift $(($OPTIND - 1))
-
# Build the dump files, which are the input of this test.
run m dump-jar tiny-framework-dump-test
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
index 44ae1d1..81e83b5 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
@@ -16,7 +16,6 @@
package com.android.server.appfunctions;
-import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@@ -24,15 +23,21 @@
/** Executors for App function operations. */
public final class AppFunctionExecutors {
+ static final int sConcurrency = Runtime.getRuntime().availableProcessors();
+
/** Executor for operations that do not need to block. */
- public static final Executor THREAD_POOL_EXECUTOR =
+ public static final ThreadPoolExecutor THREAD_POOL_EXECUTOR =
new ThreadPoolExecutor(
- /* corePoolSize= */ Runtime.getRuntime().availableProcessors(),
- /* maxConcurrency= */ Runtime.getRuntime().availableProcessors(),
- /* keepAliveTime= */ 0L,
+ /* corePoolSize= */ sConcurrency,
+ /* maxConcurrency= */ sConcurrency,
+ /* keepAliveTime= */ 1L,
/* unit= */ TimeUnit.SECONDS,
/* workQueue= */ new LinkedBlockingQueue<>(),
new NamedThreadFactory("AppFunctionExecutors"));
+ static {
+ THREAD_POOL_EXECUTOR.allowCoreThreadTimeOut(true);
+ }
+
private AppFunctionExecutors() {}
}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 0bd879b..827e3ef 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -607,7 +607,8 @@
// ... and see if these are hosts we've been awaiting.
// NOTE: We are backing up and restoring only the owner.
// TODO: http://b/22388012
- if (newPackageAdded && userId == mUserManager.getMainUser().getIdentifier()) {
+ UserHandle mainUser = mUserManager.getMainUser();
+ if (newPackageAdded && mainUser != null && userId == mainUser.getIdentifier()) {
final int uid = getUidForPackage(pkgName, userId);
if (uid >= 0 ) {
resolveHostUidLocked(pkgName, uid);
diff --git a/services/autofill/features.aconfig b/services/autofill/features.aconfig
index 1dc3b73..bd46deb 100644
--- a/services/autofill/features.aconfig
+++ b/services/autofill/features.aconfig
@@ -22,3 +22,11 @@
description: "Guards against Autofill-Credman Phase1 developer integration via new APIs"
bug: "320730001"
}
+
+flag {
+ name: "fill_dialog_improvements"
+ is_exported: true
+ namespace: "autofill"
+ description: "Improvements for Fill Dialog, including deprecation of pre-trigger API's"
+ bug: "336223371"
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index cb89f28..dfddc08 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -668,6 +668,8 @@
*/
private static final boolean ENABLE_PROC_LOCK = true;
+ private static final int DEFAULT_INTENT_CREATOR_UID = -1;
+
/**
* The lock for process management.
*
@@ -19308,22 +19310,36 @@
if (!preventIntentRedirect()) return;
if (intent == null) return;
+
+ String targetPackage = intent.getComponent() != null
+ ? intent.getComponent().getPackageName()
+ : intent.getPackage();
+ final boolean isCreatorSameAsTarget = creatorPackage != null && creatorPackage.equals(
+ targetPackage);
+ final boolean noExtraIntentKeys =
+ intent.getExtraIntentKeys() == null || intent.getExtraIntentKeys().isEmpty();
+ final int creatorUid = noExtraIntentKeys ? DEFAULT_INTENT_CREATOR_UID : Binder.getCallingUid();
+
intent.forEachNestedCreatorToken(extraIntent -> {
- IntentCreatorToken creatorToken = createIntentCreatorToken(extraIntent, creatorPackage);
+ if (isCreatorSameAsTarget) {
+ FrameworkStatsLog.write(INTENT_CREATOR_TOKEN_ADDED, creatorUid, true);
+ return;
+ }
+ IntentCreatorToken creatorToken = createIntentCreatorToken(extraIntent, creatorUid,
+ creatorPackage);
if (creatorToken != null) {
extraIntent.setCreatorToken(creatorToken);
// TODO remove Slog.wtf once proven FrameworkStatsLog works. b/375396329
Slog.wtf(TAG, "A creator token is added to an intent. creatorPackage: "
+ creatorPackage + "; intent: " + extraIntent);
- FrameworkStatsLog.write(INTENT_CREATOR_TOKEN_ADDED,
- creatorToken.getCreatorUid());
+ FrameworkStatsLog.write(INTENT_CREATOR_TOKEN_ADDED, creatorUid, false);
}
});
}
- private IntentCreatorToken createIntentCreatorToken(Intent intent, String creatorPackage) {
+ private IntentCreatorToken createIntentCreatorToken(Intent intent, int creatorUid,
+ String creatorPackage) {
if (IntentCreatorToken.isValid(intent)) return null;
- int creatorUid = getCallingUid();
IntentCreatorToken.Key key = new IntentCreatorToken.Key(creatorUid, creatorPackage, intent);
IntentCreatorToken token;
synchronized (sIntentCreatorTokenCache) {
diff --git a/services/core/java/com/android/server/am/BroadcastController.java b/services/core/java/com/android/server/am/BroadcastController.java
index 8a12858..446b367 100644
--- a/services/core/java/com/android/server/am/BroadcastController.java
+++ b/services/core/java/com/android/server/am/BroadcastController.java
@@ -57,7 +57,6 @@
import android.app.ApplicationThreadConstants;
import android.app.BackgroundStartPrivileges;
import android.app.BroadcastOptions;
-import android.app.BroadcastStickyCache;
import android.app.IApplicationThread;
import android.app.compat.CompatChanges;
import android.appwidget.AppWidgetManager;
@@ -702,7 +701,6 @@
boolean serialized, boolean sticky, int userId) {
mService.enforceNotIsolatedCaller("broadcastIntent");
- int result;
synchronized (mService) {
intent = verifyBroadcastLocked(intent);
@@ -724,7 +722,7 @@
final long origId = Binder.clearCallingIdentity();
try {
- result = broadcastIntentLocked(callerApp,
+ return broadcastIntentLocked(callerApp,
callerApp != null ? callerApp.info.packageName : null, callingFeatureId,
intent, resolvedType, resultToApp, resultTo, resultCode, resultData,
resultExtras, requiredPermissions, excludedPermissions, excludedPackages,
@@ -735,10 +733,6 @@
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
- if (sticky && result == ActivityManager.BROADCAST_SUCCESS) {
- BroadcastStickyCache.incrementVersion(intent.getAction());
- }
- return result;
}
// Not the binder call surface
@@ -749,7 +743,6 @@
boolean serialized, boolean sticky, int userId,
BackgroundStartPrivileges backgroundStartPrivileges,
@Nullable int[] broadcastAllowList) {
- int result;
synchronized (mService) {
intent = verifyBroadcastLocked(intent);
@@ -757,7 +750,7 @@
String[] requiredPermissions = requiredPermission == null ? null
: new String[] {requiredPermission};
try {
- result = broadcastIntentLocked(null, packageName, featureId, intent, resolvedType,
+ return broadcastIntentLocked(null, packageName, featureId, intent, resolvedType,
resultToApp, resultTo, resultCode, resultData, resultExtras,
requiredPermissions, null, null, OP_NONE, bOptions, serialized, sticky, -1,
uid, realCallingUid, realCallingPid, userId,
@@ -767,10 +760,6 @@
Binder.restoreCallingIdentity(origId);
}
}
- if (sticky && result == ActivityManager.BROADCAST_SUCCESS) {
- BroadcastStickyCache.incrementVersion(intent.getAction());
- }
- return result;
}
@GuardedBy("mService")
@@ -1469,7 +1458,6 @@
list.add(StickyBroadcast.create(new Intent(intent), deferUntilActive,
callingUid, callerAppProcessState, resolvedType));
}
- BroadcastStickyCache.incrementVersion(intent.getAction());
}
}
@@ -1736,7 +1724,6 @@
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
- final ArrayList<String> changedStickyBroadcasts = new ArrayList<>();
synchronized (mStickyBroadcasts) {
ArrayMap<String, ArrayList<StickyBroadcast>> stickies = mStickyBroadcasts.get(userId);
if (stickies != null) {
@@ -1753,16 +1740,12 @@
if (list.size() <= 0) {
stickies.remove(intent.getAction());
}
- changedStickyBroadcasts.add(intent.getAction());
}
if (stickies.size() <= 0) {
mStickyBroadcasts.remove(userId);
}
}
}
- for (int i = changedStickyBroadcasts.size() - 1; i >= 0; --i) {
- BroadcastStickyCache.incrementVersionIfExists(changedStickyBroadcasts.get(i));
- }
}
void finishReceiver(IBinder caller, int resultCode, String resultData,
@@ -1925,9 +1908,7 @@
private void sendPackageBroadcastLocked(int cmd, String[] packages, int userId) {
mService.mProcessList.sendPackageBroadcastLocked(cmd, packages, userId);
- }
-
- private List<ResolveInfo> collectReceiverComponents(
+ }private List<ResolveInfo> collectReceiverComponents(
Intent intent, String resolvedType, int callingUid, int callingPid,
int[] users, int[] broadcastAllowList) {
// TODO: come back and remove this assumption to triage all broadcasts
@@ -2143,18 +2124,9 @@
}
void removeStickyBroadcasts(int userId) {
- final ArrayList<String> changedStickyBroadcasts = new ArrayList<>();
synchronized (mStickyBroadcasts) {
- final ArrayMap<String, ArrayList<StickyBroadcast>> stickies =
- mStickyBroadcasts.get(userId);
- if (stickies != null) {
- changedStickyBroadcasts.addAll(stickies.keySet());
- }
mStickyBroadcasts.remove(userId);
}
- for (int i = changedStickyBroadcasts.size() - 1; i >= 0; --i) {
- BroadcastStickyCache.incrementVersionIfExists(changedStickyBroadcasts.get(i));
- }
}
@NeverCompile
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 416c110..da5b1fd 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -101,7 +101,7 @@
import java.util.Set;
import java.util.concurrent.Executor;
-public final class CachedAppOptimizer {
+public class CachedAppOptimizer {
// Flags stored in the DeviceConfig API.
@VisibleForTesting static final String KEY_USE_COMPACTION = "use_compaction";
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index c067662..b84bf6b9 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -19,6 +19,7 @@
import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL;
import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL_IMPLICIT;
import static android.app.ActivityManager.PROCESS_CAPABILITY_BFSL;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_CPU_TIME;
import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL;
import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA;
import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION;
@@ -155,6 +156,7 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
+import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
@@ -469,7 +471,6 @@
}
Process.setThreadPriority(tid, priority);
}
-
}
// TODO(b/346822474): hook up global state usage.
@@ -499,7 +500,8 @@
}
OomAdjuster(ActivityManagerService service, ProcessList processList, ActiveUids activeUids,
- ServiceThread adjusterThread, GlobalState globalState, Injector injector) {
+ ServiceThread adjusterThread, GlobalState globalState,
+ CachedAppOptimizer cachedAppOptimizer, Injector injector) {
mService = service;
mGlobalState = globalState;
mInjector = injector;
@@ -508,7 +510,7 @@
mActiveUids = activeUids;
mConstants = mService.mConstants;
- mCachedAppOptimizer = new CachedAppOptimizer(mService);
+ mCachedAppOptimizer = cachedAppOptimizer;
mCacheOomRanker = new CacheOomRanker(service);
mLogger = new OomAdjusterDebugLogger(this, mService.mConstants);
@@ -2597,6 +2599,7 @@
}
capability |= getDefaultCapability(app, procState);
+ capability |= getCpuCapability(app, now);
// Procstates below BFGS should never have this capability.
if (procState > PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
@@ -2739,8 +2742,12 @@
if (app.mOptRecord.setShouldNotFreeze(true, dryRun,
app.mOptRecord.shouldNotFreezeReason()
| client.mOptRecord.shouldNotFreezeReason(), mAdjSeq)) {
- // Bail out early, as we only care about the return value for a dryrun.
- return true;
+ if (Flags.useCpuTimeCapability()) {
+ // Do nothing, capability updated check will handle the dryrun output.
+ } else {
+ // Bail out early, as we only care about the return value for a dryrun.
+ return true;
+ }
}
}
@@ -2751,6 +2758,8 @@
// we check the final procstate, and remove it if the procsate is below BFGS.
capability |= getBfslCapabilityFromClient(client);
+ capability |= getCpuCapabilityFromClient(client);
+
if (cr.notHasFlag(Context.BIND_WAIVE_PRIORITY)) {
if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
capability |= cstate.getCurCapability();
@@ -2809,9 +2818,14 @@
app.mOptRecord.shouldNotFreezeReason()
| ProcessCachedOptimizerRecord
.SHOULD_NOT_FREEZE_REASON_BINDER_ALLOW_OOM_MANAGEMENT, mAdjSeq)) {
- // Bail out early, as we only care about the return value for a dryrun.
- return true;
+ if (Flags.useCpuTimeCapability()) {
+ // Do nothing, capability updated check will handle the dryrun output.
+ } else {
+ // Bail out early, as we only care about the return value for a dryrun.
+ return true;
+ }
}
+ capability |= PROCESS_CAPABILITY_CPU_TIME;
}
// Not doing bind OOM management, so treat
// this guy more like a started service.
@@ -3053,9 +3067,14 @@
app.mOptRecord.shouldNotFreezeReason()
| ProcessCachedOptimizerRecord
.SHOULD_NOT_FREEZE_REASON_BIND_WAIVE_PRIORITY, mAdjSeq)) {
- // Bail out early, as we only care about the return value for a dryrun.
- return true;
+ if (Flags.useCpuTimeCapability()) {
+ // Do nothing, capability updated check will handle the dryrun output.
+ } else {
+ // Bail out early, as we only care about the return value for a dryrun.
+ return true;
+ }
}
+ capability |= PROCESS_CAPABILITY_CPU_TIME;
}
}
if (cr.hasFlag(Context.BIND_TREAT_LIKE_ACTIVITY)) {
@@ -3108,9 +3127,24 @@
capability &= ~PROCESS_CAPABILITY_BFSL;
}
if (!updated) {
- updated = adj < prevRawAdj || procState < prevProcState || schedGroup > prevSchedGroup
- || (capability != prevCapability
- && (capability & prevCapability) == prevCapability);
+ if (adj < prevRawAdj || procState < prevProcState || schedGroup > prevSchedGroup) {
+ updated = true;
+ }
+
+ if (Flags.useCpuTimeCapability()) {
+ if ((capability != prevCapability)
+ && ((capability & prevCapability) == prevCapability)) {
+ updated = true;
+ }
+ } else {
+ // Ignore PROCESS_CAPABILITY_CPU_TIME in capability comparison
+ final int curFiltered = capability & ~PROCESS_CAPABILITY_CPU_TIME;
+ final int prevFiltered = prevCapability & ~PROCESS_CAPABILITY_CPU_TIME;
+ if ((curFiltered != prevFiltered)
+ && ((curFiltered & prevFiltered) == prevFiltered)) {
+ updated = true;
+ }
+ }
}
if (dryRun) {
@@ -3186,6 +3220,8 @@
// we check the final procstate, and remove it if the procsate is below BFGS.
capability |= getBfslCapabilityFromClient(client);
+ capability |= getCpuCapabilityFromClient(client);
+
if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
// If the other app is cached for any reason, for purposes here
// we are going to consider it empty.
@@ -3196,8 +3232,12 @@
if (app.mOptRecord.setShouldNotFreeze(true, dryRun,
app.mOptRecord.shouldNotFreezeReason()
| client.mOptRecord.shouldNotFreezeReason(), mAdjSeq)) {
- // Bail out early, as we only care about the return value for a dryrun.
- return true;
+ if (Flags.useCpuTimeCapability()) {
+ // Do nothing, capability updated check will handle the dryrun output.
+ } else {
+ // Bail out early, as we only care about the return value for a dryrun.
+ return true;
+ }
}
}
@@ -3273,10 +3313,25 @@
capability &= ~PROCESS_CAPABILITY_BFSL;
}
- if (dryRun && (adj < prevRawAdj || procState < prevProcState || schedGroup > prevSchedGroup
- || (capability != prevCapability
- && (capability & prevCapability) == prevCapability))) {
- return true;
+ if (dryRun) {
+ if (adj < prevRawAdj || procState < prevProcState || schedGroup > prevSchedGroup) {
+ return true;
+ }
+
+ if (Flags.useCpuTimeCapability()) {
+ if ((capability != prevCapability)
+ && ((capability & prevCapability) == prevCapability)) {
+ return true;
+ }
+ } else {
+ // Ignore PROCESS_CAPABILITY_CPU_TIME in capability comparison
+ final int curFiltered = capability & ~PROCESS_CAPABILITY_CPU_TIME;
+ final int prevFiltered = prevCapability & ~PROCESS_CAPABILITY_CPU_TIME;
+ if ((curFiltered != prevFiltered)
+ && ((curFiltered & prevFiltered) == prevFiltered)) {
+ return true;
+ }
+ }
}
if (adj < prevRawAdj) {
@@ -3328,6 +3383,29 @@
return baseCapabilities | networkCapabilities;
}
+ private static int getCpuCapability(ProcessRecord app, long nowUptime) {
+ final UidRecord uidRec = app.getUidRecord();
+ if (uidRec != null && uidRec.isCurAllowListed()) {
+ // Process has user visible activities.
+ return PROCESS_CAPABILITY_CPU_TIME;
+ }
+ if (UserHandle.isCore(app.uid)) {
+ // Make sure all system components are not frozen.
+ return PROCESS_CAPABILITY_CPU_TIME;
+ }
+ if (app.mState.getCachedHasVisibleActivities()) {
+ // Process has user visible activities.
+ return PROCESS_CAPABILITY_CPU_TIME;
+ }
+ if (app.mServices.hasUndemotedShortForegroundService(nowUptime)) {
+ // It running a short fgs, just give it cpu time.
+ return PROCESS_CAPABILITY_CPU_TIME;
+ }
+ // TODO(b/370817323): Populate this method with all of the reasons to keep a process
+ // unfrozen.
+ return 0;
+ }
+
/**
* @return the BFSL capability from a client (of a service binding or provider).
*/
@@ -3376,6 +3454,15 @@
}
/**
+ * @return the CPU capability from a client (of a service binding or provider).
+ */
+ private static int getCpuCapabilityFromClient(ProcessRecord client) {
+ // Just grant CPU capability every time
+ // TODO(b/370817323): Populate with reasons to not propagate cpu capability across bindings.
+ return client.mState.getCurCapability() & PROCESS_CAPABILITY_CPU_TIME;
+ }
+
+ /**
* Checks if for the given app and client, there's a cycle that should skip over the client
* for now or use partial values to evaluate the effect of the client binding.
* @param app
@@ -3955,6 +4042,39 @@
mCacheOomRanker.dump(pw);
}
+ /**
+ * Return whether or not a process should be frozen.
+ */
+ boolean getFreezePolicy(ProcessRecord proc) {
+ // Reasons to not freeze:
+ if (Flags.useCpuTimeCapability()) {
+ if ((proc.mState.getCurCapability() & PROCESS_CAPABILITY_CPU_TIME) != 0) {
+ /// App is important enough (see {@link #getCpuCapability}) or bound by something
+ /// important enough to not be frozen.
+ return false;
+ }
+ } else {
+ // The CPU capability handling covers all setShouldNotFreeze paths. Must check
+ // shouldNotFreeze, if the CPU capability is not being used.
+ if (proc.mOptRecord.shouldNotFreeze()) {
+ return false;
+ }
+ }
+
+ if (proc.mOptRecord.isFreezeExempt()) {
+ return false;
+ }
+
+ // Reasons to freeze:
+ if (proc.mState.getCurAdj() >= FREEZER_CUTOFF_ADJ) {
+ // Oomscore is in a high enough state, it is safe to freeze.
+ return true;
+ }
+
+ // Default, do not freeze a process.
+ return false;
+ }
+
@GuardedBy({"mService", "mProcLock"})
void updateAppFreezeStateLSP(ProcessRecord app, @OomAdjReason int oomAdjReason,
boolean immediate, int oldOomAdj) {
@@ -3969,43 +4089,44 @@
(state.getCurAdj() >= FREEZER_CUTOFF_ADJ ^ oldOomAdj >= FREEZER_CUTOFF_ADJ)
|| oldOomAdj == UNKNOWN_ADJ;
final boolean shouldNotFreezeChanged = opt.shouldNotFreezeAdjSeq() == mAdjSeq;
- if ((oomAdjChanged || shouldNotFreezeChanged)
+ final boolean hasCpuCapability =
+ (PROCESS_CAPABILITY_CPU_TIME & app.mState.getCurCapability())
+ == PROCESS_CAPABILITY_CPU_TIME;
+ final boolean usedToHaveCpuCapability =
+ (PROCESS_CAPABILITY_CPU_TIME & app.mState.getSetCapability())
+ == PROCESS_CAPABILITY_CPU_TIME;
+ final boolean cpuCapabilityChanged = hasCpuCapability != usedToHaveCpuCapability;
+ if ((oomAdjChanged || shouldNotFreezeChanged || cpuCapabilityChanged)
&& Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER,
CachedAppOptimizer.ATRACE_FREEZER_TRACK,
"updateAppFreezeStateLSP " + app.processName
+ + " pid: " + app.getPid()
+ " isFreezeExempt: " + opt.isFreezeExempt()
+ " isFrozen: " + opt.isFrozen()
+ " shouldNotFreeze: " + opt.shouldNotFreeze()
+ " shouldNotFreezeReason: " + opt.shouldNotFreezeReason()
+ " curAdj: " + state.getCurAdj()
+ " oldOomAdj: " + oldOomAdj
- + " immediate: " + immediate);
+ + " immediate: " + immediate
+ + " cpuCapability: " + hasCpuCapability);
}
}
- if (app.mOptRecord.isFreezeExempt()) {
- return;
- }
-
- // if an app is already frozen and shouldNotFreeze becomes true, immediately unfreeze
- if (opt.isFrozen() && opt.shouldNotFreeze()) {
- mCachedAppOptimizer.unfreezeAppLSP(app,
- CachedAppOptimizer.getUnfreezeReasonCodeFromOomAdjReason(oomAdjReason));
- return;
- }
-
- // Use current adjustment when freezing, set adjustment when unfreezing.
- if (state.getCurAdj() >= FREEZER_CUTOFF_ADJ && !opt.isFrozen()
- && !opt.shouldNotFreeze()) {
- if (!immediate) {
- mCachedAppOptimizer.freezeAppAsyncLSP(app);
- } else {
+ if (getFreezePolicy(app)) {
+ // This process should be frozen.
+ if (immediate && !opt.isFrozen()) {
+ // And it will be frozen immediately.
mCachedAppOptimizer.freezeAppAsyncAtEarliestLSP(app);
+ } else if (!opt.isFrozen() || !opt.isPendingFreeze()) {
+ mCachedAppOptimizer.freezeAppAsyncLSP(app);
}
- } else if (state.getSetAdj() < FREEZER_CUTOFF_ADJ) {
- mCachedAppOptimizer.unfreezeAppLSP(app,
- CachedAppOptimizer.getUnfreezeReasonCodeFromOomAdjReason(oomAdjReason));
+ } else {
+ // This process should not be frozen.
+ if (opt.isFrozen() || opt.isPendingFreeze()) {
+ mCachedAppOptimizer.unfreezeAppLSP(app,
+ CachedAppOptimizer.getUnfreezeReasonCodeFromOomAdjReason(oomAdjReason));
+ }
}
}
@@ -4029,7 +4150,8 @@
final int size = processes.size();
for (int i = 0; i < size; i++) {
ProcessRecord proc = processes.get(i);
- mCachedAppOptimizer.unfreezeTemporarily(proc, reason);
+ mCachedAppOptimizer.unfreezeTemporarily(proc,
+ CachedAppOptimizer.getUnfreezeReasonCodeFromOomAdjReason(reason));
}
processes.clear();
}
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index 8b66055..1b7e8f0 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -758,8 +758,9 @@
OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList,
ActiveUids activeUids, ServiceThread adjusterThread, GlobalState globalState,
- Injector injector) {
- super(service, processList, activeUids, adjusterThread, globalState, injector);
+ CachedAppOptimizer cachedAppOptimizer, Injector injector) {
+ super(service, processList, activeUids, adjusterThread, globalState, cachedAppOptimizer,
+ injector);
}
private final ProcessRecordNodes mProcessRecordProcStateNodes = new ProcessRecordNodes(
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index 5cb8b95..3644974 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -256,18 +256,24 @@
}
// Now we need to look at all short-FGS within the process and see if all of them are
// procstate-timed-out or not.
+ return !hasUndemotedShortForegroundService(nowUptime);
+ }
+
+ boolean hasUndemotedShortForegroundService(long nowUptime) {
for (int i = mServices.size() - 1; i >= 0; i--) {
final ServiceRecord sr = mServices.valueAt(i);
if (!sr.isShortFgs() || !sr.hasShortFgsInfo()) {
continue;
}
if (sr.getShortFgsInfo().getProcStateDemoteTime() >= nowUptime) {
- return false;
+ // This short fgs has not timed out yet.
+ return true;
}
}
- return true;
+ return false;
}
+
int getReportedForegroundServiceTypes() {
return mRepFgServiceTypes;
}
diff --git a/services/core/java/com/android/server/am/ProcessStateController.java b/services/core/java/com/android/server/am/ProcessStateController.java
index 01468c6..5789922 100644
--- a/services/core/java/com/android/server/am/ProcessStateController.java
+++ b/services/core/java/com/android/server/am/ProcessStateController.java
@@ -29,6 +29,7 @@
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.ServiceThread;
/**
@@ -44,13 +45,14 @@
private final GlobalState mGlobalState = new GlobalState();
private ProcessStateController(ActivityManagerService ams, ProcessList processList,
- ActiveUids activeUids, ServiceThread handlerThread, OomAdjuster.Injector oomAdjInjector,
+ ActiveUids activeUids, ServiceThread handlerThread,
+ CachedAppOptimizer cachedAppOptimizer, OomAdjuster.Injector oomAdjInjector,
boolean useOomAdjusterModernImpl) {
mOomAdjuster = useOomAdjusterModernImpl
? new OomAdjusterModernImpl(ams, processList, activeUids, handlerThread,
- mGlobalState, oomAdjInjector)
+ mGlobalState, cachedAppOptimizer, oomAdjInjector)
: new OomAdjuster(ams, processList, activeUids, handlerThread, mGlobalState,
- oomAdjInjector);
+ cachedAppOptimizer, oomAdjInjector);
}
/**
@@ -594,6 +596,7 @@
private final ActiveUids mActiveUids;
private ServiceThread mHandlerThread = null;
+ private CachedAppOptimizer mCachedAppOptimizer = null;
private OomAdjuster.Injector mOomAdjInjector = null;
private boolean mUseOomAdjusterModernImpl = false;
@@ -610,24 +613,38 @@
if (mHandlerThread == null) {
mHandlerThread = OomAdjuster.createAdjusterThread();
}
+ if (mCachedAppOptimizer == null) {
+ mCachedAppOptimizer = new CachedAppOptimizer(mAms);
+ }
if (mOomAdjInjector == null) {
mOomAdjInjector = new OomAdjuster.Injector();
}
return new ProcessStateController(mAms, mProcessList, mActiveUids, mHandlerThread,
- mOomAdjInjector, mUseOomAdjusterModernImpl);
+ mCachedAppOptimizer, mOomAdjInjector, mUseOomAdjusterModernImpl);
}
/**
* For Testing Purposes. Set what thread OomAdjuster will offload tasks on to.
*/
+ @VisibleForTesting
public Builder setHandlerThread(ServiceThread handlerThread) {
mHandlerThread = handlerThread;
return this;
}
/**
+ * For Testing Purposes. Set the CachedAppOptimzer used by OomAdjuster.
+ */
+ @VisibleForTesting
+ public Builder setCachedAppOptimizer(CachedAppOptimizer cachedAppOptimizer) {
+ mCachedAppOptimizer = cachedAppOptimizer;
+ return this;
+ }
+
+ /**
* For Testing Purposes. Set an injector for OomAdjuster.
*/
+ @VisibleForTesting
public Builder setOomAdjusterInjector(OomAdjuster.Injector injector) {
mOomAdjInjector = injector;
return this;
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 3dd5ec9..ef5296e 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -221,6 +221,7 @@
"preload_safety",
"printing",
"privacy_infra_policy",
+ "psap_ai",
"ravenwood",
"resource_manager",
"responsible_apis",
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 8395685..711b163 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -253,3 +253,10 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "use_cpu_time_capability"
+ namespace: "backstage_power"
+ description: "Use PROCESS_CAPABILITY_CPU_TIME to control unfreeze state."
+ bug: "370817323"
+}
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index e8fa417..afdc0c0 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -119,9 +119,16 @@
== BiometricManager.Authenticators.IDENTITY_CHECK;
boolean isMandatoryBiometricsAuthentication = false;
+ final int effectiveUserId;
+ if (Flags.effectiveUserBp()) {
+ effectiveUserId = userManager.getCredentialOwnerProfile(userId);
+ } else {
+ effectiveUserId = userId;
+ }
+
if (dropCredentialFallback(promptInfo.getAuthenticators(),
settingObserver.getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(
- userId), trustManager)) {
+ effectiveUserId), trustManager)) {
isMandatoryBiometricsAuthentication = true;
promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
if (promptInfo.getNegativeButtonText() == null) {
@@ -145,13 +152,6 @@
final List<BiometricSensor> eligibleSensors = new ArrayList<>();
final List<Pair<BiometricSensor, Integer>> ineligibleSensors = new ArrayList<>();
- final int effectiveUserId;
- if (Flags.effectiveUserBp()) {
- effectiveUserId = userManager.getCredentialOwnerProfile(userId);
- } else {
- effectiveUserId = userId;
- }
-
if (biometricRequested) {
for (BiometricSensor sensor : sensors) {
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 97f4a5c..eeac260 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -32,6 +32,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.net.Uri;
import android.os.Binder;
@@ -75,6 +76,7 @@
private final ChangeReporter mChangeReporter;
private final CompatConfig mCompatConfig;
private final AndroidBuildClassifier mBuildClassifier;
+ private Boolean mIsWear;
public PlatformCompat(Context context) {
super(PermissionEnforcer.fromContext(context));
@@ -511,9 +513,16 @@
}
private ApplicationInfo fixTargetSdk(ApplicationInfo appInfo, int uid) {
+
+ // mIsWear doesn't need to be locked, ok if executes twice
+ if (mIsWear == null) {
+ mIsWear = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
+ }
+
// b/282922910 - we don't want apps sharing system uid and targeting
// older target sdk to impact all system uid apps
- if (Flags.systemUidTargetSystemSdk() && uid == Process.SYSTEM_UID) {
+ if (Flags.systemUidTargetSystemSdk() && !mIsWear &&
+ uid == Process.SYSTEM_UID) {
appInfo.targetSdkVersion = Build.VERSION.SDK_INT;
}
return appInfo;
diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java
index 0124e25..bb0b190 100644
--- a/services/core/java/com/android/server/input/KeyGestureController.java
+++ b/services/core/java/com/android/server/input/KeyGestureController.java
@@ -21,8 +21,6 @@
import static android.view.WindowManagerPolicyConstants.FLAG_INTERACTIVE;
import static com.android.hardware.input.Flags.enableNew25q2Keycodes;
-import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
-import static com.android.hardware.input.Flags.useKeyGestureEventHandlerMultiPressGestures;
import android.annotation.BinderThread;
import android.annotation.MainThread;
@@ -51,7 +49,6 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
-import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.IndentingPrintWriter;
@@ -215,7 +212,7 @@
}
private void initKeyCombinationRules() {
- if (!useKeyGestureEventHandler() || !useKeyGestureEventHandlerMultiPressGestures()) {
+ if (!InputSettings.doesKeyGestureEventHandlerSupportMultiKeyGestures()) {
return;
}
// TODO(b/358569822): Handle Power, Back key properly since key combination gesture is
@@ -441,7 +438,8 @@
public boolean interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
final boolean interactive = (policyFlags & FLAG_INTERACTIVE) != 0;
- if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
+ if (InputSettings.doesKeyGestureEventHandlerSupportMultiKeyGestures()
+ && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
return mKeyCombinationManager.interceptKey(event, interactive);
}
return false;
@@ -457,15 +455,18 @@
final long keyConsumed = -1;
final long keyNotConsumed = 0;
- if (mKeyCombinationManager.isKeyConsumed(event)) {
- return keyConsumed;
- }
+ if (InputSettings.doesKeyGestureEventHandlerSupportMultiKeyGestures()) {
+ if (mKeyCombinationManager.isKeyConsumed(event)) {
+ return keyConsumed;
+ }
- if ((flags & KeyEvent.FLAG_FALLBACK) == 0) {
- final long now = SystemClock.uptimeMillis();
- final long interceptTimeout = mKeyCombinationManager.getKeyInterceptTimeout(keyCode);
- if (now < interceptTimeout) {
- return interceptTimeout - now;
+ if ((flags & KeyEvent.FLAG_FALLBACK) == 0) {
+ final long now = SystemClock.uptimeMillis();
+ final long interceptTimeout = mKeyCombinationManager.getKeyInterceptTimeout(
+ keyCode);
+ if (now < interceptTimeout) {
+ return interceptTimeout - now;
+ }
}
}
diff --git a/services/core/java/com/android/server/input/KeyboardBacklightController.java b/services/core/java/com/android/server/input/KeyboardBacklightController.java
index c3205af..0defd27 100644
--- a/services/core/java/com/android/server/input/KeyboardBacklightController.java
+++ b/services/core/java/com/android/server/input/KeyboardBacklightController.java
@@ -20,6 +20,7 @@
import android.annotation.BinderThread;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Color;
import android.hardware.input.IKeyboardBacklightListener;
import android.hardware.input.IKeyboardBacklightState;
@@ -81,9 +82,6 @@
private static final String UEVENT_KEYBOARD_BACKLIGHT_TAG = "kbd_backlight";
@VisibleForTesting
- static final long USER_INACTIVITY_THRESHOLD_MILLIS = Duration.ofSeconds(30).toMillis();
-
- @VisibleForTesting
static final int[] DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL =
new int[DEFAULT_NUM_BRIGHTNESS_CHANGE_STEPS + 1];
@@ -112,6 +110,7 @@
private AmbientKeyboardBacklightController.AmbientKeyboardBacklightListener mAmbientListener;
private int mAmbientBacklightValue = 0;
+ private final int mUserInactivityThresholdMs;
static {
// Fixed brightness levels to avoid issues when converting back and forth from the
@@ -139,6 +138,9 @@
mAnimatorFactory = animatorFactory;
mAmbientController = new AmbientKeyboardBacklightController(context, looper);
mUEventManager = uEventManager;
+ Resources res = mContext.getResources();
+ mUserInactivityThresholdMs = res.getInteger(
+ com.android.internal.R.integer.config_keyboardBacklightTimeoutMs);
}
@Override
@@ -300,7 +302,7 @@
}
mHandler.removeMessages(MSG_NOTIFY_USER_INACTIVITY);
mHandler.sendEmptyMessageAtTime(MSG_NOTIFY_USER_INACTIVITY,
- SystemClock.uptimeMillis() + USER_INACTIVITY_THRESHOLD_MILLIS);
+ SystemClock.uptimeMillis() + mUserInactivityThresholdMs);
}
private void handleUserInactivity() {
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
index bb4ae96..a132876b 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -46,7 +46,6 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
-import com.android.server.integrity.model.RuleMetadata;
import java.io.File;
import java.nio.charset.StandardCharsets;
diff --git a/services/core/java/com/android/server/integrity/model/BitInputStream.java b/services/core/java/com/android/server/integrity/model/BitInputStream.java
deleted file mode 100644
index e7cc81e..0000000
--- a/services/core/java/com/android/server/integrity/model/BitInputStream.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.model;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/** A wrapper class for reading a stream of bits.
- *
- * <p>Note: this class reads from underlying stream byte-by-byte. It is advised to apply buffering
- * to underlying streams.
- */
-public class BitInputStream {
-
- private long mBitsRead;
-
- private InputStream mInputStream;
-
- private byte mCurrentByte;
-
- public BitInputStream(InputStream inputStream) {
- mInputStream = inputStream;
- }
-
- /**
- * Read the next number of bits from the stream.
- *
- * @param numOfBits The number of bits to read.
- * @return The value read from the stream.
- */
- public int getNext(int numOfBits) throws IOException {
- int component = 0;
- int count = 0;
-
- while (count++ < numOfBits) {
- if (mBitsRead % 8 == 0) {
- mCurrentByte = getNextByte();
- }
- int offset = 7 - (int) (mBitsRead % 8);
-
- component <<= 1;
- component |= (mCurrentByte >>> offset) & 1;
-
- mBitsRead++;
- }
-
- return component;
- }
-
- /** Check if there are bits left in the stream. */
- public boolean hasNext() throws IOException {
- return mInputStream.available() > 0;
- }
-
- private byte getNextByte() throws IOException {
- return (byte) mInputStream.read();
- }
-}
diff --git a/services/core/java/com/android/server/integrity/model/BitOutputStream.java b/services/core/java/com/android/server/integrity/model/BitOutputStream.java
deleted file mode 100644
index 14b35fd..0000000
--- a/services/core/java/com/android/server/integrity/model/BitOutputStream.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.model;
-
-import static com.android.server.integrity.model.ComponentBitSize.BYTE_BITS;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.Arrays;
-
-/** A wrapper class for writing a stream of bits. */
-public class BitOutputStream {
-
- private static final int BUFFER_SIZE = 4 * 1024;
-
- private int mNextBitIndex;
-
- private final OutputStream mOutputStream;
- private final byte[] mBuffer;
-
- public BitOutputStream(OutputStream outputStream) {
- mBuffer = new byte[BUFFER_SIZE];
- mNextBitIndex = 0;
- mOutputStream = outputStream;
- }
-
- /**
- * Set the next number of bits in the stream to value.
- *
- * @param numOfBits The number of bits used to represent the value.
- * @param value The value to convert to bits.
- */
- public void setNext(int numOfBits, int value) throws IOException {
- if (numOfBits <= 0) {
- return;
- }
-
- // optional: we can do some clever size checking to "OR" an entire segment of bits instead
- // of setting bits one by one, but it is probably not worth it.
- int nextBitMask = 1 << (numOfBits - 1);
- while (numOfBits-- > 0) {
- setNext((value & nextBitMask) != 0);
- nextBitMask >>>= 1;
- }
- }
-
- /**
- * Set the next bit in the stream to value.
- *
- * @param value The value to set the bit to
- */
- public void setNext(boolean value) throws IOException {
- int byteToWrite = mNextBitIndex / BYTE_BITS;
- if (byteToWrite == BUFFER_SIZE) {
- mOutputStream.write(mBuffer);
- reset();
- byteToWrite = 0;
- }
- if (value) {
- mBuffer[byteToWrite] |= 1 << (BYTE_BITS - 1 - (mNextBitIndex % BYTE_BITS));
- }
- mNextBitIndex++;
- }
-
- /** Set the next bit in the stream to true. */
- public void setNext() throws IOException {
- setNext(/* value= */ true);
- }
-
- /**
- * Flush the data written to the underlying {@link java.io.OutputStream}. Any unfinished bytes
- * will be padded with 0.
- */
- public void flush() throws IOException {
- int endByte = mNextBitIndex / BYTE_BITS;
- if (mNextBitIndex % BYTE_BITS != 0) {
- // If next bit is not the first bit of a byte, then mNextBitIndex / BYTE_BITS would be
- // the byte that includes already written bits. We need to increment it so this byte
- // gets written.
- endByte++;
- }
- mOutputStream.write(mBuffer, 0, endByte);
- reset();
- }
-
- /** Reset this output stream to start state. */
- private void reset() {
- mNextBitIndex = 0;
- Arrays.fill(mBuffer, (byte) 0);
- }
-}
diff --git a/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java b/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java
deleted file mode 100644
index ceed054..0000000
--- a/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.model;
-
-import java.io.IOException;
-import java.io.OutputStream;
-
-/**
- * An output stream that tracks the total number written bytes since construction and allows
- * querying this value any time during the execution.
- *
- * <p>This class is used for constructing the rule indexing.
- */
-public class ByteTrackedOutputStream extends OutputStream {
-
- private int mWrittenBytesCount;
- private final OutputStream mOutputStream;
-
- public ByteTrackedOutputStream(OutputStream outputStream) {
- mWrittenBytesCount = 0;
- mOutputStream = outputStream;
- }
-
- @Override
- public void write(int b) throws IOException {
- mWrittenBytesCount++;
- mOutputStream.write(b);
- }
-
- /**
- * Writes the given bytes into the output stream provided in constructor and updates the total
- * number of written bytes.
- */
- @Override
- public void write(byte[] bytes) throws IOException {
- write(bytes, 0, bytes.length);
- }
-
- @Override
- public void write(byte[] b, int off, int len) throws IOException {
- mWrittenBytesCount += len;
- mOutputStream.write(b, off, len);
- }
-
- /** Returns the total number of bytes written into the output stream at the requested time. */
- public int getWrittenBytesCount() {
- return mWrittenBytesCount;
- }
-}
diff --git a/services/core/java/com/android/server/integrity/model/ComponentBitSize.java b/services/core/java/com/android/server/integrity/model/ComponentBitSize.java
deleted file mode 100644
index 94e6708..0000000
--- a/services/core/java/com/android/server/integrity/model/ComponentBitSize.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.model;
-
-import android.content.integrity.Rule;
-
-/**
- * A helper class containing information about the binary representation of different {@link Rule}
- * components.
- */
-public final class ComponentBitSize {
- public static final int FORMAT_VERSION_BITS = 8;
-
- public static final int EFFECT_BITS = 3;
- public static final int KEY_BITS = 4;
- public static final int OPERATOR_BITS = 3;
- public static final int CONNECTOR_BITS = 2;
- public static final int SEPARATOR_BITS = 3;
- public static final int VALUE_SIZE_BITS = 8;
- public static final int IS_HASHED_BITS = 1;
-
- public static final int ATOMIC_FORMULA_START = 0;
- public static final int COMPOUND_FORMULA_START = 1;
- public static final int COMPOUND_FORMULA_END = 2;
- public static final int INSTALLER_ALLOWED_BY_MANIFEST_START = 3;
-
- public static final int DEFAULT_FORMAT_VERSION = 1;
- public static final int SIGNAL_BIT = 1;
-
- public static final int BYTE_BITS = 8;
-}
diff --git a/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java b/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java
deleted file mode 100644
index 0c4052a..0000000
--- a/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.model;
-
-/** A helper class containing special indexing file constants. */
-public final class IndexingFileConstants {
- // We empirically experimented with different block sizes and identified that 50 is in the
- // optimal range of efficient computation.
- public static final int INDEXING_BLOCK_SIZE = 50;
-
- public static final String START_INDEXING_KEY = "START_KEY";
- public static final String END_INDEXING_KEY = "END_KEY";
-}
diff --git a/services/core/java/com/android/server/integrity/model/IntegrityCheckResult.java b/services/core/java/com/android/server/integrity/model/IntegrityCheckResult.java
deleted file mode 100644
index b0647fc..0000000
--- a/services/core/java/com/android/server/integrity/model/IntegrityCheckResult.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.model;
-
-import android.annotation.Nullable;
-import android.content.integrity.Rule;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * A class encapsulating the result from the evaluation engine after evaluating rules against app
- * install metadata.
- *
- * <p>It contains the outcome effect (whether to allow or block the install), and the rule causing
- * that effect.
- */
-public final class IntegrityCheckResult {
-
- public enum Effect {
- ALLOW,
- DENY
- }
-
- private final Effect mEffect;
- private final List<Rule> mRuleList;
-
- private IntegrityCheckResult(Effect effect, @Nullable List<Rule> ruleList) {
- this.mEffect = effect;
- this.mRuleList = ruleList;
- }
-
- public Effect getEffect() {
- return mEffect;
- }
-
- public List<Rule> getMatchedRules() {
- return mRuleList;
- }
-
- /**
- * Create an ALLOW evaluation outcome.
- *
- * @return An evaluation outcome with ALLOW effect and no rule.
- */
- public static IntegrityCheckResult allow() {
- return new IntegrityCheckResult(Effect.ALLOW, Collections.emptyList());
- }
-
- /**
- * Create an ALLOW evaluation outcome.
- *
- * @return An evaluation outcome with ALLOW effect and rule causing that effect.
- */
- public static IntegrityCheckResult allow(List<Rule> ruleList) {
- return new IntegrityCheckResult(Effect.ALLOW, ruleList);
- }
-
- /**
- * Create a DENY evaluation outcome.
- *
- * @param ruleList All valid rules that cause the DENY effect.
- * @return An evaluation outcome with DENY effect and rule causing that effect.
- */
- public static IntegrityCheckResult deny(List<Rule> ruleList) {
- return new IntegrityCheckResult(Effect.DENY, ruleList);
- }
-
- /** Returns true when the {@code mEffect} is caused by an app certificate mismatch. */
- public boolean isCausedByAppCertRule() {
- return mRuleList.stream().anyMatch(rule -> rule.getFormula().isAppCertificateFormula());
- }
-
- /** Returns true when the {@code mEffect} is caused by an installer rule. */
- public boolean isCausedByInstallerRule() {
- return mRuleList.stream().anyMatch(rule -> rule.getFormula().isInstallerFormula());
- }
-
-}
diff --git a/services/core/java/com/android/server/integrity/model/RuleMetadata.java b/services/core/java/com/android/server/integrity/model/RuleMetadata.java
deleted file mode 100644
index 6b582ae..0000000
--- a/services/core/java/com/android/server/integrity/model/RuleMetadata.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.model;
-
-import android.annotation.Nullable;
-
-/** Data class containing relevant metadata associated with a rule set. */
-public class RuleMetadata {
-
- private final String mRuleProvider;
- private final String mVersion;
-
- public RuleMetadata(String ruleProvider, String version) {
- mRuleProvider = ruleProvider;
- mVersion = version;
- }
-
- @Nullable
- public String getRuleProvider() {
- return mRuleProvider;
- }
-
- @Nullable
- public String getVersion() {
- return mVersion;
- }
-}
diff --git a/services/core/java/com/android/server/location/contexthub/OWNERS b/services/core/java/com/android/server/location/contexthub/OWNERS
index c62e323..6536ca0 100644
--- a/services/core/java/com/android/server/location/contexthub/OWNERS
+++ b/services/core/java/com/android/server/location/contexthub/OWNERS
@@ -1,3 +1,4 @@
bduddie@google.com
+arthuri@google.com
matthewsedam@google.com
stange@google.com
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityDbHelper.java b/services/core/java/com/android/server/media/quality/MediaQualityDbHelper.java
new file mode 100644
index 0000000..04f6216
--- /dev/null
+++ b/services/core/java/com/android/server/media/quality/MediaQualityDbHelper.java
@@ -0,0 +1,64 @@
+/*
+ * 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.media.quality;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.media.quality.MediaQualityContract.BaseParameters;
+
+public class MediaQualityDbHelper extends SQLiteOpenHelper {
+
+ private static final String TAG = "MediaQualityDbHelper";
+
+ static final int DATABASE_VERSION_1 = 1;
+ private static final String DATABASE_NAME = "media_quality.db";
+ public static final String PICTURE_QUALITY_TABLE_NAME = "picture_quality";
+ public static final String SOUND_QUALITY_TABLE_NAME = "sound_quality";
+ public static final String SETTINGS = "settings";
+
+ MediaQualityDbHelper(Context context) {
+ super(context, DATABASE_NAME, null, getDbVersion());
+ }
+
+ private static int getDbVersion() {
+ return DATABASE_VERSION_1;
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL(getTableCreateStatement(PICTURE_QUALITY_TABLE_NAME));
+ db.execSQL(getTableCreateStatement(SOUND_QUALITY_TABLE_NAME));
+ }
+
+ private String getTableCreateStatement(String tableName) {
+ return
+ "CREATE TABLE " + tableName + "("
+ + BaseParameters.PARAMETER_ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ + BaseParameters.PARAMETER_TYPE + " INTEGER,"
+ + BaseParameters.PARAMETER_NAME + " STRING,"
+ + BaseParameters.PARAMETER_PACKAGE + " STRING,"
+ + BaseParameters.PARAMETER_INPUT_ID + " STRING,"
+ + SETTINGS + " TEXT)";
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ // to do
+ }
+
+}
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java
index 21ae182..c5c8a5e 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -16,20 +16,31 @@
package com.android.server.media.quality;
+import android.content.ContentValues;
import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
import android.media.quality.AmbientBacklightSettings;
import android.media.quality.IAmbientBacklightCallback;
import android.media.quality.IMediaQualityManager;
import android.media.quality.IPictureProfileCallback;
import android.media.quality.ISoundProfileCallback;
+import android.media.quality.MediaQualityContract.PictureQuality;
import android.media.quality.ParamCapability;
import android.media.quality.PictureProfile;
import android.media.quality.SoundProfile;
+import android.os.Bundle;
+import android.util.Log;
import com.android.server.SystemService;
+import org.json.JSONException;
+import org.json.JSONObject;
+
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
+import java.util.Locale;
/**
* This service manage picture profile and sound profile for TV setting. Also communicates with the
@@ -40,10 +51,14 @@
private static final boolean DEBUG = false;
private static final String TAG = "MediaQualityService";
private final Context mContext;
+ private final MediaQualityDbHelper mMediaQualityDbHelper;
public MediaQualityService(Context context) {
super(context);
mContext = context;
+ mMediaQualityDbHelper = new MediaQualityDbHelper(mContext);
+ mMediaQualityDbHelper.setWriteAheadLoggingEnabled(true);
+ mMediaQualityDbHelper.setIdleConnectionTimeout(30);
}
@Override
@@ -53,11 +68,23 @@
// TODO: Add additional APIs. b/373951081
private final class BinderService extends IMediaQualityManager.Stub {
+
@Override
public PictureProfile createPictureProfile(PictureProfile pp) {
- // TODO: implement
- return pp;
+ SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
+
+ ContentValues values = new ContentValues();
+ values.put(PictureQuality.PARAMETER_TYPE, pp.getProfileType());
+ values.put(PictureQuality.PARAMETER_NAME, pp.getName());
+ values.put(PictureQuality.PARAMETER_PACKAGE, pp.getPackageName());
+ values.put(PictureQuality.PARAMETER_INPUT_ID, pp.getInputId());
+ values.put(mMediaQualityDbHelper.SETTINGS, bundleToJson(pp.getParameters()));
+
+ // id is auto-generated by SQLite upon successful insertion of row
+ long id = db.insert(mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME, null, values);
+ return new PictureProfile.Builder(pp).setProfileId(Long.toString(id)).build();
}
+
@Override
public void updatePictureProfile(String id, PictureProfile pp) {
// TODO: implement
@@ -66,21 +93,159 @@
public void removePictureProfile(String id) {
// TODO: implement
}
+
@Override
public PictureProfile getPictureProfile(int type, String name) {
- return null;
+ SQLiteDatabase db = mMediaQualityDbHelper.getReadableDatabase();
+
+ String selection = PictureQuality.PARAMETER_TYPE + " = ? AND "
+ + PictureQuality.PARAMETER_NAME + " = ?";
+ String[] selectionArguments = {Integer.toString(type), name};
+
+ try (
+ Cursor cursor = db.query(
+ mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME,
+ getAllPictureProfileColumns(),
+ selection,
+ selectionArguments,
+ /*groupBy=*/ null,
+ /*having=*/ null,
+ /*orderBy=*/ null)
+ ) {
+ int count = cursor.getCount();
+ if (count == 0) {
+ return null;
+ }
+ if (count > 1) {
+ Log.wtf(TAG, String.format(Locale.US, "%d entries found for type=%d and name=%s"
+ + " in %s. Should only ever be 0 or 1.", count, type, name,
+ mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME));
+ return null;
+ }
+ cursor.moveToFirst();
+ return getPictureProfileFromCursor(cursor);
+ }
}
+
+ private String bundleToJson(Bundle bundle) {
+ JSONObject jsonObject = new JSONObject();
+ if (bundle == null) {
+ return jsonObject.toString();
+ }
+ for (String key : bundle.keySet()) {
+ try {
+ jsonObject.put(key, bundle.getString(key));
+ } catch (JSONException e) {
+ Log.e(TAG, "Unable to serialize ", e);
+ }
+ }
+ return jsonObject.toString();
+ }
+
+ private Bundle jsonToBundle(String jsonString) {
+ JSONObject jsonObject = null;
+ Bundle bundle = new Bundle();
+
+ try {
+ jsonObject = new JSONObject(jsonString);
+
+ Iterator<String> keys = jsonObject.keys();
+ while (keys.hasNext()) {
+ String key = keys.next();
+ Object value = jsonObject.get(key);
+
+ if (value instanceof String) {
+ bundle.putString(key, (String) value);
+ } else if (value instanceof Integer) {
+ bundle.putInt(key, (Integer) value);
+ } else if (value instanceof Boolean) {
+ bundle.putBoolean(key, (Boolean) value);
+ } else if (value instanceof Double) {
+ bundle.putDouble(key, (Double) value);
+ } else if (value instanceof Long) {
+ bundle.putLong(key, (Long) value);
+ }
+ }
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+
+ return bundle;
+ }
+
+ private String[] getAllPictureProfileColumns() {
+ return new String[]{
+ PictureQuality.PARAMETER_ID,
+ PictureQuality.PARAMETER_TYPE,
+ PictureQuality.PARAMETER_NAME,
+ PictureQuality.PARAMETER_INPUT_ID,
+ PictureQuality.PARAMETER_PACKAGE,
+ mMediaQualityDbHelper.SETTINGS
+ };
+ }
+
+ private PictureProfile getPictureProfileFromCursor(Cursor cursor) {
+ String returnId = cursor.getString(
+ cursor.getColumnIndexOrThrow(PictureQuality.PARAMETER_ID));
+ int type = cursor.getInt(
+ cursor.getColumnIndexOrThrow(PictureQuality.PARAMETER_TYPE));
+ String name = cursor.getString(
+ cursor.getColumnIndexOrThrow(PictureQuality.PARAMETER_NAME));
+ String inputId = cursor.getString(
+ cursor.getColumnIndexOrThrow(PictureQuality.PARAMETER_INPUT_ID));
+ String packageName = cursor.getString(
+ cursor.getColumnIndexOrThrow(PictureQuality.PARAMETER_PACKAGE));
+ String settings = cursor.getString(
+ cursor.getColumnIndexOrThrow(mMediaQualityDbHelper.SETTINGS));
+ return new PictureProfile(returnId, type, name, inputId,
+ packageName, jsonToBundle(settings));
+ }
+
@Override
public List<PictureProfile> getPictureProfilesByPackage(String packageName) {
- return new ArrayList<>();
+ String selection = PictureQuality.PARAMETER_PACKAGE + " = ?";
+ String[] selectionArguments = {packageName};
+ return getPictureProfilesBasedOnConditions(getAllPictureProfileColumns(), selection,
+ selectionArguments);
}
+
@Override
public List<PictureProfile> getAvailablePictureProfiles() {
return new ArrayList<>();
}
+
@Override
public List<String> getPictureProfilePackageNames() {
- return new ArrayList<>();
+ String [] column = {PictureQuality.PARAMETER_NAME};
+ List<PictureProfile> pictureProfiles = getPictureProfilesBasedOnConditions(column,
+ null, null);
+ List<String> packageNames = new ArrayList<>();
+ for (PictureProfile pictureProfile: pictureProfiles) {
+ packageNames.add(pictureProfile.getName());
+ }
+ return packageNames;
+ }
+
+ private List<PictureProfile> getPictureProfilesBasedOnConditions(String[] columns,
+ String selection, String[] selectionArguments) {
+ SQLiteDatabase db = mMediaQualityDbHelper.getReadableDatabase();
+
+ try (
+ Cursor cursor = db.query(
+ mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME,
+ columns,
+ selection,
+ selectionArguments,
+ /*groupBy=*/ null,
+ /*having=*/ null,
+ /*orderBy=*/ null)
+ ) {
+ List<PictureProfile> pictureProfiles = new ArrayList<>();
+ while (cursor.moveToNext()) {
+ pictureProfiles.add(getPictureProfileFromCursor(cursor));
+ }
+ return pictureProfiles;
+ }
}
@Override
diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
index d5d4070..52ddb80 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
@@ -22,8 +22,11 @@
import android.app.backup.BackupRestoreEventLogger;
import android.service.notification.DeviceEffectsApplier;
+import com.android.internal.annotations.Keep;
+
import java.util.Set;
+@Keep
public interface NotificationManagerInternal {
NotificationChannel getNotificationChannel(String pkg, int uid, String channelId);
NotificationChannelGroup getNotificationChannelGroup(String pkg, int uid, String channelId);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 4d0c7ec..207764b 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -211,6 +211,7 @@
import android.app.RemoteServiceException.BadUserInitiatedJobNotificationException;
import android.app.StatsManager;
import android.app.UriGrantsManager;
+import android.app.ZenBypassingApp;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.backup.BackupManager;
import android.app.backup.BackupRestoreEventLogger;
@@ -238,7 +239,6 @@
import android.content.pm.IPackageManager;
import android.content.pm.LauncherApps;
import android.content.pm.ModuleInfo;
-import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
@@ -3089,7 +3089,7 @@
migrateDefaultNAS();
maybeShowInitialReviewPermissionsNotification();
- if (android.app.Flags.modesApi()) {
+ if (android.app.Flags.modesApi() && !mZenModeHelper.hasDeviceEffectsApplier()) {
// Cannot be done earlier, as some services aren't ready until this point.
mZenModeHelper.setDeviceEffectsApplier(
new DefaultDeviceEffectsApplier(getContext()));
@@ -4043,7 +4043,7 @@
"canNotifyAsPackage for uid " + uid);
}
- return areNotificationsEnabledForPackageInt(pkg, uid);
+ return areNotificationsEnabledForPackageInt(uid);
}
/**
@@ -4864,30 +4864,20 @@
}
@Override
- public List<String> getPackagesBypassingDnd(int userId,
- boolean includeConversationChannels) {
+ public ParceledListSlice<ZenBypassingApp> getPackagesBypassingDnd(int userId)
+ throws RemoteException {
checkCallerIsSystem();
- final ArraySet<String> packageNames = new ArraySet<>();
-
- List<PackageInfo> pkgs = mPackageManagerClient.getInstalledPackagesAsUser(0, userId);
- for (PackageInfo pi : pkgs) {
- String pkg = pi.packageName;
- // If any NotificationChannel for this package is bypassing, the
- // package is considered bypassing.
- for (NotificationChannel channel : getNotificationChannelsBypassingDnd(pkg,
- pi.applicationInfo.uid).getList()) {
- // Skips non-demoted conversation channels.
- if (!includeConversationChannels
- && !TextUtils.isEmpty(channel.getConversationId())
- && !channel.isDemoted()) {
- continue;
- }
- packageNames.add(pkg);
+ UserHandle user = UserHandle.of(userId);
+ ArrayList<ZenBypassingApp> bypassing =
+ mPreferencesHelper.getPackagesBypassingDnd(userId);
+ for (int i = bypassing.size() - 1; i >= 0; i--) {
+ String pkg = bypassing.get(i).getPkg();
+ if (!areNotificationsEnabledForPackage(pkg, getUidForPackageAndUser(pkg, user))) {
+ bypassing.remove(i);
}
}
-
- return new ArrayList<String>(packageNames);
+ return new ParceledListSlice<>(bypassing);
}
@Override
@@ -7763,7 +7753,7 @@
@Override
public boolean areNotificationsEnabledForPackage(String pkg, int uid) {
- return areNotificationsEnabledForPackageInt(pkg, uid);
+ return areNotificationsEnabledForPackageInt(uid);
}
@Override
@@ -8742,7 +8732,7 @@
}
// blocked apps
- boolean isBlocked = !areNotificationsEnabledForPackageInt(pkg, uid);
+ boolean isBlocked = !areNotificationsEnabledForPackageInt(uid);
synchronized (mNotificationLock) {
isBlocked |= isRecordBlockedLocked(r);
}
@@ -8792,7 +8782,7 @@
}
}
- private boolean areNotificationsEnabledForPackageInt(String pkg, int uid) {
+ private boolean areNotificationsEnabledForPackageInt(int uid) {
return mPermissionHelper.hasPermission(uid);
}
@@ -9328,7 +9318,7 @@
* notifying all listeners to a background thread; false otherwise.
*/
private boolean postNotification() {
- boolean appBanned = !areNotificationsEnabledForPackageInt(pkg, uid);
+ boolean appBanned = !areNotificationsEnabledForPackageInt(uid);
boolean isCallNotification = isCallNotification(pkg, uid);
boolean posted = false;
synchronized (NotificationManagerService.this.mNotificationLock) {
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 9f0b4b0..e6f784c 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -57,6 +57,7 @@
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
+import android.app.ZenBypassingApp;
import android.content.AttributionSource;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -1950,6 +1951,35 @@
}
/**
+ * Gets all apps that can bypass DND, and a boolean indicating whether all (true) or some
+ * (false) of its notification channels can currently bypass.
+ */
+ public @NonNull ArrayList<ZenBypassingApp> getPackagesBypassingDnd(@UserIdInt int userId) {
+ ArrayList<ZenBypassingApp> bypassing = new ArrayList<>();
+ synchronized (mLock) {
+ for (PackagePreferences p : mPackagePreferences.values()) {
+ if (p.userId != userId) {
+ continue;
+ }
+ int totalChannelCount = p.channels.size();
+ int bypassingCount = 0;
+ if (totalChannelCount == 0) {
+ continue;
+ }
+ for (NotificationChannel channel : p.channels.values()) {
+ if (channelIsLiveLocked(p, channel) && channel.canBypassDnd()) {
+ bypassingCount++;
+ }
+ }
+ if (bypassingCount > 0) {
+ bypassing.add(new ZenBypassingApp(p.pkg, totalChannelCount == bypassingCount));
+ }
+ }
+ }
+ return bypassing;
+ }
+
+ /**
* True for pre-O apps that only have the default channel, or pre O apps that have no
* channels yet. This method will create the default channel for pre-O apps that don't have it.
* Should never be true for O+ targeting apps, but that's enforced on boot/when an app
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index cfeacdf..ca4f83f 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -24,6 +24,8 @@
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_REMOVED;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_UNKNOWN;
import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY;
+import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG;
+import static android.app.backup.NotificationLoggingConstants.ERROR_XML_PARSING;
import static android.service.notification.Condition.SOURCE_UNKNOWN;
import static android.service.notification.Condition.SOURCE_USER_ACTION;
import static android.service.notification.Condition.STATE_FALSE;
@@ -44,8 +46,6 @@
import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
import static com.android.internal.util.Preconditions.checkArgument;
-import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG;
-import static android.app.backup.NotificationLoggingConstants.ERROR_XML_PARSING;
import static java.util.Objects.requireNonNull;
@@ -303,6 +303,15 @@
}
/**
+ * @return whether a {@link DeviceEffectsApplier} has already been set or not
+ */
+ boolean hasDeviceEffectsApplier() {
+ synchronized (mConfigLock) {
+ return mDeviceEffectsApplier != null;
+ }
+ }
+
+ /**
* Set the {@link DeviceEffectsApplier} used to apply the consolidated effects.
*
* <p>Previously calculated effects (as loaded from the user's {@link ZenModeConfig}) will be
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 76ea0b9..4690e02 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -880,7 +880,8 @@
PackageInstaller.STATUS_PENDING_USER_ACTION);
broadcastIntent.putExtra(Intent.EXTRA_INTENT, dialogIntent);
broadcastIntent.putExtra(Intent.EXTRA_USER, user);
- sendIntent(statusReceiver, packageName, /* message= */ "", broadcastIntent);
+ mPm.mHandler.post(
+ () -> sendIntent(statusReceiver, packageName, /* message= */ "", broadcastIntent));
}
private void verifyUninstallPermissions() {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 5518bfa..1052c94 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -1022,7 +1022,7 @@
// Close.
file.finishWrite(outs);
} catch (IOException e) {
- Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
+ Slog.w(TAG, "Failed to write to file " + file.getBaseFile(), e);
file.failWrite(outs);
}
}
@@ -1055,7 +1055,7 @@
final String tag = parser.getName();
if (depth == 1) {
if (!TAG_ROOT.equals(tag)) {
- Slog.e(TAG, "Invalid root tag: " + tag);
+ Slog.v(TAG, "Invalid root tag: " + tag);
return;
}
continue;
@@ -1066,7 +1066,7 @@
mRawLastResetTime.set(parseLongAttribute(parser, ATTR_VALUE));
break;
default:
- Slog.e(TAG, "Invalid tag: " + tag);
+ Slog.v(TAG, "Invalid tag: " + tag);
break;
}
}
@@ -1113,7 +1113,7 @@
// Remove all dangling bitmap files.
cleanupDanglingBitmapDirectoriesLocked(userId);
} catch (XmlPullParserException | IOException e) {
- Slog.e(TAG, "Failed to write to file " + file, e);
+ Slog.w(TAG, "Failed to write to file " + file, e);
file.failWrite(os);
}
}
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index acf62dc..09feb18 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -36,6 +36,7 @@
import android.content.pm.PermissionInfo;
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
+import android.health.connect.HealthPermissions;
import android.media.RingtoneManager;
import android.media.midi.MidiManager;
import android.net.Uri;
@@ -48,6 +49,7 @@
import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.permission.PermissionManager;
+import android.permission.flags.Flags;
import android.print.PrintManager;
import android.provider.CalendarContract;
import android.provider.ContactsContract;
@@ -64,6 +66,7 @@
import android.util.Xml;
import com.android.internal.R;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
@@ -213,8 +216,13 @@
private static final Set<String> SENSORS_PERMISSIONS = new ArraySet<>();
static {
- SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS);
- SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND);
+ if (Flags.replaceBodySensorPermissionEnabled()) {
+ SENSORS_PERMISSIONS.add(HealthPermissions.READ_HEART_RATE);
+ SENSORS_PERMISSIONS.add(HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND);
+ } else {
+ SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS);
+ SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND);
+ }
}
private static final Set<String> STORAGE_PERMISSIONS = new ArraySet<>();
@@ -1632,6 +1640,14 @@
continue;
}
+ // If the trunkstable feature flag is disabled for this
+ // exception, skip the tag.
+ if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(
+ /* pkg= */ null, parser, /* allowNoNamespace= */ true)) {
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+
final boolean fixed =
parser.getAttributeBoolean(null, ATTR_FIXED, false);
final boolean whitelisted =
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 8d039f1..dda5bcf 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -88,7 +88,6 @@
import static com.android.hardware.input.Flags.keyboardA11yShortcutControl;
import static com.android.hardware.input.Flags.modifierShortcutDump;
import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
-import static com.android.hardware.input.Flags.useKeyGestureEventHandlerMultiPressGestures;
import static com.android.server.flags.Flags.modifierShortcutManagerMultiuser;
import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY;
@@ -2496,7 +2495,7 @@
private void initKeyCombinationRules() {
mKeyCombinationManager = new KeyCombinationManager(mHandler);
- if (useKeyGestureEventHandler() && useKeyGestureEventHandlerMultiPressGestures()) {
+ if (InputSettings.doesKeyGestureEventHandlerSupportMultiKeyGestures()) {
return;
}
final boolean screenshotChordEnabled = mContext.getResources().getBoolean(
@@ -3442,7 +3441,7 @@
+ keyguardOn() + " canceled=" + event.isCanceled());
}
- if (!useKeyGestureEventHandler()) {
+ if (!InputSettings.doesKeyGestureEventHandlerSupportMultiKeyGestures()) {
if (mKeyCombinationManager.isKeyConsumed(event)) {
return keyConsumed;
}
@@ -5720,7 +5719,8 @@
}
private void handleKeyGesture(KeyEvent event, boolean interactive, boolean defaultDisplayOn) {
- if (mKeyCombinationManager.interceptKey(event, interactive)) {
+ if (!InputSettings.doesKeyGestureEventHandlerSupportMultiKeyGestures()
+ && mKeyCombinationManager.interceptKey(event, interactive)) {
// handled by combo keys manager.
mSingleKeyGestureDetector.reset();
return;
diff --git a/services/core/java/com/android/server/stats/pull/psi/OWNERS b/services/core/java/com/android/server/stats/pull/psi/OWNERS
new file mode 100644
index 0000000..f72fd7c
--- /dev/null
+++ b/services/core/java/com/android/server/stats/pull/psi/OWNERS
@@ -0,0 +1,9 @@
+jackrichardson@google.com
+dbrotikovskaya@google.com
+ivokay@google.com
+gagapov@google.com
+yigitfiliz@google.com
+rswang@google.com
+evaleriano@google.com
+igorstepanov@google.com
+iyou@google.com
diff --git a/services/core/java/com/android/server/stats/pull/psi/PsiData.java b/services/core/java/com/android/server/stats/pull/psi/PsiData.java
new file mode 100644
index 0000000..d1cbf74
--- /dev/null
+++ b/services/core/java/com/android/server/stats/pull/psi/PsiData.java
@@ -0,0 +1,106 @@
+/*
+ * 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.stats.pull.psi;
+
+/**
+ * Wraps PSI (Pressure Stall Information) corresponding to a system resource. See more details about
+ * PSI, see https://docs.kernel.org/accounting/psi.html#psi-pressure-stall-information.
+ */
+public class PsiData {
+ public enum ResourceType {
+ CPU,
+ MEMORY,
+ IO
+ }
+
+ static class AppsStallInfo {
+
+ /** Past 10s average % of wasted CPU cycles when apps tasks are stalled on mResourceType.*/
+ private final float mAvg10SecPercentage;
+
+ /** Past 60s average % of wasted CPU cycles when apps tasks are stalled on mResourceType.*/
+ private final float mAvg60SecPercentage;
+
+ /** Past 300s average % of wasted CPU cycles when apps tasks are stalled on mResourceType.*/
+ private final float mAvg300SecPercentage;
+
+ /** Total number of microseconds that apps tasks are stalled on mResourceType.*/
+ private final long mTotalUsec;
+
+ AppsStallInfo(
+ float avg10SecPercentage, float avg60SecPercentage,
+ float avg300SecPercentage, long totalUsec) {
+ mAvg10SecPercentage = avg10SecPercentage;
+ mAvg60SecPercentage = avg60SecPercentage;
+ mAvg300SecPercentage = avg300SecPercentage;
+ mTotalUsec = totalUsec;
+ }
+ }
+
+ /** The system resource type of this {@code PsiData}. */
+ private final ResourceType mResourceType;
+
+ /** Info on some tasks are stalled on mResourceType. */
+ private final AppsStallInfo mSomeAppsStallInfo;
+
+ /**
+ * Info on all non-idle tasks are stalled on mResourceType. For the CPU ResourceType,
+ * all fields will always be 0 as it's undefined.
+ */
+ private final AppsStallInfo mFullAppsStallInfo;
+
+ PsiData(
+ ResourceType resourceType,
+ AppsStallInfo someAppsStallInfo,
+ AppsStallInfo fullAppsStallInfo) {
+ mResourceType = resourceType;
+ mSomeAppsStallInfo = someAppsStallInfo;
+ mFullAppsStallInfo = fullAppsStallInfo;
+ }
+
+ public ResourceType getResourceType() {
+ return mResourceType;
+ }
+
+ public float getSomeAvg10SecPercentage() {
+ return mSomeAppsStallInfo.mAvg10SecPercentage; }
+
+ public float getSomeAvg60SecPercentage() {
+ return mSomeAppsStallInfo.mAvg60SecPercentage; }
+
+ public float getSomeAvg300SecPercentage() {
+ return mSomeAppsStallInfo.mAvg300SecPercentage; }
+
+ public long getSomeTotalUsec() {
+ return mSomeAppsStallInfo.mTotalUsec;
+ }
+
+ public float getFullAvg10SecPercentage() {
+ return mFullAppsStallInfo.mAvg10SecPercentage;
+ }
+
+ public float getFullAvg60SecPercentage() {
+ return mFullAppsStallInfo.mAvg60SecPercentage;
+ }
+
+ public float getFullAvg300SecPercentage() {
+ return mFullAppsStallInfo.mAvg300SecPercentage; }
+
+ public long getFullTotalUsec() {
+ return mFullAppsStallInfo.mTotalUsec;
+ }
+}
diff --git a/services/core/java/com/android/server/stats/pull/psi/PsiExtractor.java b/services/core/java/com/android/server/stats/pull/psi/PsiExtractor.java
new file mode 100644
index 0000000..5d0d7e1
--- /dev/null
+++ b/services/core/java/com/android/server/stats/pull/psi/PsiExtractor.java
@@ -0,0 +1,161 @@
+/*
+ * 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.stats.pull.psi;
+
+import static java.util.stream.Collectors.joining;
+
+import android.annotation.Nullable;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.text.MessageFormat;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class PsiExtractor {
+ private static final String TAG = "PsiExtractor";
+
+ // Paths for PSI files are guarded by SELinux policy. PCS needs to be explicitly
+ // allowlisted to access these files.
+ private static final String PSI_MEMORY_PATH = "/proc/pressure/memory";
+ private static final String PSI_IO_PATH = "/proc/pressure/io";
+ private static final String PSI_CPU_PATH = "/proc/pressure/cpu";
+
+ // The patterns matching a line of PSI output such as
+ // "some avg10=0.12 avg60=0.34 avg300=0.56 total=123456" or
+ // "full avg10=0.12 avg60=0.34 avg300=0.56 total=123456" to extract the stalling percentage
+ // values for "some" and "full" line of PSI output respectively.
+ private static final String PSI_PATTERN_TEMPLATE =
+ ".*{0} avg10=(\\d+.\\d+) avg60=(\\d+.\\d+) avg300=(\\d+.\\d+) total=(\\d+).*";
+ private static final String SOME = "some";
+ private static final String FULL = "full";
+ private final PsiReader mPsiReader;
+
+ public PsiExtractor() {
+ mPsiReader = new PsiReader();
+ }
+ public PsiExtractor(PsiReader psiReader) {
+ mPsiReader = psiReader;
+ }
+
+ /**
+ * Parses /pressure/proc/{resourceType} kernel file to extract the Pressure Stall Information
+ * (PSI), more information: can be found at https://docs.kernel.org/accounting/psi.html.
+ *
+ * @param resourceType (Memory/CPU/IO) to get the PSI for.
+ */
+ @Nullable
+ public PsiData getPsiData(PsiData.ResourceType resourceType) {
+ String psiFileData;
+ if (resourceType == PsiData.ResourceType.MEMORY) {
+ psiFileData = mPsiReader.read(PSI_MEMORY_PATH);
+ } else if (resourceType == PsiData.ResourceType.IO) {
+ psiFileData = mPsiReader.read(PSI_IO_PATH);
+ } else if (resourceType == PsiData.ResourceType.CPU) {
+ psiFileData = mPsiReader.read(PSI_CPU_PATH);
+ } else {
+ Log.w(TAG, "PsiExtractor failure: cannot read kernel source file, returning null");
+ return null;
+ }
+ return parsePsiData(psiFileData, resourceType);
+ }
+
+ @Nullable
+ private static PsiData.AppsStallInfo parsePsiString(
+ String psiFileData, String appType, PsiData.ResourceType resourceType) {
+ // There is an extra case of file content: the CPU full is undefined and isn't reported for
+ // earlier versions. It should be always propagated as 0, but for the current logic purposes
+ // we will report atom only if at least one value (some/full) is presented. Thus, hardcoding
+ // the "full" line as 0 only when the "some" line is presented.
+ if (appType == FULL && resourceType == PsiData.ResourceType.CPU) {
+ if (psiFileData.contains(SOME) && !psiFileData.contains(FULL)) {
+ return new PsiData.AppsStallInfo((float) 0.0, (float) 0.0, (float) 0.0, 0);
+ }
+ }
+
+ Pattern psiStringPattern = Pattern.compile(
+ MessageFormat.format(PSI_PATTERN_TEMPLATE, appType));
+ Matcher psiLineMatcher = psiStringPattern.matcher(psiFileData);
+
+ // Parsing the line starts with "some" in the expected output.
+ // The line for "some" should always be present in PSI output. The output must be somehow
+ // malformed if the line cannot be matched.
+ if (!psiLineMatcher.find()) {
+ Log.w(TAG,
+ "Returning null: the line \"" + appType + "\" is not in expected pattern.");
+ return null;
+ }
+ try {
+ return new PsiData.AppsStallInfo(
+ Float.parseFloat(psiLineMatcher.group(1)),
+ Float.parseFloat(psiLineMatcher.group(2)),
+ Float.parseFloat(psiLineMatcher.group(3)),
+ Long.parseLong(psiLineMatcher.group(4)));
+ } catch (NumberFormatException e) {
+ Log.w(TAG,
+ "Returning null: some value in line \"" + appType
+ + "\" cannot be parsed as numeric.");
+ return null;
+ }
+ }
+
+ @Nullable
+ private static PsiData parsePsiData(
+ String psiFileData, PsiData.ResourceType resourceType) {
+ PsiData.AppsStallInfo someAppsStallInfo = parsePsiString(psiFileData, SOME, resourceType);
+ PsiData.AppsStallInfo fullAppsStallInfo = parsePsiString(psiFileData, FULL, resourceType);
+
+ if (someAppsStallInfo == null && fullAppsStallInfo == null) {
+ Log.w(TAG, "Returning empty PSI: some or full line are failed to parse");
+ return null;
+ } else if (someAppsStallInfo == null) {
+ Log.d(TAG, "Replacing some info with empty PSI record for the resource type "
+ + resourceType);
+ someAppsStallInfo = new PsiData.AppsStallInfo(
+ (float) -1.0, (float) -1.0, (float) -1.0, -1);
+ } else if (fullAppsStallInfo == null) {
+ Log.d(TAG, "Replacing full info with empty PSI record for the resource type "
+ + resourceType);
+ fullAppsStallInfo = new PsiData.AppsStallInfo(
+ (float) -1.0, (float) -1.0, (float) -1.0, -1);
+ }
+ return new PsiData(resourceType, someAppsStallInfo, fullAppsStallInfo);
+ }
+
+ /** Dependency class */
+ public static class PsiReader {
+ /**
+ * Reads file from provided path and returns its content if the file found, null otherwise.
+ *
+ * @param filePath file path to read.
+ */
+ @Nullable
+ public String read(String filePath) {
+ try (BufferedReader br =
+ new BufferedReader(new InputStreamReader(
+ new FileInputStream(filePath)))) {
+ return br.lines().collect(joining(System.lineSeparator()));
+ } catch (IOException e) {
+ Log.w(TAG, "Cannot read file " + filePath);
+ return null;
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index b27a2a5..c6e6e76 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2538,6 +2538,19 @@
}
}
if (!activityAllDrawn && !isActivityHome) {
+ // Only check the special case of a fragment host task because the starting window
+ // may not be visible if the client organizer delays the transition ready.
+ if (task.mTaskFragmentHostProcessName != null) {
+ // It may be launched from a task trampoline that already has a starting window.
+ // Return NONE because 2 consecutive splashes may not look smooth in visual.
+ final Task prevTask = task.getParent().getTaskBelow(task);
+ if (prevTask != null) {
+ final ActivityRecord prevTaskTop = prevTask.getTopMostActivity();
+ if (prevTaskTop != null && prevTaskTop.hasStartingWindow()) {
+ return STARTING_WINDOW_TYPE_NONE;
+ }
+ }
+ }
return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
}
}
@@ -8896,6 +8909,7 @@
mAppCompatController.getAppCompatSizeCompatModePolicy();
if (scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance()
+ && mAppCompatDisplayInsets != null
&& !mAppCompatDisplayInsets.mIsInFixedOrientationOrAspectRatioLetterbox) {
// App prefers to keep its original size.
// If the size compat is from previous fixed orientation letterboxing, we may want to
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
index fa2c716..e8eae4f 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
@@ -70,15 +70,8 @@
mAppCompatAspectRatioState.reset();
}
- float getDesiredAspectRatio(@NonNull Configuration newParentConfig,
+ private float getDesiredAspectRatio(@NonNull Configuration newParentConfig,
@NonNull Rect parentBounds) {
- // If in camera compat mode, aspect ratio from the camera compat policy has priority over
- // default letterbox aspect ratio.
- if (AppCompatCameraPolicy.shouldCameraCompatControlAspectRatio(
- mActivityRecord)) {
- return AppCompatCameraPolicy.getCameraCompatAspectRatio(mActivityRecord);
- }
-
final float letterboxAspectRatioOverride =
mAppCompatOverrides.getAppCompatAspectRatioOverrides()
.getFixedOrientationLetterboxAspectRatio(newParentConfig);
@@ -120,7 +113,16 @@
if (mTransparentPolicy.isRunning()) {
return mTransparentPolicy.getInheritedMinAspectRatio();
}
+
final ActivityInfo info = mActivityRecord.info;
+
+ // If in camera compat mode, aspect ratio from the camera compat policy has priority over
+ // the default aspect ratio.
+ if (AppCompatCameraPolicy.shouldCameraCompatControlAspectRatio(mActivityRecord)) {
+ return Math.max(AppCompatCameraPolicy.getCameraCompatMinAspectRatio(mActivityRecord),
+ info.getMinAspectRatio());
+ }
+
final AppCompatAspectRatioOverrides aspectRatioOverrides =
mAppCompatOverrides.getAppCompatAspectRatioOverrides();
if (aspectRatioOverrides.shouldApplyUserMinAspectRatioOverride()) {
diff --git a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
index 8c5689c1..8be66cc 100644
--- a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
@@ -234,7 +234,7 @@
}
// TODO(b/369070416): have policies implement the same interface.
- static float getCameraCompatAspectRatio(@NonNull ActivityRecord activity) {
+ static float getCameraCompatMinAspectRatio(@NonNull ActivityRecord activity) {
final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
if (cameraPolicy == null) {
return 1.0f;
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 2664d8c..6091b83 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -771,20 +771,21 @@
mTopTurnScreenOnActivity.setCurrentLaunchCanTurnScreenOn(false);
}
- boolean hasChange = false;
- if (!lastKeyguardGoingAway && mKeyguardGoingAway) {
+ final boolean startedGoingAway = (!lastKeyguardGoingAway && mKeyguardGoingAway);
+ final boolean occludedChanged = (lastOccluded != mOccluded);
+
+ if (startedGoingAway) {
writeEventLog("dismissIfInsecure");
controller.handleDismissInsecureKeyguard(display);
controller.scheduleGoingAwayTimeout(mDisplayId);
- hasChange = true;
- } else if (lastOccluded != mOccluded) {
+ }
+ if (occludedChanged && (reduceKeyguardTransitions() || !startedGoingAway)) {
controller.handleOccludedChanged(mDisplayId, mTopOccludesActivity);
- hasChange = true;
}
// Collect the participants for shell transition, so that transition won't happen too
// early since the transition was set ready.
- if (hasChange && top != null && (mOccluded || mKeyguardGoingAway)) {
+ if (top != null && (startedGoingAway || (occludedChanged && mOccluded))) {
display.mTransitionController.collect(top);
}
}
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 1bb4c41..0f66b93 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -706,6 +706,10 @@
win.setRequestedVisibleTypes(requestedVisibleTypes);
win.getDisplayContent().getInsetsPolicy().onRequestedVisibleTypesChanged(win,
imeStatsToken);
+ final Task task = win.getTask();
+ if (task != null) {
+ task.dispatchTaskInfoChangedIfNeeded(/* forced= */ true);
+ }
} else {
EmbeddedWindowController.EmbeddedWindow embeddedWindow = null;
if (android.view.inputmethod.Flags.refactorInsetsController()) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 352dc52..dbc3b76c2 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3436,7 +3436,8 @@
info.isSleeping = shouldSleepActivities();
info.isTopActivityTransparent = top != null && !top.fillsParent();
info.lastNonFullscreenBounds = topTask.mLastNonFullscreenBounds;
- final WindowState windowState = top != null ? top.findMainWindow() : null;
+ final WindowState windowState = top != null
+ ? top.findMainWindow(/* includeStartingApp= */ false) : null;
info.requestedVisibleTypes = (windowState != null && Flags.enableFullyImmersiveInDesktop())
? windowState.getRequestedVisibleTypes() : WindowInsets.Type.defaultVisible();
AppCompatUtils.fillAppCompatTaskInfo(this, info, top);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 079170a..81af78e 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2989,15 +2989,7 @@
return (mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0;
}
- @Override
- void resolveOverrideConfiguration(Configuration newParentConfig) {
- super.resolveOverrideConfiguration(newParentConfig);
- if (mActivityRecord != null) {
- // Let the activity decide whether to apply the size override.
- return;
- }
- final Configuration resolvedConfig = getResolvedOverrideConfiguration();
- resolvedConfig.seq = newParentConfig.seq;
+ void applySizeOverride(Configuration newParentConfig, Configuration resolvedConfig) {
applySizeOverrideIfNeeded(
getDisplayContent(),
mSession.mProcess.mInfo,
@@ -3380,8 +3372,10 @@
if (cleanupOnResume) {
requestUpdateWallpaperIfNeeded();
}
- mDestroying = false;
- destroyedSomething = true;
+ if (!mHasSurface) {
+ mDestroying = false;
+ destroyedSomething = true;
+ }
// Since mDestroying will affect ActivityRecord#allDrawn, we need to perform another
// traversal in case we are waiting on this window to start the transition.
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 44e237a..004f406 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -672,6 +672,15 @@
getResolvedOverrideConfiguration().updateFrom(
mFixedRotationTransformState.mRotatedOverrideConfiguration);
}
+ if (asActivityRecord() == null) {
+ // Let ActivityRecord override the config if there is one. Otherwise, override here.
+ // Resolve WindowToken's configuration by the latest window.
+ final WindowState win = getTopChild();
+ if (win != null) {
+ final Configuration resolvedConfig = getResolvedOverrideConfiguration();
+ win.applySizeOverride(newParentConfig, resolvedConfig);
+ }
+ }
}
@Override
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 9841058..c19c58e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -99,10 +99,11 @@
final class DevicePolicyEngine {
static final String TAG = "DevicePolicyEngine";
- // TODO(b/281701062): reference role name from role manager once its exposed.
static final String DEVICE_LOCK_CONTROLLER_ROLE =
"android.app.role.SYSTEM_FINANCED_DEVICE_CONTROLLER";
+ static final String SYSTEM_SUPERVISION_ROLE = "android.app.role.SYSTEM_SUPERVISION";
+
private static final String CELLULAR_2G_USER_RESTRICTION_ID =
DevicePolicyIdentifiers.getIdentifierForUserRestriction(
UserManager.DISALLOW_CELLULAR_2G);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c653038..8ad8786 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -9004,26 +9004,13 @@
if (!mHasFeature) {
return;
}
-
- CallerIdentity caller;
- if (Flags.setAutoTimeEnabledCoexistence()) {
- caller = getCallerIdentity(who, callerPackageName);
- } else {
- caller = getCallerIdentity(who);
- }
-
- if (Flags.setAutoTimeEnabledCoexistence()) {
- // The effect of this policy is device-wide.
- enforcePermission(SET_TIME, caller.getPackageName(), UserHandle.USER_ALL);
- } else {
- Objects.requireNonNull(who, "ComponentName is null");
- Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
- caller));
- }
+ CallerIdentity caller = getCallerIdentity(who);
+ Objects.requireNonNull(who, "ComponentName is null");
+ Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
+ caller));
mInjector.binderWithCleanCallingIdentity(() ->
mInjector.settingsGlobalPutInt(Settings.Global.AUTO_TIME, enabled ? 1 : 0));
-
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_AUTO_TIME)
.setAdmin(caller.getPackageName())
@@ -9039,26 +9026,77 @@
if (!mHasFeature) {
return false;
}
- CallerIdentity caller;
- if (Flags.setAutoTimeEnabledCoexistence()) {
- caller = getCallerIdentity(who, callerPackageName);
- } else {
- caller = getCallerIdentity(who);
- }
+ CallerIdentity caller = getCallerIdentity(who);
- if (Flags.setAutoTimeEnabledCoexistence()) {
- enforceCanQuery(SET_TIME, caller.getPackageName(), UserHandle.USER_ALL);
- } else {
- Objects.requireNonNull(who, "ComponentName is null");
- Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
- caller));
- }
+ Objects.requireNonNull(who, "ComponentName is null");
+ Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
return mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) > 0;
}
/**
+ * Set whether auto time is enabled on the device.
+ */
+ @Override
+ public void setAutoTimePolicy(String callerPackageName, int policy) {
+ if (!mHasFeature) {
+ return;
+ }
+
+ final Set<Integer> allowedValues =
+ Set.of(
+ DevicePolicyManager.AUTO_TIME_ENABLED,
+ DevicePolicyManager.AUTO_TIME_DISABLED,
+ DevicePolicyManager.AUTO_TIME_NOT_CONTROLLED_BY_POLICY);
+ Preconditions.checkArgument(
+ allowedValues.contains(policy), "Provided mode is not one of the allowed values.");
+
+ CallerIdentity caller = getCallerIdentity(callerPackageName);
+ // The effect of this policy is device-wide.
+ EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+ /* who */ null,
+ SET_TIME,
+ caller.getPackageName(),
+ UserHandle.USER_ALL
+ );
+ if (policy == DevicePolicyManager.AUTO_TIME_NOT_CONTROLLED_BY_POLICY) {
+ mDevicePolicyEngine.removeGlobalPolicy(PolicyDefinition.AUTO_TIME, enforcingAdmin);
+ } else {
+ mDevicePolicyEngine.setGlobalPolicy(
+ PolicyDefinition.AUTO_TIME,
+ enforcingAdmin,
+ new IntegerPolicyValue(policy));
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_AUTO_TIME)
+ .setAdmin(caller.getPackageName())
+ .setBoolean(policy == DevicePolicyManager.AUTO_TIME_ENABLED)
+ .write();
+ }
+ }
+
+ /**
+ * Returns whether auto time is used on the device or not.
+ */
+ @Override
+ public int getAutoTimePolicy(String callerPackageName) {
+ if (!mHasFeature) {
+ return DevicePolicyManager.AUTO_TIME_NOT_CONTROLLED_BY_POLICY;
+ }
+ CallerIdentity caller = getCallerIdentity(callerPackageName);
+ // The effect of this policy is device-wide.
+ EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+ /* who */ null,
+ SET_TIME,
+ caller.getPackageName(),
+ UserHandle.USER_ALL
+ );
+ Integer state = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
+ PolicyDefinition.AUTO_TIME, enforcingAdmin);
+ return state != null ? state : DevicePolicyManager.AUTO_TIME_NOT_CONTROLLED_BY_POLICY;
+ }
+
+ /**
* Set whether auto time zone is enabled on the device.
*/
@Override
@@ -9068,35 +9106,14 @@
return;
}
- CallerIdentity caller;
- if (Flags.setAutoTimeZoneEnabledCoexistence()) {
- caller = getCallerIdentity(who, callerPackageName);
- } else {
- caller = getCallerIdentity(who);
- }
+ CallerIdentity caller = getCallerIdentity(who);
- if (Flags.setAutoTimeZoneEnabledCoexistence()) {
- // The effect of this policy is device-wide.
- EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
- who,
- SET_TIME_ZONE,
- caller.getPackageName(),
- UserHandle.USER_ALL
- );
- mDevicePolicyEngine.setGlobalPolicy(
- PolicyDefinition.AUTO_TIMEZONE,
- // TODO(b/260573124): add correct enforcing admin when permission changes are
- // merged.
- enforcingAdmin,
- new BooleanPolicyValue(enabled));
- } else {
- Objects.requireNonNull(who, "ComponentName is null");
- Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
- caller));
- mInjector.binderWithCleanCallingIdentity(() ->
- mInjector.settingsGlobalPutInt(Global.AUTO_TIME_ZONE, enabled ? 1 : 0));
- }
+ Objects.requireNonNull(who, "ComponentName is null");
+ Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
+ caller));
+ mInjector.binderWithCleanCallingIdentity(() ->
+ mInjector.settingsGlobalPutInt(Global.AUTO_TIME_ZONE, enabled ? 1 : 0));
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_AUTO_TIME_ZONE)
@@ -9114,26 +9131,70 @@
return false;
}
- CallerIdentity caller;
- if (Flags.setAutoTimeZoneEnabledCoexistence()) {
- caller = getCallerIdentity(who, callerPackageName);
- } else {
- caller = getCallerIdentity(who);
- }
-
- if (Flags.setAutoTimeZoneEnabledCoexistence()) {
- // The effect of this policy is device-wide.
- enforceCanQuery(SET_TIME_ZONE, caller.getPackageName(), UserHandle.USER_ALL);
- } else {
- Objects.requireNonNull(who, "ComponentName is null");
- Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
- caller));
- }
-
+ CallerIdentity caller = getCallerIdentity(who);
+ Objects.requireNonNull(who, "ComponentName is null");
+ Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
+ caller));
return mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) > 0;
}
+ /**
+ * Set auto time zone state.
+ */
+ public void setAutoTimeZonePolicy(String callerPackageName, int policy) {
+ if (!mHasFeature) {
+ return;
+ }
+
+ CallerIdentity caller = getCallerIdentity(callerPackageName);
+ // The effect of this policy is device-wide.
+ EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+ /* who */ null,
+ SET_TIME_ZONE,
+ caller.getPackageName(),
+ UserHandle.USER_ALL
+ );
+
+ if (policy != DevicePolicyManager.AUTO_TIME_ZONE_NOT_CONTROLLED_BY_POLICY) {
+ mDevicePolicyEngine.setGlobalPolicy(
+ PolicyDefinition.AUTO_TIME_ZONE,
+ enforcingAdmin,
+ new IntegerPolicyValue(policy));
+
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_AUTO_TIME_ZONE)
+ .setAdmin(caller.getPackageName())
+ .setBoolean(policy == DevicePolicyManager.AUTO_TIME_ZONE_ENABLED)
+ .write();
+ } else {
+ mDevicePolicyEngine.removeGlobalPolicy(
+ PolicyDefinition.AUTO_TIME_ZONE,
+ enforcingAdmin);
+ }
+ }
+
+ /**
+ * Returns whether auto time zone is used on the device or not.
+ */
+ @Override
+ public int getAutoTimeZonePolicy(String callerPackageName) {
+ if (!mHasFeature) {
+ return DevicePolicyManager.AUTO_TIME_ZONE_NOT_CONTROLLED_BY_POLICY;
+ }
+ CallerIdentity caller = getCallerIdentity(callerPackageName);
+ // The effect of this policy is device-wide.
+ EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+ /* who */ null,
+ SET_TIME_ZONE,
+ caller.getPackageName(),
+ UserHandle.USER_ALL
+ );
+ Integer state = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
+ PolicyDefinition.AUTO_TIME_ZONE, enforcingAdmin);
+ return state != null ? state : DevicePolicyManager.AUTO_TIME_ZONE_NOT_CONTROLLED_BY_POLICY;
+ }
+
// TODO (b/137101239): remove this method in follow-up CL
// since it's only used for split system user.
@Override
@@ -21030,6 +21091,27 @@
}
@Override
+ public boolean removeManagedProfile(int userId) {
+ Preconditions.checkCallAuthorization(
+ hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
+
+ if (!isManagedProfile(userId)){
+ throw new IllegalArgumentException("Cannot remove user as it is not a managed profile");
+ }
+
+ boolean success = false;
+ final long identity = Binder.clearCallingIdentity();
+ try{
+ success = mUserManager.removeUserEvenWhenDisallowed(userId);
+ } catch (Exception e) {
+ Slogf.e(LOG_TAG, "Remove managed profile failed due to: ", e);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return success;
+ }
+
+ @Override
public UserHandle createAndProvisionManagedProfile(
@NonNull ManagedProfileProvisioningParams provisioningParams,
@NonNull String callerPackage) {
@@ -23765,9 +23847,6 @@
Slogf.i(LOG_TAG,
"Started device policies migration to the device policy engine.");
// TODO(b/359188869): Move this to the current migration method.
- if (Flags.setAutoTimeZoneEnabledCoexistence()) {
- migrateAutoTimezonePolicy();
- }
if (Flags.setPermissionGrantStateCoexistence()) {
migratePermissionGrantStatePolicies();
}
@@ -23816,11 +23895,6 @@
// Additional migration steps should repeat the pattern above with a new backupId.
}
- private void migrateAutoTimezonePolicy() {
- Slogf.i(LOG_TAG, "Skipping Migration of AUTO_TIMEZONE policy to device policy engine,"
- + "as no way to identify if the value was set by the admin or the user.");
- }
-
private void migratePermissionGrantStatePolicies() {
Slogf.i(LOG_TAG, "Migrating PERMISSION_GRANT policy to device policy engine.");
for (UserInfo userInfo : mUserManager.getUsers()) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index f1711f5..24b16b7 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -17,6 +17,7 @@
package com.android.server.devicepolicy;
import static com.android.server.devicepolicy.DevicePolicyEngine.DEVICE_LOCK_CONTROLLER_ROLE;
+import static com.android.server.devicepolicy.DevicePolicyEngine.SYSTEM_SUPERVISION_ROLE;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -94,14 +95,18 @@
private static final MostRestrictive<Boolean> TRUE_MORE_RESTRICTIVE = new MostRestrictive<>(
List.of(new BooleanPolicyValue(true), new BooleanPolicyValue(false)));
- static PolicyDefinition<Boolean> AUTO_TIMEZONE = new PolicyDefinition<>(
+ static PolicyDefinition<Integer> AUTO_TIME_ZONE = new PolicyDefinition<>(
new NoArgsPolicyKey(DevicePolicyIdentifiers.AUTO_TIMEZONE_POLICY),
- // auto timezone is disabled by default, hence enabling it is more restrictive.
- TRUE_MORE_RESTRICTIVE,
+ // Auto time zone is enabled by default. Enabled state has higher priority given it
+ // means the time will be more precise and other applications can rely on that for
+ // their purposes.
+ new TopPriority<>(List.of(
+ EnforcingAdmin.getRoleAuthorityOf(SYSTEM_SUPERVISION_ROLE),
+ EnforcingAdmin.getRoleAuthorityOf(DEVICE_LOCK_CONTROLLER_ROLE),
+ EnforcingAdmin.DPC_AUTHORITY)),
POLICY_FLAG_GLOBAL_ONLY_POLICY,
- (Boolean value, Context context, Integer userId, PolicyKey policyKey) ->
- PolicyEnforcerCallbacks.setAutoTimezoneEnabled(value, context),
- new BooleanPolicySerializer());
+ PolicyEnforcerCallbacks::setAutoTimeZonePolicy,
+ new IntegerPolicySerializer());
static final PolicyDefinition<Integer> GENERIC_PERMISSION_GRANT =
new PolicyDefinition<>(
@@ -344,12 +349,22 @@
PolicyEnforcerCallbacks::setMtePolicy,
new IntegerPolicySerializer());
+ static PolicyDefinition<Integer> AUTO_TIME = new PolicyDefinition<>(
+ new NoArgsPolicyKey(DevicePolicyIdentifiers.AUTO_TIME_POLICY),
+ new TopPriority<>(List.of(
+ EnforcingAdmin.getRoleAuthorityOf(SYSTEM_SUPERVISION_ROLE),
+ EnforcingAdmin.getRoleAuthorityOf(DEVICE_LOCK_CONTROLLER_ROLE),
+ EnforcingAdmin.DPC_AUTHORITY)),
+ POLICY_FLAG_GLOBAL_ONLY_POLICY,
+ PolicyEnforcerCallbacks::setAutoTimePolicy,
+ new IntegerPolicySerializer());
+
private static final Map<String, PolicyDefinition<?>> POLICY_DEFINITIONS = new HashMap<>();
private static Map<String, Integer> USER_RESTRICTION_FLAGS = new HashMap<>();
// TODO(b/277218360): Revisit policies that should be marked as global-only.
static {
- POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.AUTO_TIMEZONE_POLICY, AUTO_TIMEZONE);
+ POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.AUTO_TIMEZONE_POLICY, AUTO_TIME_ZONE);
POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.PERMISSION_GRANT_POLICY,
GENERIC_PERMISSION_GRANT);
POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.SECURITY_LOGGING_POLICY,
@@ -392,6 +407,7 @@
PACKAGES_SUSPENDED);
POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY,
MEMORY_TAGGING);
+ POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.AUTO_TIME_POLICY, AUTO_TIME);
// User Restriction Policies
USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_MODIFY_ACCOUNTS, /* flags= */ 0);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index fdc0ec1..8f80004 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -81,20 +81,24 @@
return AndroidFuture.completedFuture(true);
}
- static CompletableFuture<Boolean> setAutoTimezoneEnabled(@Nullable Boolean enabled,
- @NonNull Context context) {
+ static CompletableFuture<Boolean> setAutoTimeZonePolicy(
+ @Nullable Integer policy, @NonNull Context context, int userId,
+ @NonNull PolicyKey policyKey) {
if (!Flags.setAutoTimeZoneEnabledCoexistence()) {
- Slogf.w(LOG_TAG, "Trying to enforce setAutoTimezoneEnabled while flag is off.");
+ Slogf.w(LOG_TAG, "Trying to enforce setAutoTimeZonePolicy while flag is off.");
return AndroidFuture.completedFuture(true);
}
return Binder.withCleanCallingIdentity(() -> {
Objects.requireNonNull(context);
-
- int value = enabled != null && enabled ? 1 : 0;
- return AndroidFuture.completedFuture(
- Settings.Global.putInt(
- context.getContentResolver(), Settings.Global.AUTO_TIME_ZONE,
- value));
+ if (policy != null &&
+ policy == DevicePolicyManager.AUTO_TIME_ZONE_NOT_CONTROLLED_BY_POLICY) {
+ return AndroidFuture.completedFuture(false);
+ }
+ int enabled = policy != null &&
+ policy == DevicePolicyManager.AUTO_TIME_ZONE_ENABLED ? 1 : 0;
+ return AndroidFuture.completedFuture(Settings.Global.putInt(
+ context.getContentResolver(), Settings.Global.AUTO_TIME_ZONE,
+ enabled));
});
}
@@ -208,6 +212,25 @@
return AndroidFuture.completedFuture(true);
}
+ public static CompletableFuture<Boolean> setAutoTimePolicy(
+ Integer policy, Context context, Integer userId, PolicyKey policyKey) {
+ if (!Flags.setAutoTimeEnabledCoexistence()) {
+ Slogf.w(LOG_TAG, "Trying to enforce setAutoTimePolicy while flag is off.");
+ return AndroidFuture.completedFuture(true);
+ }
+ return Binder.withCleanCallingIdentity(() -> {
+ Objects.requireNonNull(context);
+ if (policy != null
+ && policy == DevicePolicyManager.AUTO_TIME_NOT_CONTROLLED_BY_POLICY) {
+ return AndroidFuture.completedFuture(false);
+ }
+ int enabled = policy != null && policy == DevicePolicyManager.AUTO_TIME_ENABLED ? 1 : 0;
+ return AndroidFuture.completedFuture(
+ Settings.Global.putInt(
+ context.getContentResolver(), Settings.Global.AUTO_TIME, enabled));
+ });
+ }
+
private static class BlockingCallback {
private final CountDownLatch mLatch = new CountDownLatch(1);
private final AtomicReference<Boolean> mValue = new AtomicReference<>();
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionManagerInternal.java b/services/supervision/java/com/android/server/supervision/SupervisionManagerInternal.java
index fead05b..5df9dd5 100644
--- a/services/supervision/java/com/android/server/supervision/SupervisionManagerInternal.java
+++ b/services/supervision/java/com/android/server/supervision/SupervisionManagerInternal.java
@@ -35,6 +35,14 @@
public abstract boolean isSupervisionEnabledForUser(@UserIdInt int userId);
/**
+ * Set whether supervision is enabled for the specified user.
+ *
+ * @param userId The user to set the supervision state for
+ * @param enabled Whether or not the user should be supervised
+ */
+ public abstract void setSupervisionEnabledForUser(@UserIdInt int userId, boolean enabled);
+
+ /**
* Sets whether the supervision lock screen should be shown for the specified user
*
* @param userId The user set the superivision state for
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionService.java b/services/supervision/java/com/android/server/supervision/SupervisionService.java
index 4c515c1..67e2547 100644
--- a/services/supervision/java/com/android/server/supervision/SupervisionService.java
+++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java
@@ -19,7 +19,9 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.admin.DevicePolicyManagerInternal;
import android.app.supervision.ISupervisionManager;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.Bundle;
@@ -28,19 +30,19 @@
import android.os.ShellCallback;
import android.util.SparseArray;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
import com.android.server.pm.UserManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-/**
- * Service for handling system supervision.
- */
+/** Service for handling system supervision. */
public class SupervisionService extends ISupervisionManager.Stub {
private static final String LOG_TAG = "SupervisionService";
@@ -52,14 +54,25 @@
@GuardedBy("getLockObject()")
private final SparseArray<SupervisionUserData> mUserData = new SparseArray<>();
+ private final DevicePolicyManagerInternal mDpmInternal;
private final UserManagerInternal mUserManagerInternal;
public SupervisionService(Context context) {
mContext = context.createAttributionContext(LOG_TAG);
+ mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class);
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
mUserManagerInternal.addUserLifecycleListener(new UserLifecycleListener());
}
+ void syncStateWithDevicePolicyManager(TargetUser user) {
+ if (user.isPreCreated()) return;
+
+ // Ensure that supervision is enabled when supervision app is the profile owner.
+ if (android.app.admin.flags.Flags.enableSupervisionServiceSync() && isProfileOwner(user)) {
+ setSupervisionEnabledForUser(user.getUserIdentifier(), true);
+ }
+ }
+
@Override
public boolean isSupervisionEnabledForUser(@UserIdInt int userId) {
synchronized (getLockObject()) {
@@ -74,14 +87,15 @@
@Nullable FileDescriptor err,
@NonNull String[] args,
@Nullable ShellCallback callback,
- @NonNull ResultReceiver resultReceiver) throws RemoteException {
+ @NonNull ResultReceiver resultReceiver)
+ throws RemoteException {
new SupervisionServiceShellCommand(this)
.exec(this, in, out, err, args, callback, resultReceiver);
}
@Override
- protected void dump(@NonNull FileDescriptor fd,
- @NonNull PrintWriter printWriter, @Nullable String[] args) {
+ protected void dump(
+ @NonNull FileDescriptor fd, @NonNull PrintWriter printWriter, @Nullable String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, printWriter)) return;
try (var pw = new IndentingPrintWriter(printWriter, " ")) {
@@ -120,6 +134,17 @@
}
}
+ /** Returns whether the supervision app has profile owner status. */
+ private boolean isProfileOwner(TargetUser user) {
+ ComponentName profileOwner = mDpmInternal.getProfileOwnerAsUser(user.getUserIdentifier());
+ if (profileOwner == null) {
+ return false;
+ }
+
+ String configPackage = mContext.getResources().getString(R.string.config_systemSupervision);
+ return profileOwner.getPackageName().equals(configPackage);
+ }
+
public static class Lifecycle extends SystemService {
private final SupervisionService mSupervisionService;
@@ -133,13 +158,24 @@
publishLocalService(SupervisionManagerInternal.class, mSupervisionService.mInternal);
publishBinderService(Context.SUPERVISION_SERVICE, mSupervisionService);
}
+
+ @Override
+ public void onUserStarting(@NonNull TargetUser user) {
+ mSupervisionService.syncStateWithDevicePolicyManager(user);
+ }
}
- final SupervisionManagerInternal mInternal = new SupervisionManagerInternal() {
+ final SupervisionManagerInternal mInternal = new SupervisionManagerInternalImpl();
+
+ private final class SupervisionManagerInternalImpl extends SupervisionManagerInternal {
+ @Override
public boolean isSupervisionEnabledForUser(@UserIdInt int userId) {
- synchronized (getLockObject()) {
- return getUserDataLocked(userId).supervisionEnabled;
- }
+ return SupervisionService.this.isSupervisionEnabledForUser(userId);
+ }
+
+ @Override
+ public void setSupervisionEnabledForUser(@UserIdInt int userId, boolean enabled) {
+ SupervisionService.this.setSupervisionEnabledForUser(userId, enabled);
}
@Override
@@ -151,7 +187,7 @@
data.supervisionLockScreenOptions = options;
}
}
- };
+ }
private final class UserLifecycleListener implements UserManagerInternal.UserLifecycleListener {
@Override
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index 69714f3..3fdb53f 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -582,6 +582,34 @@
PackageImpl::setSystemExt,
true
),
+ getSetByValue(
+ AndroidPackage::getAlternateLauncherIconResIds,
+ PackageImpl::setAlternateLauncherIconResIds,
+ intArrayOf(3, 5, 7),
+ compare = { first, second ->
+ equalBy(
+ first, second,
+ { it.size },
+ { it[0] },
+ { it[1] },
+ { it[2] }
+ )
+ }
+ ),
+ getSetByValue(
+ AndroidPackage::getAlternateLauncherLabelResIds,
+ PackageImpl::setAlternateLauncherLabelResIds,
+ intArrayOf(3, 5, 7),
+ compare = { first, second ->
+ equalBy(
+ first, second,
+ { it.size },
+ { it[0] },
+ { it[1] },
+ { it[2] }
+ )
+ }
+ ),
)
override fun initialObject() = PackageImpl.forParsing(
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 4a131558..f82a860 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -109,6 +109,7 @@
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArrayMap;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import com.android.server.LocalServices;
@@ -175,6 +176,7 @@
private ActiveUids mActiveUids;
private PackageManagerInternal mPackageManagerInternal;
private ActivityManagerService mService;
+ private TestCachedAppOptimizer mTestCachedAppOptimizer;
private OomAdjusterInjector mInjector = new OomAdjusterInjector();
private int mUiTierSize;
@@ -242,9 +244,11 @@
doNothing().when(pr).enqueueProcessChangeItemLocked(anyInt(), anyInt(), anyInt(),
anyBoolean());
mActiveUids = new ActiveUids(mService, false);
+ mTestCachedAppOptimizer = new TestCachedAppOptimizer(mService);
mProcessStateController = new ProcessStateController.Builder(mService,
mService.mProcessList, mActiveUids)
.useModernOomAdjuster(mService.mConstants.ENABLE_NEW_OOMADJ)
+ .setCachedAppOptimizer(mTestCachedAppOptimizer)
.setOomAdjusterInjector(mInjector)
.build();
mService.mProcessStateController = mProcessStateController;
@@ -3110,13 +3114,13 @@
mProcessStateController.setUidTempAllowlistStateLSP(MOCKAPP_UID, true);
assertEquals(true, app.getUidRecord().isSetAllowListed());
- assertEquals(true, app.mOptRecord.shouldNotFreeze());
- assertEquals(true, app2.mOptRecord.shouldNotFreeze());
+ assertFreezeState(app, false);
+ assertFreezeState(app2, false);
mProcessStateController.setUidTempAllowlistStateLSP(MOCKAPP_UID, false);
assertEquals(false, app.getUidRecord().isSetAllowListed());
- assertEquals(false, app.mOptRecord.shouldNotFreeze());
- assertEquals(false, app2.mOptRecord.shouldNotFreeze());
+ assertFreezeState(app, true);
+ assertFreezeState(app2, true);
}
@SuppressWarnings("GuardedBy")
@@ -3138,25 +3142,25 @@
assertEquals(true, app.getUidRecord().isSetAllowListed());
assertEquals(true, app2.getUidRecord().isSetAllowListed());
- assertEquals(true, app.mOptRecord.shouldNotFreeze());
- assertEquals(true, app2.mOptRecord.shouldNotFreeze());
- assertEquals(true, app3.mOptRecord.shouldNotFreeze());
+ assertFreezeState(app, false);
+ assertFreezeState(app2, false);
+ assertFreezeState(app3, false);
// Remove app1 from allowlist.
mProcessStateController.setUidTempAllowlistStateLSP(MOCKAPP_UID, false);
assertEquals(false, app.getUidRecord().isSetAllowListed());
assertEquals(true, app2.getUidRecord().isSetAllowListed());
- assertEquals(false, app.mOptRecord.shouldNotFreeze());
- assertEquals(true, app2.mOptRecord.shouldNotFreeze());
- assertEquals(true, app3.mOptRecord.shouldNotFreeze());
+ assertFreezeState(app, true);
+ assertFreezeState(app2, false);
+ assertFreezeState(app3, false);
// Now remove app2 from allowlist.
mProcessStateController.setUidTempAllowlistStateLSP(MOCKAPP2_UID, false);
assertEquals(false, app.getUidRecord().isSetAllowListed());
assertEquals(false, app2.getUidRecord().isSetAllowListed());
- assertEquals(false, app.mOptRecord.shouldNotFreeze());
- assertEquals(false, app2.mOptRecord.shouldNotFreeze());
- assertEquals(false, app3.mOptRecord.shouldNotFreeze());
+ assertFreezeState(app, true);
+ assertFreezeState(app2, true);
+ assertFreezeState(app3, true);
}
@SuppressWarnings("GuardedBy")
@@ -3370,6 +3374,14 @@
assertEquals(expectedCached, state.isCached());
}
+ @SuppressWarnings("GuardedBy")
+ private void assertFreezeState(ProcessRecord app, boolean expectedFreezeState) {
+ boolean actualFreezeState = mTestCachedAppOptimizer.mLastSetFreezeState.get(app.getPid(),
+ false);
+ assertEquals("Unexcepted freeze state for " + app.processName, expectedFreezeState,
+ actualFreezeState);
+ }
+
private class ProcessRecordBuilder {
@SuppressWarnings("UnusedVariable")
int mPid;
@@ -3513,6 +3525,39 @@
return app;
}
}
+ private static final class TestProcessDependencies
+ implements CachedAppOptimizer.ProcessDependencies {
+ @Override
+ public long[] getRss(int pid) {
+ return new long[]{/*totalRSS*/ 0, /*fileRSS*/ 0, /*anonRSS*/ 0, /*swap*/ 0};
+ }
+
+ @Override
+ public void performCompaction(CachedAppOptimizer.CompactProfile action, int pid) {}
+ }
+
+ private static class TestCachedAppOptimizer extends CachedAppOptimizer {
+ private SparseBooleanArray mLastSetFreezeState = new SparseBooleanArray();
+
+ TestCachedAppOptimizer(ActivityManagerService ams) {
+ super(ams, null, new TestProcessDependencies());
+ }
+
+ @Override
+ public boolean useFreezer() {
+ return true;
+ }
+
+ @Override
+ public void freezeAppAsyncLSP(ProcessRecord app) {
+ mLastSetFreezeState.put(app.getPid(), true);
+ }
+
+ @Override
+ public void unfreezeAppLSP(ProcessRecord app, @UnfreezeReason int reason) {
+ mLastSetFreezeState.put(app.getPid(), false);
+ }
+ }
static class OomAdjusterInjector extends OomAdjuster.Injector {
// Jump ahead in time by this offset amount.
@@ -3524,7 +3569,6 @@
mLastSetOomAdj.clear();
}
-
void jumpUptimeAheadTo(long uptimeMillis) {
final long jumpMs = uptimeMillis - getUptimeMillis();
if (jumpMs <= 0) return;
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
index c418151..96c6cbc 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
@@ -123,7 +123,7 @@
Assert.assertEquals("mic mute reporting wrong value",
muted, mAudioService.isMicrophoneMuted());
// verify the intent for mic mute changed is supposed to be fired
- Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS);
+ mTestLooper.dispatchAll();
verify(mSpySystemServer, times(1))
.sendMicrophoneMuteChangedIntent();
reset(mSpySystemServer);
@@ -148,7 +148,7 @@
Assert.assertEquals("mic mute reporting wrong value",
!muted, mAudioService.isMicrophoneMuted());
// verify the intent for mic mute changed is supposed to be fired
- Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS);
+ mTestLooper.dispatchAll();
verify(mSpySystemServer, times(1))
.sendMicrophoneMuteChangedIntent();
reset(mSpySystemServer);
@@ -159,8 +159,7 @@
public void testRingNotifAlias() throws Exception {
Log.i(TAG, "running testRingNotifAlias");
Assert.assertNotNull(mAudioService);
- // TODO add initialization message that can be caught here instead of sleeping
- Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS); // wait for full AudioService initialization
+ mTestLooper.dispatchAll(); // wait for full AudioService initialization
// test with aliasing RING and NOTIFICATION
mAudioService.setNotifAliasRingForTest(true);
@@ -171,7 +170,7 @@
mAudioService.setStreamVolume(AudioSystem.STREAM_NOTIFICATION,
ringVol, 0, "bla");
mAudioService.setStreamVolume(AudioSystem.STREAM_RING, ringMaxVol, 0, "bla");
- Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS);
+ mTestLooper.dispatchAll();
Assert.assertEquals(ringMaxVol,
mAudioService.getStreamVolume(AudioSystem.STREAM_NOTIFICATION));
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
index cf628ad..b81bf3c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
@@ -332,6 +332,28 @@
assertThat(preAuthInfo.callingUserId).isEqualTo(USER_ID);
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EFFECTIVE_USER_BP)
+ public void testCredentialOwnerIdAsUserId_forMandatoryBiometrics() throws Exception {
+ when(mUserManager.getCredentialOwnerProfile(USER_ID)).thenReturn(OWNER_ID);
+ when(mSettingObserver.getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(
+ OWNER_ID)).thenReturn(true);
+ when(mSettingObserver.getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(
+ USER_ID)).thenReturn(false);
+ when(mTrustManager.isInSignificantPlace()).thenReturn(false);
+
+ final BiometricSensor sensor = getFaceSensor();
+ final PromptInfo promptInfo = new PromptInfo();
+ promptInfo.setAuthenticators(BiometricManager.Authenticators.IDENTITY_CHECK);
+ promptInfo.setNegativeButtonText(TEST_PACKAGE_NAME);
+ final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+ mSettingObserver, List.of(sensor), USER_ID , promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager,
+ mUserManager);
+
+ assertThat(preAuthInfo.getIsMandatoryBiometricsAuthentication()).isTrue();
+ }
+
private BiometricSensor getFingerprintSensor() {
BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FINGERPRINT,
TYPE_FINGERPRINT, BiometricManager.Authenticators.BIOMETRIC_STRONG,
diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
index 93aa10b..fd22118 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
@@ -68,7 +68,6 @@
import com.android.internal.R;
import com.android.server.compat.PlatformCompat;
-import com.android.server.integrity.model.IntegrityCheckResult;
import com.android.server.testutils.TestUtils;
import org.junit.After;
diff --git a/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java
deleted file mode 100644
index 57274bf..0000000
--- a/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.model;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.io.ByteArrayOutputStream;
-
-@RunWith(JUnit4.class)
-public class ByteTrackedOutputStreamTest {
-
- @Test
- public void testConstructorStartsWithZeroBytesWritten() {
- ByteTrackedOutputStream byteTrackedOutputStream =
- new ByteTrackedOutputStream(new ByteArrayOutputStream());
-
- assertThat(byteTrackedOutputStream.getWrittenBytesCount()).isEqualTo(0);
- }
-
- @Test
- public void testSuccessfulWriteAndValidateWrittenBytesCount_directFromByteArray()
- throws Exception {
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- ByteTrackedOutputStream byteTrackedOutputStream = new ByteTrackedOutputStream(outputStream);
-
- byte[] outputContent = "This is going to be outputed for tests.".getBytes();
- byteTrackedOutputStream.write(outputContent);
-
- assertThat(byteTrackedOutputStream.getWrittenBytesCount()).isEqualTo(outputContent.length);
- assertThat(outputStream.toByteArray().length).isEqualTo(outputContent.length);
- }
-
- @Test
- public void testSuccessfulWriteAndValidateWrittenBytesCount_fromBitStream() throws Exception {
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- ByteTrackedOutputStream byteTrackedOutputStream = new ByteTrackedOutputStream(outputStream);
-
- BitOutputStream bitOutputStream = new BitOutputStream(byteTrackedOutputStream);
- bitOutputStream.setNext(/* numOfBits= */5, /* value= */1);
- bitOutputStream.flush();
-
- // Even though we wrote 5 bits, this will complete to 1 byte.
- assertThat(byteTrackedOutputStream.getWrittenBytesCount()).isEqualTo(1);
-
- // Add a bit less than 2 bytes (10 bits).
- bitOutputStream.setNext(/* numOfBits= */10, /* value= */1);
- bitOutputStream.flush();
- assertThat(byteTrackedOutputStream.getWrittenBytesCount()).isEqualTo(3);
-
- assertThat(outputStream.toByteArray().length).isEqualTo(3);
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/model/IntegrityCheckResultTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/IntegrityCheckResultTest.java
deleted file mode 100644
index d31ed68..0000000
--- a/services/tests/servicestests/src/com/android/server/integrity/model/IntegrityCheckResultTest.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.model;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.Rule;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.Arrays;
-import java.util.Collections;
-
-@RunWith(JUnit4.class)
-public class IntegrityCheckResultTest {
-
- @Test
- public void createAllowResult() {
- IntegrityCheckResult allowResult = IntegrityCheckResult.allow();
-
- assertThat(allowResult.getEffect()).isEqualTo(IntegrityCheckResult.Effect.ALLOW);
- assertThat(allowResult.getMatchedRules()).isEmpty();
- }
-
- @Test
- public void createAllowResultWithRule() {
- String packageName = "com.test.deny";
- Rule forceAllowRule =
- new Rule(
- new AtomicFormula.StringAtomicFormula(AtomicFormula.PACKAGE_NAME,
- packageName),
- Rule.FORCE_ALLOW);
-
- IntegrityCheckResult allowResult =
- IntegrityCheckResult.allow(Collections.singletonList(forceAllowRule));
-
- assertThat(allowResult.getEffect()).isEqualTo(IntegrityCheckResult.Effect.ALLOW);
- assertThat(allowResult.getMatchedRules()).containsExactly(forceAllowRule);
- }
-
- @Test
- public void createDenyResultWithRule() {
- String packageName = "com.test.deny";
- Rule failedRule =
- new Rule(
- new AtomicFormula.StringAtomicFormula(AtomicFormula.PACKAGE_NAME,
- packageName),
- Rule.DENY);
-
- IntegrityCheckResult denyResult =
- IntegrityCheckResult.deny(Collections.singletonList(failedRule));
-
- assertThat(denyResult.getEffect()).isEqualTo(IntegrityCheckResult.Effect.DENY);
- assertThat(denyResult.getMatchedRules()).containsExactly(failedRule);
- }
-
- @Test
- public void isDenyCausedByAppCertificate() {
- String packageName = "com.test.deny";
- String appCert = "app-cert";
- Rule failedRule =
- new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME, packageName),
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.APP_CERTIFICATE, appCert))),
- Rule.DENY);
- Rule otherFailedRule =
- new Rule(
- new AtomicFormula.LongAtomicFormula(AtomicFormula.VERSION_CODE,
- AtomicFormula.EQ, 12),
- Rule.DENY);
-
- IntegrityCheckResult denyResult =
- IntegrityCheckResult.deny(Arrays.asList(failedRule, otherFailedRule));
-
- assertThat(denyResult.isCausedByAppCertRule()).isTrue();
- assertThat(denyResult.isCausedByInstallerRule()).isFalse();
- }
-
- @Test
- public void isDenyCausedByInstaller() {
- String packageName = "com.test.deny";
- String appCert = "app-cert";
- Rule failedRule =
- new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME, packageName),
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.INSTALLER_CERTIFICATE, appCert))),
- Rule.DENY);
- Rule otherFailedRule =
- new Rule(
- new AtomicFormula.LongAtomicFormula(AtomicFormula.VERSION_CODE,
- AtomicFormula.EQ, 12),
- Rule.DENY);
-
- IntegrityCheckResult denyResult =
- IntegrityCheckResult.deny(Arrays.asList(failedRule, otherFailedRule));
-
- assertThat(denyResult.isCausedByAppCertRule()).isFalse();
- assertThat(denyResult.isCausedByInstallerRule()).isTrue();
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/stats/pull/psi/OWNERS b/services/tests/servicestests/src/com/android/server/stats/pull/psi/OWNERS
new file mode 100644
index 0000000..e068a84
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/stats/pull/psi/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/stats/pull/psi/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/stats/pull/psi/PsiExtractorTest.java b/services/tests/servicestests/src/com/android/server/stats/pull/psi/PsiExtractorTest.java
new file mode 100644
index 0000000..b563c08
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/stats/pull/psi/PsiExtractorTest.java
@@ -0,0 +1,305 @@
+/*
+ * 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.stats.pull.psi;
+
+import static org.testng.AssertJUnit.assertEquals;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+
+public class PsiExtractorTest {
+ @Mock
+ private PsiExtractor.PsiReader mPsiReader;
+ private PsiExtractor mPsiExtractor;
+ // PSI file content with both some and full lines.
+ private static final String PSI_FILE_CONTENT_BOTH_LINES =
+ "some avg10=0.12 avg60=0.34 avg300=0.56 total=12345678\n"
+ + "full avg10=0.21 avg60=0.43 avg300=0.65 total=87654321";
+ // PSI file content with only some line.
+ private static final String PSI_FILE_CONTENT_ONLY_SOME_LINE =
+ "some avg10=0.12 avg60=0.34 avg300=0.56 total=12345678";
+
+ // PSI file content with only full line.
+ private static final String PSI_FILE_CONTENT_ONLY_FULL_LINE =
+ "\nfull avg10=0.21 avg60=0.43 avg300=0.65 total=87654321";
+
+ // PSI file content that is malformed with "avg60" missing from the both lines.
+ private static final String BOTH_AVG60_MISSING_PSI_FILE_CONTENT =
+ "some avg10=0.12 avg300=0.56 total=12345678\n"
+ + "full avg10=0.21 avg300=0.65 total=87654321";
+
+ // PSI file content that is malformed with non number "avg10" from the both lines.
+ private static final String NON_NUM_AVG10_PSI_FILE_CONTENT =
+ "some avg10=1.a2 avg300=0.56 total=12345678\n"
+ + "full avg10=0.2s1 avg60=0.43 avg300=0.65 total=87654321";
+
+ // PSI file content that is malformed with non number "avg300" from the both lines.
+ private static final String NON_NUM_AVG300_PSI_FILE_CONTENT =
+ "some avg10=0.2 avg60=0.43 avg300=0.5ss6 total=12345678\n"
+ + "full avg10=0.21 avg60=0.43 avg300=0.6b5 total=87654321";
+
+ // PSI file content that is malformed with non number "total" from the both lines.
+ private static final String BOTH_TOTAL_MISSING_PSI_FILE_CONTENT =
+ "some avg10=0.2 avg60=0.43 avg300=0.56\n"
+ + "full avg10=0.21 avg60=0.43 avg300=0.65";
+
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mPsiExtractor = new PsiExtractor(mPsiReader);
+ }
+
+ @Test
+ public void getPsiData_bothLinesPresentedAndValidMemory() {
+ Mockito.when(mPsiReader.read("/proc/pressure/memory")).thenReturn(
+ PSI_FILE_CONTENT_BOTH_LINES);
+ PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.MEMORY);
+ assertEquals(psiData.getResourceType(), PsiData.ResourceType.MEMORY);
+ assertEquals(psiData.getSomeAvg10SecPercentage(), (float) 0.12);
+ assertEquals(psiData.getSomeAvg60SecPercentage(), (float) 0.34);
+ assertEquals(psiData.getSomeAvg300SecPercentage(), (float) 0.56);
+ assertEquals(psiData.getSomeTotalUsec(), 12345678);
+ assertEquals(psiData.getFullAvg10SecPercentage(), (float) 0.21);
+ assertEquals(psiData.getFullAvg60SecPercentage(), (float) 0.43);
+ assertEquals(psiData.getFullAvg300SecPercentage(), (float) 0.65);
+ assertEquals(psiData.getFullTotalUsec(), 87654321);
+ }
+
+ @Test
+ public void getPsiData_bothLinesPresentedAndValidCpu() {
+ Mockito.when(mPsiReader.read("/proc/pressure/cpu")).thenReturn(
+ PSI_FILE_CONTENT_BOTH_LINES);
+ PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.CPU);
+ assertEquals(psiData.getResourceType(), PsiData.ResourceType.CPU);
+ assertEquals(psiData.getSomeAvg10SecPercentage(), (float) 0.12);
+ assertEquals(psiData.getSomeAvg60SecPercentage(), (float) 0.34);
+ assertEquals(psiData.getSomeAvg300SecPercentage(), (float) 0.56);
+ assertEquals(psiData.getSomeTotalUsec(), 12345678);
+ assertEquals(psiData.getFullAvg10SecPercentage(), (float) 0.21);
+ assertEquals(psiData.getFullAvg60SecPercentage(), (float) 0.43);
+ assertEquals(psiData.getFullAvg300SecPercentage(), (float) 0.65);
+ assertEquals(psiData.getFullTotalUsec(), 87654321);
+ }
+
+ @Test
+ public void getPsiData_bothLinesPresentedAndValidIO() {
+ Mockito.when(mPsiReader.read("/proc/pressure/io")).thenReturn(
+ PSI_FILE_CONTENT_BOTH_LINES);
+ PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.IO);
+ assertEquals(psiData.getResourceType(), PsiData.ResourceType.IO);
+ assertEquals(psiData.getSomeAvg10SecPercentage(), (float) 0.12);
+ assertEquals(psiData.getSomeAvg60SecPercentage(), (float) 0.34);
+ assertEquals(psiData.getSomeAvg300SecPercentage(), (float) 0.56);
+ assertEquals(psiData.getSomeTotalUsec(), 12345678);
+ assertEquals(psiData.getFullAvg10SecPercentage(), (float) 0.21);
+ assertEquals(psiData.getFullAvg60SecPercentage(), (float) 0.43);
+ assertEquals(psiData.getFullAvg300SecPercentage(), (float) 0.65);
+ assertEquals(psiData.getFullTotalUsec(), 87654321);
+ }
+
+ @Test
+ public void getPsiData_onlySomePresentedAndValidMemory() {
+ Mockito.when(mPsiReader.read("/proc/pressure/memory")).thenReturn(
+ PSI_FILE_CONTENT_ONLY_SOME_LINE);
+ PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.MEMORY);
+ assertEquals(psiData.getResourceType(), PsiData.ResourceType.MEMORY);
+ assertEquals(psiData.getSomeAvg10SecPercentage(), (float) 0.12);
+ assertEquals(psiData.getSomeAvg60SecPercentage(), (float) 0.34);
+ assertEquals(psiData.getSomeAvg300SecPercentage(), (float) 0.56);
+ assertEquals(psiData.getSomeTotalUsec(), 12345678);
+ assertEquals(psiData.getFullAvg10SecPercentage(), (float) -1.0);
+ assertEquals(psiData.getFullAvg60SecPercentage(), (float) -1.0);
+ assertEquals(psiData.getFullAvg300SecPercentage(), (float) -1.0);
+ assertEquals(psiData.getFullTotalUsec(), -1);
+ }
+
+ @Test
+ public void getPsiData_onlySomePresentedAndValidCpu() {
+ Mockito.when(mPsiReader.read("/proc/pressure/cpu")).thenReturn(
+ PSI_FILE_CONTENT_ONLY_SOME_LINE);
+ PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.CPU);
+ assertEquals(psiData.getResourceType(), PsiData.ResourceType.CPU);
+ assertEquals(psiData.getSomeAvg10SecPercentage(), (float) 0.12);
+ assertEquals(psiData.getSomeAvg60SecPercentage(), (float) 0.34);
+ assertEquals(psiData.getSomeAvg300SecPercentage(), (float) 0.56);
+ assertEquals(psiData.getSomeTotalUsec(), 12345678);
+ assertEquals(psiData.getFullAvg10SecPercentage(), (float) 0.0);
+ assertEquals(psiData.getFullAvg60SecPercentage(), (float) 0.0);
+ assertEquals(psiData.getFullAvg300SecPercentage(), (float) 0.0);
+ assertEquals(psiData.getFullTotalUsec(), 0);
+ }
+
+ @Test
+ public void getPsiData_onlySomePresentedAndValidIO() {
+ Mockito.when(mPsiReader.read("/proc/pressure/io")).thenReturn(
+ PSI_FILE_CONTENT_ONLY_SOME_LINE);
+ PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.IO);
+ assertEquals(psiData.getResourceType(), PsiData.ResourceType.IO);
+ assertEquals(psiData.getSomeAvg10SecPercentage(), (float) 0.12);
+ assertEquals(psiData.getSomeAvg60SecPercentage(), (float) 0.34);
+ assertEquals(psiData.getSomeAvg300SecPercentage(), (float) 0.56);
+ assertEquals(psiData.getSomeTotalUsec(), 12345678);
+ assertEquals(psiData.getFullAvg10SecPercentage(), (float) -1.0);
+ assertEquals(psiData.getFullAvg60SecPercentage(), (float) -1.0);
+ assertEquals(psiData.getFullAvg300SecPercentage(), (float) -1.0);
+ assertEquals(psiData.getFullTotalUsec(), -1);
+ }
+
+ @Test
+ public void getPsiData_onlyFullPresentedAndValidMemory() {
+ Mockito.when(mPsiReader.read("/proc/pressure/memory")).thenReturn(
+ PSI_FILE_CONTENT_ONLY_FULL_LINE);
+ PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.MEMORY);
+ assertEquals(psiData.getResourceType(), PsiData.ResourceType.MEMORY);
+ assertEquals(psiData.getSomeAvg10SecPercentage(), (float) -1.0);
+ assertEquals(psiData.getSomeAvg60SecPercentage(), (float) -1.0);
+ assertEquals(psiData.getSomeAvg300SecPercentage(), (float) -1.0);
+ assertEquals(psiData.getSomeTotalUsec(), -1);
+ assertEquals(psiData.getFullAvg10SecPercentage(), (float) 0.21);
+ assertEquals(psiData.getFullAvg60SecPercentage(), (float) 0.43);
+ assertEquals(psiData.getFullAvg300SecPercentage(), (float) 0.65);
+ assertEquals(psiData.getFullTotalUsec(), 87654321);
+ }
+
+ @Test
+ public void getPsiData_onlyFullPresentedAndValidCpu() {
+ Mockito.when(mPsiReader.read("/proc/pressure/cpu")).thenReturn(
+ PSI_FILE_CONTENT_ONLY_FULL_LINE);
+ PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.CPU);
+ assertEquals(psiData.getResourceType(), PsiData.ResourceType.CPU);
+ assertEquals(psiData.getSomeAvg10SecPercentage(), (float) -1.0);
+ assertEquals(psiData.getSomeAvg60SecPercentage(), (float) -1.0);
+ assertEquals(psiData.getSomeAvg300SecPercentage(), (float) -1.0);
+ assertEquals(psiData.getSomeTotalUsec(), -1);
+ assertEquals(psiData.getFullAvg10SecPercentage(), (float) 0.21);
+ assertEquals(psiData.getFullAvg60SecPercentage(), (float) 0.43);
+ assertEquals(psiData.getFullAvg300SecPercentage(), (float) 0.65);
+ assertEquals(psiData.getFullTotalUsec(), 87654321);
+ }
+
+ @Test
+ public void getPsiData_onlyFullPresentedAndValidIO() {
+ Mockito.when(mPsiReader.read("/proc/pressure/io")).thenReturn(
+ PSI_FILE_CONTENT_ONLY_FULL_LINE);
+ PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.IO);
+ assertEquals(psiData.getResourceType(), PsiData.ResourceType.IO);
+ assertEquals(psiData.getSomeAvg10SecPercentage(), (float) -1.0);
+ assertEquals(psiData.getSomeAvg60SecPercentage(), (float) -1.0);
+ assertEquals(psiData.getSomeAvg300SecPercentage(), (float) -1.0);
+ assertEquals(psiData.getSomeTotalUsec(), -1);
+ assertEquals(psiData.getFullAvg10SecPercentage(), (float) 0.21);
+ assertEquals(psiData.getFullAvg60SecPercentage(), (float) 0.43);
+ assertEquals(psiData.getFullAvg300SecPercentage(), (float) 0.65);
+ assertEquals(psiData.getFullTotalUsec(), 87654321);
+ }
+
+ @Test
+ public void getPsiData_emptyFile() {
+ Mockito.when(mPsiReader.read("/proc/pressure/memory")).thenReturn("");
+ PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.MEMORY);
+ assertEquals(psiData, null);
+
+ Mockito.when(mPsiReader.read("/proc/pressure/cpu")).thenReturn("");
+ psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.CPU);
+ assertEquals(psiData, null);
+
+ Mockito.when(mPsiReader.read("/proc/pressure/io")).thenReturn("");
+ psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.IO);
+ assertEquals(psiData, null);
+ }
+
+ @Test
+ public void getPsiData_avg60Missing() {
+ Mockito.when(mPsiReader.read("/proc/pressure/memory")).thenReturn(
+ BOTH_AVG60_MISSING_PSI_FILE_CONTENT);
+ PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.MEMORY);
+ assertEquals(psiData, null);
+
+ Mockito.when(mPsiReader.read("/proc/pressure/cpu")).thenReturn(
+ BOTH_AVG60_MISSING_PSI_FILE_CONTENT);
+ psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.CPU);
+ assertEquals(psiData, null);
+
+ Mockito.when(mPsiReader.read("/proc/pressure/io")).thenReturn(
+ BOTH_AVG60_MISSING_PSI_FILE_CONTENT);
+ psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.IO);
+ assertEquals(psiData, null);
+ }
+
+ @Test
+ public void getPsiData_totalMissing() {
+ Mockito.when(mPsiReader.read("/proc/pressure/memory")).thenReturn(
+ BOTH_TOTAL_MISSING_PSI_FILE_CONTENT);
+ PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.MEMORY);
+ assertEquals(psiData, null);
+
+ Mockito.when(mPsiReader.read("/proc/pressure/cpu")).thenReturn(
+ BOTH_TOTAL_MISSING_PSI_FILE_CONTENT);
+ psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.CPU);
+ assertEquals(psiData, null);
+
+ Mockito.when(mPsiReader.read("/proc/pressure/io")).thenReturn(
+ BOTH_TOTAL_MISSING_PSI_FILE_CONTENT);
+ psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.IO);
+ assertEquals(psiData, null);
+ }
+
+ @Test
+ public void getPsiData_avg10NonNum() {
+ Mockito.when(mPsiReader.read("/proc/pressure/memory")).thenReturn(
+ NON_NUM_AVG10_PSI_FILE_CONTENT);
+ PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.MEMORY);
+ assertEquals(psiData, null);
+
+ Mockito.when(mPsiReader.read("/proc/pressure/cpu")).thenReturn(
+ NON_NUM_AVG10_PSI_FILE_CONTENT);
+ psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.CPU);
+ assertEquals(psiData, null);
+
+ Mockito.when(mPsiReader.read("/proc/pressure/io")).thenReturn(
+ NON_NUM_AVG10_PSI_FILE_CONTENT);
+ psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.IO);
+ assertEquals(psiData, null);
+ }
+
+ @Test
+ public void getPsiData_avg300NonNum() {
+ Mockito.when(mPsiReader.read("/proc/pressure/memory")).thenReturn(
+ NON_NUM_AVG300_PSI_FILE_CONTENT);
+ PsiData psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.MEMORY);
+ assertEquals(psiData, null);
+
+ Mockito.when(mPsiReader.read("/proc/pressure/cpu")).thenReturn(
+ NON_NUM_AVG300_PSI_FILE_CONTENT);
+ psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.CPU);
+ assertEquals(psiData, null);
+
+ Mockito.when(mPsiReader.read("/proc/pressure/io")).thenReturn(
+ NON_NUM_AVG300_PSI_FILE_CONTENT);
+ psiData = mPsiExtractor.getPsiData(PsiData.ResourceType.IO);
+ assertEquals(psiData, null);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
index 6bd4279..79b06236 100644
--- a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
+++ b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
@@ -16,12 +16,20 @@
package com.android.server.supervision
+import android.app.admin.DevicePolicyManagerInternal
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.UserInfo
import android.os.Bundle
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.internal.R
import com.android.server.LocalServices
+import com.android.server.SystemService.TargetUser
import com.android.server.pm.UserManagerInternal
import com.google.common.truth.Truth.assertThat
-import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -29,11 +37,12 @@
import org.mockito.Mock
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.whenever
/**
- * Unit tests for {@link SupervisionService}.
- * <p/>
- * Run with <code>atest SupervisionServiceTest</code>.
+ * Unit tests for [SupervisionService].
+ *
+ * Run with `atest SupervisionServiceTest`.
*/
@RunWith(AndroidJUnit4::class)
class SupervisionServiceTest {
@@ -41,18 +50,21 @@
const val USER_ID = 100
}
+ @get:Rule val mocks: MockitoRule = MockitoJUnit.rule()
+ @get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+ @Mock private lateinit var mockDpmInternal: DevicePolicyManagerInternal
+ @Mock private lateinit var mockUserManagerInternal: UserManagerInternal
+
+ private lateinit var context: Context
private lateinit var service: SupervisionService
- @Rule
- @JvmField
- val mocks: MockitoRule = MockitoJUnit.rule()
-
- @Mock
- private lateinit var mockUserManagerInternal: UserManagerInternal
-
@Before
- fun setup() {
- val context = InstrumentationRegistry.getInstrumentation().context
+ fun setUp() {
+ context = InstrumentationRegistry.getInstrumentation().context
+
+ LocalServices.removeServiceForTest(DevicePolicyManagerInternal::class.java)
+ LocalServices.addService(DevicePolicyManagerInternal::class.java, mockDpmInternal)
LocalServices.removeServiceForTest(UserManagerInternal::class.java)
LocalServices.addService(UserManagerInternal::class.java, mockUserManagerInternal)
@@ -61,7 +73,46 @@
}
@Test
- fun testSetSupervisionEnabledForUser() {
+ @RequiresFlagsEnabled(android.app.admin.flags.Flags.FLAG_ENABLE_SUPERVISION_SERVICE_SYNC)
+ fun syncStateWithDevicePolicyManager_supervisionAppIsProfileOwner_enablesSupervision() {
+ val supervisionPackageName =
+ context.getResources().getString(R.string.config_systemSupervision)
+
+ whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
+ .thenReturn(ComponentName(supervisionPackageName, "MainActivity"))
+
+ service.syncStateWithDevicePolicyManager(newTargetUser(USER_ID))
+
+ assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue()
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.app.admin.flags.Flags.FLAG_ENABLE_SUPERVISION_SERVICE_SYNC)
+ fun syncStateWithDevicePolicyManager_userPreCreated_doesNotEnableSupervision() {
+ val supervisionPackageName =
+ context.getResources().getString(R.string.config_systemSupervision)
+
+ whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
+ .thenReturn(ComponentName(supervisionPackageName, "MainActivity"))
+
+ service.syncStateWithDevicePolicyManager(newTargetUser(USER_ID, preCreated = true))
+
+ assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.app.admin.flags.Flags.FLAG_ENABLE_SUPERVISION_SERVICE_SYNC)
+ fun syncStateWithDevicePolicyManager_supervisionAppIsNotProfileOwner_doesNotEnableSupervision() {
+ whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
+ .thenReturn(ComponentName("other.package", "MainActivity"))
+
+ service.syncStateWithDevicePolicyManager(newTargetUser(USER_ID))
+
+ assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
+ }
+
+ @Test
+ fun setSupervisionEnabledForUser() {
assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
service.setSupervisionEnabledForUser(USER_ID, true)
@@ -72,7 +123,18 @@
}
@Test
- fun testSetSupervisionLockscreenEnabledForUser() {
+ fun supervisionEnabledForUser_internal() {
+ assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
+
+ service.mInternal.setSupervisionEnabledForUser(USER_ID, true)
+ assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue()
+
+ service.mInternal.setSupervisionEnabledForUser(USER_ID, false)
+ assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
+ }
+
+ @Test
+ fun setSupervisionLockscreenEnabledForUser() {
var userData = service.getUserDataLocked(USER_ID)
assertThat(userData.supervisionLockScreenEnabled).isFalse()
assertThat(userData.supervisionLockScreenOptions).isNull()
@@ -87,4 +149,10 @@
assertThat(userData.supervisionLockScreenEnabled).isFalse()
assertThat(userData.supervisionLockScreenOptions).isNull()
}
+
+ private fun newTargetUser(userId: Int, preCreated: Boolean = false): TargetUser {
+ val userInfo = UserInfo(userId, /* name= */ "tempUser", /* flags= */ 0)
+ userInfo.preCreated = preCreated
+ return TargetUser(userInfo)
+ }
}
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 eae587b..704c1b8 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -210,6 +210,7 @@
import android.app.RemoteInput;
import android.app.RemoteInputHistoryItem;
import android.app.StatsManager;
+import android.app.ZenBypassingApp;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.backup.BackupRestoreEventLogger;
import android.app.job.JobScheduler;
@@ -360,6 +361,9 @@
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
@@ -374,9 +378,6 @@
import java.util.concurrent.CountDownLatch;
import java.util.function.Consumer;
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
@SmallTest
@RunWith(ParameterizedAndroidJunit4.class)
@RunWithLooper
@@ -489,7 +490,8 @@
private final NotificationChannel mParentChannel =
new NotificationChannel(PARENT_CHANNEL_ID, "parentName", IMPORTANCE_DEFAULT);
private final NotificationChannel mConversationChannel =
- new NotificationChannel(CONVERSATION_CHANNEL_ID, "conversationName", IMPORTANCE_DEFAULT);
+ new NotificationChannel(
+ CONVERSATION_CHANNEL_ID, "conversationName", IMPORTANCE_DEFAULT);
private static final String PARENT_CHANNEL_ID = "parentChannelId";
private static final String CONVERSATION_CHANNEL_ID = "conversationChannelId";
@@ -4296,8 +4298,13 @@
new NotificationChannel("foo", "foo", IMPORTANCE_HIGH));
Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
- mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "testTvExtenderChannelOverride_onTv", 0,
- generateNotificationRecord(null, tv).getNotification(), mUserId);
+ mBinderService.enqueueNotificationWithTag(
+ mPkg,
+ mPkg,
+ "testTvExtenderChannelOverride_onTv",
+ 0,
+ generateNotificationRecord(null, tv).getNotification(),
+ mUserId);
verify(mPreferencesHelper, times(1)).getConversationNotificationChannel(
anyString(), anyInt(), eq("foo"), eq(null), anyBoolean(), anyBoolean());
}
@@ -4311,8 +4318,13 @@
mTestNotificationChannel);
Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
- mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "testTvExtenderChannelOverride_notOnTv",
- 0, generateNotificationRecord(null, tv).getNotification(), mUserId);
+ mBinderService.enqueueNotificationWithTag(
+ mPkg,
+ mPkg,
+ "testTvExtenderChannelOverride_notOnTv",
+ 0,
+ generateNotificationRecord(null, tv).getNotification(),
+ mUserId);
verify(mPreferencesHelper, times(1)).getConversationNotificationChannel(
anyString(), anyInt(), eq(mTestNotificationChannel.getId()), eq(null),
anyBoolean(), anyBoolean());
@@ -7745,9 +7757,21 @@
.setContentTitle("foo")
.setSmallIcon(android.R.drawable.sym_def_app_icon)
.setStyle(new Notification.MessagingStyle("").addMessage(message2));
- NotificationRecord recordB = new NotificationRecord(mContext, new StatusBarNotification(mPkg,
- mPkg, 0, "tag", mUid, 0, nbB.build(), UserHandle.getUserHandleForUid(mUid), null, 0),
- c);
+ NotificationRecord recordB =
+ new NotificationRecord(
+ mContext,
+ new StatusBarNotification(
+ mPkg,
+ mPkg,
+ 0,
+ "tag",
+ mUid,
+ 0,
+ nbB.build(),
+ UserHandle.getUserHandleForUid(mUid),
+ null,
+ 0),
+ c);
// Update means we drop access to first
reset(mUgmInternal);
@@ -13174,6 +13198,37 @@
}
@Test
+ public void getPackagesBypassingDnd_blocked()
+ throws RemoteException, PackageManager.NameNotFoundException {
+
+ NotificationChannel channel1 = new NotificationChannel("id1", "name1",
+ NotificationManager.IMPORTANCE_MAX);
+ NotificationChannel channel2 = new NotificationChannel("id3", "name3",
+ NotificationManager.IMPORTANCE_MAX);
+ NotificationChannel channel3 = new NotificationChannel("id4", "name3",
+ NotificationManager.IMPORTANCE_MAX);
+ channel1.setBypassDnd(true);
+ channel2.setBypassDnd(true);
+ channel3.setBypassDnd(false);
+ // has DND access, so can set bypassDnd attribute
+ mService.mPreferencesHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true,
+ /*has DND access*/ true, UID_N_MR1, false);
+ mService.mPreferencesHelper.createNotificationChannel(PKG_P, UID_P, channel2, true, true,
+ UID_P, false);
+ mService.mPreferencesHelper.createNotificationChannel(PKG_P, UID_P, channel3, true, true,
+ UID_P, false);
+
+ when(mPackageManager.getPackageUid(eq(PKG_P), anyLong(), anyInt())).thenReturn(UID_P);
+ when(mPackageManager.getPackageUid(eq(PKG_N_MR1), anyLong(), anyInt()))
+ .thenReturn(UID_N_MR1);
+ when(mPermissionHelper.hasPermission(UID_N_MR1)).thenReturn(false);
+ when(mPermissionHelper.hasPermission(UID_P)).thenReturn(true);
+
+ assertThat(mBinderService.getPackagesBypassingDnd(UserHandle.getUserId(UID_P)).getList())
+ .containsExactly(new ZenBypassingApp(PKG_P, false));
+ }
+
+ @Test
public void testGetNotificationChannelsBypassingDnd_blocked() throws RemoteException {
mService.setPreferencesHelper(mPreferencesHelper);
@@ -13187,125 +13242,11 @@
@Test
public void testGetPackagesBypassingDnd_empty() throws RemoteException {
mService.setPreferencesHelper(mPreferencesHelper);
- List<String> result = mBinderService.getPackagesBypassingDnd(mUserId, true);
+ List<String> result = mBinderService.getPackagesBypassingDnd(mUserId).getList();
assertThat(result).isEmpty();
}
@Test
- public void testGetPackagesBypassingDnd_excludeConversationChannels() throws RemoteException {
- mService.setPreferencesHelper(mPreferencesHelper);
-
- // Set packages
- PackageInfo pkg0 = new PackageInfo();
- pkg0.packageName = "pkg0";
- pkg0.applicationInfo = new ApplicationInfo();
- pkg0.applicationInfo.uid = mUid;
- PackageInfo pkg1 = new PackageInfo();
- pkg1.packageName = "pkg1";
- pkg1.applicationInfo = new ApplicationInfo();
- pkg1.applicationInfo.uid = mUid;
- PackageInfo pkg2 = new PackageInfo();
- pkg2.packageName = "pkg2";
- pkg2.applicationInfo = new ApplicationInfo();
- pkg2.applicationInfo.uid = mUid;
-
- when(mPackageManagerClient.getInstalledPackagesAsUser(0, mUserId))
- .thenReturn(List.of(pkg0, pkg1, pkg2));
-
- // Conversation channels
- NotificationChannel nc0 = new NotificationChannel("id0", "id0",
- NotificationManager.IMPORTANCE_HIGH);
- nc0.setConversationId("parentChannel", "conversationId");
-
- // Demoted conversation channel
- NotificationChannel nc1 = new NotificationChannel("id1", "id1",
- NotificationManager.IMPORTANCE_HIGH);
- nc1.setConversationId("parentChannel", "conversationId");
- nc1.setDemoted(true);
-
- // Non-conversation channels
- NotificationChannel nc2 = new NotificationChannel("id2", "id2",
- NotificationManager.IMPORTANCE_HIGH);
- NotificationChannel nc3 = new NotificationChannel("id3", "id3",
- NotificationManager.IMPORTANCE_HIGH);
-
- ParceledListSlice<NotificationChannel> pls0 =
- new ParceledListSlice(ImmutableList.of(nc0));
- ParceledListSlice<NotificationChannel> pls1 =
- new ParceledListSlice(ImmutableList.of(nc1));
- ParceledListSlice<NotificationChannel> pls2 =
- new ParceledListSlice(ImmutableList.of(nc2, nc3));
-
- when(mPreferencesHelper.getNotificationChannelsBypassingDnd("pkg0", mUid))
- .thenReturn(pls0);
- when(mPreferencesHelper.getNotificationChannelsBypassingDnd("pkg1", mUid))
- .thenReturn(pls1);
- when(mPreferencesHelper.getNotificationChannelsBypassingDnd("pkg2", mUid))
- .thenReturn(pls2);
-
- List<String> result = mBinderService.getPackagesBypassingDnd(mUserId, false);
-
- assertThat(result).containsExactly("pkg1", "pkg2");
- }
-
- @Test
- public void testGetPackagesBypassingDnd_includeConversationChannels() throws RemoteException {
- mService.setPreferencesHelper(mPreferencesHelper);
-
- // Set packages
- PackageInfo pkg0 = new PackageInfo();
- pkg0.packageName = "pkg0";
- pkg0.applicationInfo = new ApplicationInfo();
- pkg0.applicationInfo.uid = mUid;
- PackageInfo pkg1 = new PackageInfo();
- pkg1.packageName = "pkg1";
- pkg1.applicationInfo = new ApplicationInfo();
- pkg1.applicationInfo.uid = mUid;
- PackageInfo pkg2 = new PackageInfo();
- pkg2.packageName = "pkg2";
- pkg2.applicationInfo = new ApplicationInfo();
- pkg2.applicationInfo.uid = mUid;
-
- when(mPackageManagerClient.getInstalledPackagesAsUser(0, mUserId))
- .thenReturn(List.of(pkg0, pkg1, pkg2));
-
- // Conversation channels
- NotificationChannel nc0 = new NotificationChannel("id0", "id0",
- NotificationManager.IMPORTANCE_HIGH);
- nc0.setConversationId("parentChannel", "conversationId");
-
- // Demoted conversation channel
- NotificationChannel nc1 = new NotificationChannel("id1", "id1",
- NotificationManager.IMPORTANCE_HIGH);
- nc1.setConversationId("parentChannel", "conversationId");
- nc1.setDemoted(true);
-
- // Non-conversation channels
- NotificationChannel nc2 = new NotificationChannel("id2", "id2",
- NotificationManager.IMPORTANCE_HIGH);
- NotificationChannel nc3 = new NotificationChannel("id3", "id3",
- NotificationManager.IMPORTANCE_HIGH);
-
- ParceledListSlice<NotificationChannel> pls0 =
- new ParceledListSlice(ImmutableList.of(nc0));
- ParceledListSlice<NotificationChannel> pls1 =
- new ParceledListSlice(ImmutableList.of(nc1));
- ParceledListSlice<NotificationChannel> pls2 =
- new ParceledListSlice(ImmutableList.of(nc2, nc3));
-
- when(mPreferencesHelper.getNotificationChannelsBypassingDnd("pkg0", mUid))
- .thenReturn(pls0);
- when(mPreferencesHelper.getNotificationChannelsBypassingDnd("pkg1", mUid))
- .thenReturn(pls1);
- when(mPreferencesHelper.getNotificationChannelsBypassingDnd("pkg2", mUid))
- .thenReturn(pls2);
-
- List<String> result = mBinderService.getPackagesBypassingDnd(mUserId, true);
-
- assertThat(result).containsExactly("pkg0", "pkg1", "pkg2");
- }
-
- @Test
public void testMatchesCallFilter_noPermissionShouldThrow() throws Exception {
// set the testable NMS to not system uid/appid
mService.isSystemUid = false;
@@ -15482,8 +15423,13 @@
for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS; i++) {
StatusBarNotification sbn = generateNotificationRecord(mTestNotificationChannel,
i, null, false).getSbn();
- mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "testCannotPostNonUijWhenOverLimit",
- sbn.getId(), sbn.getNotification(), sbn.getUserId());
+ mBinderService.enqueueNotificationWithTag(
+ mPkg,
+ mPkg,
+ "testCannotPostNonUijWhenOverLimit",
+ sbn.getId(),
+ sbn.getNotification(),
+ sbn.getUserId());
waitForIdle();
}
@@ -16213,6 +16159,8 @@
initNMS(SystemService.PHASE_SYSTEM_SERVICES_READY);
mInternalService.setDeviceEffectsApplier(mock(DeviceEffectsApplier.class));
+
+ mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START, mMainLooper);
// No exception!
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index e1b478c..dda060d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -108,6 +108,7 @@
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
+import android.app.ZenBypassingApp;
import android.content.AttributionSource;
import android.content.ContentProvider;
import android.content.ContentResolver;
@@ -2620,6 +2621,72 @@
}
@Test
+ public void getPackagesBypassingDnd_noChannelsBypassing() throws Exception {
+ assertThat(mHelper.getPackagesBypassingDnd(UserHandle.getUserId(UID_N_MR1))).isEmpty();
+ }
+
+ @Test
+ public void getPackagesBypassingDnd_oneChannelBypassing_deleted() {
+ NotificationChannel channel1 = new NotificationChannel("id1", "name1",
+ NotificationManager.IMPORTANCE_MAX);
+ channel1.setBypassDnd(true);
+ channel1.setDeleted(true);
+ // has DND access, so can set bypassDnd attribute
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true,
+ /*has DND access*/ true, UID_N_MR1, false);
+
+ assertThat(mHelper.getPackagesBypassingDnd(UserHandle.getUserId(UID_N_MR1))).isEmpty();
+ }
+
+ @Test
+ public void getPackagesBypassingDnd_oneChannelBypassing_groupBlocked() {
+ int uid = UID_N_MR1;
+ NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
+ NotificationChannel channel1 = new NotificationChannel("id1", "name1",
+ NotificationManager.IMPORTANCE_MAX);
+ channel1.setBypassDnd(true);
+ channel1.setGroup(ncg.getId());
+ mHelper.createNotificationChannelGroup(PKG_N_MR1, uid, ncg, /* fromTargetApp */ true,
+ uid, false);
+ mHelper.createNotificationChannel(PKG_N_MR1, uid, channel1, true, /*has DND access*/ true,
+ uid, false);
+ ncg.setBlocked(true);
+
+ assertThat(mHelper.getPackagesBypassingDnd(UserHandle.getUserId(uid))).isEmpty();
+ }
+
+ @Test
+ public void getPackagesBypassingDnd_multipleApps() {
+ List<ZenBypassingApp> expected = ImmutableList.of(
+ new ZenBypassingApp(PKG_O, true), new ZenBypassingApp(PKG_P, false));
+
+ NotificationChannel channel1 = new NotificationChannel("id1", "name1",
+ NotificationManager.IMPORTANCE_MAX);
+ NotificationChannel channel2 = new NotificationChannel("id2", "name2",
+ NotificationManager.IMPORTANCE_MAX);
+ NotificationChannel channel3 = new NotificationChannel("id3", "name3",
+ NotificationManager.IMPORTANCE_MAX);
+ NotificationChannel channel4 = new NotificationChannel("id4", "name3",
+ NotificationManager.IMPORTANCE_MAX);
+ channel1.setBypassDnd(false);
+ channel2.setBypassDnd(true);
+ channel3.setBypassDnd(true);
+ channel4.setBypassDnd(false);
+ // has DND access, so can set bypassDnd attribute
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true,
+ /*has DND access*/ true, UID_N_MR1, false);
+ mHelper.createNotificationChannel(PKG_O, UID_O, channel2, true, true,
+ UID_O, false);
+ mHelper.createNotificationChannel(PKG_P, UID_P, channel3, true, true,
+ UID_P, false);
+ mHelper.createNotificationChannel(PKG_P, UID_P, channel4, true, true,
+ UID_P, false);
+
+ assertThat(mHelper.getPackagesBypassingDnd(UserHandle.getUserId(UID_O)))
+ .containsExactlyElementsIn(expected);
+ }
+
+ @Test
public void testCreateAndDeleteCanChannelsBypassDnd_localSettings() {
int uid = UserManager.isHeadlessSystemUserMode() ? UID_HEADLESS : UID_N_MR1;
when(mPermissionHelper.hasPermission(uid)).thenReturn(true);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 4b94e10..020670d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -49,6 +49,8 @@
import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_STARRED;
import static android.app.NotificationManager.Policy.STATE_PRIORITY_CHANNELS_BLOCKED;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
+import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG;
+import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_RULES;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Process.SYSTEM_UID;
import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
@@ -84,8 +86,6 @@
import static com.android.os.dnd.DNDProtoEnums.ROOT_CONFIG;
import static com.android.os.dnd.DNDProtoEnums.STATE_ALLOW;
import static com.android.os.dnd.DNDProtoEnums.STATE_DISALLOW;
-import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG;
-import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_RULES;
import static com.android.server.notification.ZenModeEventLogger.ACTIVE_RULE_TYPE_MANUAL;
import static com.android.server.notification.ZenModeHelper.RULE_LIMIT_PER_PACKAGE;
@@ -102,6 +102,7 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
@@ -200,6 +201,9 @@
import org.mockito.MockitoAnnotations;
import org.xmlpull.v1.XmlPullParserException;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
@@ -219,9 +223,6 @@
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
@SmallTest
@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
@RunWith(ParameterizedAndroidJunit4.class)
@@ -5348,6 +5349,22 @@
mTestableLooper.processAllMessages();
verify(mDeviceEffectsApplier).apply(eq(effects), eq(ORIGIN_APP));
+ assertTrue(mZenModeHelper.hasDeviceEffectsApplier());
+ }
+
+ @Test
+ public void testHasDeviceEffectsApplier_returnsFalseIfNotSet() {
+ assertFalse(mZenModeHelper.hasDeviceEffectsApplier());
+ }
+
+ @Test
+ @EnableFlags(FLAG_MODES_API)
+ public void testSettingDeviceEffects_throwsExceptionIfAlreadySet() {
+ mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index c3466b9..fee646d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -3139,11 +3139,13 @@
@Test
public void testOnStartingWindowDrawn() {
+ // Skip unnecessary resume top.
+ mSupervisor.beginDeferResume();
final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
// The task-has-been-visible should not affect the decision of making transition ready.
activity.getTask().setHasBeenVisible(true);
activity.detachFromProcess();
- activity.mStartingData = mock(StartingData.class);
+ activity.mStartingData = new SplashScreenStartingData(mWm, 0, 0);
registerTestTransitionPlayer();
final Transition transition = activity.mTransitionController.requestTransitionIfNeeded(
WindowManager.TRANSIT_OPEN, 0 /* flags */, null /* trigger */, mDisplayContent);
@@ -3151,7 +3153,11 @@
assertTrue(activity.mStartingData.mIsDisplayed);
// The transition can be ready by the starting window of a visible-requested activity
// without a running process.
- assertTrue(transition.allReady());
+ if (!transition.allReady()) {
+ // Print unsatisfied conditions.
+ transition.onReadyTimeout();
+ Assert.fail(transition + " must be ready by onStartingWindowDrawn");
+ }
// If other event makes the transition unready, the reentrant of onStartingWindowDrawn
// should not replace the readiness again.
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 62a4711..41f1e23 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -87,6 +87,7 @@
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.times;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
@@ -4782,6 +4783,114 @@
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ public void testCameraCompatAspectRatioAppliedForFixedOrientationCameraActivities() {
+ // Needed to create camera compat policy in DisplayContent.
+ allowDesktopMode();
+ // Create display that has all stable insets and does not rotate.
+ final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1080, 600)
+ .setSystemDecorations(true).setCanRotate(false).build();
+
+ final float cameraCompatAspectRatio = 4.0f;
+ setupCameraCompatAspectRatio(cameraCompatAspectRatio, display);
+
+ // Create task on test display.
+ final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
+
+ // Create fixed portrait activity.
+ final ActivityRecord fixedOrientationActivity = new ActivityBuilder(mAtm)
+ .setTask(task).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT).build();
+ final Rect fixedOrientationAppBounds = new Rect(fixedOrientationActivity.getConfiguration()
+ .windowConfiguration.getAppBounds());
+
+ assertEquals(cameraCompatAspectRatio, computeAspectRatio(fixedOrientationAppBounds),
+ DELTA_ASPECT_RATIO_TOLERANCE);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ public void testCameraCompatAspectRatioForFixedOrientationCameraActivitiesPortraitWindow() {
+ // Needed to create camera compat policy in DisplayContent.
+ allowDesktopMode();
+ // Create portrait display that has all stable insets and does not rotate.
+ final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1080, 1600)
+ .setSystemDecorations(true).setCanRotate(false).build();
+
+ final float cameraCompatAspectRatio = 4.0f;
+ setupCameraCompatAspectRatio(cameraCompatAspectRatio, display);
+
+ // Create task on test display.
+ final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
+
+ // Create fixed portrait activity.
+ final ActivityRecord fixedOrientationActivity = new ActivityBuilder(mAtm)
+ .setTask(task).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT).build();
+ final Rect fixedOrientationAppBounds = new Rect(fixedOrientationActivity.getConfiguration()
+ .windowConfiguration.getAppBounds());
+
+ assertEquals(cameraCompatAspectRatio, computeAspectRatio(fixedOrientationAppBounds),
+ DELTA_ASPECT_RATIO_TOLERANCE);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ public void testCameraCompatAspectRatioAppliedInsteadOfDefaultAspectRatio() {
+ // Needed to create camera compat policy in DisplayContent.
+ allowDesktopMode();
+ // Create display that has all stable insets and does not rotate.
+ final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1080, 600)
+ .setSystemDecorations(true).setCanRotate(false).build();
+
+ final float cameraCompatAspectRatio = 5.0f;
+ setupCameraCompatAspectRatio(cameraCompatAspectRatio, display);
+
+ // Create task on test display.
+ final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
+
+ // App's target min aspect ratio - this should not be used, as camera controls aspect ratio.
+ final float targetMinAspectRatio = 4.0f;
+
+ // Create fixed portrait activity with min aspect ratio greater than parent aspect ratio.
+ final ActivityRecord minAspectRatioActivity = new ActivityBuilder(mAtm)
+ .setTask(task).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+ .setMinAspectRatio(targetMinAspectRatio).build();
+ final Rect minAspectRatioAppBounds = new Rect(minAspectRatioActivity.getConfiguration()
+ .windowConfiguration.getAppBounds());
+
+ assertEquals(cameraCompatAspectRatio, computeAspectRatio(minAspectRatioAppBounds),
+ DELTA_ASPECT_RATIO_TOLERANCE);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ public void testCameraCompatAspectRatio_defualtAspectRatioAppliedWhenGreater() {
+ // Needed to create camera compat policy in DisplayContent.
+ allowDesktopMode();
+ // Create display that has all stable insets and does not rotate.
+ final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1080, 600)
+ .setSystemDecorations(true).setCanRotate(false).build();
+
+ final float cameraCompatAspectRatio = 5.0f;
+ setupCameraCompatAspectRatio(cameraCompatAspectRatio, display);
+
+ // Create task on test display.
+ final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
+
+ // App's target min aspect ratio bigger than camera compat aspect ratio - use that instead.
+ final float targetMinAspectRatio = 6.0f;
+
+ // Create fixed portrait activity with min aspect ratio greater than parent aspect ratio.
+ final ActivityRecord minAspectRatioActivity = new ActivityBuilder(mAtm)
+ .setTask(task).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+ .setMinAspectRatio(targetMinAspectRatio).build();
+ final Rect minAspectRatioAppBounds = new Rect(minAspectRatioActivity.getConfiguration()
+ .windowConfiguration.getAppBounds());
+
+ assertEquals(targetMinAspectRatio, computeAspectRatio(minAspectRatioAppBounds),
+ DELTA_ASPECT_RATIO_TOLERANCE);
+ }
+
+ @Test
public void testUniversalResizeable() {
mWm.mConstants.mIgnoreActivityOrientationRequest = true;
setUpApp(mDisplayContent);
@@ -4868,6 +4977,25 @@
assertEquals(newDensity, mActivity.getConfiguration().densityDpi);
}
+ /**
+ * {@code canEnterDesktopMode} is called when {@link CameraCompatFreeformPolicy} is created in
+ * {@link AppCompatCameraPolicy}.
+ *
+ * <p>{@link #allowDesktopMode()} needs to be called before {@link DisplayContent} is created.
+ */
+ private void allowDesktopMode() {
+ doReturn(true).when(() -> DesktopModeHelper.canEnterDesktopMode(any()));
+ }
+
+ private void setupCameraCompatAspectRatio(float cameraCompatAspectRatio,
+ @NonNull DisplayContent display) {
+ CameraCompatFreeformPolicy cameraPolicy = display.mAppCompatCameraPolicy
+ .mCameraCompatFreeformPolicy;
+ spyOn(cameraPolicy);
+ doReturn(true).when(cameraPolicy).shouldCameraCompatControlAspectRatio(any());
+ doReturn(cameraCompatAspectRatio).when(cameraPolicy).getCameraCompatAspectRatio(any());
+ }
+
private void setUpAllowThinLetterboxed(boolean thinLetterboxAllowed) {
final AppCompatReachabilityOverrides reachabilityOverrides =
mActivity.mAppCompatController.getAppCompatReachabilityOverrides();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 1750a14..b737d35 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -353,6 +353,29 @@
}
@Test
+ public void testDestroySurface() {
+ final WindowState win = createWindow(null, TYPE_APPLICATION, "win");
+ win.mHasSurface = win.mAnimatingExit = true;
+ win.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class);
+ win.onExitAnimationDone();
+
+ assertFalse("Case 1 destroySurface no-op",
+ win.destroySurface(false /* cleanupOnResume */, false /* appStopped */));
+ assertTrue(win.mHasSurface);
+ assertTrue(win.mDestroying);
+
+ assertFalse("Case 2 destroySurface no-op",
+ win.destroySurface(true /* cleanupOnResume */, false /* appStopped */));
+ assertTrue(win.mHasSurface);
+ assertTrue(win.mDestroying);
+
+ assertTrue("Case 3 destroySurface destroys surface",
+ win.destroySurface(false /* cleanupOnResume */, true /* appStopped */));
+ assertFalse(win.mDestroying);
+ assertFalse(win.mHasSurface);
+ }
+
+ @Test
public void testPrepareWindowToDisplayDuringRelayout() {
// Call prepareWindowToDisplayDuringRelayout for a window without FLAG_TURN_SCREEN_ON before
// calling setCurrentLaunchCanTurnScreenOn for windows with flag in the same activity.
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 7e3d99a..887b798 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -3511,6 +3511,40 @@
}
}
+ /**
+ * Inform whether application supports NTN SMS in satellite mode.
+ *
+ * This method is used by default messaging application to inform framework whether it supports
+ * NTN SMS or not.
+ *
+ * Invoking this API will internally result in triggering
+ * {@link android.telephony.TelephonyCallback.CarrierRoamingNtnModeListener
+ * #onCarrierRoamingNtnAvailableServicesChanged(List)} and
+ * {@link android.telephony.TelephonyCallback.CarrierRoamingNtnModeListener
+ * #onCarrierRoamingNtnEligibleStateChanged(boolean)} callbacks.
+ *
+ * @param ntnSmsSupported {@code true} If application supports NTN SMS, else {@code false}.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ * @hide
+ */
+ @RequiresPermission(allOf = {Manifest.permission.SATELLITE_COMMUNICATION,
+ Manifest.permission.SEND_SMS})
+ public void setNtnSmsSupported(boolean ntnSmsSupported) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ telephony.setNtnSmsSupported(ntnSmsSupported);
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("setNtnSmsSupported() RemoteException:" + ex);
+ ex.rethrowAsRuntimeException();
+ }
+ }
+
@Nullable
private static ITelephony getITelephony() {
ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 210200b..a584273 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3497,4 +3497,15 @@
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
void deprovisionSatellite(in List<SatelliteSubscriberInfo> list, in ResultReceiver result);
+
+ /**
+ * Inform whether application supports NTN SMS in satellite mode.
+ *
+ * This method is used by default messaging application to inform framework whether it supports
+ * NTN SMS or not.
+ *
+ * @param ntnSmsSupported {@code true} If application supports NTN SMS, else {@code false}.
+ * @hide
+ */
+ void setNtnSmsSupported(boolean ntnSmsSupported);
}
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index ea61ad9..9f5e6d1 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -347,6 +347,14 @@
waitForTransitionToFullscreen(wmHelper)
}
+ /** Maximize an app by dragging the app handle to the top drag zone. */
+ fun maximizeAppWithDragToTopDragZone(
+ wmHelper: WindowManagerStateHelper,
+ device: UiDevice,
+ ) {
+ dragAppWindowToTopDragZone(wmHelper, device)
+ }
+
private fun dragAppWindowToTopDragZone(wmHelper: WindowManagerStateHelper, device: UiDevice) {
val windowRect = wmHelper.getWindowRegion(innerHelper).bounds
val displayRect = getDisplayRect(wmHelper)
diff --git a/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt b/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt
index 58fb4e1..938e2f8 100644
--- a/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt
@@ -19,6 +19,7 @@
import android.animation.ValueAnimator
import android.content.Context
import android.content.ContextWrapper
+import android.content.res.Resources
import android.graphics.Color
import android.hardware.input.IKeyboardBacklightListener
import android.hardware.input.IKeyboardBacklightState
@@ -28,11 +29,12 @@
import android.os.test.TestLooper
import android.platform.test.annotations.Presubmit
import android.view.InputDevice
+import android.util.TypedValue
import androidx.test.annotation.UiThreadTest
import androidx.test.core.app.ApplicationProvider
+import com.android.internal.R
import com.android.server.input.KeyboardBacklightController.DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL
import com.android.server.input.KeyboardBacklightController.MAX_BRIGHTNESS_CHANGE_STEPS
-import com.android.server.input.KeyboardBacklightController.USER_INACTIVITY_THRESHOLD_MILLIS
import com.android.test.input.MockInputManagerRule
import java.io.FileNotFoundException
import java.io.FileOutputStream
@@ -49,6 +51,7 @@
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito.any
+import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.eq
import org.mockito.Mockito.spy
@@ -94,6 +97,7 @@
const val LIGHT_ID = 2
const val SECOND_LIGHT_ID = 3
const val MAX_BRIGHTNESS = 255
+ const val USER_INACTIVITY_THRESHOLD_MILLIS = 30000
}
@get:Rule
@@ -105,6 +109,8 @@
private lateinit var native: NativeInputManagerService
@Mock
private lateinit var uEventManager: UEventManager
+ @Mock
+ private lateinit var resources: Resources
private lateinit var keyboardBacklightController: KeyboardBacklightController
private lateinit var context: Context
private lateinit var dataStore: PersistentDataStore
@@ -117,6 +123,7 @@
@Before
fun setup() {
context = spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+ `when`(context.resources).thenReturn(resources)
dataStore = PersistentDataStore(object : PersistentDataStore.Injector() {
override fun openRead(): InputStream? {
throw FileNotFoundException()
@@ -129,6 +136,7 @@
override fun finishWrite(fos: FileOutputStream?, success: Boolean) {}
})
testLooper = TestLooper()
+ setupConfig()
keyboardBacklightController = KeyboardBacklightController(context, native, dataStore,
testLooper.looper, FakeAnimatorFactory(), uEventManager)
val inputManager = InputManager(context)
@@ -147,7 +155,31 @@
sysfsNodeChanges++
}
}
-
+ private fun setupConfig() {
+ val brightnessValues = intArrayOf(100, 200, 0)
+ val decreaseThresholds = intArrayOf(-1, 900, 1900)
+ val increaseThresholds = intArrayOf(1000, 2000, -1)
+ `when`(resources.getIntArray(R.array.config_autoKeyboardBacklightBrightnessValues))
+ .thenReturn(brightnessValues)
+ `when`(resources.getIntArray(R.array.config_autoKeyboardBacklightDecreaseLuxThreshold))
+ .thenReturn(decreaseThresholds)
+ `when`(resources.getIntArray(R.array.config_autoKeyboardBacklightIncreaseLuxThreshold))
+ .thenReturn(increaseThresholds)
+ `when`(resources.getInteger(R.integer.config_keyboardBacklightTimeoutMs))
+ .thenReturn(USER_INACTIVITY_THRESHOLD_MILLIS)
+ `when`(
+ resources.getValue(
+ eq(R.dimen.config_autoKeyboardBrightnessSmoothingConstant),
+ any(TypedValue::class.java),
+ anyBoolean()
+ )
+ ).then {
+ val args = it.arguments
+ val outValue = args[1] as TypedValue
+ outValue.data = java.lang.Float.floatToRawIntBits(1.0f)
+ Unit
+ }
+ }
@Test
fun testKeyboardBacklightIncrementDecrement() {
KeyboardBacklightFlags(
@@ -365,7 +397,7 @@
lightColorMap[LIGHT_ID]
)
- testLooper.moveTimeForward(USER_INACTIVITY_THRESHOLD_MILLIS + 1000)
+ testLooper.moveTimeForward((USER_INACTIVITY_THRESHOLD_MILLIS + 1000).toLong())
testLooper.dispatchNext()
assertEquals(
"Keyboard backlight level should be turned off after inactivity",
diff --git a/tests/broadcasts/OWNERS b/tests/broadcasts/OWNERS
deleted file mode 100644
index d2e1f81..0000000
--- a/tests/broadcasts/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# Bug component: 316181
-include platform/frameworks/base:/BROADCASTS_OWNERS
diff --git a/tests/broadcasts/unit/Android.bp b/tests/broadcasts/unit/Android.bp
deleted file mode 100644
index 47166a7..0000000
--- a/tests/broadcasts/unit/Android.bp
+++ /dev/null
@@ -1,45 +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 {
- // 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"],
- default_team: "trendy_team_framework_backstage_power",
-}
-
-android_test {
- name: "BroadcastUnitTests",
- srcs: ["src/**/*.java"],
- defaults: [
- "modules-utils-extended-mockito-rule-defaults",
- ],
- static_libs: [
- "androidx.test.runner",
- "androidx.test.rules",
- "androidx.test.ext.junit",
- "mockito-target-extended-minus-junit4",
- "truth",
- "flag-junit",
- "android.app.flags-aconfig-java",
- ],
- certificate: "platform",
- platform_apis: true,
- test_suites: ["device-tests"],
-}
diff --git a/tests/broadcasts/unit/AndroidManifest.xml b/tests/broadcasts/unit/AndroidManifest.xml
deleted file mode 100644
index e9c5248..0000000
--- a/tests/broadcasts/unit/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.broadcasts.unit" >
-
- <application android:debuggable="true">
- <uses-library android:name="android.test.runner" />
- </application>
-
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.broadcasts.unit"
- android:label="Broadcasts Unit Tests"/>
-</manifest>
\ No newline at end of file
diff --git a/tests/broadcasts/unit/AndroidTest.xml b/tests/broadcasts/unit/AndroidTest.xml
deleted file mode 100644
index b91e4783..0000000
--- a/tests/broadcasts/unit/AndroidTest.xml
+++ /dev/null
@@ -1,29 +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.
--->
-<configuration description="Runs Broadcasts tests">
- <option name="test-suite-tag" value="apct" />
- <option name="test-tag" value="BroadcastUnitTests" />
-
- <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="cleanup-apks" value="true" />
- <option name="test-file-name" value="BroadcastUnitTests.apk" />
- </target_preparer>
-
- <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
- <option name="package" value="com.android.broadcasts.unit" />
- <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
- <option name="hidden-api-checks" value="false"/>
- </test>
-</configuration>
\ No newline at end of file
diff --git a/tests/broadcasts/unit/TEST_MAPPING b/tests/broadcasts/unit/TEST_MAPPING
deleted file mode 100644
index 8919fdc..0000000
--- a/tests/broadcasts/unit/TEST_MAPPING
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "presubmit": [
- {
- "name": "BroadcastUnitTests",
- "options": [
- {
- "exclude-annotation": "androidx.test.filters.FlakyTest"
- },
- {
- "exclude-annotation": "org.junit.Ignore"
- }
- ]
- }
- ]
-}
diff --git a/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java b/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java
deleted file mode 100644
index b7c412d..0000000
--- a/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java
+++ /dev/null
@@ -1,258 +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.app;
-
-import static android.content.Intent.ACTION_BATTERY_CHANGED;
-import static android.content.Intent.ACTION_DEVICE_STORAGE_LOW;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.anyString;
-
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.BatteryManager;
-import android.os.Bundle;
-import android.os.SystemProperties;
-import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
-import android.util.ArrayMap;
-
-import androidx.annotation.GuardedBy;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.modules.utils.testing.ExtendedMockitoRule;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mockito;
-
-@EnableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE)
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class BroadcastStickyCacheTest {
- @ClassRule
- public static final SetFlagsRule.ClassRule mClassRule = new SetFlagsRule.ClassRule();
- @Rule
- public final SetFlagsRule mSetFlagsRule = mClassRule.createSetFlagsRule();
-
- @Rule
- public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
- .mockStatic(SystemProperties.class)
- .build();
-
- private static final String PROP_KEY_BATTERY_CHANGED = BroadcastStickyCache.getKey(
- ACTION_BATTERY_CHANGED);
-
- private final TestSystemProps mTestSystemProps = new TestSystemProps();
-
- @Before
- public void setUp() {
- doAnswer(invocation -> {
- final String name = invocation.getArgument(0);
- final long value = Long.parseLong(invocation.getArgument(1));
- mTestSystemProps.add(name, value);
- return null;
- }).when(() -> SystemProperties.set(anyString(), anyString()));
- doAnswer(invocation -> {
- final String name = invocation.getArgument(0);
- final TestSystemProps.Handle testHandle = mTestSystemProps.query(name);
- if (testHandle == null) {
- return null;
- }
- final SystemProperties.Handle handle = Mockito.mock(SystemProperties.Handle.class);
- doAnswer(handleInvocation -> testHandle.getLong(-1)).when(handle).getLong(anyLong());
- return handle;
- }).when(() -> SystemProperties.find(anyString()));
- }
-
- @After
- public void tearDown() {
- mTestSystemProps.clear();
- BroadcastStickyCache.clearForTest();
- }
-
- @Test
- public void testUseCache_nullFilter() {
- assertThat(BroadcastStickyCache.useCache(null)).isEqualTo(false);
- }
-
- @Test
- public void testUseCache_noActions() {
- final IntentFilter filter = new IntentFilter();
- assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false);
- }
-
- @Test
- public void testUseCache_multipleActions() {
- final IntentFilter filter = new IntentFilter();
- filter.addAction(ACTION_DEVICE_STORAGE_LOW);
- filter.addAction(ACTION_BATTERY_CHANGED);
- assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false);
- }
-
- @Test
- public void testUseCache_valueNotSet() {
- final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED);
- assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false);
- }
-
- @Test
- public void testUseCache() {
- final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED);
- final Intent intent = new Intent(ACTION_BATTERY_CHANGED)
- .putExtra(BatteryManager.EXTRA_LEVEL, 90);
- BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
- BroadcastStickyCache.add(filter, intent);
- assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(true);
- }
-
- @Test
- public void testUseCache_versionMismatch() {
- final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED);
- final Intent intent = new Intent(ACTION_BATTERY_CHANGED)
- .putExtra(BatteryManager.EXTRA_LEVEL, 90);
- BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
- BroadcastStickyCache.add(filter, intent);
- BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
-
- assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false);
- }
-
- @Test
- public void testAdd() {
- final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED);
- Intent intent = new Intent(ACTION_BATTERY_CHANGED)
- .putExtra(BatteryManager.EXTRA_LEVEL, 90);
- BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
- BroadcastStickyCache.add(filter, intent);
- assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(true);
- Intent actualIntent = BroadcastStickyCache.getIntentUnchecked(filter);
- assertThat(actualIntent).isNotNull();
- assertEquals(actualIntent, intent);
-
- intent = new Intent(ACTION_BATTERY_CHANGED)
- .putExtra(BatteryManager.EXTRA_LEVEL, 99);
- BroadcastStickyCache.add(filter, intent);
- actualIntent = BroadcastStickyCache.getIntentUnchecked(filter);
- assertThat(actualIntent).isNotNull();
- assertEquals(actualIntent, intent);
- }
-
- @Test
- public void testIncrementVersion_propExists() {
- SystemProperties.set(PROP_KEY_BATTERY_CHANGED, String.valueOf(100));
-
- BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
- assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(101);
- BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
- assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(102);
- }
-
- @Test
- public void testIncrementVersion_propNotExists() {
- // Verify that the property doesn't exist
- assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(-1);
-
- BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
- assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(1);
- BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
- assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(2);
- }
-
- @Test
- public void testIncrementVersionIfExists_propExists() {
- BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
-
- BroadcastStickyCache.incrementVersionIfExists(ACTION_BATTERY_CHANGED);
- assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(2);
- BroadcastStickyCache.incrementVersionIfExists(ACTION_BATTERY_CHANGED);
- assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(3);
- }
-
- @Test
- public void testIncrementVersionIfExists_propNotExists() {
- // Verify that the property doesn't exist
- assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(-1);
-
- BroadcastStickyCache.incrementVersionIfExists(ACTION_BATTERY_CHANGED);
- assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(-1);
- // Verify that property is not added as part of the querying.
- BroadcastStickyCache.incrementVersionIfExists(ACTION_BATTERY_CHANGED);
- assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(-1);
- }
-
- private void assertEquals(Intent actualIntent, Intent expectedIntent) {
- assertThat(actualIntent.getAction()).isEqualTo(expectedIntent.getAction());
- assertEquals(actualIntent.getExtras(), expectedIntent.getExtras());
- }
-
- private void assertEquals(Bundle actualExtras, Bundle expectedExtras) {
- assertWithMessage("Extras expected=%s, actual=%s", expectedExtras, actualExtras)
- .that(actualExtras.kindofEquals(expectedExtras)).isTrue();
- }
-
- private static final class TestSystemProps {
- @GuardedBy("mSysProps")
- private final ArrayMap<String, Long> mSysProps = new ArrayMap<>();
-
- public void add(String name, long value) {
- synchronized (mSysProps) {
- mSysProps.put(name, value);
- }
- }
-
- public long get(String name, long defaultValue) {
- synchronized (mSysProps) {
- final int idx = mSysProps.indexOfKey(name);
- return idx >= 0 ? mSysProps.valueAt(idx) : defaultValue;
- }
- }
-
- public Handle query(String name) {
- synchronized (mSysProps) {
- return mSysProps.containsKey(name) ? new Handle(name) : null;
- }
- }
-
- public void clear() {
- synchronized (mSysProps) {
- mSysProps.clear();
- }
- }
-
- public class Handle {
- private final String mName;
-
- Handle(String name) {
- mName = name;
- }
-
- public long getLong(long defaultValue) {
- return get(mName, defaultValue);
- }
- }
- }
-}