diff options
153 files changed, 3443 insertions, 1142 deletions
diff --git a/Android.bp b/Android.bp index 54cb2684068d..49a6a2b3ec65 100644 --- a/Android.bp +++ b/Android.bp @@ -103,10 +103,10 @@ filegroup { ":android.hardware.gnss-V2-java-source", ":android.hardware.graphics.common-V3-java-source", ":android.hardware.keymaster-V4-java-source", - ":android.hardware.radio-V3-java-source", - ":android.hardware.radio.data-V3-java-source", - ":android.hardware.radio.network-V3-java-source", - ":android.hardware.radio.voice-V3-java-source", + ":android.hardware.radio-V4-java-source", + ":android.hardware.radio.data-V4-java-source", + ":android.hardware.radio.network-V4-java-source", + ":android.hardware.radio.voice-V4-java-source", ":android.hardware.security.secureclock-V1-java-source", ":android.hardware.thermal-V3-java-source", ":android.hardware.tv.tuner-V3-java-source", @@ -232,13 +232,13 @@ java_library { "android.hardware.gnss-V2.1-java", "android.hardware.health-V1.0-java-constants", "android.hardware.radio-V1.6-java", - "android.hardware.radio.data-V3-java", - "android.hardware.radio.ims-V2-java", - "android.hardware.radio.messaging-V3-java", - "android.hardware.radio.modem-V3-java", - "android.hardware.radio.network-V3-java", - "android.hardware.radio.sim-V3-java", - "android.hardware.radio.voice-V3-java", + "android.hardware.radio.data-V4-java", + "android.hardware.radio.ims-V3-java", + "android.hardware.radio.messaging-V4-java", + "android.hardware.radio.modem-V4-java", + "android.hardware.radio.network-V4-java", + "android.hardware.radio.sim-V4-java", + "android.hardware.radio.voice-V4-java", "android.hardware.thermal-V1.0-java-constants", "android.hardware.thermal-V1.0-java", "android.hardware.thermal-V1.1-java", diff --git a/core/api/current.txt b/core/api/current.txt index 5a7f2bdb2949..76f3e5a3cd39 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -3830,7 +3830,7 @@ package android.accounts { method @RequiresPermission(value="android.permission.AUTHENTICATE_ACCOUNTS", apis="..22") public boolean notifyAccountAuthenticated(android.accounts.Account); method @RequiresPermission(value="android.permission.AUTHENTICATE_ACCOUNTS", apis="..22") public String peekAuthToken(android.accounts.Account, String); method @Deprecated @RequiresPermission(value="android.permission.MANAGE_ACCOUNTS", apis="..22") public android.accounts.AccountManagerFuture<java.lang.Boolean> removeAccount(android.accounts.Account, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler); - method @RequiresPermission(value="android.permission.MANAGE_ACCOUNTS", apis="..22") public android.accounts.AccountManagerFuture<android.os.Bundle> removeAccount(android.accounts.Account, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler); + method @FlaggedApi("android.app.admin.flags.split_create_managed_profile_enabled") @RequiresPermission(value="android.permission.REMOVE_ACCOUNTS", conditional=true) public android.accounts.AccountManagerFuture<android.os.Bundle> removeAccount(android.accounts.Account, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler); method @RequiresPermission(value="android.permission.AUTHENTICATE_ACCOUNTS", apis="..22") public boolean removeAccountExplicitly(android.accounts.Account); method public void removeOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener); method @RequiresPermission(value="android.permission.AUTHENTICATE_ACCOUNTS", apis="..22") public android.accounts.AccountManagerFuture<android.accounts.Account> renameAccount(android.accounts.Account, @Size(min=1) String, android.accounts.AccountManagerCallback<android.accounts.Account>, android.os.Handler); @@ -13809,7 +13809,7 @@ package android.content.pm { field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CAMERA}, anyOf={android.Manifest.permission.CAMERA}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CAMERA = 64; // 0x40 field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE}, anyOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.CHANGE_NETWORK_STATE, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE, android.Manifest.permission.NFC, android.Manifest.permission.TRANSMIT_IR, android.Manifest.permission.UWB_RANGING, android.Manifest.permission.RANGING}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 16; // 0x10 field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 1; // 0x1 - field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_HEALTH}, anyOf={android.Manifest.permission.ACTIVITY_RECOGNITION, android.Manifest.permission.BODY_SENSORS, android.Manifest.permission.HIGH_SAMPLING_RATE_SENSORS}) public static final int FOREGROUND_SERVICE_TYPE_HEALTH = 256; // 0x100 + field @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled") @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_HEALTH}, anyOf={android.Manifest.permission.ACTIVITY_RECOGNITION, android.Manifest.permission.HIGH_SAMPLING_RATE_SENSORS, android.health.connect.HealthPermissions.READ_HEART_RATE, android.health.connect.HealthPermissions.READ_SKIN_TEMPERATURE, android.health.connect.HealthPermissions.READ_OXYGEN_SATURATION}) public static final int FOREGROUND_SERVICE_TYPE_HEALTH = 256; // 0x100 field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_LOCATION}, anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_LOCATION = 8; // 0x8 field public static final int FOREGROUND_SERVICE_TYPE_MANIFEST = -1; // 0xffffffff field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK = 2; // 0x2 @@ -21214,6 +21214,7 @@ package android.inputmethodservice { method public void setExtractView(android.view.View); method public void setExtractViewShown(boolean); method public void setInputView(android.view.View); + method @FlaggedApi("android.view.inputmethod.adaptive_handwriting_bounds") public final void setStylusHandwritingRegion(@NonNull android.graphics.Region); method public final void setStylusHandwritingSessionTimeout(@NonNull java.time.Duration); method public final boolean shouldOfferSwitchingToNextInputMethod(); method public void showStatusIcon(@DrawableRes int); @@ -41180,6 +41181,18 @@ package android.service.carrier { method @Deprecated public void onSendTextSms(@NonNull String, int, @NonNull String, @NonNull android.service.carrier.CarrierMessagingService.ResultCallback<android.service.carrier.CarrierMessagingService.SendSmsResult>); method public void onSendTextSms(@NonNull String, int, @NonNull String, int, @NonNull android.service.carrier.CarrierMessagingService.ResultCallback<android.service.carrier.CarrierMessagingService.SendSmsResult>); field public static final int DOWNLOAD_STATUS_ERROR = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int DOWNLOAD_STATUS_MMS_ERROR_CONFIGURATION_ERROR = 606; // 0x25e + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int DOWNLOAD_STATUS_MMS_ERROR_DATA_DISABLED = 610; // 0x262 + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int DOWNLOAD_STATUS_MMS_ERROR_HTTP_FAILURE = 603; // 0x25b + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int DOWNLOAD_STATUS_MMS_ERROR_INACTIVE_SUBSCRIPTION = 609; // 0x261 + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int DOWNLOAD_STATUS_MMS_ERROR_INVALID_APN = 601; // 0x259 + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int DOWNLOAD_STATUS_MMS_ERROR_INVALID_SUBSCRIPTION_ID = 608; // 0x260 + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int DOWNLOAD_STATUS_MMS_ERROR_IO_ERROR = 604; // 0x25c + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int DOWNLOAD_STATUS_MMS_ERROR_MMS_DISABLED_BY_CARRIER = 611; // 0x263 + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int DOWNLOAD_STATUS_MMS_ERROR_NO_DATA_NETWORK = 607; // 0x25f + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int DOWNLOAD_STATUS_MMS_ERROR_RETRY = 605; // 0x25d + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int DOWNLOAD_STATUS_MMS_ERROR_UNABLE_CONNECT_MMS = 602; // 0x25a + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int DOWNLOAD_STATUS_MMS_ERROR_UNSPECIFIED = 600; // 0x258 field public static final int DOWNLOAD_STATUS_OK = 0; // 0x0 field public static final int DOWNLOAD_STATUS_RETRY_ON_CARRIER_NETWORK = 1; // 0x1 field public static final int RECEIVE_OPTIONS_DEFAULT = 0; // 0x0 @@ -41187,7 +41200,38 @@ package android.service.carrier { field public static final int RECEIVE_OPTIONS_SKIP_NOTIFY_WHEN_CREDENTIAL_PROTECTED_STORAGE_UNAVAILABLE = 2; // 0x2 field public static final int SEND_FLAG_REQUEST_DELIVERY_STATUS = 1; // 0x1 field public static final int SEND_STATUS_ERROR = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_MMS_ERROR_CONFIGURATION_ERROR = 406; // 0x196 + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_MMS_ERROR_DATA_DISABLED = 410; // 0x19a + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_MMS_ERROR_HTTP_FAILURE = 403; // 0x193 + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_MMS_ERROR_INACTIVE_SUBSCRIPTION = 409; // 0x199 + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_MMS_ERROR_INVALID_APN = 401; // 0x191 + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_MMS_ERROR_INVALID_SUBSCRIPTION_ID = 408; // 0x198 + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_MMS_ERROR_IO_ERROR = 404; // 0x194 + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_MMS_ERROR_MMS_DISABLED_BY_CARRIER = 411; // 0x19b + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_MMS_ERROR_NO_DATA_NETWORK = 407; // 0x197 + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_MMS_ERROR_RETRY = 405; // 0x195 + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_MMS_ERROR_UNABLE_CONNECT_MMS = 402; // 0x192 + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_MMS_ERROR_UNSPECIFIED = 400; // 0x190 field public static final int SEND_STATUS_OK = 0; // 0x0 + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_CANCELLED = 215; // 0xd7 + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_ENCODING_ERROR = 212; // 0xd4 + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_ERROR_FDN_CHECK_FAILURE = 204; // 0xcc + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_ERROR_GENERIC_FAILURE = 200; // 0xc8 + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_ERROR_LIMIT_EXCEEDED = 203; // 0xcb + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_ERROR_NO_SERVICE = 202; // 0xca + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_ERROR_NULL_PDU = 201; // 0xc9 + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED = 206; // 0xce + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_ERROR_SHORT_CODE_NOT_ALLOWED = 205; // 0xcd + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_INVALID_ARGUMENTS = 208; // 0xd0 + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_INVALID_SMSC_ADDRESS = 213; // 0xd5 + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_INVALID_SMS_FORMAT = 210; // 0xd2 + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_INVALID_STATE = 209; // 0xd1 + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_NETWORK_ERROR = 211; // 0xd3 + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_NETWORK_REJECT = 207; // 0xcf + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_OPERATION_NOT_ALLOWED = 214; // 0xd6 + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_REQUEST_NOT_SUPPORTED = 216; // 0xd8 + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_SMS_BLOCKED_DURING_EMERGENCY = 217; // 0xd9 + field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_SMS_SEND_RETRY_FAILED = 218; // 0xda field public static final int SEND_STATUS_RETRY_ON_CARRIER_NETWORK = 1; // 0x1 field public static final String SERVICE_INTERFACE = "android.service.carrier.CarrierMessagingService"; } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 14a6c8c4928d..a171f3dc4c17 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -135,6 +135,7 @@ package android { field public static final String CONTROL_KEYGUARD_SECURE_NOTIFICATIONS = "android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"; field public static final String CONTROL_OEM_PAID_NETWORK_PREFERENCE = "android.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE"; field public static final String CONTROL_VPN = "android.permission.CONTROL_VPN"; + field @FlaggedApi("android.app.admin.flags.split_create_managed_profile_enabled") public static final String COPY_ACCOUNTS = "android.permission.COPY_ACCOUNTS"; field public static final String CREATE_USERS = "android.permission.CREATE_USERS"; field public static final String CREATE_VIRTUAL_DEVICE = "android.permission.CREATE_VIRTUAL_DEVICE"; field public static final String CRYPT_KEEPER = "android.permission.CRYPT_KEEPER"; @@ -347,6 +348,7 @@ package android { field public static final String REGISTER_SIM_SUBSCRIPTION = "android.permission.REGISTER_SIM_SUBSCRIPTION"; field public static final String REGISTER_STATS_PULL_ATOM = "android.permission.REGISTER_STATS_PULL_ATOM"; field public static final String REMOTE_DISPLAY_PROVIDER = "android.permission.REMOTE_DISPLAY_PROVIDER"; + field @FlaggedApi("android.app.admin.flags.split_create_managed_profile_enabled") public static final String REMOVE_ACCOUNTS = "android.permission.REMOVE_ACCOUNTS"; field public static final String REMOVE_DRM_CERTIFICATES = "android.permission.REMOVE_DRM_CERTIFICATES"; field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS"; field public static final String RENOUNCE_PERMISSIONS = "android.permission.RENOUNCE_PERMISSIONS"; @@ -572,6 +574,7 @@ package android.accessibilityservice { package android.accounts { public class AccountManager { + method @FlaggedApi("android.app.admin.flags.split_create_managed_profile_enabled") @NonNull @RequiresPermission(anyOf={android.Manifest.permission.COPY_ACCOUNTS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public android.accounts.AccountManagerFuture<java.lang.Boolean> copyAccountToUser(@NonNull android.accounts.Account, @NonNull android.os.UserHandle, @NonNull android.os.UserHandle, @Nullable android.accounts.AccountManagerCallback<java.lang.Boolean>, @Nullable android.os.Handler); method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public android.accounts.AccountManagerFuture<android.os.Bundle> finishSessionAsUser(android.os.Bundle, android.app.Activity, android.os.UserHandle, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler); } @@ -9896,7 +9899,7 @@ package android.media.tv.tuner.frontend { method public int getSignalStrength(); method public int getSnr(); method public int getSpectralInversion(); - method @FlaggedApi("android.media.tv.flags.tuner_w_apis") @NonNull public android.media.tv.tuner.frontend.StandardExt getStandardExt(); + method @FlaggedApi("android.media.tv.flags.tuner_w_apis") @NonNull public android.media.tv.tuner.frontend.StandardExtension getStandardExtension(); method @NonNull public int[] getStreamIds(); method public int getSymbolRate(); method @IntRange(from=0, to=65535) public int getSystemId(); @@ -9951,7 +9954,7 @@ package android.media.tv.tuner.frontend { field public static final int FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH = 6; // 0x6 field public static final int FRONTEND_STATUS_TYPE_SNR = 1; // 0x1 field public static final int FRONTEND_STATUS_TYPE_SPECTRAL = 10; // 0xa - field @FlaggedApi("android.media.tv.flags.tuner_w_apis") public static final int FRONTEND_STATUS_TYPE_STANDARD_EXT = 47; // 0x2f + field @FlaggedApi("android.media.tv.flags.tuner_w_apis") public static final int FRONTEND_STATUS_TYPE_STANDARD_EXTENSION = 47; // 0x2f field public static final int FRONTEND_STATUS_TYPE_STREAM_IDS = 39; // 0x27 field public static final int FRONTEND_STATUS_TYPE_SYMBOL_RATE = 7; // 0x7 field public static final int FRONTEND_STATUS_TYPE_T2_SYSTEM_ID = 29; // 0x1d @@ -10243,9 +10246,9 @@ package android.media.tv.tuner.frontend { method public default void onUnlocked(); } - @FlaggedApi("android.media.tv.flags.tuner_w_apis") public final class StandardExt { - method public int getDvbsStandardExt(); - method public int getDvbtStandardExt(); + @FlaggedApi("android.media.tv.flags.tuner_w_apis") public final class StandardExtension { + method public int getDvbsStandardExtension(); + method public int getDvbtStandardExtension(); } } diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index 87acbbf65b2f..72450999993d 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -16,9 +16,13 @@ package android.accounts; +import static android.Manifest.permission.COPY_ACCOUNTS; +import static android.Manifest.permission.REMOVE_ACCOUNTS; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; +import static android.app.admin.flags.Flags.FLAG_SPLIT_CREATE_MANAGED_PROFILE_ENABLED; import android.annotation.BroadcastBehavior; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -26,6 +30,7 @@ import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.Size; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.UserHandleAware; @@ -1312,7 +1317,8 @@ public class AccountManager { * {@link AccountManagerFuture} must not be used on the main thread. * * <p>This method requires the caller to have a signature match with the - * authenticator that manages the specified account. + * authenticator that manages the specified account, be a profile owner or have the + * {@link android.Manifest.permission#REMOVE_ACCOUNTS} permission. * * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before, * MANAGE_ACCOUNTS permission is needed for those platforms. See docs for @@ -1344,6 +1350,8 @@ public class AccountManager { * </ul> */ @UserHandleAware + @RequiresPermission(value = REMOVE_ACCOUNTS, conditional = true) + @FlaggedApi(FLAG_SPLIT_CREATE_MANAGED_PROFILE_ENABLED) public AccountManagerFuture<Bundle> removeAccount(final Account account, final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) { return removeAccountAsUser(account, activity, callback, handler, mContext.getUser()); @@ -2019,9 +2027,15 @@ public class AccountManager { * succeeded. * @hide */ + @SuppressLint("SamShouldBeLast") + @NonNull + @SystemApi + @RequiresPermission(anyOf = {COPY_ACCOUNTS, INTERACT_ACROSS_USERS_FULL}) + @FlaggedApi(FLAG_SPLIT_CREATE_MANAGED_PROFILE_ENABLED) public AccountManagerFuture<Boolean> copyAccountToUser( - final Account account, final UserHandle fromUser, final UserHandle toUser, - AccountManagerCallback<Boolean> callback, Handler handler) { + @NonNull final Account account, @NonNull final UserHandle fromUser, + @NonNull final UserHandle toUser, @Nullable AccountManagerCallback<Boolean> callback, + @Nullable Handler handler) { if (account == null) throw new IllegalArgumentException("account is null"); if (toUser == null || fromUser == null) { throw new IllegalArgumentException("fromUser and toUser cannot be null"); diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index ab75069cc5d8..33ba05865042 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -2679,62 +2679,6 @@ public class ActivityManager { */ public static class RecentTaskInfo extends TaskInfo implements Parcelable { /** - * @hide - */ - public static class PersistedTaskSnapshotData { - /** - * The bounds of the task when the last snapshot was taken, may be null if the task is - * not yet attached to the hierarchy. - * @see {@link android.window.TaskSnapshot#mTaskSize}. - * @hide - */ - public @Nullable Point taskSize; - - /** - * The content insets of the task when the task snapshot was taken. - * @see {@link android.window.TaskSnapshot#mContentInsets}. - * @hide - */ - public @Nullable Rect contentInsets; - - /** - * The size of the last snapshot taken, may be null if there is no associated snapshot. - * @see {@link android.window.TaskSnapshot#mSnapshot}. - * @hide - */ - public @Nullable Point bufferSize; - - /** - * Sets the data from the other data. - * @hide - */ - public void set(PersistedTaskSnapshotData other) { - taskSize = other.taskSize; - contentInsets = other.contentInsets; - bufferSize = other.bufferSize; - } - - /** - * Sets the data from the provided {@param snapshot}. - * @hide - */ - public void set(TaskSnapshot snapshot) { - if (snapshot == null) { - taskSize = null; - contentInsets = null; - bufferSize = null; - return; - } - final HardwareBuffer buffer = snapshot.getHardwareBuffer(); - taskSize = new Point(snapshot.getTaskSize()); - contentInsets = new Rect(snapshot.getContentInsets()); - bufferSize = buffer != null - ? new Point(buffer.getWidth(), buffer.getHeight()) - : null; - } - } - - /** * If this task is currently running, this is the identifier for it. * If it is not running, this will be -1. * @@ -2770,24 +2714,6 @@ public class ActivityManager { @Deprecated public int affiliatedTaskId; - /** - * Information of organized child tasks. - * - * @deprecated No longer used - * @hide - */ - @Deprecated - public ArrayList<RecentTaskInfo> childrenTaskInfos = new ArrayList<>(); - - /** - * Information about the last snapshot taken for this task. - * - * @deprecated No longer used - * @hide - */ - @Deprecated - public PersistedTaskSnapshotData lastSnapshotData = new PersistedTaskSnapshotData(); - public RecentTaskInfo() { } @@ -2803,10 +2729,6 @@ public class ActivityManager { public void readFromParcel(Parcel source) { id = source.readInt(); persistentId = source.readInt(); - childrenTaskInfos = source.readArrayList(RecentTaskInfo.class.getClassLoader(), android.app.ActivityManager.RecentTaskInfo.class); - lastSnapshotData.taskSize = source.readTypedObject(Point.CREATOR); - lastSnapshotData.contentInsets = source.readTypedObject(Rect.CREATOR); - lastSnapshotData.bufferSize = source.readTypedObject(Point.CREATOR); super.readTaskFromParcel(source); } @@ -2814,10 +2736,6 @@ public class ActivityManager { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(id); dest.writeInt(persistentId); - dest.writeList(childrenTaskInfos); - dest.writeTypedObject(lastSnapshotData.taskSize, flags); - dest.writeTypedObject(lastSnapshotData.contentInsets, flags); - dest.writeTypedObject(lastSnapshotData.bufferSize, flags); super.writeTaskToParcel(dest, flags); } @@ -2884,11 +2802,6 @@ public class ActivityManager { pw.println(" }"); } pw.print(" "); - pw.print(" lastSnapshotData {"); - pw.print(" taskSize=" + lastSnapshotData.taskSize); - pw.print(" contentInsets=" + lastSnapshotData.contentInsets); - pw.print(" bufferSize=" + lastSnapshotData.bufferSize); - pw.println(" }"); } } diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java index 16444dc5adde..6efc4ef55180 100644 --- a/core/java/android/app/ForegroundServiceTypePolicy.java +++ b/core/java/android/app/ForegroundServiceTypePolicy.java @@ -62,6 +62,7 @@ import android.content.pm.ServiceInfo.ForegroundServiceType; import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; +import android.health.connect.HealthPermissions; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; @@ -484,21 +485,35 @@ public abstract class ForegroundServiceTypePolicy { */ public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_HEALTH = new ForegroundServiceTypePolicyInfo( - FOREGROUND_SERVICE_TYPE_HEALTH, - ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID, - ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID, - new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] { - new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_HEALTH) - }, true), - new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] { - new RegularPermission(Manifest.permission.ACTIVITY_RECOGNITION), - new RegularPermission(Manifest.permission.BODY_SENSORS), - new RegularPermission(Manifest.permission.HIGH_SAMPLING_RATE_SENSORS), - }, false), - FGS_TYPE_PERM_ENFORCEMENT_FLAG_HEALTH /* permissionEnforcementFlag */, - true /* permissionEnforcementFlagDefaultValue */, - false /* foregroundOnlyPermission */ - ); + FOREGROUND_SERVICE_TYPE_HEALTH, + ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID, + ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID, + new ForegroundServiceTypePermissions( + new ForegroundServiceTypePermission[] { + new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_HEALTH) + }, + true), + new ForegroundServiceTypePermissions(getAllowedHealthPermissions(), false), + FGS_TYPE_PERM_ENFORCEMENT_FLAG_HEALTH /* permissionEnforcementFlag */, + true /* permissionEnforcementFlagDefaultValue */, + false /* foregroundOnlyPermission */); + + /** Returns the permissions needed for the policy of the health foreground service type. */ + private static ForegroundServiceTypePermission[] getAllowedHealthPermissions() { + final ArrayList<ForegroundServiceTypePermission> permissions = new ArrayList<>(); + permissions.add(new RegularPermission(Manifest.permission.ACTIVITY_RECOGNITION)); + permissions.add(new RegularPermission(Manifest.permission.HIGH_SAMPLING_RATE_SENSORS)); + + if (android.permission.flags.Flags.replaceBodySensorPermissionEnabled()) { + permissions.add(new RegularPermission(HealthPermissions.READ_HEART_RATE)); + permissions.add(new RegularPermission(HealthPermissions.READ_SKIN_TEMPERATURE)); + permissions.add(new RegularPermission(HealthPermissions.READ_OXYGEN_SATURATION)); + } else { + permissions.add(new RegularPermission(Manifest.permission.BODY_SENSORS)); + } + + return permissions.toArray(new ForegroundServiceTypePermission[permissions.size()]); + } /** * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING}. diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index ff73382c43e6..b78f11148178 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -3257,7 +3257,7 @@ public class Notification implements Parcelable */ @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING) public boolean hasPromotableCharacteristics() { - return isColorized() + return isColorizedRequested() && hasTitle() && !containsCustomViews() && hasPromotableStyle(); @@ -4083,6 +4083,12 @@ public class Notification implements Parcelable flags &= ~FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY; } } + if (Flags.apiRichOngoing()) { + if ((flags & FLAG_PROMOTED_ONGOING) != 0) { + flagStrings.add("PROMOTED_ONGOING"); + flags &= ~FLAG_PROMOTED_ONGOING; + } + } if (android.service.notification.Flags.notificationSilentFlag()) { if ((flags & FLAG_SILENT) != 0) { @@ -7792,8 +7798,16 @@ public class Notification implements Parcelable * @hide */ public boolean isColorized() { - return extras.getBoolean(EXTRA_COLORIZED) - && (hasColorizedPermission() || isFgsOrUij()); + return isColorizedRequested() + && (hasColorizedPermission() || isFgsOrUij() || isPromotedOngoing()); + } + + /** + * @return true if this notification has requested to be colorized, regardless of whether it + * meets the requirements to be displayed that way. + */ + private boolean isColorizedRequested() { + return extras.getBoolean(EXTRA_COLORIZED); } /** @@ -7807,6 +7821,19 @@ public class Notification implements Parcelable } /** + * Returns whether this notification is a promoted ongoing notification. + * + * This requires the Notification.FLAG_PROMOTED_ONGOING flag to be set + * (which may be true once the api_rich_ongoing feature flag is enabled), + * and requires that the ui_rich_ongoing feature flag is enabled. + * + * @hide + */ + public boolean isPromotedOngoing() { + return Flags.uiRichOngoing() && (flags & Notification.FLAG_PROMOTED_ONGOING) != 0; + } + + /** * @return true if this is a media style notification with a media session * * @hide @@ -11671,8 +11698,10 @@ public class Notification implements Parcelable return points; } - @NonNull - private NotificationProgressModel createProgressModel(int defaultProgressColor, + /** + * @hide + */ + public @NonNull NotificationProgressModel createProgressModel(int defaultProgressColor, int backgroundColor) { final NotificationProgressModel model; if (mIndeterminate) { diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index 087e246e8841..599a46b131d5 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -174,54 +174,22 @@ public class ResourcesManager { } /** - * 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 + * Apply the registered library paths to the passed impl object + * @return the hash code for the current version of the registered paths */ - public @NonNull Pair<AssetManager, Integer> updateResourceImplAssetsWithRegisteredLibs( - @NonNull AssetManager assets, boolean reuseAssets) { + public int updateResourceImplWithRegisteredLibs(@NonNull ResourcesImpl impl) { if (!Flags.registerResourcePaths()) { - return new Pair<>(assets, 0); + return 0; } - 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); - } - } - 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); - } + 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); } - 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); + impl.getAssets().addPresetApkKeys(extractApkKeys(collector.collectedKey())); + return size; } public static class ApkKey { @@ -656,23 +624,6 @@ public class ResourcesManager { 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. * @@ -801,7 +752,7 @@ public class ResourcesManager { final Configuration config = generateConfig(key); final DisplayMetrics displayMetrics = getDisplayMetrics(generateDisplayId(key), daj); - final ResourcesImpl impl = new ResourcesImpl(assets, displayMetrics, config, daj, true); + final ResourcesImpl impl = new ResourcesImpl(assets, displayMetrics, config, daj); if (DEBUG) { Slog.d(TAG, "- creating impl=" + impl + " with key: " + key); @@ -1881,32 +1832,31 @@ public class ResourcesManager { 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) { - 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); + 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()); r.setImpl(newImpl); } else { - Slog.w(TAG, "Skip appending shared library asset paths for " - + "the Resources as its assets are not up to date."); + Slog.w(TAG, "Skip appending shared library asset paths for the " + + "Resource as its assets are not up to date."); } } } diff --git a/core/java/android/app/jank/FrameOverrunHistogram.java b/core/java/android/app/jank/FrameOverrunHistogram.java index e28ac126a90a..3ad6531a46bf 100644 --- a/core/java/android/app/jank/FrameOverrunHistogram.java +++ b/core/java/android/app/jank/FrameOverrunHistogram.java @@ -39,7 +39,7 @@ public class FrameOverrunHistogram { * Create a new instance of FrameOverrunHistogram. */ public FrameOverrunHistogram() { - mBucketCounts = new int[sBucketEndpoints.length - 1]; + mBucketCounts = new int[sBucketEndpoints.length]; } /** diff --git a/core/java/android/app/jank/JankDataProcessor.java b/core/java/android/app/jank/JankDataProcessor.java index 7525d0402ee4..7ceaeb3fb070 100644 --- a/core/java/android/app/jank/JankDataProcessor.java +++ b/core/java/android/app/jank/JankDataProcessor.java @@ -59,7 +59,6 @@ public class JankDataProcessor { * @param appUid the uid of the app. */ public void processJankData(List<JankData> jankData, String activityName, int appUid) { - mCurrentBatchCount++; // add all the previous and active states to the pending states list. mStateTracker.retrieveAllStates(mPendingStates); @@ -79,9 +78,8 @@ public class JankDataProcessor { } } // At this point we have attributed all frames to a state. - if (mCurrentBatchCount >= LOG_BATCH_FREQUENCY) { - logMetricCounts(); - } + incrementBatchCountAndMaybeLogStats(); + // return the StatData object back to the pool to be reused. jankDataProcessingComplete(); } @@ -91,7 +89,73 @@ public class JankDataProcessor { * stats */ public void mergeJankStats(AppJankStats jankStats, String activityName) { - // TODO b/377572463 Add Merging Logic + // Each state has a key which is a combination of widget category, widget id and widget + // state, this key is also used to identify pending stats, a pending stat is essentially a + // state with frames associated with it. + String stateKey = mStateTracker.getStateKey(jankStats.getWidgetCategory(), + jankStats.getWidgetId(), jankStats.getWidgetState()); + + if (mPendingJankStats.containsKey(stateKey)) { + mergeExistingStat(stateKey, jankStats); + } else { + mergeNewStat(stateKey, activityName, jankStats); + } + + incrementBatchCountAndMaybeLogStats(); + } + + private void mergeExistingStat(String stateKey, AppJankStats jankStat) { + PendingJankStat pendingStat = mPendingJankStats.get(stateKey); + + pendingStat.mJankyFrames += jankStat.getJankyFrameCount(); + pendingStat.mTotalFrames += jankStat.getTotalFrameCount(); + + mergeOverrunHistograms(pendingStat.mFrameOverrunBuckets, + jankStat.getFrameOverrunHistogram().getBucketCounters()); + } + + private void mergeNewStat(String stateKey, String activityName, AppJankStats jankStats) { + // Check if we have space for a new stat + if (mPendingJankStats.size() > MAX_IN_MEMORY_STATS) { + return; + } + + PendingJankStat pendingStat = mPendingJankStatsPool.acquire(); + if (pendingStat == null) { + pendingStat = new PendingJankStat(); + + } + pendingStat.clearStats(); + + pendingStat.mActivityName = activityName; + pendingStat.mUid = jankStats.getUid(); + pendingStat.mWidgetId = jankStats.getWidgetId(); + pendingStat.mWidgetCategory = jankStats.getWidgetCategory(); + pendingStat.mWidgetState = jankStats.getWidgetState(); + pendingStat.mTotalFrames = jankStats.getTotalFrameCount(); + pendingStat.mJankyFrames = jankStats.getJankyFrameCount(); + + mergeOverrunHistograms(pendingStat.mFrameOverrunBuckets, + jankStats.getFrameOverrunHistogram().getBucketCounters()); + + mPendingJankStats.put(stateKey, pendingStat); + } + + private void mergeOverrunHistograms(int[] mergeTarget, int[] mergeSource) { + // The length of each histogram should be identical, if they are not then its possible the + // buckets are not in sync, these records should not be recorded. + if (mergeTarget.length != mergeSource.length) return; + + for (int i = 0; i < mergeTarget.length; i++) { + mergeTarget[i] += mergeSource[i]; + } + } + + private void incrementBatchCountAndMaybeLogStats() { + mCurrentBatchCount++; + if (mCurrentBatchCount >= LOG_BATCH_FREQUENCY) { + logMetricCounts(); + } } /** diff --git a/core/java/android/app/jank/JankTracker.java b/core/java/android/app/jank/JankTracker.java index 202281f98c97..469521668d25 100644 --- a/core/java/android/app/jank/JankTracker.java +++ b/core/java/android/app/jank/JankTracker.java @@ -89,7 +89,12 @@ public class JankTracker { * stats */ public void mergeAppJankStats(AppJankStats appJankStats) { - mJankDataProcessor.mergeJankStats(appJankStats, mActivityName); + getHandler().post(new Runnable() { + @Override + public void run() { + mJankDataProcessor.mergeJankStats(appJankStats, mActivityName); + } + }); } public void setActivityName(@NonNull String activityName) { diff --git a/core/java/android/app/jank/StateTracker.java b/core/java/android/app/jank/StateTracker.java index c86d5a5cff20..21bb5e8280ee 100644 --- a/core/java/android/app/jank/StateTracker.java +++ b/core/java/android/app/jank/StateTracker.java @@ -180,7 +180,11 @@ public class StateTracker { } } - private String getStateKey(String widgetCategory, String widgetId, String widgetState) { + /** + * Returns a concatenated string of the inputs. This key can be used to retrieve both pending + * stats and the state that was used to create the pending stat. + */ + public String getStateKey(String widgetCategory, String widgetId, String widgetState) { return widgetCategory + widgetId + widgetState; } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 5754e7e30c34..19cd2e69cfdf 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -4529,7 +4529,6 @@ public abstract class Context { * @see #AUTHENTICATION_POLICY_SERVICE * @see android.security.authenticationpolicy.AuthenticationPolicyManager */ - // TODO(b/347269120): Re-add @Nullable public abstract Object getSystemService(@ServiceName @NonNull String name); /** @@ -4576,7 +4575,6 @@ public abstract class Context { */ @SuppressWarnings("unchecked") @RavenwoodKeep - // TODO(b/347269120): Re-add @Nullable public final <T> T getSystemService(@NonNull Class<T> serviceClass) { // Because subclasses may override getSystemService(String) we cannot // perform a lookup by class alone. We must first map the class to its diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 23d17cb5ce50..413eb9886392 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -960,7 +960,6 @@ public class ContextWrapper extends Context { } @Override - // TODO(b/347269120): Re-add @Nullable public Object getSystemService(String name) { return mBase.getSystemService(name); } diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java index 4285b0a2b91a..8243d88e6260 100644 --- a/core/java/android/content/pm/ServiceInfo.java +++ b/core/java/android/content/pm/ServiceInfo.java @@ -20,6 +20,7 @@ import android.Manifest; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.RequiresPermission; +import android.health.connect.HealthPermissions; import android.os.Parcel; import android.os.Parcelable; import android.util.Printer; @@ -361,8 +362,10 @@ public class ServiceInfo extends ComponentInfo * {@link android.Manifest.permission#FOREGROUND_SERVICE_HEALTH} and one of the following * permissions: * {@link android.Manifest.permission#ACTIVITY_RECOGNITION}, - * {@link android.Manifest.permission#BODY_SENSORS}, * {@link android.Manifest.permission#HIGH_SAMPLING_RATE_SENSORS}. + * {@link android.health.connect.HealthPermissions#READ_HEART_RATE}, + * {@link android.health.connect.HealthPermissions#READ_SKIN_TEMPERATURE}, + * {@link android.health.connect.HealthPermissions#READ_OXYGEN_SATURATION}, */ @RequiresPermission( allOf = { @@ -370,10 +373,13 @@ public class ServiceInfo extends ComponentInfo }, anyOf = { Manifest.permission.ACTIVITY_RECOGNITION, - Manifest.permission.BODY_SENSORS, Manifest.permission.HIGH_SAMPLING_RATE_SENSORS, + HealthPermissions.READ_HEART_RATE, + HealthPermissions.READ_SKIN_TEMPERATURE, + HealthPermissions.READ_OXYGEN_SATURATION, } ) + @FlaggedApi(android.permission.flags.Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED) public static final int FOREGROUND_SERVICE_TYPE_HEALTH = 1 << 8; /** diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java index 908999b64961..68b5d782bfbf 100644 --- a/core/java/android/content/res/ApkAssets.java +++ b/core/java/android/content/res/ApkAssets.java @@ -124,13 +124,11 @@ public final class ApkAssets { @Nullable @GuardedBy("this") - private StringBlock mStringBlock; // null or closed if mNativePtr = 0. + private final StringBlock mStringBlock; // null or closed if mNativePtr = 0. @PropertyFlags private final int mFlags; - private final boolean mIsOverlay; - @Nullable private final AssetsProvider mAssets; @@ -304,43 +302,40 @@ public final class ApkAssets { 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) { - this(FORMAT_APK, flags, assets); + mFlags = flags; 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 @@ -430,18 +425,6 @@ public final class ApkAssets { } } - 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/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index bcaceb24d767..e6b93427f413 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -203,25 +203,9 @@ public class ResourcesImpl { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics, @Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) { - // 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; + mAssets = assets; + mAppliedSharedLibsHash = + ResourcesManager.getInstance().updateResourceImplWithRegisteredLibs(this); mMetrics.setToDefaults(); mDisplayAdjustments = displayAdjustments; mConfiguration.setToDefaults(); diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig index f23c193e2da0..6fc7d90a8237 100644 --- a/core/java/android/content/res/flags.aconfig +++ b/core/java/android/content/res/flags.aconfig @@ -105,3 +105,12 @@ flag { # This flag is used to control aapt2 behavior. is_fixed_read_only: true } + +flag { + name: "resources_minor_version_support" + is_exported: true + namespace: "resource_manager" + description: "Feature flag for supporting minor version in Resources" + bug: "373535266" + is_fixed_read_only: true +} diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index dadb5c386b76..977c5bd927cf 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -407,6 +407,12 @@ public class InputMethodService extends AbstractInputMethodService { private boolean mUsingCtrlShiftShortcut = false; /** + * Last handwriting bounds used for stylus handwriting + * {@link #setStylusHandwritingRegion(Region)}. + */ + private Region mLastHandwritingRegion; + + /** * Returns whether {@link InputMethodService} is responsible for rendering the back button and * the IME switcher button or not when the gestural navigation is enabled. * @@ -1532,6 +1538,7 @@ public class InputMethodService extends AbstractInputMethodService { return; } editorInfo.makeCompatible(getApplicationInfo().targetSdkVersion); + mLastHandwritingRegion = null; getInputMethodInternal().restartInput(new RemoteInputConnection(ric, sessionId), editorInfo); } @@ -2840,6 +2847,7 @@ public class InputMethodService extends AbstractInputMethodService { mHandler.removeCallbacks(mFinishHwRunnable); } mFinishHwRunnable = null; + mLastHandwritingRegion = null; final int requestId = mHandwritingRequestId.getAsInt(); mHandwritingRequestId = OptionalInt.empty(); @@ -3166,6 +3174,40 @@ public class InputMethodService extends AbstractInputMethodService { registerDefaultOnBackInvokedCallback(); } + /** + * Sets a new stylus handwriting region as user continues to write on an editor on screen. + * Stylus strokes that are started within the {@code touchableRegion} are treated as + * continuation of handwriting and all the events outside are passed-through to the IME target + * app, causing stylus handwriting to finish {@link #finishStylusHandwriting()}. + * By default, {@link WindowManager#getMaximumWindowMetrics()} is handwritable and + * {@code touchableRegion} resets after each handwriting session. + * <p> + * For example, the IME can use this API to dynamically expand the stylus handwriting region on + * every stylus stroke as user continues to write on an editor. The region should grow around + * the last stroke so that a UI element below the IME window is still interactable when it is + * spaced sufficiently away (~2 character dimensions) from last stroke. + * </p> + * <p> + * Note: Setting handwriting touchable region is supported on IMEs that support stylus + * handwriting {@link InputMethodInfo#supportsStylusHandwriting()}. + * </p> + * + * @param handwritingRegion new stylus handwritable {@link Region} that can accept stylus touch. + */ + @FlaggedApi(Flags.FLAG_ADAPTIVE_HANDWRITING_BOUNDS) + public final void setStylusHandwritingRegion(@NonNull Region handwritingRegion) { + if (handwritingRegion.equals(mLastHandwritingRegion)) { + Log.v(TAG, "Failed to set setStylusHandwritingRegion():" + + " same region set twice."); + return; + } + + if (DEBUG) { + Log.d(TAG, "Setting new handwriting region for stylus handwriting " + + handwritingRegion + " from last " + mLastHandwritingRegion); + } + mLastHandwritingRegion = handwritingRegion; + } /** * Registers an {@link OnBackInvokedCallback} to handle back invocation when ahead-of-time diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java index 036ccd84a600..4c9f08d80d4b 100644 --- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java +++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java @@ -123,11 +123,6 @@ public final class MessageQueue { // We can lift this restriction in the future after we've made it possible for test authors // to test Looper and MessageQueue without resorting to reflection. - // Holdback study. - if (mUseConcurrent && Flags.messageQueueForceLegacy()) { - mUseConcurrent = false; - } - mQuitAllowed = quitAllowed; mPtr = nativeInit(); } diff --git a/core/java/android/os/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java index 224b10d0eaca..2b0042d653b1 100644 --- a/core/java/android/os/PerformanceHintManager.java +++ b/core/java/android/os/PerformanceHintManager.java @@ -22,6 +22,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemService; import android.annotation.TestApi; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import com.android.internal.util.Preconditions; @@ -106,7 +107,9 @@ public final class PerformanceHintManager { * All timings should be in {@link SystemClock#uptimeNanos()}. */ public static class Session implements Closeable { - private long mNativeSessionPtr; + /** @hide */ + @UnsupportedAppUsage + public long mNativeSessionPtr; /** @hide */ public Session(long nativeSessionPtr) { diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index 9b1bf057b815..2ef8764a5221 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -4,15 +4,6 @@ container: "system" # keep-sorted start block=yes newline_separated=yes flag { - # Holdback study for concurrent MessageQueue. - # Do not promote beyond trunkfood. - namespace: "system_performance" - name: "message_queue_force_legacy" - description: "Whether to holdback concurrent MessageQueue (force legacy)." - bug: "336880969" -} - -flag { name: "adpf_gpu_report_actual_work_duration" is_exported: true namespace: "game" diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index b5a822b715d5..1093503a45a5 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -418,3 +418,12 @@ flag { description: "Add new checkOp APIs that accept attributionTag" bug: "240617242" } + +flag { + name: "device_policy_management_role_split_create_managed_profile_enabled" + is_fixed_read_only: true + is_exported: true + namespace: "enterprise" + description: "Gives the device policy management role the ability to create a managed profile using new APIs" + bug: "375382324" +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index a35c9c1cd4ec..19b0c6fcdd77 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -20454,6 +20454,29 @@ public final class Settings { * @hide */ public static final String AUTO_BEDTIME_MODE = "auto_bedtime_mode"; + + /** + * Indicates that all elements of the system status tray on wear should be rendered + * by default wear system. + * + * @hide + */ + public static final int STATUS_TRAY_CONFIGURATION_DEFAULT = 0; + + /** + * Indicates that all elements of the system status tray on wear should be hidden. + * + * @hide + */ + public static final int STATUS_TRAY_CONFIGURATION_SYSTEM_HIDDEN = 1; + + /** + * Configuration of system status tray in wear. + * + * @hide + */ + public static final String WEAR_SYSTEM_STATUS_TRAY_CONFIGURATION = + "wear_system_status_tray_configuration"; } } diff --git a/core/java/android/service/carrier/CarrierMessagingService.java b/core/java/android/service/carrier/CarrierMessagingService.java index 61213e6293ba..a825a7e110f5 100644 --- a/core/java/android/service/carrier/CarrierMessagingService.java +++ b/core/java/android/service/carrier/CarrierMessagingService.java @@ -16,6 +16,7 @@ package android.service.carrier; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -26,6 +27,8 @@ import android.net.Uri; import android.os.IBinder; import android.os.RemoteException; +import com.android.internal.telephony.flags.Flags; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; @@ -97,16 +100,317 @@ public abstract class CarrierMessagingService extends Service { public static final int SEND_STATUS_RETRY_ON_CARRIER_NETWORK = 1; /** - * SMS/MMS sending failed. We should not retry via the carrier network. + * SMS/MMS sending failed due to an unspecified issue. Sending will not be retried via the + * carrier network. + * + * <p>Maps to SmsManager.RESULT_RIL_GENERIC_FAILURE for SMS and SmsManager.MMS_ERROR_UNSPECIFIED + * for MMS. */ public static final int SEND_STATUS_ERROR = 2; + /** + * More precise error reasons for outbound SMS send requests. These will not be retried on the + * carrier network. + * + * <p>Each code maps directly to an SmsManager code (e.g. SEND_STATS_RESULT_ERROR_NULL_PDU maps + * to SmsManager.RESULT_ERROR_NULL_PDU). + */ + + /** + * Generic failure cause. + * + * @see android.telephony.SmsManager.RESULT_ERROR_GENERIC_FAILURE + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int SEND_STATUS_RESULT_ERROR_GENERIC_FAILURE = 200; + + /** + * Failed because no pdu provided. + * + * @see android.telephony.SmsManager.RESULT_ERROR_NULL_PDU + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int SEND_STATUS_RESULT_ERROR_NULL_PDU = 201; + + /** + * Failed because service is currently unavailable. + * + * @see android.telephony.SmsManager.RESULT_ERROR_NO_SERVICE + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int SEND_STATUS_RESULT_ERROR_NO_SERVICE = 202; + + /** + * Failed because we reached the sending queue limit. + * + * @see android.telephony.SmsManager.RESULT_ERROR_LIMIT_EXCEEDED + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int SEND_STATUS_RESULT_ERROR_LIMIT_EXCEEDED = 203; + + /** + * Failed because FDN is enabled. + * + * @see android.telephony.SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int SEND_STATUS_RESULT_ERROR_FDN_CHECK_FAILURE = 204; + + /** + * Failed because user denied the sending of this short code. + * + * @see android.telephony.SmsManager.RESULT_ERROR_SHORT_CODE_NOT_ALLOWED + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int SEND_STATUS_RESULT_ERROR_SHORT_CODE_NOT_ALLOWED = 205; + + /** + * Failed because the user has denied this app ever send premium short codes. + * + * @see android.telephony.SmsManager.RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int SEND_STATUS_RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED = 206; + + /** + * Failed because of network rejection. + * + * @see android.telephony.SmsManager.RESULT_NETWORK_REJECT + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int SEND_STATUS_RESULT_NETWORK_REJECT = 207; + + /** + * Failed because of invalid arguments. + * + * @see android.telephony.SmsManager.RESULT_INVALID_ARGUMENTS + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int SEND_STATUS_RESULT_INVALID_ARGUMENTS = 208; + + /** + * Failed because of an invalid state. + * + * @see android.telephony.SmsManager.RESULT_INVALID_STATE + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int SEND_STATUS_RESULT_INVALID_STATE = 209; + + /** + * Failed because the sms format is not valid. + * + * @see android.telephony.SmsManager.RESULT_INVALID_SMS_FORMAT + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int SEND_STATUS_RESULT_INVALID_SMS_FORMAT = 210; + + /** + * Failed because of a network error. + * + * @see android.telephony.SmsManager.RESULT_NETWORK_ERROR + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int SEND_STATUS_RESULT_NETWORK_ERROR = 211; + + /** + * Failed because of an encoding error. + * + * @see android.telephony.SmsManager.RESULT_ENCODING_ERROR + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int SEND_STATUS_RESULT_ENCODING_ERROR = 212; + + /** + * Failed because of an invalid smsc address + * + * @see android.telephony.SmsManager.RESULT_INVALID_SMSC_ADDRESS + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int SEND_STATUS_RESULT_INVALID_SMSC_ADDRESS = 213; + + /** + * Failed because the operation is not allowed. + * + * @see android.telephony.SmsManager.RESULT_OPERATION_NOT_ALLOWED + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int SEND_STATUS_RESULT_OPERATION_NOT_ALLOWED = 214; + + /** + * Failed because the operation was cancelled. + * + * @see android.telephony.SmsManager.RESULT_CANCELLED + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int SEND_STATUS_RESULT_CANCELLED = 215; + + /** + * Failed because the request is not supported. + * + * @see android.telephony.SmsManager.RESULT_REQUEST_NOT_SUPPORTED + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int SEND_STATUS_RESULT_REQUEST_NOT_SUPPORTED = 216; + + /** + * Failed sending during an emergency call. + * + * @see android.telephony.SmsManager.RESULT_SMS_BLOCKED_DURING_EMERGENCY + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int SEND_STATUS_RESULT_SMS_BLOCKED_DURING_EMERGENCY = 217; + + /** + * Failed to send an sms retry. + * + * @see android.telephony.SmsManager.RESULT_SMS_SEND_RETRY_FAILED + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int SEND_STATUS_RESULT_SMS_SEND_RETRY_FAILED = 218; + + /** + * More precise error reasons for outbound MMS send requests. These will not be retried on the + * carrier network. + * + * <p>Each code maps directly to an SmsManager code (e.g. + * SEND_STATUS_MMS_ERROR_UNABLE_CONNECT_MMS maps to SmsManager.MMS_ERROR_UNABLE_CONNECT_MMS). + */ + + /** + * Unspecific MMS error occurred during send. + * + * @see android.telephony.SmsManager.MMS_ERROR_UNSPECIFIED + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int SEND_STATUS_MMS_ERROR_UNSPECIFIED = 400; + + /** + * ApnException occurred during MMS network setup. + * + * @see android.telephony.SmsManager.MMS_ERROR_INVALID_APN + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int SEND_STATUS_MMS_ERROR_INVALID_APN = 401; + + /** + * An error occurred during the MMS connection setup. + * + * @see android.telephony.SmsManager.MMS_ERROR_UNABLE_CONNECT_MMS + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int SEND_STATUS_MMS_ERROR_UNABLE_CONNECT_MMS = 402; + + /** + * An error occurred during the HTTP client setup. + * + * @see android.telephony.SmsManager.MMS_ERROR_HTTP_FAILURE + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int SEND_STATUS_MMS_ERROR_HTTP_FAILURE = 403; + + /** + * An I/O error occurred reading the PDU. + * + * @see android.telephony.SmsManager.MMS_ERROR_IO_ERROR + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int SEND_STATUS_MMS_ERROR_IO_ERROR = 404; + + /** + * An error occurred while retrying sending the MMS. + * + * @see android.telephony.SmsManager.MMS_ERROR_RETRY + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int SEND_STATUS_MMS_ERROR_RETRY = 405; + + /** + * The carrier-dependent configuration values could not be loaded. + * + * @see android.telephony.SmsManager.MMS_ERROR_CONFIGURATION_ERROR + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int SEND_STATUS_MMS_ERROR_CONFIGURATION_ERROR = 406; + + /** + * There is neither Wi-Fi nor mobile data network. + * + * @see android.telephony.SmsManager.MMS_ERROR_NO_DATA_NETWORK + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int SEND_STATUS_MMS_ERROR_NO_DATA_NETWORK = 407; + + /** + * The subscription id for the send is invalid. + * + * @see android.telephony.SmsManager.MMS_ERROR_INVALID_SUBSCRIPTION_ID + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int SEND_STATUS_MMS_ERROR_INVALID_SUBSCRIPTION_ID = 408; + + /** + * The subscription id for the send is inactive. + * + * @see android.telephony.SmsManager.MMS_ERROR_INACTIVE_SUBSCRIPTION + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int SEND_STATUS_MMS_ERROR_INACTIVE_SUBSCRIPTION = 409; + + /** + * Data is disabled for the MMS APN. + * + * @see android.telephony.SmsManager.MMS_ERROR_DATA_DISABLED + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int SEND_STATUS_MMS_ERROR_DATA_DISABLED = 410; + + /** + * MMS is disabled by a carrier. + * + * @see android.telephony.SmsManager.MMS_ERROR_MMS_DISABLED_BY_CARRIER + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int SEND_STATUS_MMS_ERROR_MMS_DISABLED_BY_CARRIER = 411; + /** @hide */ - @IntDef(prefix = { "SEND_STATUS_" }, value = { - SEND_STATUS_OK, - SEND_STATUS_RETRY_ON_CARRIER_NETWORK, - SEND_STATUS_ERROR - }) + @IntDef( + prefix = {"SEND_STATUS_"}, + value = { + SEND_STATUS_OK, + SEND_STATUS_RETRY_ON_CARRIER_NETWORK, + SEND_STATUS_ERROR, + SEND_STATUS_RESULT_ERROR_GENERIC_FAILURE, + SEND_STATUS_RESULT_ERROR_NULL_PDU, + SEND_STATUS_RESULT_ERROR_NO_SERVICE, + SEND_STATUS_RESULT_ERROR_LIMIT_EXCEEDED, + SEND_STATUS_RESULT_ERROR_FDN_CHECK_FAILURE, + SEND_STATUS_RESULT_ERROR_SHORT_CODE_NOT_ALLOWED, + SEND_STATUS_RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED, + SEND_STATUS_RESULT_NETWORK_REJECT, + SEND_STATUS_RESULT_INVALID_ARGUMENTS, + SEND_STATUS_RESULT_INVALID_STATE, + SEND_STATUS_RESULT_INVALID_SMS_FORMAT, + SEND_STATUS_RESULT_NETWORK_ERROR, + SEND_STATUS_RESULT_ENCODING_ERROR, + SEND_STATUS_RESULT_INVALID_SMSC_ADDRESS, + SEND_STATUS_RESULT_OPERATION_NOT_ALLOWED, + SEND_STATUS_RESULT_CANCELLED, + SEND_STATUS_RESULT_REQUEST_NOT_SUPPORTED, + SEND_STATUS_RESULT_SMS_BLOCKED_DURING_EMERGENCY, + SEND_STATUS_RESULT_SMS_SEND_RETRY_FAILED, + SEND_STATUS_MMS_ERROR_UNSPECIFIED, + SEND_STATUS_MMS_ERROR_INVALID_APN, + SEND_STATUS_MMS_ERROR_UNABLE_CONNECT_MMS, + SEND_STATUS_MMS_ERROR_HTTP_FAILURE, + SEND_STATUS_MMS_ERROR_IO_ERROR, + SEND_STATUS_MMS_ERROR_RETRY, + SEND_STATUS_MMS_ERROR_CONFIGURATION_ERROR, + SEND_STATUS_MMS_ERROR_NO_DATA_NETWORK, + SEND_STATUS_MMS_ERROR_INVALID_SUBSCRIPTION_ID, + SEND_STATUS_MMS_ERROR_INACTIVE_SUBSCRIPTION, + SEND_STATUS_MMS_ERROR_DATA_DISABLED, + SEND_STATUS_MMS_ERROR_MMS_DISABLED_BY_CARRIER + }) @Retention(RetentionPolicy.SOURCE) public @interface SendResult {} @@ -121,16 +425,138 @@ public abstract class CarrierMessagingService extends Service { public static final int DOWNLOAD_STATUS_RETRY_ON_CARRIER_NETWORK = 1; /** - * MMS downloading failed. We should not retry via the carrier network. + * MMS downloading failed due to an unspecified issue. Downloading will not be retried via the + * carrier network. + * + * <p>Maps to SmsManager.MMR_ERROR_UNSPECIFIED. */ public static final int DOWNLOAD_STATUS_ERROR = 2; + /** + * More precise error reasons for inbound MMS download requests. These will not be retried on + * the carrier network. + * + * <p>Each code maps directly to an SmsManager code (e.g. + * DOWNLOAD_STATUS_MMS_ERROR_UNABLE_CONNECT_MMS maps to + * SmsManager.MMS_ERROR_UNABLE_CONNECT_MMS). + */ + + /** + * Unspecific MMS error occurred during download. + * + * @see android.telephony.SmsManager.MMS_ERROR_UNSPECIFIED + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int DOWNLOAD_STATUS_MMS_ERROR_UNSPECIFIED = 600; + + /** + * ApnException occurred during MMS network setup. + * + * @see android.telephony.SmsManager.MMS_ERROR_INVALID_APN + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int DOWNLOAD_STATUS_MMS_ERROR_INVALID_APN = 601; + + /** + * An error occurred during the MMS connection setup. + * + * @see android.telephony.SmsManager.MMS_ERROR_UNABLE_CONNECT_MMS + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int DOWNLOAD_STATUS_MMS_ERROR_UNABLE_CONNECT_MMS = 602; + + /** + * An error occurred during the HTTP client setup. + * + * @see android.telephony.SmsManager.MMS_ERROR_HTTP_FAILURE + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int DOWNLOAD_STATUS_MMS_ERROR_HTTP_FAILURE = 603; + + /** + * An I/O error occurred reading the PDU. + * + * @see android.telephony.SmsManager.MMS_ERROR_IO_ERROR + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int DOWNLOAD_STATUS_MMS_ERROR_IO_ERROR = 604; + + /** + * An error occurred while retrying downloading the MMS. + * + * @see android.telephony.SmsManager.MMS_ERROR_RETRY + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int DOWNLOAD_STATUS_MMS_ERROR_RETRY = 605; + + /** + * The carrier-dependent configuration values could not be loaded. + * + * @see android.telephony.SmsManager.MMS_ERROR_CONFIGURATION_ERROR + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int DOWNLOAD_STATUS_MMS_ERROR_CONFIGURATION_ERROR = 606; + + /** + * There is neither Wi-Fi nor mobile data network. + * + * @see android.telephony.SmsManager.MMS_ERROR_NO_DATA_NETWORK + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int DOWNLOAD_STATUS_MMS_ERROR_NO_DATA_NETWORK = 607; + + /** + * The subscription id for the download is invalid. + * + * @see android.telephony.SmsManager.MMS_ERROR_INVALID_SUBSCRIPTION_ID + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int DOWNLOAD_STATUS_MMS_ERROR_INVALID_SUBSCRIPTION_ID = 608; + + /** + * The subscription id for the download is inactive. + * + * @see android.telephony.SmsManager.MMS_ERROR_INACTIVE_SUBSCRIPTION + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int DOWNLOAD_STATUS_MMS_ERROR_INACTIVE_SUBSCRIPTION = 609; + + /** + * Data is disabled for the MMS APN. + * + * @see android.telephony.SmsManager.MMS_ERROR_DATA_DISABLED + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int DOWNLOAD_STATUS_MMS_ERROR_DATA_DISABLED = 610; + + /** + * MMS is disabled by a carrier. + * + * @see android.telephony.SmsManager.MMS_ERROR_MMS_DISABLED_BY_CARRIER + */ + @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE) + public static final int DOWNLOAD_STATUS_MMS_ERROR_MMS_DISABLED_BY_CARRIER = 611; + /** @hide */ - @IntDef(prefix = { "DOWNLOAD_STATUS_" }, value = { - DOWNLOAD_STATUS_OK, - DOWNLOAD_STATUS_RETRY_ON_CARRIER_NETWORK, - DOWNLOAD_STATUS_ERROR - }) + @IntDef( + prefix = {"DOWNLOAD_STATUS_"}, + value = { + DOWNLOAD_STATUS_OK, + DOWNLOAD_STATUS_RETRY_ON_CARRIER_NETWORK, + DOWNLOAD_STATUS_ERROR, + DOWNLOAD_STATUS_MMS_ERROR_UNSPECIFIED, + DOWNLOAD_STATUS_MMS_ERROR_INVALID_APN, + DOWNLOAD_STATUS_MMS_ERROR_UNABLE_CONNECT_MMS, + DOWNLOAD_STATUS_MMS_ERROR_HTTP_FAILURE, + DOWNLOAD_STATUS_MMS_ERROR_IO_ERROR, + DOWNLOAD_STATUS_MMS_ERROR_RETRY, + DOWNLOAD_STATUS_MMS_ERROR_CONFIGURATION_ERROR, + DOWNLOAD_STATUS_MMS_ERROR_NO_DATA_NETWORK, + DOWNLOAD_STATUS_MMS_ERROR_INVALID_SUBSCRIPTION_ID, + DOWNLOAD_STATUS_MMS_ERROR_INACTIVE_SUBSCRIPTION, + DOWNLOAD_STATUS_MMS_ERROR_DATA_DISABLED, + DOWNLOAD_STATUS_MMS_ERROR_MMS_DISABLED_BY_CARRIER + }) @Retention(RetentionPolicy.SOURCE) public @interface DownloadResult {} diff --git a/core/jni/android_os_PerformanceHintManager.cpp b/core/jni/android_os_PerformanceHintManager.cpp index aebe7ea7ee61..0f78c9e93a00 100644 --- a/core/jni/android_os_PerformanceHintManager.cpp +++ b/core/jni/android_os_PerformanceHintManager.cpp @@ -88,9 +88,10 @@ void ensureAPerformanceHintBindingInitialized() { "Failed to find required symbol " "APerformanceHint_getPreferredUpdateRateNanos!"); - gAPH_createSessionFn = (APH_createSession)dlsym(handle_, "APerformanceHint_createSession"); + gAPH_createSessionFn = + (APH_createSession)dlsym(handle_, "APerformanceHint_createSessionFromJava"); LOG_ALWAYS_FATAL_IF(gAPH_createSessionFn == nullptr, - "Failed to find required symbol APerformanceHint_createSession!"); + "Failed to find required symbol APerformanceHint_createSessionFromJava!"); gAPH_updateTargetWorkDurationFn = (APH_updateTargetWorkDuration)dlsym(handle_, @@ -106,9 +107,9 @@ void ensureAPerformanceHintBindingInitialized() { "Failed to find required symbol " "APerformanceHint_reportActualWorkDuration!"); - gAPH_closeSessionFn = (APH_closeSession)dlsym(handle_, "APerformanceHint_closeSession"); + gAPH_closeSessionFn = (APH_closeSession)dlsym(handle_, "APerformanceHint_closeSessionFromJava"); LOG_ALWAYS_FATAL_IF(gAPH_closeSessionFn == nullptr, - "Failed to find required symbol APerformanceHint_closeSession!"); + "Failed to find required symbol APerformanceHint_closeSessionFromJava!"); gAPH_sendHintFn = (APH_sendHint)dlsym(handle_, "APerformanceHint_sendHint"); LOG_ALWAYS_FATAL_IF(gAPH_sendHintFn == nullptr, diff --git a/core/res/Android.bp b/core/res/Android.bp index 26e63bc092fa..8042b30df4dc 100644 --- a/core/res/Android.bp +++ b/core/res/Android.bp @@ -171,6 +171,7 @@ android_app { "android.os.vibrator.flags-aconfig", "android.media.tv.flags-aconfig", "android.security.flags-aconfig", + "device_policy_aconfig_flags", "com.android.hardware.input.input-aconfig", "aconfig_trade_in_mode_flags", "art-aconfig-flags", diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 8cc7b0b8f942..9b2d3384d99c 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2649,6 +2649,22 @@ android:label="@string/permlab_getAccounts" /> <uses-permission android:name="android.permission.GET_ACCOUNTS"/> + <!-- @SystemApi Allows access to remove an account. + @FlaggedApi(android.app.admin.flags.Flags.FLAG_SPLIT_CREATE_MANAGED_PROFILE_ENABLED) + <p>Not for use by third-party applications. + @hide --> + <permission android:name="android.permission.REMOVE_ACCOUNTS" + android:protectionLevel="signature|role" + android:featureFlag="android.app.admin.flags.split_create_managed_profile_enabled" /> + + <!-- @SystemApi Allows access to copy an account to another user. + @FlaggedApi(android.app.admin.flags.Flags.FLAG_SPLIT_CREATE_MANAGED_PROFILE_ENABLED) + <p>Not for use by third-party applications. + @hide --> + <permission android:name="android.permission.COPY_ACCOUNTS" + android:protectionLevel="signature|role" + android:featureFlag="android.app.admin.flags.split_create_managed_profile_enabled" /> + <!-- Allows applications to call into AccountAuthenticators. <p>Not for use by third-party applications. --> <permission android:name="android.permission.ACCOUNT_MANAGER" diff --git a/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java b/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java index 75aca1b8820c..7ce2ed823540 100644 --- a/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java +++ b/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java @@ -23,10 +23,11 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import android.platform.test.ravenwood.RavenwoodConfig; +import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.filters.SmallTest; +import org.junit.Rule; import org.junit.Test; import java.util.Objects; @@ -39,8 +40,8 @@ public class SystemPropertiesTest { private static final String PERSIST_KEY = "persist.sys.testkey"; private static final String NONEXIST_KEY = "doesnotexist_2341431"; - @RavenwoodConfig.Config - public static final RavenwoodConfig mRavenwood = new RavenwoodConfig.Builder() + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder() .setSystemPropertyMutable(KEY, null) .setSystemPropertyMutable(UNSET_KEY, null) .setSystemPropertyMutable(PERSIST_KEY, null) diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml index 5e41865cd31e..375968ab0ad2 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml @@ -113,7 +113,7 @@ style="?android:attr/buttonBarButtonStyle" android:layout_width="41dp" android:layout_height="@dimen/desktop_mode_maximize_menu_button_height" - android:layout_marginRight="4dp" + android:layout_marginEnd="4dp" android:background="@drawable/desktop_mode_maximize_menu_button_background" android:importantForAccessibility="yes" android:contentDescription="@string/desktop_mode_maximize_menu_snap_left_button_text" diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 7078d66d265c..21ec84d9bc0a 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -504,17 +504,15 @@ <dimen name="desktop_mode_maximize_menu_buttons_fill_radius">4dp</dimen> <!-- The padding between the outline and fill of the maximize menu snap and maximize buttons. --> <dimen name="desktop_mode_maximize_menu_snap_and_maximize_buttons_fill_padding">4dp</dimen> - <!-- The padding between the outline and fill of the maximize menu snap and maximize buttons. --> - <dimen name="desktop_mode_maximize_menu_snap_and_maximize_buttons_fill_padding_bottom">8dp</dimen> <!-- The vertical padding between the outline and fill of the maximize menu restore button. --> <dimen name="desktop_mode_maximize_menu_restore_button_fill_vertical_padding">13dp</dimen> <!-- The horizontal padding between the outline and fill of the maximize menu restore button. --> - <dimen name="desktop_mode_maximize_menu_restore_button_fill_horizontal_padding">21dp</dimen> + <dimen name="desktop_mode_maximize_menu_restore_button_fill_horizontal_padding">15dp</dimen> <!-- The padding between the outline and fill of the maximize menu immersive button. --> - <dimen name="desktop_mode_maximize_menu_immersive_button_fill_padding">4dp</dimen> + <dimen name="desktop_mode_maximize_menu_immersive_button_fill_padding">0dp</dimen> <!-- The corner radius of the maximize menu. --> - <dimen name="desktop_mode_maximize_menu_corner_radius">8dp</dimen> + <dimen name="desktop_mode_maximize_menu_corner_radius">16dp</dimen> <!-- The radius of the Maximize menu shadow. --> <dimen name="desktop_mode_maximize_menu_shadow_radius">8dp</dimen> diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml index 012579a6d40c..468c345259d0 100644 --- a/libs/WindowManager/Shell/res/values/strings.xml +++ b/libs/WindowManager/Shell/res/values/strings.xml @@ -318,7 +318,7 @@ <!-- Maximize menu maximize button string. --> <string name="desktop_mode_maximize_menu_maximize_text">Maximize Screen</string> <!-- Maximize menu snap buttons string. --> - <string name="desktop_mode_maximize_menu_snap_text">Snap Screen</string> + <string name="desktop_mode_maximize_menu_snap_text">Resize</string> <!-- Snap resizing non-resizable string. --> <string name="desktop_mode_non_resizable_snap_text">App can\'t be moved here</string> <!-- Accessibility text for the Maximize Menu's immersive button [CHAR LIMIT=NONE] --> 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 603a9ec6bedb..b82496e45415 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 @@ -1327,7 +1327,11 @@ public class BubbleController implements ConfigurationChangeListener, /** Promote the provided bubble from the overflow view. */ public void promoteBubbleFromOverflow(Bubble bubble) { - mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BACK_TO_STACK); + if (isShowingAsBubbleBar()) { + mLogger.log(bubble, BubbleLogger.Event.BUBBLE_BAR_OVERFLOW_REMOVE_BACK_TO_BAR); + } else { + mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BACK_TO_STACK); + } ProtoLog.d(WM_SHELL_BUBBLES, "promoteBubbleFromOverflow=%s", bubble.getKey()); bubble.setInflateSynchronously(mInflateSynchronously); bubble.setShouldAutoExpand(true); @@ -1350,11 +1354,7 @@ public class BubbleController implements ConfigurationChangeListener, if (BubbleOverflow.KEY.equals(key)) { mBubbleData.setSelectedBubbleFromLauncher(mBubbleData.getOverflow()); mLayerView.showExpandedView(mBubbleData.getOverflow()); - if (wasExpanded) { - mLogger.log(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_SWITCHED); - } else { - mLogger.log(BubbleLogger.Event.BUBBLE_BAR_EXPANDED); - } + mLogger.log(BubbleLogger.Event.BUBBLE_BAR_OVERFLOW_SELECTED); return; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index 4de9dfa54c5d..294569190f68 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -759,7 +759,9 @@ public class BubbleData { if (b != null) { b.stopInflation(); } - mLogger.logOverflowRemove(b, reason); + if (!mPositioner.isShowingInBubbleBar()) { + mLogger.logStackOverflowRemove(b, reason); + } mOverflowBubbles.remove(b); mStateChange.bubbleRemoved(b, reason); mStateChange.removedOverflowBubble = b; @@ -802,6 +804,27 @@ public class BubbleData { setNewSelectedIndex(indexToRemove); } maybeSendDeleteIntent(reason, bubbleToRemove); + + if (mPositioner.isShowingInBubbleBar()) { + logBubbleBarBubbleRemoved(bubbleToRemove, reason); + } + } + + private void logBubbleBarBubbleRemoved(Bubble bubble, @DismissReason int reason) { + switch (reason) { + case Bubbles.DISMISS_NOTIF_CANCEL: + mLogger.log(bubble, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_REMOVED_CANCELED); + break; + case Bubbles.DISMISS_TASK_FINISHED: + mLogger.log(bubble, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_ACTIVITY_FINISH); + break; + case Bubbles.DISMISS_BLOCKED: + case Bubbles.DISMISS_NO_LONGER_BUBBLE: + mLogger.log(bubble, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_REMOVED_BLOCKED); + break; + default: + // skip logging other events + } } private void setNewSelectedIndex(int indexOfSelected) { @@ -862,7 +885,7 @@ public class BubbleData { return; } ProtoLog.d(WM_SHELL_BUBBLES, "overflowBubble=%s", bubble.getKey()); - mLogger.logOverflowAdd(bubble, reason); + mLogger.logOverflowAdd(bubble, mPositioner.isShowingInBubbleBar(), reason); if (mOverflowBubbles.isEmpty()) { mStateChange.showOverflowChanged = true; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java index 36630733e1da..347df330c4b3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java @@ -182,10 +182,12 @@ public class BubbleLogger { } /** + * Log when a bubble is removed from overflow in stack view + * * @param b Bubble removed from overflow * @param r Reason that bubble was removed */ - public void logOverflowRemove(Bubble b, @Bubbles.DismissReason int r) { + public void logStackOverflowRemove(Bubble b, @Bubbles.DismissReason int r) { if (r == Bubbles.DISMISS_NOTIF_CANCEL) { log(b, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_CANCEL); } else if (r == Bubbles.DISMISS_GROUP_CANCELLED) { @@ -201,13 +203,19 @@ public class BubbleLogger { * @param b Bubble added to overflow * @param r Reason that bubble was added to overflow */ - public void logOverflowAdd(Bubble b, @Bubbles.DismissReason int r) { - if (r == Bubbles.DISMISS_AGED) { - log(b, Event.BUBBLE_OVERFLOW_ADD_AGED); - } else if (r == Bubbles.DISMISS_USER_GESTURE) { - log(b, Event.BUBBLE_OVERFLOW_ADD_USER_GESTURE); - } else if (r == Bubbles.DISMISS_RELOAD_FROM_DISK) { - log(b, Event.BUBBLE_OVERFLOW_RECOVER); + public void logOverflowAdd(Bubble b, boolean bubbleBar, @Bubbles.DismissReason int r) { + if (bubbleBar) { + if (r == Bubbles.DISMISS_AGED) { + log(b, Event.BUBBLE_BAR_OVERFLOW_ADD_AGED); + } + } else { + if (r == Bubbles.DISMISS_AGED) { + log(b, Event.BUBBLE_OVERFLOW_ADD_AGED); + } else if (r == Bubbles.DISMISS_USER_GESTURE) { + log(b, Event.BUBBLE_OVERFLOW_ADD_USER_GESTURE); + } else if (r == Bubbles.DISMISS_RELOAD_FROM_DISK) { + log(b, Event.BUBBLE_OVERFLOW_RECOVER); + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java index c386c9398624..068b2d246500 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java @@ -830,6 +830,13 @@ public class BubblePositioner { mShowingInBubbleBar = showingInBubbleBar; } + /** + * Whether bubbles ar showing in the bubble bar from launcher. + */ + boolean isShowingInBubbleBar() { + return mShowingInBubbleBar; + } + public void setBubbleBarLocation(BubbleBarLocation location) { mBubbleBarLocation = location; } 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 6fc6eb783a17..a0bdd9fad510 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 @@ -1668,6 +1668,13 @@ class DesktopTasksController( transition, wct, task.displayId ) } + } else if (taskRepository.isActiveTask(task.taskId)) { + // If a freeform task receives a request for a fullscreen launch, apply the same + // changes we do for similar transitions. The task not having WINDOWING_MODE_UNDEFINED + // set when needed can interfere with future split / multi-instance transitions. + return WindowContainerTransaction().also { wct -> + addMoveToFullscreenChanges(wct, task) + } } return null } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java index 319bfac734ba..e4f83333edbf 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java @@ -173,6 +173,10 @@ public class KeyguardTransitionHandler return mKeyguardShowing; } + public boolean isKeyguardAnimating() { + return !mStartedTransitions.isEmpty(); + } + @Override public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) { mDreamToken = taskInfo.getActivityType() == ACTIVITY_TYPE_DREAM ? taskInfo.token : null; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index 17483dd68632..92d1f9c26bbc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -370,7 +370,8 @@ public class DefaultMixedHandler implements MixedTransitionHandler, if (mRecentsHandler != null) { if (mSplitHandler.isSplitScreenVisible()) { return this::setRecentsTransitionDuringSplit; - } else if (mKeyguardHandler.isKeyguardShowing()) { + } else if (mKeyguardHandler.isKeyguardShowing() + && !mKeyguardHandler.isKeyguardAnimating()) { return this::setRecentsTransitionDuringKeyguard; } else if (mDesktopTasksController != null // Check on the default display. Recents/gesture nav is only available there diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java index fd4d568326d0..8cdbe26a2c76 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java @@ -117,6 +117,11 @@ class RecentsMixedTransition extends DefaultMixedHandler.MixedTransition { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Mixed transition for Recents during" + " Keyguard #%d", info.getDebugId()); + if (!mKeyguardHandler.isKeyguardShowing() || mKeyguardHandler.isKeyguardAnimating()) { + ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Cancel mixed transition because " + + "keyguard state was changed #%d", info.getDebugId()); + return false; + } if (mInfo == null) { mInfo = info; mFinishT = finishTransaction; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt index 4bb1e7b6cc05..11a7cf8da8d8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt @@ -70,6 +70,7 @@ import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHo import com.android.wm.shell.windowdecor.common.DecorThemeUtil import com.android.wm.shell.windowdecor.common.OPACITY_12 import com.android.wm.shell.windowdecor.common.OPACITY_40 +import com.android.wm.shell.windowdecor.common.OPACITY_60 import com.android.wm.shell.windowdecor.common.withAlpha import java.util.function.Supplier @@ -310,8 +311,6 @@ class MaximizeMenu( .desktop_mode_maximize_menu_immersive_button_fill_padding) private val maximizeFillPaddingDefault = context.resources.getDimensionPixelSize(R.dimen .desktop_mode_maximize_menu_snap_and_maximize_buttons_fill_padding) - private val maximizeFillPaddingBottom = context.resources.getDimensionPixelSize(R.dimen - .desktop_mode_maximize_menu_snap_and_maximize_buttons_fill_padding_bottom) private val maximizeRestoreFillPaddingVertical = context.resources.getDimensionPixelSize( R.dimen.desktop_mode_maximize_menu_restore_button_fill_vertical_padding) private val maximizeRestoreFillPaddingHorizontal = context.resources.getDimensionPixelSize( @@ -320,7 +319,7 @@ class MaximizeMenu( maximizeFillPaddingDefault, maximizeFillPaddingDefault, maximizeFillPaddingDefault, - maximizeFillPaddingBottom + maximizeFillPaddingDefault ) private val maximizeRestoreFillPaddingRect = Rect( maximizeRestoreFillPaddingHorizontal, @@ -684,7 +683,7 @@ class MaximizeMenu( inactiveSnapSideColor = colorScheme.outlineVariant.toArgb(), semiActiveSnapSideColor = colorScheme.primary.toArgb().withAlpha(OPACITY_40), activeSnapSideColor = colorScheme.primary.toArgb(), - inactiveStrokeColor = colorScheme.outlineVariant.toArgb(), + inactiveStrokeColor = colorScheme.outlineVariant.toArgb().withAlpha(OPACITY_60), activeStrokeColor = colorScheme.primary.toArgb(), inactiveBackgroundColor = menuBackgroundColor, activeBackgroundColor = colorScheme.primary.toArgb().withAlpha(OPACITY_12) @@ -753,7 +752,8 @@ class MaximizeMenu( val activeStrokeAndFill = colorScheme.primary.toArgb() val activeBackground = colorScheme.primary.toArgb().withAlpha(OPACITY_12) val activeDrawable = createMaximizeOrImmersiveButtonDrawable( - strokeAndFillColor = activeStrokeAndFill, + strokeColor = activeStrokeAndFill, + fillColor = activeStrokeAndFill, backgroundColor = activeBackground, // Add a mask with the menu background's color because the active background color is // semi transparent, otherwise the transparency will reveal the stroke/fill color @@ -770,7 +770,8 @@ class MaximizeMenu( addState( StateSet.WILD_CARD, createMaximizeOrImmersiveButtonDrawable( - strokeAndFillColor = colorScheme.outlineVariant.toArgb(), + strokeColor = colorScheme.outlineVariant.toArgb().withAlpha(OPACITY_60), + fillColor = colorScheme.outlineVariant.toArgb(), backgroundColor = colorScheme.surfaceContainerLow.toArgb(), backgroundMask = null, // not needed because the bg color is fully opaque fillPadding = fillPadding, @@ -780,7 +781,8 @@ class MaximizeMenu( } private fun createMaximizeOrImmersiveButtonDrawable( - @ColorInt strokeAndFillColor: Int, + @ColorInt strokeColor: Int, + @ColorInt fillColor: Int, @ColorInt backgroundColor: Int, @ColorInt backgroundMask: Int?, fillPadding: Rect, @@ -794,7 +796,7 @@ class MaximizeMenu( null /* inset */, null /* innerRadii */ ) - paint.color = strokeAndFillColor + paint.color = strokeColor paint.style = Paint.Style.FILL }) // Second layer, a mask for the next (background) layer if needed because of @@ -829,7 +831,7 @@ class MaximizeMenu( null /* inset */, null /* innerRadii */ ) - paint.color = strokeAndFillColor + paint.color = fillColor paint.style = Paint.Style.FILL }) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ThemeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ThemeUtils.kt index f7cfbfa88485..c5057aa3cc18 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ThemeUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ThemeUtils.kt @@ -52,6 +52,7 @@ const val OPACITY_12 = 31 const val OPACITY_15 = 38 const val OPACITY_40 = 102 const val OPACITY_55 = 140 +const val OPACITY_60 = 153 const val OPACITY_65 = 166 /** diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS index 65e50f86e8fe..19829e7e5677 100644 --- a/libs/WindowManager/Shell/tests/OWNERS +++ b/libs/WindowManager/Shell/tests/OWNERS @@ -6,6 +6,7 @@ pablogamito@google.com lbill@google.com madym@google.com hwwang@google.com +gabiyev@google.com chenghsiuchang@google.com atsjenk@google.com jorgegil@google.com diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/Android.bp b/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/Android.bp deleted file mode 100644 index 85e6a8d1d865..000000000000 --- a/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/Android.bp +++ /dev/null @@ -1,38 +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 { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -android_test { - name: "WMShellFlickerTestsMediaProjection", - defaults: ["WMShellFlickerTestsDefault"], - manifest: "AndroidManifest.xml", - test_config_template: "AndroidTestTemplate.xml", - srcs: ["src/**/*.kt"], - static_libs: [ - "WMShellFlickerTestsBase", - "WMShellScenariosMediaProjection", - "WMShellTestUtils", - ], - data: ["trace_config/*"], -} diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/AndroidManifest.xml b/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/AndroidManifest.xml deleted file mode 100644 index 74b0daf3a2aa..000000000000 --- a/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/AndroidManifest.xml +++ /dev/null @@ -1,85 +0,0 @@ -<!-- - ~ Copyright (C) 2023 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - package="com.android.wm.shell.flicker"> - - <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/> - <!-- Read and write traces from external storage --> - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> - <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> - <!-- Allow the test to write directly to /sdcard/ --> - <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> - <!-- Write secure settings --> - <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> - <!-- Capture screen contents --> - <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" /> - <!-- Enable / Disable tracing !--> - <uses-permission android:name="android.permission.DUMP" /> - <!-- Run layers trace --> - <uses-permission android:name="android.permission.HARDWARE_TEST"/> - <!-- Capture screen recording --> - <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"/> - <!-- Workaround grant runtime permission exception from b/152733071 --> - <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/> - <uses-permission android:name="android.permission.READ_LOGS"/> - <!-- Force-stop test apps --> - <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/> - <!-- Control test app's media session --> - <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/> - <!-- ATM.removeRootTasksWithActivityTypes() --> - <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" /> - <!-- Enable bubble notification--> - <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" /> - <!-- Allow the test to connect to perfetto trace processor --> - <uses-permission android:name="android.permission.INTERNET"/> - <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" /> - <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" /> - - <!-- Allow the test to write directly to /sdcard/ and connect to trace processor --> - <application android:requestLegacyExternalStorage="true" - android:networkSecurityConfig="@xml/network_security_config" - android:largeHeap="true"> - <uses-library android:name="android.test.runner"/> - - <service android:name=".NotificationListener" - android:exported="true" - android:label="WMShellTestsNotificationListenerService" - android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"> - <intent-filter> - <action android:name="android.service.notification.NotificationListenerService" /> - </intent-filter> - </service> - - <service android:name="com.android.wm.shell.flicker.utils.MediaProjectionService" - android:foregroundServiceType="mediaProjection" - android:label="WMShellTestsMediaProjectionService" - android:enabled="true"> - </service> - - <!-- (b/197936012) Remove startup provider due to test timeout issue --> - <provider - android:name="androidx.startup.InitializationProvider" - android:authorities="${applicationId}.androidx-startup" - tools:node="remove" /> - </application> - - <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" - android:targetPackage="com.android.wm.shell.flicker" - android:label="WindowManager Shell Flicker Tests"> - </instrumentation> -</manifest> diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/AndroidTestTemplate.xml deleted file mode 100644 index 40dbbac32c7f..000000000000 --- a/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/AndroidTestTemplate.xml +++ /dev/null @@ -1,97 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2023 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<configuration description="Runs WindowManager Shell Flicker Tests {MODULE}"> - <option name="test-tag" value="FlickerTests"/> - <!-- Needed for storing the perfetto trace files in the sdcard/test_results--> - <option name="isolated-storage" value="false"/> - - <target_preparer class="com.android.tradefed.targetprep.DeviceSetup"> - <!-- disable DeprecatedTargetSdk warning --> - <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/> - <!-- keeps the screen on during tests --> - <option name="screen-always-on" value="on"/> - <!-- prevents the phone from restarting --> - <option name="force-skip-system-props" value="true"/> - <!-- set WM tracing verbose level to all --> - <option name="run-command" value="cmd window tracing level all"/> - <!-- set WM tracing to frame (avoid incomplete states) --> - <option name="run-command" value="cmd window tracing frame"/> - <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests --> - <option name="run-command" value="pm disable com.google.android.internal.betterbug"/> - <!-- ensure lock screen mode is swipe --> - <option name="run-command" value="locksettings set-disabled false"/> - <!-- restart launcher to activate TAPL --> - <option name="run-command" - value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher"/> - <!-- Increase trace size: 20mb for WM and 80mb for SF --> - <option name="run-command" value="cmd window tracing size 20480"/> - <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920"/> - </target_preparer> - <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> - <option name="test-user-token" value="%TEST_USER%"/> - <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> - <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> - <option name="run-command" value="settings put system show_touches 1"/> - <option name="run-command" value="settings put system pointer_location 1"/> - <option name="teardown-command" - value="settings delete secure show_ime_with_hard_keyboard"/> - <option name="teardown-command" value="settings delete system show_touches"/> - <option name="teardown-command" value="settings delete system pointer_location"/> - <option name="teardown-command" - value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/> - </target_preparer> - <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> - <option name="cleanup-apks" value="true"/> - <option name="test-file-name" value="{MODULE}.apk"/> - <option name="test-file-name" value="FlickerTestApp.apk"/> - </target_preparer> - - <!-- Needed for pushing the trace config file --> - <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/> - <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> - <option name="push-file" - key="trace_config.textproto" - value="/data/misc/perfetto-traces/trace_config.textproto" - /> - <!--Install the content provider automatically when we push some file in sdcard folder.--> - <!--Needed to avoid the installation during the test suite.--> - <option name="push-file" key="trace_config.textproto" value="/sdcard/sample.textproto"/> - </target_preparer> - <test class="com.android.tradefed.testtype.AndroidJUnitTest"> - <option name="package" value="{PACKAGE}"/> - <option name="shell-timeout" value="6600s"/> - <option name="test-timeout" value="6000s"/> - <option name="hidden-api-checks" value="false"/> - <option name="device-listeners" value="android.device.collectors.PerfettoListener"/> - <!-- PerfettoListener related arguments --> - <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/> - <option name="instrumentation-arg" - key="perfetto_config_file" - value="trace_config.textproto" - /> - <option name="instrumentation-arg" key="per_run" value="true"/> - <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/> - </test> - <!-- Needed for pulling the collected trace config on to the host --> - <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> - <option name="pull-pattern-keys" value="perfetto_file_path"/> - <option name="directory-keys" - value="/data/user/0/com.android.wm.shell.flicker/files"/> - <option name="collect-on-run-ended-only" value="true"/> - <option name="clean-up" value="true"/> - </metrics_collector> -</configuration> diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/trace_config/trace_config.textproto deleted file mode 100644 index 9f2e49755fec..000000000000 --- a/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/trace_config/trace_config.textproto +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright (C) 2023 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# proto-message: TraceConfig - -# Enable periodic flushing of the trace buffer into the output file. -write_into_file: true - -# Writes the userspace buffer into the file every 1s. -file_write_period_ms: 2500 - -# See b/126487238 - we need to guarantee ordering of events. -flush_period_ms: 30000 - -# The trace buffers needs to be big enough to hold |file_write_period_ms| of -# trace data. The trace buffer sizing depends on the number of trace categories -# enabled and the device activity. - -# RSS events -buffers: { - size_kb: 63488 - fill_policy: RING_BUFFER -} - -data_sources { - config { - name: "linux.process_stats" - target_buffer: 0 - # polled per-process memory counters and process/thread names. - # If you don't want the polled counters, remove the "process_stats_config" - # section, but keep the data source itself as it still provides on-demand - # thread/process naming for ftrace data below. - process_stats_config { - scan_all_processes_on_start: true - } - } -} - -data_sources: { - config { - name: "linux.ftrace" - ftrace_config { - ftrace_events: "ftrace/print" - ftrace_events: "task/task_newtask" - ftrace_events: "task/task_rename" - atrace_categories: "ss" - atrace_categories: "wm" - atrace_categories: "am" - atrace_categories: "aidl" - atrace_categories: "input" - atrace_categories: "binder_driver" - atrace_categories: "sched_process_exit" - atrace_apps: "com.android.server.wm.flicker.testapp" - atrace_apps: "com.android.systemui" - atrace_apps: "com.android.wm.shell.flicker.service" - atrace_apps: "com.google.android.apps.nexuslauncher" - } - } -} - diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartAppMediaProjectionFromSplitScreenTest.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartAppMediaProjectionFromSplitScreenTest.kt new file mode 100644 index 000000000000..2b9772d9cbdd --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartAppMediaProjectionFromSplitScreenTest.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.StartAppMediaProjectionFromSplitScreen +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [StartAppMediaProjectionFromSplitScreen]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class StartAppMediaProjectionFromSplitScreenTest() : StartAppMediaProjectionFromSplitScreen()
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartAppMediaProjectionInSplitScreenTest.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartAppMediaProjectionInSplitScreenTest.kt new file mode 100644 index 000000000000..e92297b48166 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartAppMediaProjectionInSplitScreenTest.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.StartAppMediaProjectionInSplitScreen +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [StartAppMediaProjectionInSplitScreen]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class StartAppMediaProjectionInSplitScreenTest() : StartAppMediaProjectionInSplitScreen()
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartAppMediaProjectionTest.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartAppMediaProjectionTest.kt new file mode 100644 index 000000000000..3f8107592667 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartAppMediaProjectionTest.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.StartAppMediaProjection +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [StartAppMediaProjection]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class StartAppMediaProjectionTest() : StartAppMediaProjection()
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartAppMediaProjectionWithExtraIntentTest.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartAppMediaProjectionWithExtraIntentTest.kt new file mode 100644 index 000000000000..1975cc7f86d2 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartAppMediaProjectionWithExtraIntentTest.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.StartAppMediaProjectionWithExtraIntent +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [StartAppMediaProjectionWithExtraIntent]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class StartAppMediaProjectionWithExtraIntentTest : StartAppMediaProjectionWithExtraIntent()
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartRecentAppMediaProjectionFromSplitScreenTest.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartRecentAppMediaProjectionFromSplitScreenTest.kt new file mode 100644 index 000000000000..943033c1819c --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartRecentAppMediaProjectionFromSplitScreenTest.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.StartRecentAppMediaProjectionFromSplitScreen +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [StartRecentAppMediaProjectionFromSplitScreen]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class StartRecentAppMediaProjectionFromSplitScreenTest() : StartRecentAppMediaProjectionFromSplitScreen()
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartRecentAppMediaProjectionInSplitScreenTest.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartRecentAppMediaProjectionInSplitScreenTest.kt new file mode 100644 index 000000000000..6facfd5b0063 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartRecentAppMediaProjectionInSplitScreenTest.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.StartRecentAppMediaProjectionInSplitScreen +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [StartRecentAppMediaProjectionInSplitScreen]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class StartRecentAppMediaProjectionInSplitScreenTest() : StartRecentAppMediaProjectionInSplitScreen()
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartRecentAppMediaProjectionTest.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartRecentAppMediaProjectionTest.kt new file mode 100644 index 000000000000..bab09052715a --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartRecentAppMediaProjectionTest.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.StartRecentAppMediaProjection +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [StartRecentAppMediaProjection]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class StartRecentAppMediaProjectionTest() : StartRecentAppMediaProjection()
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjection.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjection.kt new file mode 100644 index 000000000000..fe2c57801f72 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjection.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.scenarios + +import android.app.Instrumentation +import android.platform.test.annotations.Postsubmit +import android.tools.NavBar +import android.tools.Rotation +import android.tools.device.apphelpers.CalculatorAppHelper +import android.tools.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper +import com.android.wm.shell.Utils +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/** + * Test scenario which requests an a single-app MediaProjection session. + * + * This is for testing that the requested app is opened as expected upon selecting it from the app + * selector, so capture can proceed as expected. + */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +open class StartAppMediaProjection { + + val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + val tapl = LauncherInstrumentation() + val wmHelper = WindowManagerStateHelper(instrumentation) + val device = UiDevice.getInstance(instrumentation) + + private val initialRotation = Rotation.ROTATION_0 + private val targetApp = CalculatorAppHelper(instrumentation) + private val testApp = StartMediaProjectionAppHelper(instrumentation) + + @Rule + @JvmField + val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, initialRotation) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(initialRotation.value) + testApp.launchViaIntent(wmHelper) + } + + @Test + open fun startMediaProjection() { + testApp.startSingleAppMediaProjection(wmHelper, targetApp) + } + + @After + fun teardown() { + testApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionFromSplitScreen.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionFromSplitScreen.kt new file mode 100644 index 000000000000..3beece8c38b8 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionFromSplitScreen.kt @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.scenarios + +import android.app.Instrumentation +import android.platform.test.annotations.Postsubmit +import android.tools.NavBar +import android.tools.Rotation +import android.tools.device.apphelpers.CalculatorAppHelper +import android.tools.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper +import com.android.wm.shell.Utils +import com.android.wm.shell.flicker.utils.SplitScreenUtils +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/** + * Test scenario which requests an a single-app MediaProjection session, while the HOST app is in + * split screen + * + * This is for testing that the requested app is opened as expected upon selecting it from the app + * selector, so capture can proceed as expected. + */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +open class StartAppMediaProjectionFromSplitScreen { + + val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + val tapl = LauncherInstrumentation() + val wmHelper = WindowManagerStateHelper(instrumentation) + val device = UiDevice.getInstance(instrumentation) + + private val initialRotation = Rotation.ROTATION_0 + private val targetApp = CalculatorAppHelper(instrumentation) + private val simpleApp = SimpleAppHelper(instrumentation) + private val testApp = StartMediaProjectionAppHelper(instrumentation) + + @Rule + @JvmField + val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, initialRotation) + + @Before + fun setup() { + tapl.workspace.switchToOverview().dismissAllTasks() + + tapl.setEnableRotation(true) + tapl.setExpectedRotation(initialRotation.value) + SplitScreenUtils.enterSplit(wmHelper, tapl, device, simpleApp, testApp, initialRotation) + SplitScreenUtils.waitForSplitComplete(wmHelper, simpleApp, testApp) + } + + @Test + open fun startMediaProjection() { + testApp.startSingleAppMediaProjection(wmHelper, targetApp) + } + + @After + fun teardown() { + testApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionInSplitScreen.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionInSplitScreen.kt new file mode 100644 index 000000000000..d3186ae88081 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionInSplitScreen.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.scenarios + +import android.app.Instrumentation +import android.platform.test.annotations.Postsubmit +import android.tools.NavBar +import android.tools.Rotation +import android.tools.device.apphelpers.CalculatorAppHelper +import android.tools.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper +import com.android.wm.shell.Utils +import com.android.wm.shell.flicker.utils.SplitScreenUtils +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/** + * Test scenario which requests an a single-app MediaProjection session, while the TARGET app is in + * split screen (next to the host app) + * + * This is for testing that the split pair isn't broken, and capture still proceeds as expected + */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +open class StartAppMediaProjectionInSplitScreen { + + val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + val tapl = LauncherInstrumentation() + val wmHelper = WindowManagerStateHelper(instrumentation) + val device = UiDevice.getInstance(instrumentation) + + private val initialRotation = Rotation.ROTATION_0 + private val targetApp = CalculatorAppHelper(instrumentation) + private val testApp = StartMediaProjectionAppHelper(instrumentation) + + @Rule + @JvmField + val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, initialRotation) + + @Before + fun setup() { + tapl.workspace.switchToOverview().dismissAllTasks() + tapl.setEnableRotation(true) + tapl.setExpectedRotation(initialRotation.value) + SplitScreenUtils.enterSplit(wmHelper, tapl, device, targetApp, testApp, initialRotation) + } + + @Test + open fun startMediaProjection() { + testApp.startSingleAppMediaProjection(wmHelper, targetApp) + + wmHelper + .StateSyncBuilder() + .withAppTransitionIdle() + .withWindowSurfaceAppeared(targetApp) + .withWindowSurfaceAppeared(testApp) + .waitForAndVerify() + } + + @After + fun teardown() { + testApp.exit(wmHelper) + targetApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithExtraIntent.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithExtraIntent.kt new file mode 100644 index 000000000000..0b2a1ca23cdb --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithExtraIntent.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.scenarios + +import android.app.Instrumentation +import android.platform.test.annotations.Postsubmit +import android.tools.NavBar +import android.tools.Rotation +import android.tools.device.apphelpers.CalculatorAppHelper +import android.tools.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper +import com.android.wm.shell.Utils +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/** + * Test scenario which requests an a single-app MediaProjection session but launches an intent to + * return to the home screen, before the intent for opening the requested app to capture. + * + * This is for testing that even if a different intent interrupts the process the launching the + * requested capture target, the MediaProjection process isn't interrupted and the device is still + * interactive. + */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +open class StartAppMediaProjectionWithExtraIntent { + + val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + val tapl = LauncherInstrumentation() + val wmHelper = WindowManagerStateHelper(instrumentation) + val device = UiDevice.getInstance(instrumentation) + + private val initialRotation = Rotation.ROTATION_0 + private val targetApp = CalculatorAppHelper(instrumentation) + private val testApp = StartMediaProjectionAppHelper(instrumentation) + + @Rule + @JvmField + val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, initialRotation) + + @Before + fun setup() { + tapl.setEnableRotation(true) + testApp.launchViaIntent(wmHelper) + } + + @Test + open fun startMediaProjection() { + testApp.startSingleAppMediaProjectionWithExtraIntent(wmHelper, targetApp) + + // Check we can still interact with device after + tapl.workspace.switchToAllApps().getAppIcon(targetApp.appName).launch(targetApp.packageName) + + wmHelper + .StateSyncBuilder() + .withAppTransitionIdle() + .withWindowSurfaceAppeared(targetApp) + .waitForAndVerify() + } + + @After + fun teardown() { + testApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartRecentAppMediaProjection.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartRecentAppMediaProjection.kt new file mode 100644 index 000000000000..30e0e4aa490a --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartRecentAppMediaProjection.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.scenarios + +import android.app.Instrumentation +import android.platform.test.annotations.Postsubmit +import android.tools.NavBar +import android.tools.Rotation +import android.tools.device.apphelpers.CalculatorAppHelper +import android.tools.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper +import com.android.wm.shell.Utils +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/** + * Test scenario which requests an a single-app MediaProjection session from recents. + * + * This is for testing that the app is started from recents and capture proceeds as expected. + */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +open class StartRecentAppMediaProjection { + + val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + val tapl = LauncherInstrumentation() + val wmHelper = WindowManagerStateHelper(instrumentation) + val device = UiDevice.getInstance(instrumentation) + + private val initialRotation = Rotation.ROTATION_0 + private val targetApp = CalculatorAppHelper(instrumentation) + private val testApp = StartMediaProjectionAppHelper(instrumentation) + + @Rule + @JvmField + val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, initialRotation) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(initialRotation.value) + targetApp.open() + testApp.launchViaIntent(wmHelper) + } + + @Test + open fun startMediaProjection() { + testApp.startSingleAppMediaProjectionFromRecents(wmHelper, targetApp) + } + + @After + fun teardown() { + testApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartRecentAppMediaProjectionFromSplitScreen.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartRecentAppMediaProjectionFromSplitScreen.kt new file mode 100644 index 000000000000..f1dcf1fab140 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartRecentAppMediaProjectionFromSplitScreen.kt @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.scenarios + +import android.app.Instrumentation +import android.platform.test.annotations.Postsubmit +import android.tools.NavBar +import android.tools.Rotation +import android.tools.device.apphelpers.CalculatorAppHelper +import android.tools.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper +import com.android.wm.shell.Utils +import com.android.wm.shell.flicker.utils.SplitScreenUtils +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/** + * Test scenario which requests an a single-app MediaProjection session from recents while the HOST + * app is in split screen. + * + * This is for testing that the split pair isn't broken, and capture still proceeds as expected + */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +open class StartRecentAppMediaProjectionFromSplitScreen { + + val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + val tapl = LauncherInstrumentation() + val wmHelper = WindowManagerStateHelper(instrumentation) + val device = UiDevice.getInstance(instrumentation) + + private val initialRotation = Rotation.ROTATION_0 + private val simpleApp = SimpleAppHelper(instrumentation) + private val targetApp = CalculatorAppHelper(instrumentation) + private val testApp = StartMediaProjectionAppHelper(instrumentation) + + @Rule + @JvmField + val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, initialRotation) + + @Before + fun setup() { + tapl.workspace.switchToOverview().dismissAllTasks() + tapl.setEnableRotation(true) + tapl.setExpectedRotation(initialRotation.value) + targetApp.open() + SplitScreenUtils.enterSplit(wmHelper, tapl, device, simpleApp, testApp, initialRotation) + } + + @Test + open fun startMediaProjection() { + // The app we want to open for PSS will be the second item in the carousel, + // because the first will be the app open in split screen alongside the MediaProjection app + testApp.startSingleAppMediaProjectionFromRecents(wmHelper, targetApp, recentTasksIndex = 1) + } + + @After + fun teardown() { + testApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartRecentAppMediaProjectionInSplitScreen.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartRecentAppMediaProjectionInSplitScreen.kt new file mode 100644 index 000000000000..0a6992f9a152 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartRecentAppMediaProjectionInSplitScreen.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.scenarios + +import android.app.Instrumentation +import android.platform.test.annotations.Postsubmit +import android.tools.NavBar +import android.tools.Rotation +import android.tools.device.apphelpers.CalculatorAppHelper +import android.tools.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper +import com.android.wm.shell.Utils +import com.android.wm.shell.flicker.utils.SplitScreenUtils +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/** + * Test scenario which requests an a single-app MediaProjection session from recents while the + * TARGET app is in split screen (with host app). + * + * This is for testing that the split pair isn't broken, and capture still proceeds as expected + */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +open class StartRecentAppMediaProjectionInSplitScreen { + + val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + val tapl = LauncherInstrumentation() + val wmHelper = WindowManagerStateHelper(instrumentation) + val device = UiDevice.getInstance(instrumentation) + + private val initialRotation = Rotation.ROTATION_0 + private val targetApp = CalculatorAppHelper(instrumentation) + private val testApp = StartMediaProjectionAppHelper(instrumentation) + + @Rule + @JvmField + val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, initialRotation) + + @Before + fun setup() { + tapl.workspace.switchToOverview().dismissAllTasks() + tapl.setEnableRotation(true) + tapl.setExpectedRotation(initialRotation.value) + SplitScreenUtils.enterSplit(wmHelper, tapl, device, targetApp, testApp, initialRotation) + } + + @Test + open fun startMediaProjection() { + testApp.startSingleAppMediaProjectionFromRecents(wmHelper, targetApp) + + wmHelper + .StateSyncBuilder() + .withAppTransitionIdle() + .withWindowSurfaceAppeared(targetApp) + .withWindowSurfaceAppeared(testApp) + .waitForAndVerify() + } + + @After + fun teardown() { + testApp.exit(wmHelper) + targetApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MediaProjectionUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MediaProjectionUtils.kt index f9706969ff11..8c2bdad364fc 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MediaProjectionUtils.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MediaProjectionUtils.kt @@ -17,7 +17,11 @@ package com.android.wm.shell.flicker.utils object MediaProjectionUtils { - const val REQUEST_CODE: Int = 99 + // Request code for the normal media projection request + const val REQUEST_CODE_NORMAL: Int = 11 + // Request code for the media projection request which will include an extra intent to open + // home screen before starting requested app + const val REQUEST_CODE_EXTRA_INTENT: Int = 12 const val MSG_START_FOREGROUND_DONE: Int = 1 const val MSG_SERVICE_DESTROYED: Int = 2 const val EXTRA_MESSENGER: String = "messenger" diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java index 6fa37885b724..ce640b5e5195 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java @@ -47,6 +47,7 @@ import android.view.WindowManager; import androidx.test.filters.SmallTest; +import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.bubbles.BubbleData.TimeSource; import com.android.wm.shell.common.ShellExecutor; @@ -102,6 +103,7 @@ public class BubbleDataTest extends ShellTestCase { private BubbleData mBubbleData; private TestableBubblePositioner mPositioner; + private UiEventLoggerFake mUiEventLogger; @Mock private TimeSource mTimeSource; @@ -112,8 +114,6 @@ public class BubbleDataTest extends ShellTestCase { @Mock private PendingIntent mDeleteIntent; @Mock - private BubbleLogger mBubbleLogger; - @Mock private BubbleEducationController mEducationController; @Mock private ShellExecutor mMainExecutor; @@ -196,10 +196,12 @@ public class BubbleDataTest extends ShellTestCase { mock(Icon.class), mMainExecutor, mBgExecutor); + mUiEventLogger = new UiEventLoggerFake(); + mPositioner = new TestableBubblePositioner(mContext, mContext.getSystemService(WindowManager.class)); - mBubbleData = new BubbleData(getContext(), mBubbleLogger, mPositioner, mEducationController, - mMainExecutor, mBgExecutor); + mBubbleData = new BubbleData(getContext(), new BubbleLogger(mUiEventLogger), mPositioner, + mEducationController, mMainExecutor, mBgExecutor); // Used by BubbleData to set lastAccessedTime when(mTimeSource.currentTimeMillis()).thenReturn(1000L); @@ -297,6 +299,82 @@ public class BubbleDataTest extends ShellTestCase { } @Test + public void testRemoveBubbleFromBubbleBar_notifCancelled_logEvent() { + mPositioner.setShowingInBubbleBar(true); + + sendUpdatedEntryAtTime(mEntryA1, 1000); + mBubbleData.setListener(mListener); + + mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_NOTIF_CANCEL); + assertThat(mUiEventLogger.numLogs()).isEqualTo(1); + assertThat(mUiEventLogger.eventId(0)).isEqualTo( + BubbleLogger.Event.BUBBLE_BAR_BUBBLE_REMOVED_CANCELED.getId()); + } + + @Test + public void testRemoveBubbleFromBubbleBar_taskFinished_logEvent() { + mPositioner.setShowingInBubbleBar(true); + + sendUpdatedEntryAtTime(mEntryA1, 1000); + mBubbleData.setListener(mListener); + + mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_TASK_FINISHED); + assertThat(mUiEventLogger.numLogs()).isEqualTo(1); + assertThat(mUiEventLogger.eventId(0)).isEqualTo( + BubbleLogger.Event.BUBBLE_BAR_BUBBLE_ACTIVITY_FINISH.getId()); + } + + @Test + public void testRemoveBubbleFromBubbleBar_notifBlocked_logEvent() { + mPositioner.setShowingInBubbleBar(true); + + sendUpdatedEntryAtTime(mEntryA1, 1000); + mBubbleData.setListener(mListener); + + mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_BLOCKED); + assertThat(mUiEventLogger.numLogs()).isEqualTo(1); + assertThat(mUiEventLogger.eventId(0)).isEqualTo( + BubbleLogger.Event.BUBBLE_BAR_BUBBLE_REMOVED_BLOCKED.getId()); + } + + @Test + public void testRemoveBubbleFromBubbleBar_noLongerBubble_logEvent() { + mPositioner.setShowingInBubbleBar(true); + + sendUpdatedEntryAtTime(mEntryA1, 1000); + mBubbleData.setListener(mListener); + + mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_NO_LONGER_BUBBLE); + assertThat(mUiEventLogger.numLogs()).isEqualTo(1); + assertThat(mUiEventLogger.eventId(0)).isEqualTo( + BubbleLogger.Event.BUBBLE_BAR_BUBBLE_REMOVED_BLOCKED.getId()); + } + + @Test + public void testRemoveBubbleFromBubbleBar_addToOverflow_logEvent() { + mPositioner.setShowingInBubbleBar(true); + + sendUpdatedEntryAtTime(mEntryA1, 1000); + mBubbleData.setListener(mListener); + + mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_AGED); + assertThat(mUiEventLogger.numLogs()).isEqualTo(1); + assertThat(mUiEventLogger.eventId(0)).isEqualTo( + BubbleLogger.Event.BUBBLE_BAR_OVERFLOW_ADD_AGED.getId()); + } + + @Test + public void testRemoveBubble_notifCancelled_noLog() { + mPositioner.setShowingInBubbleBar(false); + + sendUpdatedEntryAtTime(mEntryA1, 1000); + mBubbleData.setListener(mListener); + + mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_BLOCKED); + assertThat(mUiEventLogger.numLogs()).isEqualTo(0); + } + + @Test public void ifSuppress_hideFlyout() { // Setup mBubbleData.setListener(mListener); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java index 5f58265b45f5..289fd2d838fd 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java @@ -36,6 +36,8 @@ import android.content.ComponentName; import android.content.pm.ActivityInfo; import android.graphics.Rect; import android.os.RemoteException; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.Rational; @@ -46,6 +48,7 @@ import android.window.WindowContainerToken; import androidx.test.filters.SmallTest; +import com.android.wm.shell.Flags; import com.android.wm.shell.MockSurfaceControlHelper; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; @@ -67,6 +70,8 @@ import com.android.wm.shell.pip.phone.PhonePipMenuController; import com.android.wm.shell.splitscreen.SplitScreenController; import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentMatchers; @@ -81,7 +86,13 @@ import java.util.Optional; @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper +@DisableFlags(Flags.FLAG_ENABLE_PIP2) public class PipTaskOrganizerTest extends ShellTestCase { + @ClassRule + public static final SetFlagsRule.ClassRule mClassRule = new SetFlagsRule.ClassRule(); + @Rule + public final SetFlagsRule mSetFlagsRule = mClassRule.createSetFlagsRule(); + private PipTaskOrganizer mPipTaskOrganizer; @Mock private DisplayController mMockDisplayController; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java index 96003515a485..b123f4dfac9e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java @@ -42,11 +42,14 @@ import android.graphics.Rect; import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import androidx.test.filters.SmallTest; +import com.android.wm.shell.Flags; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.common.DisplayController; @@ -74,6 +77,8 @@ import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -88,7 +93,11 @@ import java.util.Set; @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper +@DisableFlags(Flags.FLAG_ENABLE_PIP2) public class PipControllerTest extends ShellTestCase { + @ClassRule public static final SetFlagsRule.ClassRule mClassRule = new SetFlagsRule.ClassRule(); + @Rule public final SetFlagsRule mSetFlagsRule = mClassRule.createSetFlagsRule(); + private PipController mPipController; private ShellInit mShellInit; private ShellController mShellController; diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h index fb58a69747b3..b72e066e64ae 100644 --- a/libs/hwui/DeviceInfo.h +++ b/libs/hwui/DeviceInfo.h @@ -84,6 +84,7 @@ public: // this value is only valid after the GPU has been initialized and there is a valid graphics // context or if you are using the HWUI_NULL_GPU int maxTextureSize() const; + bool hasMaxTextureSize() const { return mMaxTextureSize > 0; } sk_sp<SkColorSpace> getWideColorSpace() const { return mWideColorSpace; } SkColorType getWideColorType() { static std::once_flag kFlag; diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index 2c23864317a4..4801bd1038a3 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -186,7 +186,7 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) { // If we are not a layer OR we cannot be rendered (eg, view was detached) // we need to destroy any Layers we may have had previously if (CC_LIKELY(layerType != LayerType::RenderLayer) || CC_UNLIKELY(!isRenderable()) || - CC_UNLIKELY(properties().getWidth() == 0) || CC_UNLIKELY(properties().getHeight() == 0) || + CC_UNLIKELY(properties().getWidth() <= 0) || CC_UNLIKELY(properties().getHeight() <= 0) || CC_UNLIKELY(!properties().fitsOnLayer())) { if (CC_UNLIKELY(hasLayer())) { this->setLayerSurface(nullptr); diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h index b1ad8b2eb1b9..4dc57004e401 100644 --- a/libs/hwui/RenderProperties.h +++ b/libs/hwui/RenderProperties.h @@ -545,7 +545,8 @@ public: bool fitsOnLayer() const { const DeviceInfo* deviceInfo = DeviceInfo::get(); return mPrimitiveFields.mWidth <= deviceInfo->maxTextureSize() && - mPrimitiveFields.mHeight <= deviceInfo->maxTextureSize(); + mPrimitiveFields.mHeight <= deviceInfo->maxTextureSize() && + mPrimitiveFields.mWidth > 0 && mPrimitiveFields.mHeight > 0; } bool promotedToLayer() const { diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 8ec04304a808..b36b8be10779 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -418,6 +418,11 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy RenderNode* target) { mRenderThread.removeFrameCallback(this); + // Make sure we have a valid device info + if (!DeviceInfo::get()->hasMaxTextureSize()) { + (void)mRenderThread.requireGrContext(); + } + // If the previous frame was dropped we don't need to hold onto it, so // just keep using the previous frame's structure instead const auto reason = wasSkipped(mCurrentFrameInfo); diff --git a/libs/hwui/tests/unit/RenderPropertiesTests.cpp b/libs/hwui/tests/unit/RenderPropertiesTests.cpp index 3e8e0576bf49..6ec042cf23b0 100644 --- a/libs/hwui/tests/unit/RenderPropertiesTests.cpp +++ b/libs/hwui/tests/unit/RenderPropertiesTests.cpp @@ -40,7 +40,11 @@ TEST(RenderProperties, layerValidity) { props.setLeftTopRightBottom(0, 0, maxTextureSize + 1, maxTextureSize + 1); ASSERT_FALSE(props.fitsOnLayer()); - // Too small, but still 'fits'. Not fitting is an error case, so don't report empty as such. + // Too small, we can't create a layer for a 0 width or height props.setLeftTopRightBottom(0, 0, 100, 0); - ASSERT_TRUE(props.fitsOnLayer()); + ASSERT_FALSE(props.fitsOnLayer()); + + // Can't create a negative-sized layer + props.setLeftTopRightBottom(0, 0, -100, 300); + ASSERT_FALSE(props.fitsOnLayer()); } diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp index 4330f0addaa9..d993b8715260 100644 --- a/libs/input/MouseCursorController.cpp +++ b/libs/input/MouseCursorController.cpp @@ -28,15 +28,13 @@ #define INDENT " " #define INDENT2 " " -namespace android { - namespace { - // Time to spend fading out the pointer completely. const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms - } // namespace +namespace android { + // --- MouseCursorController --- MouseCursorController::MouseCursorController(PointerControllerContext& context) @@ -66,21 +64,17 @@ MouseCursorController::~MouseCursorController() { mLocked.pointerSprite.clear(); } -vec2 MouseCursorController::move(float deltaX, float deltaY) { +void MouseCursorController::move(float deltaX, float deltaY) { #if DEBUG_MOUSE_CURSOR_UPDATES ALOGD("Move pointer by deltaX=%0.3f, deltaY=%0.3f", deltaX, deltaY); #endif if (deltaX == 0.0f && deltaY == 0.0f) { - return {}; + return; } - // When transition occurs, the MouseCursorController object may or may not be deleted, depending - // if there's another display on the other side of the transition. At this point we still need - // to move the cursor to the boundary. std::scoped_lock lock(mLock); - const vec2 pos{mLocked.pointerX + deltaX, mLocked.pointerY + deltaY}; - setPositionLocked(pos.x, pos.y); - return vec2{pos.x - mLocked.pointerX, pos.y - mLocked.pointerY}; + + setPositionLocked(mLocked.pointerX + deltaX, mLocked.pointerY + deltaY); } void MouseCursorController::setPosition(float x, float y) { @@ -88,26 +82,22 @@ void MouseCursorController::setPosition(float x, float y) { ALOGD("Set pointer position to x=%0.3f, y=%0.3f", x, y); #endif std::scoped_lock lock(mLock); - setPositionLocked(x, y); } -FloatRect MouseCursorController::getBoundsLocked() REQUIRES(mLock) { +void MouseCursorController::setPositionLocked(float x, float y) REQUIRES(mLock) { + const auto& v = mLocked.viewport; + if (!v.isValid()) return; + // The valid bounds for a mouse cursor. Since the right and bottom edges are considered outside // the display, clip the bounds by one pixel instead of letting the cursor get arbitrarily // close to the outside edge. - return FloatRect{ + const FloatRect bounds{ static_cast<float>(mLocked.viewport.logicalLeft), static_cast<float>(mLocked.viewport.logicalTop), static_cast<float>(mLocked.viewport.logicalRight - 1), static_cast<float>(mLocked.viewport.logicalBottom - 1), }; -} -void MouseCursorController::setPositionLocked(float x, float y) REQUIRES(mLock) { - const auto& v = mLocked.viewport; - if (!v.isValid()) return; - - const FloatRect bounds = getBoundsLocked(); mLocked.pointerX = std::max(bounds.left, std::min(bounds.right, x)); mLocked.pointerY = std::max(bounds.top, std::min(bounds.bottom, y)); diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h index 10f0d7645105..12b31a8c531a 100644 --- a/libs/input/MouseCursorController.h +++ b/libs/input/MouseCursorController.h @@ -20,6 +20,9 @@ #include <gui/DisplayEventReceiver.h> #include <input/DisplayViewport.h> #include <input/Input.h> +#include <utils/BitSet.h> +#include <utils/Looper.h> +#include <utils/RefBase.h> #include <functional> #include <map> @@ -40,8 +43,7 @@ public: MouseCursorController(PointerControllerContext& context); ~MouseCursorController(); - // Move the pointer and return unconsumed delta - vec2 move(float deltaX, float deltaY); + void move(float deltaX, float deltaY); void setPosition(float x, float y); FloatPoint getPosition() const; ui::LogicalDisplayId getDisplayId() const; @@ -111,7 +113,6 @@ private: bool doFadingAnimationLocked(nsecs_t timestamp); void startAnimationLocked(); - FloatRect getBoundsLocked(); }; } // namespace android diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index 59397dab592f..78d7d3a7051b 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -138,18 +138,15 @@ std::mutex& PointerController::getLock() const { return mDisplayInfoListener->mLock; } -vec2 PointerController::move(float deltaX, float deltaY) { +void PointerController::move(float deltaX, float deltaY) { const ui::LogicalDisplayId displayId = mCursorController.getDisplayId(); - ui::Transform transform; + vec2 transformed; { std::scoped_lock lock(getLock()); - transform = getTransformForDisplayLocked(displayId); + const auto& transform = getTransformForDisplayLocked(displayId); + transformed = transformWithoutTranslation(transform, {deltaX, deltaY}); } - - vec2 transformed = transformWithoutTranslation(transform, {deltaX, deltaY}); - - vec2 unconsumedDelta = mCursorController.move(transformed.x, transformed.y); - return transformWithoutTranslation(transform.inverse(), unconsumedDelta); + mCursorController.move(transformed.x, transformed.y); } void PointerController::setPosition(float x, float y) { diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index 69bef546b788..ee8d1211341f 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -51,7 +51,7 @@ public: ~PointerController() override; - vec2 move(float deltaX, float deltaY) override; + void move(float deltaX, float deltaY) override; void setPosition(float x, float y) override; FloatPoint getPosition() const override; ui::LogicalDisplayId getDisplayId() const override; @@ -165,7 +165,7 @@ public: ~TouchPointerController() override; - vec2 move(float, float) override { + void move(float, float) override { LOG_ALWAYS_FATAL("Should not be called"); } void setPosition(float, float) override { diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp index 80c934a9bd95..5b00fca4d857 100644 --- a/libs/input/tests/PointerController_test.cpp +++ b/libs/input/tests/PointerController_test.cpp @@ -40,8 +40,6 @@ enum TestCursorType { CURSOR_TYPE_CUSTOM = -1, }; -static constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION; - using ::testing::AllOf; using ::testing::Field; using ::testing::NiceMock; @@ -401,135 +399,6 @@ INSTANTIATE_TEST_SUITE_P(PointerControllerSkipScreenshotFlagTest, testing::Values(PointerControllerInterface::ControllerType::MOUSE, PointerControllerInterface::ControllerType::STYLUS)); -class MousePointerControllerTest : public PointerControllerTest { -protected: - MousePointerControllerTest() { - sp<MockSprite> testPointerSprite(new NiceMock<MockSprite>); - EXPECT_CALL(*mSpriteController, createSprite).WillOnce(Return(testPointerSprite)); - - // create a mouse pointer controller - mPointerController = - PointerController::create(mPolicy, mLooper, *mSpriteController, - PointerControllerInterface::ControllerType::MOUSE); - - // set display viewport - DisplayViewport viewport; - viewport.displayId = ui::LogicalDisplayId::DEFAULT; - viewport.logicalRight = 5; - viewport.logicalBottom = 5; - viewport.physicalRight = 5; - viewport.physicalBottom = 5; - viewport.deviceWidth = 5; - viewport.deviceHeight = 5; - mPointerController->setDisplayViewport(viewport); - } -}; - -struct MousePointerControllerTestParam { - vec2 startPosition; - vec2 delta; - vec2 endPosition; - vec2 unconsumedDelta; -}; - -class PointerControllerViewportTransitionTest - : public MousePointerControllerTest, - public testing::WithParamInterface<MousePointerControllerTestParam> {}; - -TEST_P(PointerControllerViewportTransitionTest, testPointerViewportTransition) { - const auto& params = GetParam(); - - mPointerController->setPosition(params.startPosition.x, params.startPosition.y); - auto unconsumedDelta = mPointerController->move(params.delta.x, params.delta.y); - - auto position = mPointerController->getPosition(); - EXPECT_NEAR(position.x, params.endPosition.x, EPSILON); - EXPECT_NEAR(position.y, params.endPosition.y, EPSILON); - EXPECT_NEAR(unconsumedDelta.x, params.unconsumedDelta.x, EPSILON); - EXPECT_NEAR(unconsumedDelta.y, params.unconsumedDelta.y, EPSILON); -} - -INSTANTIATE_TEST_SUITE_P(PointerControllerViewportTransitionTest, - PointerControllerViewportTransitionTest, - testing::Values( - // no transition - MousePointerControllerTestParam{{2.0f, 2.0f}, - {2.0f, 2.0f}, - {4.0f, 4.0f}, - {0.0f, 0.0f}}, - // right boundary - MousePointerControllerTestParam{{2.0f, 2.0f}, - {3.0f, 0.0f}, - {4.0f, 2.0f}, - {1.0f, 0.0f}}, - MousePointerControllerTestParam{{2.0f, 2.0f}, - {3.0f, -1.0f}, - {4.0f, 1.0f}, - {1.0f, 0.0f}}, - MousePointerControllerTestParam{{2.0f, 2.0f}, - {3.0f, 1.0f}, - {4.0f, 3.0f}, - {1.0f, 0.0f}}, - // left boundary - MousePointerControllerTestParam{{2.0f, 2.0f}, - {-3.0f, 0.0f}, - {0.0f, 2.0f}, - {-1.0f, 0.0f}}, - MousePointerControllerTestParam{{2.0f, 2.0f}, - {-3.0f, -1.0f}, - {0.0f, 1.0f}, - {-1.0f, 0.0f}}, - MousePointerControllerTestParam{{2.0f, 2.0f}, - {-3.0f, 1.0f}, - {0.0f, 3.0f}, - {-1.0f, 0.0f}}, - // bottom boundary - MousePointerControllerTestParam{{2.0f, 2.0f}, - {0.0f, 3.0f}, - {2.0f, 4.0f}, - {0.0f, 1.0f}}, - MousePointerControllerTestParam{{2.0f, 2.0f}, - {-1.0f, 3.0f}, - {1.0f, 4.0f}, - {0.0f, 1.0f}}, - MousePointerControllerTestParam{{2.0f, 2.0f}, - {1.0f, 3.0f}, - {3.0f, 4.0f}, - {0.0f, 1.0f}}, - // top boundary - MousePointerControllerTestParam{{2.0f, 2.0f}, - {0.0f, -3.0f}, - {2.0f, 0.0f}, - {0.0f, -1.0f}}, - MousePointerControllerTestParam{{2.0f, 2.0f}, - {-1.0f, -3.0f}, - {1.0f, 0.0f}, - {0.0f, -1.0f}}, - MousePointerControllerTestParam{{2.0f, 2.0f}, - {1.0f, -3.0f}, - {3.0f, 0.0f}, - {0.0f, -1.0f}}, - // top-left corner - MousePointerControllerTestParam{{2.0f, 2.0f}, - {-3.0f, -3.0f}, - {0.0f, 0.0f}, - {-1.0f, -1.0f}}, - // top-right corner - MousePointerControllerTestParam{{2.0f, 2.0f}, - {3.0f, -3.0f}, - {4.0f, 0.0f}, - {1.0f, -1.0f}}, - // bottom-right corner - MousePointerControllerTestParam{{2.0f, 2.0f}, - {3.0f, 3.0f}, - {4.0f, 4.0f}, - {1.0f, 1.0f}}, - // bottom-left corner - MousePointerControllerTestParam{{2.0f, 2.0f}, - {-3.0f, 3.0f}, - {0.0f, 4.0f}, - {-1.0f, 1.0f}})); - class PointerControllerWindowInfoListenerTest : public Test {}; TEST_F(PointerControllerWindowInfoListenerTest, diff --git a/media/java/android/media/AudioDevicePort.java b/media/java/android/media/AudioDevicePort.java index f5913c763b82..4b3962e6dd74 100644 --- a/media/java/android/media/AudioDevicePort.java +++ b/media/java/android/media/AudioDevicePort.java @@ -22,6 +22,7 @@ import android.os.Build; import com.android.aconfig.annotations.VisibleForTesting; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -68,14 +69,14 @@ public class AudioDevicePort extends AudioPort { return new AudioDevicePort( new AudioHandle(/* id= */ 0), /* name= */ "testAudioDevicePort", - /* profiles= */ null, + /* profiles= */ new ArrayList<>(), /* gains= */ null, /* type= */ AudioManager.DEVICE_OUT_SPEAKER, /* address= */ "testAddress", /* speakerLayoutChannelMask= */ speakerLayoutChannelMask, /* encapsulationModes= */ null, /* encapsulationMetadataTypes= */ null, - /* descriptors= */ null); + /* descriptors= */ new ArrayList<>()); } private final int mType; diff --git a/media/java/android/media/quality/IMediaQualityManager.aidl b/media/java/android/media/quality/IMediaQualityManager.aidl index aaedf21999f7..1c85c7b97a6e 100644 --- a/media/java/android/media/quality/IMediaQualityManager.aidl +++ b/media/java/android/media/quality/IMediaQualityManager.aidl @@ -21,6 +21,7 @@ import android.media.quality.IAmbientBacklightCallback; import android.media.quality.IPictureProfileCallback; import android.media.quality.ISoundProfileCallback; import android.media.quality.ParamCapability; +import android.media.quality.PictureProfileHandle; import android.media.quality.PictureProfile; import android.media.quality.SoundProfile; @@ -38,6 +39,7 @@ interface IMediaQualityManager { List<String> getPictureProfilePackageNames(); List<String> getPictureProfileAllowList(); void setPictureProfileAllowList(in List<String> packages); + PictureProfileHandle getPictureProfileHandle(in String id); SoundProfile createSoundProfile(in SoundProfile pp); void updateSoundProfile(in String id, in SoundProfile pp); diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java index 28fe9b6c8112..43e884a8271e 100644 --- a/media/java/android/media/quality/MediaQualityManager.java +++ b/media/java/android/media/quality/MediaQualityManager.java @@ -271,6 +271,17 @@ public final class MediaQualityManager { } } + /** + * Gets picture profile handle by profile ID. + * @hide + */ + public PictureProfileHandle getPictureProfileHandle(String id) { + try { + return mService.getPictureProfileHandle(id); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } /** * Creates a picture profile and store it in the system. diff --git a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java index 898a8bf02edb..4de71235df8c 100644 --- a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java +++ b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java @@ -63,7 +63,7 @@ public class FrontendStatus { FRONTEND_STATUS_TYPE_DVBT_CELL_IDS, FRONTEND_STATUS_TYPE_ATSC3_ALL_PLP_INFO, FRONTEND_STATUS_TYPE_IPTV_CONTENT_URL, FRONTEND_STATUS_TYPE_IPTV_PACKETS_LOST, FRONTEND_STATUS_TYPE_IPTV_PACKETS_RECEIVED, FRONTEND_STATUS_TYPE_IPTV_WORST_JITTER_MS, - FRONTEND_STATUS_TYPE_IPTV_AVERAGE_JITTER_MS, FRONTEND_STATUS_TYPE_STANDARD_EXT}) + FRONTEND_STATUS_TYPE_IPTV_AVERAGE_JITTER_MS, FRONTEND_STATUS_TYPE_STANDARD_EXTENSION}) @Retention(RetentionPolicy.SOURCE) public @interface FrontendStatusType {} @@ -317,7 +317,7 @@ public class FrontendStatus { * Standard extension. */ @FlaggedApi(Flags.FLAG_TUNER_W_APIS) - public static final int FRONTEND_STATUS_TYPE_STANDARD_EXT = + public static final int FRONTEND_STATUS_TYPE_STANDARD_EXTENSION = android.hardware.tv.tuner.FrontendStatusType.STANDARD_EXT; /** @hide */ @@ -567,7 +567,7 @@ public class FrontendStatus { private Long mIptvPacketsReceived; private Integer mIptvWorstJitterMs; private Integer mIptvAverageJitterMs; - private StandardExt mStandardExt; + private StandardExtension mStandardExtension; // Constructed and fields set by JNI code. private FrontendStatus() { @@ -1298,12 +1298,12 @@ public class FrontendStatus { */ @NonNull @FlaggedApi(Flags.FLAG_TUNER_W_APIS) - public StandardExt getStandardExt() { + public StandardExtension getStandardExtension() { TunerVersionChecker.checkHigherOrEqualVersionTo( - TunerVersionChecker.TUNER_VERSION_4_0, "StandardExt status"); - if (mStandardExt == null) { - throw new IllegalStateException("StandardExt status is empty"); + TunerVersionChecker.TUNER_VERSION_4_0, "StandardExtension status"); + if (mStandardExtension == null) { + throw new IllegalStateException("StandardExtension status is empty"); } - return mStandardExt; + return mStandardExtension; } } diff --git a/media/java/android/media/tv/tuner/frontend/StandardExt.java b/media/java/android/media/tv/tuner/frontend/StandardExtension.java index 490727278b46..8bff3dd99cdb 100644 --- a/media/java/android/media/tv/tuner/frontend/StandardExt.java +++ b/media/java/android/media/tv/tuner/frontend/StandardExtension.java @@ -29,16 +29,16 @@ import android.media.tv.flags.Flags; */ @SystemApi @FlaggedApi(Flags.FLAG_TUNER_W_APIS) -public final class StandardExt { - private final int mDvbsStandardExt; - private final int mDvbtStandardExt; +public final class StandardExtension { + private final int mDvbsStandardExtension; + private final int mDvbtStandardExtension; /** * Private constructor called by JNI only. */ - private StandardExt(int dvbsStandardExt, int dvbtStandardExt) { - mDvbsStandardExt = dvbsStandardExt; - mDvbtStandardExt = dvbtStandardExt; + private StandardExtension(int dvbsStandardExtension, int dvbtStandardExtension) { + mDvbsStandardExtension = dvbsStandardExtension; + mDvbtStandardExtension = dvbtStandardExtension; } /** @@ -50,11 +50,11 @@ public final class StandardExt { * @see android.media.tv.tuner.frontend.DvbsFrontendSettings */ @DvbsFrontendSettings.Standard - public int getDvbsStandardExt() { - if (mDvbsStandardExt == FrontendDvbsStandard.UNDEFINED) { + public int getDvbsStandardExtension() { + if (mDvbsStandardExtension == FrontendDvbsStandard.UNDEFINED) { throw new IllegalStateException("No DVB-S standard transition"); } - return mDvbsStandardExt; + return mDvbsStandardExtension; } /** @@ -66,10 +66,10 @@ public final class StandardExt { * @see android.media.tv.tuner.frontend.DvbtFrontendSettings */ @DvbtFrontendSettings.Standard - public int getDvbtStandardExt() { - if (mDvbtStandardExt == FrontendDvbtStandard.UNDEFINED) { + public int getDvbtStandardExtension() { + if (mDvbtStandardExtension == FrontendDvbtStandard.UNDEFINED) { throw new IllegalStateException("No DVB-T standard transition"); } - return mDvbtStandardExt; + return mDvbtStandardExtension; } } diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index 80ca4f239a26..2fe069af638a 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -2940,10 +2940,10 @@ jobject JTuner::getFrontendStatus(jintArray types) { break; } case FrontendStatus::Tag::standardExt: { - jfieldID field = env->GetFieldID(clazz, "mStandardExt", - "Landroid/media/tv/tuner/frontend/StandardExt;"); + jfieldID field = env->GetFieldID(clazz, "mStandardExtension", + "Landroid/media/tv/tuner/frontend/StandardExtension;"); ScopedLocalRef standardExtClazz(env, - env->FindClass("android/media/tv/tuner/frontend/StandardExt")); + env->FindClass("android/media/tv/tuner/frontend/StandardExtension")); jmethodID initStandardExt = env->GetMethodID(standardExtClazz.get(), "<init>", "(II)V"); diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt index 2d1fbf9e7f66..6b41ddde5cc8 100644 --- a/native/android/libandroid.map.txt +++ b/native/android/libandroid.map.txt @@ -363,6 +363,7 @@ LIBANDROID { APerformanceHint_reportActualWorkDuration2; # introduced=VanillaIceCream APerformanceHint_notifyWorkloadIncrease; # introduced=36 APerformanceHint_notifyWorkloadReset; # introduced=36 + APerformanceHint_borrowSessionFromJava; # introduced=36 AWorkDuration_create; # introduced=VanillaIceCream AWorkDuration_release; # introduced=VanillaIceCream AWorkDuration_setWorkPeriodStartTimestampNanos; # introduced=VanillaIceCream @@ -383,6 +384,8 @@ LIBANDROID_PLATFORM { APerformanceHint_setUseFMQForTesting; APerformanceHint_getRateLimiterPropertiesForTesting; APerformanceHint_setUseNewLoadHintBehaviorForTesting; + APerformanceHint_closeSessionFromJava; + APerformanceHint_createSessionFromJava; extern "C++" { ASurfaceControl_registerSurfaceStatsListener*; ASurfaceControl_unregisterSurfaceStatsListener*; diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp index e2fa94dd39bb..bc1945e37072 100644 --- a/native/android/performance_hint.cpp +++ b/native/android/performance_hint.cpp @@ -36,6 +36,7 @@ #include <cutils/trace.h> #include <fmq/AidlMessageQueue.h> #include <inttypes.h> +#include <jni_wrappers.h> #include <performance_hint_private.h> #include <utils/SystemClock.h> @@ -137,10 +138,14 @@ public: APerformanceHintSession* createSession(const int32_t* threadIds, size_t size, int64_t initialTargetWorkDurationNanos, - hal::SessionTag tag = hal::SessionTag::APP); + hal::SessionTag tag = hal::SessionTag::APP, + bool isJava = false); + APerformanceHintSession* getSessionFromJava(JNIEnv* _Nonnull env, jobject _Nonnull sessionObj); + int64_t getPreferredRateNanos() const; FMQWrapper& getFMQWrapper(); bool canSendLoadHints(std::vector<hal::SessionHint>& hints, int64_t now) REQUIRES(sHintMutex); + void initJava(JNIEnv* _Nonnull env); private: // Necessary to create an empty binder object @@ -161,13 +166,16 @@ private: FMQWrapper mFMQWrapper; double mHintBudget = kMaxLoadHintsPerInterval; int64_t mLastBudgetReplenish = 0; + bool mJavaInitialized = false; + jclass mJavaSessionClazz; + jfieldID mJavaSessionNativePtr; }; struct APerformanceHintSession { public: APerformanceHintSession(std::shared_ptr<IHintManager> hintManager, std::shared_ptr<IHintSession> session, int64_t preferredRateNanos, - int64_t targetDurationNanos, + int64_t targetDurationNanos, bool isJava, std::optional<hal::SessionConfig> sessionConfig); APerformanceHintSession() = delete; ~APerformanceHintSession(); @@ -181,6 +189,7 @@ public: int getThreadIds(int32_t* const threadIds, size_t* size); int setPreferPowerEfficiency(bool enabled); int reportActualWorkDuration(AWorkDuration* workDuration); + bool isJava(); private: friend struct APerformanceHintManager; @@ -203,6 +212,8 @@ private: std::vector<int64_t> mLastHintSentTimestamp GUARDED_BY(sHintMutex); // Cached samples std::vector<hal::WorkDuration> mActualWorkDurations GUARDED_BY(sHintMutex); + // Is this session backing an SDK wrapper object + const bool mIsJava; std::string mSessionName; static int64_t sIDCounter GUARDED_BY(sHintMutex); // The most recent set of thread IDs @@ -299,7 +310,7 @@ bool APerformanceHintManager::canSendLoadHints(std::vector<hal::SessionHint>& hi APerformanceHintSession* APerformanceHintManager::createSession( const int32_t* threadIds, size_t size, int64_t initialTargetWorkDurationNanos, - hal::SessionTag tag) { + hal::SessionTag tag, bool isJava) { std::vector<int32_t> tids(threadIds, threadIds + size); std::shared_ptr<IHintSession> session; ndk::ScopedAStatus ret; @@ -312,7 +323,7 @@ APerformanceHintSession* APerformanceHintManager::createSession( return nullptr; } auto out = new APerformanceHintSession(mHintManager, std::move(session), mPreferredRateNanos, - initialTargetWorkDurationNanos, + initialTargetWorkDurationNanos, isJava, sessionConfig.id == -1 ? std::nullopt : std::make_optional<hal::SessionConfig>( @@ -324,6 +335,18 @@ APerformanceHintSession* APerformanceHintManager::createSession( return out; } +APerformanceHintSession* APerformanceHintManager::getSessionFromJava(JNIEnv* env, + jobject sessionObj) { + initJava(env); + LOG_ALWAYS_FATAL_IF(!env->IsInstanceOf(sessionObj, mJavaSessionClazz), + "Wrong java type passed to APerformanceHint_getSessionFromJava"); + APerformanceHintSession* out = reinterpret_cast<APerformanceHintSession*>( + env->GetLongField(sessionObj, mJavaSessionNativePtr)); + LOG_ALWAYS_FATAL_IF(out == nullptr, "Java-wrapped native hint session is nullptr"); + LOG_ALWAYS_FATAL_IF(!out->isJava(), "Unmanaged native hint session returned from Java SDK"); + return out; +} + int64_t APerformanceHintManager::getPreferredRateNanos() const { return mPreferredRateNanos; } @@ -332,13 +355,23 @@ FMQWrapper& APerformanceHintManager::getFMQWrapper() { return mFMQWrapper; } +void APerformanceHintManager::initJava(JNIEnv* _Nonnull env) { + if (mJavaInitialized) { + return; + } + jclass sessionClazz = FindClassOrDie(env, "android/os/PerformanceHintManager$Session"); + mJavaSessionClazz = MakeGlobalRefOrDie(env, sessionClazz); + mJavaSessionNativePtr = GetFieldIDOrDie(env, mJavaSessionClazz, "mNativeSessionPtr", "J"); + mJavaInitialized = true; +} + // ===================================== APerformanceHintSession implementation constexpr int kNumEnums = enum_size<hal::SessionHint>(); APerformanceHintSession::APerformanceHintSession(std::shared_ptr<IHintManager> hintManager, std::shared_ptr<IHintSession> session, int64_t preferredRateNanos, - int64_t targetDurationNanos, + int64_t targetDurationNanos, bool isJava, std::optional<hal::SessionConfig> sessionConfig) : mHintManager(hintManager), mHintSession(std::move(session)), @@ -347,6 +380,7 @@ APerformanceHintSession::APerformanceHintSession(std::shared_ptr<IHintManager> h mFirstTargetMetTimestamp(0), mLastTargetMetTimestamp(0), mLastHintSentTimestamp(std::vector<int64_t>(kNumEnums, 0)), + mIsJava(isJava), mSessionConfig(sessionConfig) { if (sessionConfig->id > INT32_MAX) { ALOGE("Session ID too large, must fit 32-bit integer"); @@ -401,6 +435,10 @@ int APerformanceHintSession::reportActualWorkDuration(int64_t actualDurationNano return reportActualWorkDurationInternal(static_cast<AWorkDuration*>(&workDuration)); } +bool APerformanceHintSession::isJava() { + return mIsJava; +} + int APerformanceHintSession::sendHints(std::vector<hal::SessionHint>& hints, int64_t now, const char*) { std::scoped_lock lock(sHintMutex); @@ -826,6 +864,22 @@ APerformanceHintSession* APerformanceHint_createSessionInternal( static_cast<hal::SessionTag>(tag)); } +APerformanceHintSession* APerformanceHint_createSessionFromJava( + APerformanceHintManager* manager, const int32_t* threadIds, size_t size, + int64_t initialTargetWorkDurationNanos) { + VALIDATE_PTR(manager) + VALIDATE_PTR(threadIds) + return manager->createSession(threadIds, size, initialTargetWorkDurationNanos, + hal::SessionTag::APP, true); +} + +APerformanceHintSession* APerformanceHint_borrowSessionFromJava(JNIEnv* env, + jobject sessionObj) { + VALIDATE_PTR(env) + VALIDATE_PTR(sessionObj) + return APerformanceHintManager::getInstance()->getSessionFromJava(env, sessionObj); +} + int64_t APerformanceHint_getPreferredUpdateRateNanos(APerformanceHintManager* manager) { VALIDATE_PTR(manager) return manager->getPreferredRateNanos(); @@ -846,6 +900,16 @@ int APerformanceHint_reportActualWorkDuration(APerformanceHintSession* session, void APerformanceHint_closeSession(APerformanceHintSession* session) { VALIDATE_PTR(session) + if (session->isJava()) { + LOG_ALWAYS_FATAL("%s: Java-owned PerformanceHintSession cannot be closed in native", + __FUNCTION__); + return; + } + delete session; +} + +void APerformanceHint_closeSessionFromJava(APerformanceHintSession* session) { + VALIDATE_PTR(session) delete session; } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java index c90ba8249d54..32d4580f67ec 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java @@ -464,5 +464,14 @@ public class GlobalSettingsValidators { )); VALIDATORS.put(Global.HEARING_DEVICE_LOCAL_AMBIENT_VOLUME, ANY_STRING_VALIDATOR); VALIDATORS.put(Global.HEARING_DEVICE_LOCAL_NOTIFICATION, ANY_STRING_VALIDATOR); + VALIDATORS.put( + Global.Wearable.WEAR_SYSTEM_STATUS_TRAY_CONFIGURATION, + new DiscreteValueValidator( + new String[] { + String.valueOf( + Global.Wearable.STATUS_TRAY_CONFIGURATION_DEFAULT), + String.valueOf( + Global.Wearable.STATUS_TRAY_CONFIGURATION_SYSTEM_HIDDEN) + })); } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 603a91195d04..03fea37deaf5 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -2434,28 +2434,40 @@ public class SettingsProvider extends ContentProvider { context.checkCallingOrSelfPermission( Manifest.permission.WRITE_DEVICE_CONFIG) == PackageManager.PERMISSION_GRANTED; - boolean isRoot = Binder.getCallingUid() == Process.ROOT_UID; + // Only the shell user and tests request the allowlist permission; this is used to force + // the WRITE_ALLOWLISTED_DEVICE_CONFIG path to log any flags that need to be allowlisted. + boolean isRestrictedShell = android.security.Flags.protectDeviceConfigFlags() + && hasAllowlistPermission; - if (isRoot) { - return; - } - - if (hasWritePermission) { + if (!isRestrictedShell && hasWritePermission) { assertCallingUserDenyList(flags); } else if (hasAllowlistPermission) { for (String flag : flags) { boolean namespaceAllowed = false; - for (String allowlistedPrefix : WritableNamespacePrefixes.ALLOWLIST) { - if (flag.startsWith(allowlistedPrefix)) { + if (isRestrictedShell) { + int delimiterIndex = flag.indexOf("/"); + String flagNamespace; + if (delimiterIndex != -1) { + flagNamespace = flag.substring(0, delimiterIndex); + } else { + flagNamespace = flag; + } + if (WritableNamespaces.ALLOWLIST.contains(flagNamespace)) { namespaceAllowed = true; - break; + } + } else { + for (String allowlistedPrefix : WritableNamespacePrefixes.ALLOWLIST) { + if (flag.startsWith(allowlistedPrefix)) { + namespaceAllowed = true; + break; + } } } if (!namespaceAllowed && !DeviceConfig.getAdbWritableFlags().contains(flag)) { - throw new SecurityException("Permission denial for flag '" - + flag - + "'; allowlist permission granted, but must add flag to the allowlist."); + Slog.wtf(LOG_TAG, "Permission denial for flag '" + flag + + "'; allowlist permission granted, but must add flag to the " + + "allowlist"); } } assertCallingUserDenyList(flags); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java new file mode 100644 index 000000000000..d835c5f5c179 --- /dev/null +++ b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.providers.settings; + +import android.util.ArraySet; + +import java.util.Arrays; +import java.util.Set; + +/** + * Contains the list of namespaces in which any flag can be written by adb without root + * permissions. + * <p> + * A security review is required for any namespace that's added to this list. To add to + * the list, create a change and tag the OWNER. In the commit message, include a + * description of the flag's functionality, and a justification for why it needs to be + * allowlisted. + */ +final class WritableNamespaces { + public static final Set<String> ALLOWLIST = + new ArraySet<String>(Arrays.asList( + "exo" + )); +} diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 9de7faf04df6..a62b7fd3db81 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -635,7 +635,8 @@ public class SettingsBackupTest { Settings.Global.Wearable.WEAR_MEDIA_SESSIONS_PACKAGE, Settings.Global.Wearable.WEAR_POWER_ANOMALY_SERVICE_ENABLED, Settings.Global.Wearable.CONNECTIVITY_KEEP_DATA_ON, - Settings.Global.Wearable.PHONE_SWITCHING_REQUEST_SOURCE); + Settings.Global.Wearable.PHONE_SWITCHING_REQUEST_SOURCE, + Settings.Global.Wearable.WEAR_SYSTEM_STATUS_TRAY_CONFIGURATION); private static final Set<String> BACKUP_DENY_LIST_SECURE_SETTINGS = newHashSet( diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 1070ebdbb946..fef0f8c7857a 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -242,6 +242,8 @@ <uses-permission android:name="android.permission.READ_LOWPAN_CREDENTIAL" /> <uses-permission android:name="android.permission.BLUETOOTH_STACK" /> <uses-permission android:name="android.permission.GET_ACCOUNTS" /> + <uses-permission android:name="android.permission.COPY_ACCOUNTS" /> + <uses-permission android:name="android.permission.REMOVE_ACCOUNTS" /> <uses-permission android:name="android.permission.RETRIEVE_WINDOW_TOKEN" /> <uses-permission android:name="android.permission.FRAME_STATS" /> <uses-permission android:name="android.permission.BIND_APPWIDGET" /> diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 67666c3db81f..c82c63c7c78b 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -1798,3 +1798,17 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "gsf_bouncer" + namespace: "systemui" + description: "Applies GSF font styles to Bouncer surfaces." + bug: "379364381" +} + +flag { + name: "gsf_quick_settings" + namespace: "systemui" + description: "Applies GSF font styles to Quick Settings surfaces." + bug: "379364381" +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt index 9390664d1283..597cbf24729b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt @@ -31,6 +31,7 @@ import androidx.compose.ui.viewinterop.AndroidView import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.SceneScope import com.android.systemui.biometrics.AuthController +import com.android.systemui.customization.R as customR import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags @@ -147,7 +148,7 @@ constructor( } else { val scaleFactor = authController.scaleFactor val bottomPaddingPx = - context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom) + context.resources.getDimensionPixelSize(customR.dimen.lock_icon_margin_bottom) val heightPx = windowViewBounds.bottom.toFloat() Pair( 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 b3fd097946d0..4bccac1e3ba0 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 @@ -430,14 +430,6 @@ internal class MutableSceneTransitionLayoutStateImpl( check(transitionStates.size == 1) check(transitionStates[0] is TransitionState.Idle) transitionStates = listOf(transition) - } else if (currentState == transition.replacedTransition) { - // Replace the transition. - transitionStates = - transitionStates.subList(0, transitionStates.lastIndex) + transition - - // Make sure it is removed from the finishedTransitions set if it was already - // finished. - finishedTransitions.remove(currentState) } else { // Append the new transition. transitionStates = transitionStates + transition diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt index b87cc5c88335..3622369b8ff9 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt @@ -132,6 +132,9 @@ class InterruptionHandlerTest { assertThat(state.currentTransitions) .comparingElementsUsing(FromToCurrentTriple) .containsExactly( + // Initial transition, A => B. + Triple(SceneA, SceneB, SceneB), + // Initial transition reversed, B back to A. Triple(SceneA, SceneB, SceneA), diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/res/xml/network_security_config.xml b/packages/SystemUI/customization/res/values-land/dimens.xml index 4bd9ca049f55..50f220c882bd 100644 --- a/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/res/xml/network_security_config.xml +++ b/packages/SystemUI/customization/res/values-land/dimens.xml @@ -1,6 +1,5 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2023 The Android Open Source Project +<?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. @@ -15,8 +14,6 @@ ~ limitations under the License. --> -<network-security-config> - <domain-config cleartextTrafficPermitted="true"> - <domain includeSubdomains="true">localhost</domain> - </domain-config> -</network-security-config> +<resources> + <dimen name="lock_icon_margin_bottom">24dp</dimen> +</resources>
\ No newline at end of file diff --git a/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml index 18073adf9e7a..876028195ff3 100644 --- a/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml +++ b/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml @@ -17,4 +17,5 @@ <resources> <dimen name="keyguard_smartspace_top_offset">0dp</dimen> <dimen name="status_view_margin_horizontal">8dp</dimen> + <dimen name="lock_icon_margin_bottom">60dp</dimen> </resources>
\ No newline at end of file diff --git a/packages/SystemUI/customization/res/values/dimens.xml b/packages/SystemUI/customization/res/values/dimens.xml index 041ae62670e5..21b4c7165226 100644 --- a/packages/SystemUI/customization/res/values/dimens.xml +++ b/packages/SystemUI/customization/res/values/dimens.xml @@ -40,4 +40,5 @@ <dimen name="date_weather_view_height">24dp</dimen> <dimen name="enhanced_smartspace_height">104dp</dimen> <dimen name="status_view_margin_horizontal">0dp</dimen> + <dimen name="lock_icon_margin_bottom">74dp</dimen> </resources>
\ No newline at end of file diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/development/data/repository/DevelopmentSettingRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/development/data/repository/DevelopmentSettingRepositoryTest.kt new file mode 100644 index 000000000000..e17b66e90c2d --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/development/data/repository/DevelopmentSettingRepositoryTest.kt @@ -0,0 +1,167 @@ +/* + * 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.development.data.repository + +import android.content.pm.UserInfo +import android.os.Build +import android.os.UserHandle +import android.os.UserManager +import android.os.userManager +import android.provider.Settings +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.android.systemui.util.settings.fakeGlobalSettings +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test +import kotlinx.coroutines.test.runTest +import org.junit.runner.RunWith +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.eq +import org.mockito.kotlin.stub + +@RunWith(AndroidJUnit4::class) +@SmallTest +class DevelopmentSettingRepositoryTest : SysuiTestCase() { + + private val kosmos = testKosmos() + + private val underTest = kosmos.developmentSettingRepository + + @Test + fun nonAdminUser_unrestricted_neverDevelopmentEnabled() = + with(kosmos) { + testScope.runTest { + val userInfo = nonAdminUserInfo + val settingEnabled by + collectLastValue(underTest.isDevelopmentSettingEnabled(userInfo)) + + setUserRestriction(userInfo.userHandle, restricted = false) + + assertThat(settingEnabled).isFalse() + + setSettingValue(false) + assertThat(settingEnabled).isFalse() + + setSettingValue(true) + assertThat(settingEnabled).isFalse() + } + } + + @Test + fun nonAdminUser_restricted_neverDevelopmentEnabled() = + with(kosmos) { + testScope.runTest { + val userInfo = nonAdminUserInfo + val settingEnabled by + collectLastValue(underTest.isDevelopmentSettingEnabled(userInfo)) + + setUserRestriction(userInfo.userHandle, restricted = true) + + assertThat(settingEnabled).isFalse() + + setSettingValue(false) + assertThat(settingEnabled).isFalse() + + setSettingValue(true) + assertThat(settingEnabled).isFalse() + } + } + + @Test + fun adminUser_unrestricted_defaultValueOfSetting() = + with(kosmos) { + testScope.runTest { + val userInfo = adminUserInfo + val settingEnabled by + collectLastValue(underTest.isDevelopmentSettingEnabled(userInfo)) + + setUserRestriction(userInfo.userHandle, restricted = false) + + val defaultValue = Build.TYPE == "eng" + + assertThat(settingEnabled).isEqualTo(defaultValue) + } + } + + @Test + fun adminUser_unrestricted_enabledTracksSetting() = + with(kosmos) { + testScope.runTest { + val userInfo = adminUserInfo + val settingEnabled by + collectLastValue(underTest.isDevelopmentSettingEnabled(userInfo)) + + setUserRestriction(userInfo.userHandle, restricted = false) + + setSettingValue(false) + assertThat(settingEnabled).isFalse() + + setSettingValue(true) + assertThat(settingEnabled).isTrue() + } + } + + @Test + fun adminUser_restricted_neverDevelopmentEnabled() = + with(kosmos) { + testScope.runTest { + val userInfo = adminUserInfo + val settingEnabled by + collectLastValue(underTest.isDevelopmentSettingEnabled(userInfo)) + + setUserRestriction(userInfo.userHandle, restricted = true) + + assertThat(settingEnabled).isFalse() + + setSettingValue(false) + assertThat(settingEnabled).isFalse() + + setSettingValue(true) + assertThat(settingEnabled).isFalse() + } + } + + private companion object { + const val USER_RESTRICTION = UserManager.DISALLOW_DEBUGGING_FEATURES + const val SETTING_NAME = Settings.Global.DEVELOPMENT_SETTINGS_ENABLED + + val adminUserInfo = + UserInfo( + /* id= */ 10, + /* name= */ "", + /* flags */ UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL, + ) + val nonAdminUserInfo = + UserInfo(/* id= */ 11, /* name= */ "", /* flags */ UserInfo.FLAG_FULL) + + fun Kosmos.setUserRestriction(userHandle: UserHandle, restricted: Boolean) { + userManager.stub { + on { hasUserRestrictionForUser(eq(USER_RESTRICTION), eq(userHandle)) } doReturn + restricted + } + } + + fun Kosmos.setSettingValue(enabled: Boolean) { + fakeGlobalSettings.putInt(SETTING_NAME, if (enabled) 1 else 0) + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/development/domain/interactor/BuildNumberInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/development/domain/interactor/BuildNumberInteractorTest.kt new file mode 100644 index 000000000000..f29dabe98664 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/development/domain/interactor/BuildNumberInteractorTest.kt @@ -0,0 +1,138 @@ +/* + * 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.development.domain.interactor + +import android.content.ClipData +import android.content.ClipDescription +import android.content.clipboardManager +import android.content.pm.UserInfo +import android.content.res.mainResources +import android.os.Build +import android.provider.Settings +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.internal.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.development.shared.model.BuildNumber +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runCurrent +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.fakeUserRepository +import com.android.systemui.util.settings.fakeGlobalSettings +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.verify + +@RunWith(AndroidJUnit4::class) +@SmallTest +class BuildNumberInteractorTest : SysuiTestCase() { + + private val kosmos = + testKosmos().apply { + fakeUserRepository.setUserInfos(listOf(adminUserInfo, nonAdminUserInfo)) + } + + private val expectedBuildNumber = + BuildNumber( + kosmos.mainResources.getString( + R.string.bugreport_status, + Build.VERSION.RELEASE_OR_CODENAME, + Build.ID, + ) + ) + + private val clipLabel = + kosmos.mainResources.getString( + com.android.systemui.res.R.string.build_number_clip_data_label + ) + + private val underTest = kosmos.buildNumberInteractor + + @Test + fun nonAdminUser_settingEnabled_buildNumberNull() = + with(kosmos) { + testScope.runTest { + val buildNumber by collectLastValue(underTest.buildNumber) + + fakeUserRepository.setSelectedUserInfo(nonAdminUserInfo) + setSettingValue(true) + + assertThat(buildNumber).isNull() + } + } + + @Test + fun adminUser_buildNumberCorrect_onlyWhenSettingEnabled() = + with(kosmos) { + testScope.runTest { + val buildNumber by collectLastValue(underTest.buildNumber) + + fakeUserRepository.setSelectedUserInfo(adminUserInfo) + + setSettingValue(false) + assertThat(buildNumber).isNull() + + setSettingValue(true) + assertThat(buildNumber).isEqualTo(expectedBuildNumber) + } + } + + @Test + fun copyToClipboard() = + with(kosmos) { + testScope.runTest { + fakeUserRepository.setSelectedUserInfo(adminUserInfo) + + underTest.copyBuildNumber() + runCurrent() + + val argumentCaptor = argumentCaptor<ClipData>() + + verify(clipboardManager).setPrimaryClip(argumentCaptor.capture()) + + with(argumentCaptor.firstValue) { + assertThat(description.label).isEqualTo(clipLabel) + assertThat(description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) + .isTrue() + assertThat(itemCount).isEqualTo(1) + assertThat(getItemAt(0).text).isEqualTo(expectedBuildNumber.value) + } + } + } + + private companion object { + const val SETTING_NAME = Settings.Global.DEVELOPMENT_SETTINGS_ENABLED + + val adminUserInfo = + UserInfo( + /* id= */ 10, + /* name= */ "", + /* flags */ UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL, + ) + val nonAdminUserInfo = + UserInfo(/* id= */ 11, /* name= */ "", /* flags */ UserInfo.FLAG_FULL) + + fun Kosmos.setSettingValue(enabled: Boolean) { + fakeGlobalSettings.putInt(SETTING_NAME, if (enabled) 1 else 0) + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt index c39c3fe5f527..2d54337def13 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt @@ -36,6 +36,7 @@ import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.ui.viewmodel.fakeDeviceEntryIconViewModelTransition import com.android.systemui.kosmos.testScope +import com.android.systemui.res.R import com.android.systemui.shade.shadeTestUtil import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat @@ -43,6 +44,7 @@ import kotlin.test.Test import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest +import org.junit.After import org.junit.Before import org.junit.runner.RunWith import platform.test.runner.parameterized.ParameterizedAndroidJunit4 @@ -84,6 +86,12 @@ class UdfpsAccessibilityOverlayViewModelTest(flags: FlagsParameterization) : Sys @Before fun setup() { underTest = kosmos.deviceEntryUdfpsAccessibilityOverlayViewModel + overrideResource(R.integer.udfps_padding_debounce_duration, 0) + } + + @After + fun teardown() { + mContext.orCreateTestableResources.removeOverride(R.integer.udfps_padding_debounce_duration) } @Test @@ -118,6 +126,7 @@ class UdfpsAccessibilityOverlayViewModelTest(flags: FlagsParameterization) : Sys runCurrent() assertThat(visible).isFalse() } + fun fpNotRunning_overlayNotVisible() = testScope.runTest { val visible by collectLastValue(underTest.visible) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModelTest.kt index b0959e4eea0b..d42b538cf355 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModelTest.kt @@ -27,10 +27,13 @@ import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepos import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.kosmos.testScope +import com.android.systemui.res.R import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest +import org.junit.After +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -43,6 +46,16 @@ class DeviceEntryForegroundViewModelTest : SysuiTestCase() { private val underTest: DeviceEntryForegroundViewModel = kosmos.deviceEntryForegroundIconViewModel + @Before + fun setup() { + context.orCreateTestableResources.addOverride(R.integer.udfps_padding_debounce_duration, 0) + } + + @After + fun teardown() { + context.orCreateTestableResources.removeOverride(R.integer.udfps_padding_debounce_duration) + } + @Test fun aodIconColorWhite() = testScope.runTest { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt index 450aadd70171..ebc00c3897cb 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt @@ -20,6 +20,7 @@ import android.content.Context import android.content.Intent import android.graphics.drawable.Icon import android.os.UserHandle +import com.android.wm.shell.bubbles.Bubble import com.android.wm.shell.bubbles.Bubbles import java.util.Optional import kotlinx.coroutines.CoroutineDispatcher @@ -33,14 +34,29 @@ import kotlinx.coroutines.CoroutineDispatcher class FakeNoteTaskBubbleController( unUsed1: Context, unsUsed2: CoroutineDispatcher, - private val optionalBubbles: Optional<Bubbles> + private val optionalBubbles: Optional<Bubbles>, ) : NoteTaskBubblesController(unUsed1, unsUsed2) { override suspend fun areBubblesAvailable() = optionalBubbles.isPresent - override suspend fun showOrHideAppBubble(intent: Intent, userHandle: UserHandle, icon: Icon) { + override suspend fun showOrHideAppBubble( + intent: Intent, + userHandle: UserHandle, + icon: Icon, + bubbleExpandBehavior: NoteTaskBubbleExpandBehavior, + ) { optionalBubbles.ifPresentOrElse( - { bubbles -> bubbles.showOrHideAppBubble(intent, userHandle, icon) }, - { throw IllegalAccessException() } + { bubbles -> + if ( + bubbleExpandBehavior == NoteTaskBubbleExpandBehavior.KEEP_IF_EXPANDED && + bubbles.isBubbleExpanded( + Bubble.getAppBubbleKeyForApp(intent.`package`, userHandle) + ) + ) { + return@ifPresentOrElse + } + bubbles.showOrHideAppBubble(intent, userHandle, icon) + }, + { throw IllegalAccessException() }, ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt index 9ef6b9c13315..e55d6ad6c5a0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt @@ -21,9 +21,9 @@ import android.graphics.drawable.Icon import android.os.UserHandle import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.notetask.NoteTaskBubblesController.NoteTaskBubblesService +import com.android.systemui.res.R import com.android.wm.shell.bubbles.Bubbles import com.google.common.truth.Truth.assertThat import java.util.Optional @@ -33,6 +33,9 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.never +import org.mockito.kotlin.whenever /** atest SystemUITests:NoteTaskBubblesServiceTest */ @SmallTest @@ -61,12 +64,40 @@ internal class NoteTaskBubblesServiceTest : SysuiTestCase() { } @Test - fun showOrHideAppBubble() { + fun showOrHideAppBubble_defaultExpandBehavior_shouldCallBubblesApi() { val intent = Intent() val user = UserHandle.SYSTEM val icon = Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget) + val bubbleExpandBehavior = NoteTaskBubbleExpandBehavior.DEFAULT + whenever(bubbles.isBubbleExpanded(any())).thenReturn(false) + + createServiceBinder().showOrHideAppBubble(intent, user, icon, bubbleExpandBehavior) + + verify(bubbles).showOrHideAppBubble(intent, user, icon) + } + + @Test + fun showOrHideAppBubble_keepIfExpanded_bubbleShown_shouldNotCallBubblesApi() { + val intent = Intent().apply { setPackage("test") } + val user = UserHandle.SYSTEM + val icon = Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget) + val bubbleExpandBehavior = NoteTaskBubbleExpandBehavior.KEEP_IF_EXPANDED + whenever(bubbles.isBubbleExpanded(any())).thenReturn(true) + + createServiceBinder().showOrHideAppBubble(intent, user, icon, bubbleExpandBehavior) + + verify(bubbles, never()).showOrHideAppBubble(intent, user, icon) + } + + @Test + fun showOrHideAppBubble_keepIfExpanded_bubbleNotShown_shouldCallBubblesApi() { + val intent = Intent().apply { setPackage("test") } + val user = UserHandle.SYSTEM + val icon = Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget) + val bubbleExpandBehavior = NoteTaskBubbleExpandBehavior.KEEP_IF_EXPANDED + whenever(bubbles.isBubbleExpanded(any())).thenReturn(false) - createServiceBinder().showOrHideAppBubble(intent, user, icon) + createServiceBinder().showOrHideAppBubble(intent, user, icon, bubbleExpandBehavior) verify(bubbles).showOrHideAppBubble(intent, user, icon) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt index 8f4078b88fc0..d3578fb9f69b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt @@ -19,6 +19,7 @@ import android.os.UserHandle import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.notetask.NoteTaskEntryPoint.QS_NOTES_TILE import com.android.systemui.notetask.NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE import com.google.common.truth.Truth.assertThat import org.junit.Test @@ -44,10 +45,19 @@ internal class NoteTaskInfoTest : SysuiTestCase() { } @Test - fun launchMode_keyguardUnlocked_launchModeAppBubble() { + fun launchMode_keyguardUnlocked_launchModeAppBubble_withDefaultExpandBehavior() { val underTest = DEFAULT_INFO.copy(isKeyguardLocked = false) - assertThat(underTest.launchMode).isEqualTo(NoteTaskLaunchMode.AppBubble) + assertThat(underTest.launchMode) + .isEqualTo(NoteTaskLaunchMode.AppBubble(NoteTaskBubbleExpandBehavior.DEFAULT)) + } + + @Test + fun launchMode_keyguardUnlocked_qsTileEntryPoint_launchModeAppBubble_withKeepIfExpandedExpandBehavior() { + val underTest = DEFAULT_INFO.copy(isKeyguardLocked = false, entryPoint = QS_NOTES_TILE) + + assertThat(underTest.launchMode) + .isEqualTo(NoteTaskLaunchMode.AppBubble(NoteTaskBubbleExpandBehavior.KEEP_IF_EXPANDED)) } private companion object { diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml index 235015b5286f..70ae5c1832af 100644 --- a/packages/SystemUI/res/values-land/dimens.xml +++ b/packages/SystemUI/res/values-land/dimens.xml @@ -91,7 +91,6 @@ <dimen name="notification_blocker_channel_list_height">128dp</dimen> <dimen name="keyguard_indication_margin_bottom">8dp</dimen> - <dimen name="lock_icon_margin_bottom">24dp</dimen> <!-- Keyboard shortcuts helper --> <dimen name="ksh_container_horizontal_margin">48dp</dimen> diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml index 75bee9f9266a..055c3a641d1b 100644 --- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml @@ -20,7 +20,6 @@ <!-- keyguard--> <dimen name="keyguard_indication_margin_bottom">25dp</dimen> <dimen name="ambient_indication_margin_bottom">115dp</dimen> - <dimen name="lock_icon_margin_bottom">60dp</dimen> <!-- margin from keyguard status bar to clock. For split shade it should be keyguard_split_shade_top_margin - status_bar_header_height_keyguard = 8dp --> <dimen name="keyguard_clock_top_margin">8dp</dimen> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 580f6d302b37..8cab15506d20 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -948,7 +948,6 @@ <dimen name="keyguard_translate_distance_on_swipe_up">-200dp</dimen> <dimen name="keyguard_indication_margin_bottom">32dp</dimen> - <dimen name="lock_icon_margin_bottom">74dp</dimen> <dimen name="ambient_indication_margin_bottom">71dp</dimen> @@ -2057,6 +2056,9 @@ <!-- UDFPS view attributes --> <!-- UDFPS icon size in microns/um --> <dimen name="udfps_icon_size" format="float">6000</dimen> + <!-- Limits the updates to at most one update per debounce duration to avoid too many + updates due to quick changes to padding. --> + <integer name="udfps_padding_debounce_duration">100</integer> <!-- Microns/ums (1000 um = 1mm) per pixel for the given device. If unspecified, UI that relies on this value will not be sized correctly. --> <item name="pixel_pitch" format="float" type="dimen">-1</item> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 245ba0aca876..c3f4222a5eb8 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -3214,6 +3214,9 @@ <!-- Text to display when copying the build number off QS [CHAR LIMIT=NONE]--> <string name="build_number_copy_toast">Build number copied to clipboard.</string> + <!-- Text for accessibility action for copying content to clipboard [CHAR LIMIT=NONE]--> + <string name="copy_to_clipboard_a11y_action">copy to clipboard.</string> + <!-- Status for conversation without interaction data [CHAR LIMIT=120] --> <string name="basic_status">Open conversation</string> <!--Title text for Conversation widget set up screen [CHAR LIMIT=180] --> diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt index abbbd730c47e..976329580c60 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt @@ -36,8 +36,10 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn +import kotlin.math.max /** Encapsulates business logic for interacting with the UDFPS overlay. */ @SysUISingleton @@ -124,8 +126,9 @@ constructor( udfpsOverlayParams.map { params -> val sensorWidth = params.nativeSensorBounds.right - params.nativeSensorBounds.left val nativePadding = (sensorWidth - iconSize) / 2 - (nativePadding * params.scaleFactor).toInt() - } + // padding can be negative when udfpsOverlayParams has not been initialized yet. + max(0, (nativePadding * params.scaleFactor).toInt()) + }.distinctUntilChanged() companion object { private const val TAG = "UdfpsOverlayInteractor" diff --git a/packages/SystemUI/src/com/android/systemui/development/data/repository/DevelopmentSettingRepository.kt b/packages/SystemUI/src/com/android/systemui/development/data/repository/DevelopmentSettingRepository.kt new file mode 100644 index 000000000000..a8fa9797ebfd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/development/data/repository/DevelopmentSettingRepository.kt @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.development.data.repository + +import android.content.pm.UserInfo +import android.os.Build +import android.os.UserManager +import android.provider.Settings +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.util.kotlin.emitOnStart +import com.android.systemui.util.settings.GlobalSettings +import com.android.systemui.util.settings.SettingsProxyExt.observerFlow +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.withContext + +@SysUISingleton +class DevelopmentSettingRepository +@Inject +constructor( + private val globalSettings: GlobalSettings, + private val userManager: UserManager, + @Background private val backgroundDispatcher: CoroutineDispatcher, +) { + private val settingFlow = globalSettings.observerFlow(SETTING) + + /** + * Indicates whether development settings is enabled for this user. The conditions are: + * * Setting is enabled (defaults to true in eng builds) + * * User is an admin + * * User is not restricted from Debugging features. + */ + fun isDevelopmentSettingEnabled(userInfo: UserInfo): Flow<Boolean> { + return settingFlow + .emitOnStart() + .map { checkDevelopmentSettingEnabled(userInfo) } + .flowOn(backgroundDispatcher) + } + + private suspend fun checkDevelopmentSettingEnabled(userInfo: UserInfo): Boolean { + val hasUserRestriction = + withContext(backgroundDispatcher) { + userManager.hasUserRestrictionForUser( + UserManager.DISALLOW_DEBUGGING_FEATURES, + userInfo.userHandle, + ) + } + val isSettingEnabled = + withContext(backgroundDispatcher) { + globalSettings.getInt(SETTING, DEFAULT_ENABLED) != 0 + } + val isAdmin = userInfo.isAdmin + return isAdmin && !hasUserRestriction && isSettingEnabled + } + + private companion object { + val DEFAULT_ENABLED = if (Build.TYPE == "eng") 1 else 0 + + const val SETTING = Settings.Global.DEVELOPMENT_SETTINGS_ENABLED + } +} diff --git a/packages/SystemUI/src/com/android/systemui/development/domain/interactor/BuildNumberInteractor.kt b/packages/SystemUI/src/com/android/systemui/development/domain/interactor/BuildNumberInteractor.kt new file mode 100644 index 000000000000..4d786fe76bc1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/development/domain/interactor/BuildNumberInteractor.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.development.domain.interactor + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.res.Resources +import android.os.Build +import android.os.UserHandle +import com.android.internal.R as InternalR +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.development.data.repository.DevelopmentSettingRepository +import com.android.systemui.development.shared.model.BuildNumber +import com.android.systemui.res.R as SystemUIR +import com.android.systemui.user.data.repository.UserRepository +import com.android.systemui.user.utils.UserScopedService +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapConcat +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.withContext + +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class BuildNumberInteractor +@Inject +constructor( + repository: DevelopmentSettingRepository, + @Main resources: Resources, + private val userRepository: UserRepository, + private val clipboardManagerProvider: UserScopedService<ClipboardManager>, + @Background private val backgroundDispatcher: CoroutineDispatcher, +) { + + /** + * Build number, or `null` if Development Settings is not enabled for the current user. + * + * @see DevelopmentSettingRepository.isDevelopmentSettingEnabled + */ + val buildNumber: Flow<BuildNumber?> = + userRepository.selectedUserInfo + .flatMapConcat { userInfo -> repository.isDevelopmentSettingEnabled(userInfo) } + .map { enabled -> buildText.takeIf { enabled } } + + private val buildText = + BuildNumber( + resources.getString( + InternalR.string.bugreport_status, + Build.VERSION.RELEASE_OR_CODENAME, + Build.ID, + ) + ) + + private val clipLabel = resources.getString(SystemUIR.string.build_number_clip_data_label) + + private val currentUserHandle: UserHandle + get() = userRepository.getSelectedUserInfo().userHandle + + /** + * Copy to the clipboard the build number for the current user. + * + * This can be performed regardless of the current user having Development Settings enabled + */ + suspend fun copyBuildNumber() { + withContext(backgroundDispatcher) { + clipboardManagerProvider + .forUser(currentUserHandle) + .setPrimaryClip(ClipData.newPlainText(clipLabel, buildText.value)) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/development/shared/model/BuildNumber.kt b/packages/SystemUI/src/com/android/systemui/development/shared/model/BuildNumber.kt new file mode 100644 index 000000000000..5bd713fc4840 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/development/shared/model/BuildNumber.kt @@ -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 com.android.systemui.development.shared.model + +@JvmInline value class BuildNumber(val value: String) diff --git a/packages/SystemUI/src/com/android/systemui/development/ui/compose/BuildNumber.kt b/packages/SystemUI/src/com/android/systemui/development/ui/compose/BuildNumber.kt new file mode 100644 index 000000000000..72e1cede0002 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/development/ui/compose/BuildNumber.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.development.ui.compose + +import androidx.compose.foundation.basicMarquee +import androidx.compose.foundation.focusable +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.material3.Text +import androidx.compose.material3.minimumInteractiveComponentSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.hapticfeedback.HapticFeedbackType +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalHapticFeedback +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.onLongClick +import androidx.compose.ui.semantics.semantics +import com.android.systemui.communal.ui.compose.extensions.detectLongPressGesture +import com.android.systemui.development.ui.viewmodel.BuildNumberViewModel +import com.android.systemui.lifecycle.rememberViewModel +import com.android.systemui.res.R + +@Composable +fun BuildNumber( + viewModelFactory: BuildNumberViewModel.Factory, + textColor: Color, + modifier: Modifier = Modifier, +) { + val viewModel = rememberViewModel(traceName = "BuildNumber") { viewModelFactory.create() } + + val buildNumber = viewModel.buildNumber + + if (buildNumber != null) { + val haptics = LocalHapticFeedback.current + val copyToClipboardActionLabel = stringResource(id = R.string.copy_to_clipboard_a11y_action) + + Text( + text = buildNumber.value, + modifier = + modifier + .focusable() + .wrapContentWidth() + // Using this instead of combinedClickable because this node should not support + // single click + .pointerInput(Unit) { + detectLongPressGesture { + haptics.performHapticFeedback(HapticFeedbackType.LongPress) + viewModel.onBuildNumberLongPress() + } + } + .semantics { + onLongClick(copyToClipboardActionLabel) { + viewModel.onBuildNumberLongPress() + true + } + } + .basicMarquee(iterations = 1, initialDelayMillis = 2000) + .minimumInteractiveComponentSize(), + color = textColor, + maxLines = 1, + ) + } else { + Spacer(modifier) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/development/ui/viewmodel/BuildNumberViewModel.kt b/packages/SystemUI/src/com/android/systemui/development/ui/viewmodel/BuildNumberViewModel.kt new file mode 100644 index 000000000000..68c51ea80ffd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/development/ui/viewmodel/BuildNumberViewModel.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.development.ui.viewmodel + +import androidx.compose.runtime.getValue +import com.android.systemui.development.domain.interactor.BuildNumberInteractor +import com.android.systemui.development.shared.model.BuildNumber +import com.android.systemui.lifecycle.ExclusiveActivatable +import com.android.systemui.lifecycle.Hydrator +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.launch + +/** View model for UI that (optionally) shows the build number and copies it on long press. */ +class BuildNumberViewModel +@AssistedInject +constructor(private val buildNumberInteractor: BuildNumberInteractor) : ExclusiveActivatable() { + + private val hydrator = Hydrator("BuildNumberViewModel") + + private val copyRequests = Channel<Unit>() + + val buildNumber: BuildNumber? by + hydrator.hydratedStateOf( + traceName = "buildNumber", + initialValue = null, + source = buildNumberInteractor.buildNumber, + ) + + fun onBuildNumberLongPress() { + copyRequests.trySend(Unit) + } + + override suspend fun onActivated(): Nothing { + coroutineScope { + launch { hydrator.activate() } + launch { + copyRequests.receiveAsFlow().collect { buildNumberInteractor.copyBuildNumber() } + } + awaitCancellation() + } + } + + @AssistedFactory + interface Factory { + fun create(): BuildNumberViewModel + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt index b51bb7b229ee..aa7eb2933992 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt @@ -28,6 +28,7 @@ import androidx.annotation.VisibleForTesting import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import com.android.systemui.biometrics.AuthController +import com.android.systemui.customization.R as customR import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags @@ -115,7 +116,7 @@ constructor( val scaleFactor: Float = authController.scaleFactor val mBottomPaddingPx = - context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom) + context.resources.getDimensionPixelSize(customR.dimen.lock_icon_margin_bottom) val bounds = windowManager.currentWindowMetrics.bounds var widthPixels = bounds.right.toFloat() if (featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt index 5065fcbbac93..19652525bee0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt @@ -31,8 +31,10 @@ import com.android.systemui.shade.ShadeDisplayAware import javax.inject.Inject import kotlin.math.roundToInt import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf @@ -40,6 +42,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart /** Models the UI state for the device entry icon foreground view (displayed icon). */ +@OptIn(FlowPreview::class) @ExperimentalCoroutinesApi @SysUISingleton class DeviceEntryForegroundViewModel @@ -97,7 +100,7 @@ constructor( private val padding: Flow<Int> = deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { udfpsSupported -> if (udfpsSupported) { - udfpsOverlayInteractor.iconPadding + udfpsOverlayInteractor.iconPadding.debounce(udfpsPaddingDebounceDuration.toLong()) } else { configurationInteractor.scaleForResolution.map { scale -> (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale) @@ -120,6 +123,9 @@ constructor( ) } + private val udfpsPaddingDebounceDuration: Int + get() = context.resources.getInteger(R.integer.udfps_padding_debounce_duration) + data class ForegroundIconViewModel( val type: DeviceEntryIconView.IconType, val useAodVariant: Boolean, diff --git a/packages/SystemUI/src/com/android/systemui/notetask/INoteTaskBubblesService.aidl b/packages/SystemUI/src/com/android/systemui/notetask/INoteTaskBubblesService.aidl index 3e947d9d2b16..7803f229dda7 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/INoteTaskBubblesService.aidl +++ b/packages/SystemUI/src/com/android/systemui/notetask/INoteTaskBubblesService.aidl @@ -16,6 +16,7 @@ package com.android.systemui.notetask; +import com.android.systemui.notetask.NoteTaskBubbleExpandBehavior; import android.content.Intent; import android.graphics.drawable.Icon; import android.os.UserHandle; @@ -25,5 +26,6 @@ interface INoteTaskBubblesService { boolean areBubblesAvailable(); - void showOrHideAppBubble(in Intent intent, in UserHandle userHandle, in Icon icon); + void showOrHideAppBubble(in Intent intent, in UserHandle userHandle, in Icon icon, + in NoteTaskBubbleExpandBehavior bubbleExpandBehavior); } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubbleExpandBehavior.aidl b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubbleExpandBehavior.aidl new file mode 100644 index 000000000000..86a06a568a4b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubbleExpandBehavior.aidl @@ -0,0 +1,3 @@ +package com.android.systemui.notetask; + +parcelable NoteTaskBubbleExpandBehavior;
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubbleExpandBehavior.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubbleExpandBehavior.kt new file mode 100644 index 000000000000..63b38a1b73f9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubbleExpandBehavior.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.notetask + +import android.os.Parcel +import android.os.Parcelable + +enum class NoteTaskBubbleExpandBehavior : Parcelable { + /** + * The default bubble expand behavior for note task bubble: The bubble will collapse if there is + * already an expanded bubble, The bubble will expand if there is a collapsed bubble. + */ + DEFAULT, + /** + * The special bubble expand behavior for note task bubble: The bubble will stay expanded, not + * collapse, if there is already an expanded bubble, The bubble will expand if there is a + * collapsed bubble. + */ + KEEP_IF_EXPANDED; + + override fun describeContents(): Int { + return 0 + } + + override fun writeToParcel(dest: Parcel, flags: Int) { + dest.writeString(name) + } + + companion object CREATOR : Parcelable.Creator<NoteTaskBubbleExpandBehavior> { + override fun createFromParcel(parcel: Parcel?): NoteTaskBubbleExpandBehavior { + return parcel?.readString()?.let { valueOf(it) } ?: DEFAULT + } + + override fun newArray(size: Int) = arrayOfNulls<NoteTaskBubbleExpandBehavior>(size) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubblesController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubblesController.kt index ec205f87d9fa..169285f6742e 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubblesController.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubblesController.kt @@ -27,6 +27,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.log.DebugLogger.debugLog +import com.android.wm.shell.bubbles.Bubble import com.android.wm.shell.bubbles.Bubbles import java.util.Optional import javax.inject.Inject @@ -48,7 +49,7 @@ open class NoteTaskBubblesController @Inject constructor( @Application private val context: Context, - @Background private val bgDispatcher: CoroutineDispatcher + @Background private val bgDispatcher: CoroutineDispatcher, ) { private val serviceConnector: ServiceConnector<INoteTaskBubblesService> = @@ -57,7 +58,7 @@ constructor( Intent(context, NoteTaskBubblesService::class.java), Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE, UserHandle.USER_SYSTEM, - INoteTaskBubblesService.Stub::asInterface + INoteTaskBubblesService.Stub::asInterface, ) /** Returns whether notes app bubble is supported. */ @@ -79,11 +80,12 @@ constructor( open suspend fun showOrHideAppBubble( intent: Intent, userHandle: UserHandle, - icon: Icon + icon: Icon, + bubbleExpandBehavior: NoteTaskBubbleExpandBehavior, ) { withContext(bgDispatcher) { serviceConnector - .post { it.showOrHideAppBubble(intent, userHandle, icon) } + .post { it.showOrHideAppBubble(intent, userHandle, icon, bubbleExpandBehavior) } .whenComplete { _, error -> if (error != null) { debugLog(error = error) { @@ -120,16 +122,28 @@ constructor( override fun showOrHideAppBubble( intent: Intent, userHandle: UserHandle, - icon: Icon + icon: Icon, + bubbleExpandBehavior: NoteTaskBubbleExpandBehavior, ) { mOptionalBubbles.ifPresentOrElse( - { bubbles -> bubbles.showOrHideAppBubble(intent, userHandle, icon) }, + { bubbles -> + if ( + bubbleExpandBehavior == + NoteTaskBubbleExpandBehavior.KEEP_IF_EXPANDED && + bubbles.isBubbleExpanded( + Bubble.getAppBubbleKeyForApp(intent.`package`, userHandle) + ) + ) { + return@ifPresentOrElse + } + bubbles.showOrHideAppBubble(intent, userHandle, icon) + }, { debugLog { "Failed to show or hide bubble for intent $intent," + "user $user, and icon $icon as bubble is empty." } - } + }, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt index 1fa5baaa21ae..a615963ed2ca 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt @@ -84,7 +84,7 @@ constructor( private val userTracker: UserTracker, private val secureSettings: SecureSettings, @Application private val applicationScope: CoroutineScope, - @Background private val bgCoroutineContext: CoroutineContext + @Background private val bgCoroutineContext: CoroutineContext, ) { @VisibleForTesting val infoReference = AtomicReference<NoteTaskInfo?>() @@ -98,7 +98,7 @@ constructor( if (key != Bubble.getAppBubbleKeyForApp(info.packageName, info.user)) return // Safe guard mechanism, this callback should only be called for app bubbles. - if (info.launchMode != NoteTaskLaunchMode.AppBubble) return + if (info.launchMode !is NoteTaskLaunchMode.AppBubble) return if (isExpanding) { debugLog { "onBubbleExpandChanged - expanding: $info" } @@ -117,10 +117,8 @@ constructor( } else { getUserForHandlingNotesTaking(entryPoint) } - activityContext.startActivityAsUser( - createNotesRoleHolderSettingsIntent(), - user - ) + + activityContext.startActivityAsUser(createNotesRoleHolderSettingsIntent(), user) } /** @@ -140,8 +138,7 @@ constructor( entryPoint == QUICK_AFFORDANCE -> { userTracker.userProfiles .firstOrNull { userManager.isManagedProfile(it.id) } - ?.userHandle - ?: userTracker.userHandle + ?.userHandle ?: userTracker.userHandle } // On work profile devices, SysUI always run in the main user. else -> userTracker.userHandle @@ -158,19 +155,14 @@ constructor( * * That will let users open other apps in full screen, and take contextual notes. */ - fun showNoteTask( - entryPoint: NoteTaskEntryPoint, - ) { + fun showNoteTask(entryPoint: NoteTaskEntryPoint) { if (!isEnabled) return showNoteTaskAsUser(entryPoint, getUserForHandlingNotesTaking(entryPoint)) } /** A variant of [showNoteTask] which launches note task in the given [user]. */ - fun showNoteTaskAsUser( - entryPoint: NoteTaskEntryPoint, - user: UserHandle, - ) { + fun showNoteTaskAsUser(entryPoint: NoteTaskEntryPoint, user: UserHandle) { if (!isEnabled) return applicationScope.launch("$TAG#showNoteTaskAsUser") { @@ -178,10 +170,7 @@ constructor( } } - private suspend fun awaitShowNoteTaskAsUser( - entryPoint: NoteTaskEntryPoint, - user: UserHandle, - ) { + private suspend fun awaitShowNoteTaskAsUser(entryPoint: NoteTaskEntryPoint, user: UserHandle) { if (!isEnabled) return if (!noteTaskBubblesController.areBubblesAvailable()) { @@ -222,7 +211,13 @@ constructor( val intent = createNoteTaskIntent(info) val icon = Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget) - noteTaskBubblesController.showOrHideAppBubble(intent, user, icon) + noteTaskBubblesController.showOrHideAppBubble( + intent, + user, + icon, + info.launchMode.bubbleExpandBehavior, + ) + // App bubble logging happens on `onBubbleExpandChanged`. debugLog { "onShowNoteTask - opened as app bubble: $info" } } @@ -399,8 +394,8 @@ constructor( const val EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE = "extra_shortcut_badge_override_package" /** Returns notes role holder settings intent. */ - fun createNotesRoleHolderSettingsIntent() = Intent(Intent.ACTION_MANAGE_DEFAULT_APP). - putExtra(Intent.EXTRA_ROLE_NAME, ROLE_NOTES) + fun createNotesRoleHolderSettingsIntent() = + Intent(Intent.ACTION_MANAGE_DEFAULT_APP).putExtra(Intent.EXTRA_ROLE_NAME, ROLE_NOTES) } } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt index 269eb870686c..8319e07525d8 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt @@ -31,6 +31,10 @@ data class NoteTaskInfo( if (isKeyguardLocked || entryPoint == WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE) { NoteTaskLaunchMode.Activity } else { - NoteTaskLaunchMode.AppBubble + if (entryPoint == NoteTaskEntryPoint.QS_NOTES_TILE) { + NoteTaskLaunchMode.AppBubble(NoteTaskBubbleExpandBehavior.KEEP_IF_EXPANDED) + } else { + NoteTaskLaunchMode.AppBubble(NoteTaskBubbleExpandBehavior.DEFAULT) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskLaunchMode.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskLaunchMode.kt index 836e103f4d69..6c85f20f2bce 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskLaunchMode.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskLaunchMode.kt @@ -26,7 +26,8 @@ import com.android.wm.shell.bubbles.Bubbles sealed class NoteTaskLaunchMode { /** @see Bubbles.showOrHideAppBubble */ - object AppBubble : NoteTaskLaunchMode() + data class AppBubble(val bubbleExpandBehavior: NoteTaskBubbleExpandBehavior) : + NoteTaskLaunchMode() /** @see Context.startActivity */ object Activity : NoteTaskLaunchMode() diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt index 4e094cc77eae..789fdebc36eb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt @@ -16,12 +16,17 @@ package com.android.systemui.qs.panels.ui.compose -import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.requiredHeight +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.CornerSize import androidx.compose.foundation.shape.RoundedCornerShape @@ -39,16 +44,17 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.padding import com.android.systemui.compose.modifiers.sysuiResTag +import com.android.systemui.development.ui.compose.BuildNumber +import com.android.systemui.development.ui.viewmodel.BuildNumberViewModel import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.qs.panels.dagger.PaginatedBaseLayoutType -import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout.Dimensions.FooterHeight -import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout.Dimensions.InterPageSpacing +import com.android.systemui.qs.panels.ui.compose.Dimensions.FooterHeight +import com.android.systemui.qs.panels.ui.compose.Dimensions.InterPageSpacing import com.android.systemui.qs.panels.ui.viewmodel.PaginatedGridViewModel import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel import com.android.systemui.qs.ui.compose.borderOnFocus @@ -121,38 +127,78 @@ constructor( TileGrid(tiles = page, modifier = Modifier, editModeStart = {}) } } - // Use requiredHeight so it won't be squished if the view doesn't quite fit. As this is - // expected to be inside a scrollable container, this should not be an issue. - Box(modifier = Modifier.requiredHeight(FooterHeight).fillMaxWidth()) { - PagerDots( - pagerState = pagerState, - activeColor = MaterialTheme.colorScheme.primary, - nonActiveColor = MaterialTheme.colorScheme.surfaceVariant, - modifier = Modifier.align(Alignment.Center), - ) - CompositionLocalProvider(value = LocalContentColor provides Color.White) { - IconButton( - onClick = editModeStart, - shape = RoundedCornerShape(CornerSize(28.dp)), - modifier = - Modifier.align(Alignment.CenterEnd) - .borderOnFocus( - color = MaterialTheme.colorScheme.secondary, - cornerSize = CornerSize(FooterHeight / 2), - ), - ) { - Icon( - imageVector = Icons.Default.Edit, - contentDescription = stringResource(id = R.string.qs_edit), + FooterBar( + buildNumberViewModelFactory = viewModel.buildNumberViewModelFactory, + pagerState = pagerState, + editModeStart = editModeStart, + ) + } + } +} + +private object Dimensions { + val FooterHeight = 48.dp + val InterPageSpacing = 16.dp +} + +@Composable +private fun FooterBar( + buildNumberViewModelFactory: BuildNumberViewModel.Factory, + pagerState: PagerState, + editModeStart: () -> Unit, +) { + // Use requiredHeight so it won't be squished if the view doesn't quite fit. As this is + // expected to be inside a scrollable container, this should not be an issue. + // Also, we construct the layout this way to do the following: + // * PagerDots is centered in the row, taking as much space as it needs. + // * On the start side, we place the BuildNumber, taking as much space as it needs, but + // constrained by the available space left over after PagerDots + // * On the end side, we place the edit mode button, with the same constraints as for + // BuildNumber (but it will usually fit, as it's just a square button). + Row( + modifier = Modifier.requiredHeight(FooterHeight).fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = spacedBy(8.dp), + ) { + Row(Modifier.weight(1f)) { + BuildNumber( + viewModelFactory = buildNumberViewModelFactory, + textColor = MaterialTheme.colorScheme.onSurface, + modifier = + Modifier.borderOnFocus( + color = MaterialTheme.colorScheme.secondary, + cornerSize = CornerSize(1.dp), ) - } + .wrapContentSize(), + ) + Spacer(modifier = Modifier.weight(1f)) + } + PagerDots( + pagerState = pagerState, + activeColor = MaterialTheme.colorScheme.primary, + nonActiveColor = MaterialTheme.colorScheme.surfaceVariant, + modifier = Modifier.wrapContentWidth(), + ) + Row(Modifier.weight(1f)) { + Spacer(modifier = Modifier.weight(1f)) + CompositionLocalProvider( + value = LocalContentColor provides MaterialTheme.colorScheme.onSurface + ) { + IconButton( + onClick = editModeStart, + shape = RoundedCornerShape(CornerSize(28.dp)), + modifier = + Modifier.borderOnFocus( + color = MaterialTheme.colorScheme.secondary, + cornerSize = CornerSize(FooterHeight / 2), + ), + ) { + Icon( + imageVector = Icons.Default.Edit, + contentDescription = stringResource(id = R.string.qs_edit), + ) } } } } - - private object Dimensions { - val FooterHeight = 48.dp - val InterPageSpacing = 16.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 cb57c6710553..0a80a19871fd 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 @@ -126,7 +126,7 @@ fun Tile( val currentBounceableInfo by rememberUpdatedState(bounceableInfo) val resources = resources() val uiState = remember(state, resources) { state.toUiState(resources) } - val colors = TileDefaults.getColorForState(uiState) + val colors = TileDefaults.getColorForState(uiState, iconOnly) val hapticsViewModel: TileHapticsViewModel? = rememberViewModel(traceName = "TileHapticsViewModel") { tileHapticsViewModelFactoryProvider.getHapticsViewModelFactory()?.create(tile) @@ -365,22 +365,24 @@ private object TileDefaults { ) @Composable - fun getColorForState(uiState: TileUiState): TileColors { + fun getColorForState(uiState: TileUiState, iconOnly: Boolean): TileColors { return when (uiState.state) { STATE_ACTIVE -> { - if (uiState.handlesSecondaryClick) { + if (uiState.handlesSecondaryClick && !iconOnly) { activeDualTargetTileColors() } else { activeTileColors() } } + STATE_INACTIVE -> { - if (uiState.handlesSecondaryClick) { + if (uiState.handlesSecondaryClick && !iconOnly) { inactiveDualTargetTileColors() } else { inactiveTileColors() } } + else -> unavailableTileColors() } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt index e5607eb6e620..bff330b98fda 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs.panels.ui.viewmodel import androidx.compose.runtime.getValue +import com.android.systemui.development.ui.viewmodel.BuildNumberViewModel import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.lifecycle.Hydrator import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QS @@ -34,6 +35,7 @@ constructor( columnsWithMediaViewModelFactory: QSColumnsViewModel.Factory, paginatedGridInteractor: PaginatedGridInteractor, inFirstPageViewModel: InFirstPageViewModel, + val buildNumberViewModelFactory: BuildNumberViewModel.Factory, ) : IconTilesViewModel by iconTilesViewModel, ExclusiveActivatable() { private val hydrator = Hydrator("PaginatedGridViewModel") diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt index 35b1b9636263..ab3862b75ee8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt @@ -81,7 +81,7 @@ constructor( // additional // guidance on how to auto add your tile throw UnsupportedOperationException( - "Turning on tile is not supported now" + "Turning on tile is not supported now. Tile spec: $tileSpec" ) } } 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 856333ea724e..aad8b4ba1191 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -2707,6 +2707,44 @@ public class BubblesTest extends SysuiTestCase { eq(BubbleLogger.Event.BUBBLE_BAR_EXPANDED)); } + @EnableFlags(FLAG_ENABLE_BUBBLE_BAR) + @Test + public void testEventLogging_bubbleBar_openOverflow() { + mBubbleProperties.mIsBubbleBarEnabled = true; + mPositioner.setIsLargeScreen(true); + FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener(); + mBubbleController.registerBubbleStateListener(bubbleStateListener); + + mEntryListener.onEntryAdded(mRow); + + clearInvocations(mBubbleLogger); + mBubbleController.expandStackAndSelectBubbleFromLauncher(BubbleOverflow.KEY, 0); + verify(mBubbleLogger).log(BubbleLogger.Event.BUBBLE_BAR_OVERFLOW_SELECTED); + verifyNoMoreInteractions(mBubbleLogger); + } + + @EnableFlags(FLAG_ENABLE_BUBBLE_BAR) + @Test + public void testEventLogging_bubbleBar_fromOverflowToBar() { + mBubbleProperties.mIsBubbleBarEnabled = true; + mPositioner.setIsLargeScreen(true); + FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener(); + mBubbleController.registerBubbleStateListener(bubbleStateListener); + + mEntryListener.onEntryAdded(mRow); + + // Dismiss the bubble so it's in the overflow + mBubbleController.removeBubble( + mRow.getKey(), Bubbles.DISMISS_USER_GESTURE); + Bubble overflowBubble = mBubbleData.getOverflowBubbleWithKey(mRow.getKey()); + assertThat(overflowBubble).isNotNull(); + + // Promote overflow bubble and check that it is logged + mBubbleController.promoteBubbleFromOverflow(overflowBubble); + verify(mBubbleLogger).log(eqBubbleWithKey(overflowBubble.getKey()), + eq(BubbleLogger.Event.BUBBLE_BAR_OVERFLOW_REMOVE_BACK_TO_BAR)); + } + /** 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/android/content/ClipboardManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/content/ClipboardManagerKosmos.kt new file mode 100644 index 000000000000..379c00842b62 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/android/content/ClipboardManagerKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content + +import com.android.systemui.kosmos.Kosmos +import org.mockito.kotlin.mock + +val Kosmos.clipboardManager by Kosmos.Fixture { mock<ClipboardManager>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/development/data/repository/DevelopmentSettingRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/development/data/repository/DevelopmentSettingRepositoryKosmos.kt new file mode 100644 index 000000000000..3ce119576096 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/development/data/repository/DevelopmentSettingRepositoryKosmos.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.development.data.repository + +import android.os.userManager +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.util.settings.fakeGlobalSettings + +val Kosmos.developmentSettingRepository by + Kosmos.Fixture { DevelopmentSettingRepository(fakeGlobalSettings, userManager, testDispatcher) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/development/domain/interactor/BuildNumberInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/development/domain/interactor/BuildNumberInteractorKosmos.kt new file mode 100644 index 000000000000..aa4dd18a6cba --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/development/domain/interactor/BuildNumberInteractorKosmos.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.development.domain.interactor + +import android.content.clipboardManager +import android.content.res.mainResources +import com.android.systemui.development.data.repository.developmentSettingRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.user.data.repository.userRepository + +val Kosmos.buildNumberInteractor by + Kosmos.Fixture { + BuildNumberInteractor( + developmentSettingRepository, + mainResources, + userRepository, + { clipboardManager }, + testDispatcher, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/development/ui/viewmodel/BuildNumberViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/development/ui/viewmodel/BuildNumberViewModelKosmos.kt new file mode 100644 index 000000000000..c827311a6ac3 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/development/ui/viewmodel/BuildNumberViewModelKosmos.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.development.ui.viewmodel + +import com.android.systemui.development.domain.interactor.buildNumberInteractor +import com.android.systemui.kosmos.Kosmos + +val Kosmos.buildNumberViewModelFactory by + Kosmos.Fixture { + object : BuildNumberViewModel.Factory { + override fun create(): BuildNumberViewModel { + return BuildNumberViewModel(buildNumberInteractor) + } + } + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt index 0e5edb75846d..2e80293eafff 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.panels.ui.viewmodel +import com.android.systemui.development.ui.viewmodel.buildNumberViewModelFactory import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.panels.domain.interactor.paginatedGridInteractor @@ -26,5 +27,6 @@ val Kosmos.paginatedGridViewModel by qsColumnsViewModelFactory, paginatedGridInteractor, inFirstPageViewModel, + buildNumberViewModelFactory, ) } diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java index 619c8e30c78e..7ca9239d2062 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java @@ -142,34 +142,32 @@ public final class RavenwoodConfig { } /** - * Configure the given system property as immutable for the duration of the test. - * Read access to the key is allowed, and write access will fail. When {@code value} is - * {@code null}, the value is left as undefined. - * - * All properties in the {@code debug.*} namespace are automatically mutable, with no - * developer action required. - * - * Has no effect on non-Ravenwood environments. + * @deprecated Use {@link RavenwoodRule.Builder#setSystemPropertyImmutable(String, Object)} */ + @Deprecated public Builder setSystemPropertyImmutable(@NonNull String key, @Nullable Object value) { - mConfig.mSystemProperties.setValue(key, value); - mConfig.mSystemProperties.setAccessReadOnly(key); return this; } /** - * Configure the given system property as mutable for the duration of the test. - * Both read and write access to the key is allowed, and its value will be reset between - * each test. When {@code value} is {@code null}, the value is left as undefined. - * - * All properties in the {@code debug.*} namespace are automatically mutable, with no - * developer action required. - * - * Has no effect on non-Ravenwood environments. + * @deprecated Use {@link RavenwoodRule.Builder#setSystemPropertyMutable(String, Object)} */ + @Deprecated public Builder setSystemPropertyMutable(@NonNull String key, @Nullable Object value) { + return this; + } + + Builder setSystemPropertyImmutableReal(@NonNull String key, + @Nullable Object value) { + mConfig.mSystemProperties.setValue(key, value); + mConfig.mSystemProperties.setAccessReadOnly(key); + return this; + } + + Builder setSystemPropertyMutableReal(@NonNull String key, + @Nullable Object value) { mConfig.mSystemProperties.setValue(key, value); mConfig.mSystemProperties.setAccessReadWrite(key); return this; diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java index f7acd9022300..5681a9040f63 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java @@ -152,7 +152,7 @@ public final class RavenwoodRule implements TestRule { * Has no effect on non-Ravenwood environments. */ public Builder setSystemPropertyImmutable(@NonNull String key, @Nullable Object value) { - mBuilder.setSystemPropertyImmutable(key, value); + mBuilder.setSystemPropertyImmutableReal(key, value); return this; } @@ -167,7 +167,7 @@ public final class RavenwoodRule implements TestRule { * Has no effect on non-Ravenwood environments. */ public Builder setSystemPropertyMutable(@NonNull String key, @Nullable Object value) { - mBuilder.setSystemPropertyMutable(key, value); + mBuilder.setSystemPropertyMutableReal(key, value); return this; } diff --git a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java index dbeca82ade89..2d3782fb3181 100644 --- a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java +++ b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java @@ -16,6 +16,8 @@ package com.android.server.companion.securechannel; +import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_UNKNOWN; + import android.annotation.NonNull; import android.content.Context; import android.os.Build; @@ -67,7 +69,7 @@ public class SecureChannel { private D2DConnectionContextV1 mConnectionContext; private String mAlias; - private int mVerificationResult; + private int mVerificationResult = FLAG_FAILURE_UNKNOWN; private boolean mPskVerified; diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 679c7ac3ceac..3ce645158fe4 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -16,6 +16,10 @@ package com.android.server.accounts; +import static android.Manifest.permission.COPY_ACCOUNTS; +import static android.Manifest.permission.REMOVE_ACCOUNTS; +import static android.app.admin.flags.Flags.splitCreateManagedProfileEnabled; + import android.Manifest; import android.accounts.AbstractAccountAuthenticator; import android.accounts.Account; @@ -1739,9 +1743,11 @@ public class AccountManagerService public void copyAccountToUser(final IAccountManagerResponse response, final Account account, final int userFrom, int userTo) { int callingUid = Binder.getCallingUid(); - if (isCrossUser(callingUid, UserHandle.USER_ALL)) { + if (isCrossUser(callingUid, UserHandle.USER_ALL) + && !hasCopyAccountsPermission()) { throw new SecurityException("Calling copyAccountToUser requires " - + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); + + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL + + " or " + COPY_ACCOUNTS); } final UserAccounts fromAccounts = getUserAccounts(userFrom); final UserAccounts toAccounts = getUserAccounts(userTo); @@ -1793,6 +1799,12 @@ public class AccountManagerService } } + private boolean hasCopyAccountsPermission() { + return splitCreateManagedProfileEnabled() + && mContext.checkCallingOrSelfPermission(COPY_ACCOUNTS) + == PackageManager.PERMISSION_GRANTED; + } + @Override public boolean accountAuthenticated(final Account account) { final int callingUid = Binder.getCallingUid(); @@ -2346,7 +2358,8 @@ public class AccountManagerService UserHandle user = UserHandle.of(userId); if (!isAccountManagedByCaller(account.type, callingUid, user.getIdentifier()) && !isSystemUid(callingUid) - && !isProfileOwner(callingUid)) { + && !isProfileOwner(callingUid) + && !hasRemoveAccountsPermission()) { String msg = String.format( "uid %s cannot remove accounts of type: %s", callingUid, @@ -2408,6 +2421,12 @@ public class AccountManagerService } } + private boolean hasRemoveAccountsPermission() { + return splitCreateManagedProfileEnabled() + && mContext.checkCallingOrSelfPermission(REMOVE_ACCOUNTS) + == PackageManager.PERMISSION_GRANTED; + } + @Override public boolean removeAccountExplicitly(Account account) { final int callingUid = Binder.getCallingUid(); diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index ef5296eef492..78c4f74f3afa 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -257,6 +257,7 @@ public class SettingsToPropertiesMapper { "wear_systems", "wear_sysui", "wear_system_managed_surfaces", + "wear_watchfaces", "window_surfaces", "windowing_frontend", "xr", diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index d8483f721306..b7af9a4b17bd 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -1310,7 +1310,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // Do not reset the default (current) IME when it is a 3rd-party IME String selectedMethodId = bindingController.getSelectedMethodId(); final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); - if (selectedMethodId != null + if (selectedMethodId != null && settings.getMethodMap().get(selectedMethodId) != null && !settings.getMethodMap().get(selectedMethodId).isSystem()) { return; } diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java index 946e89604553..7c9d9c57a8b0 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -326,8 +326,15 @@ public class ContextHubService extends IContextHubService.Stub { } if (Flags.offloadApi()) { - mHubInfoRegistry = new HubInfoRegistry(mContextHubWrapper); - Log.i(TAG, "Enabling generic offload API"); + HubInfoRegistry registry; + try { + registry = new HubInfoRegistry(mContextHubWrapper); + Log.i(TAG, "Enabling generic offload API"); + } catch (UnsupportedOperationException e) { + registry = null; + Log.w(TAG, "Generic offload API not supported, disabling"); + } + mHubInfoRegistry = registry; } else { mHubInfoRegistry = null; Log.i(TAG, "Disabling generic offload API"); 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 00bab8af44f3..8495b6c403bf 100644 --- a/services/core/java/com/android/server/media/quality/MediaQualityService.java +++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java @@ -28,6 +28,7 @@ import android.media.quality.ISoundProfileCallback; import android.media.quality.MediaQualityContract; import android.media.quality.ParamCapability; import android.media.quality.PictureProfile; +import android.media.quality.PictureProfileHandle; import android.media.quality.SoundProfile; import android.os.PersistableBundle; import android.util.Log; @@ -249,6 +250,11 @@ public class MediaQualityService extends SystemService { } @Override + public PictureProfileHandle getPictureProfileHandle(String id) { + return null; + } + + @Override public SoundProfile createSoundProfile(SoundProfile pp) { // TODO: implement return pp; diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 4857b02eaf7c..70a8f563275f 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -344,6 +344,11 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { private ActivityRecord mTopResumedActivity; /** + * Cached value of the topmost resumed activity that reported to the client. + */ + private ActivityRecord mLastReportedTopResumedActivity; + + /** * Flag indicating whether we're currently waiting for the previous top activity to handle the * loss of the state and report back before making new activity top resumed. */ @@ -2287,15 +2292,13 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { * sent to the new top resumed activity. */ ActivityRecord updateTopResumedActivityIfNeeded(String reason) { - if (!readyToResume()) { - return mTopResumedActivity; - } final ActivityRecord prevTopActivity = mTopResumedActivity; final Task topRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask(); if (topRootTask == null || topRootTask.getTopResumedActivity() == prevTopActivity) { if (topRootTask == null) { // There's no focused task and there won't have any resumed activity either. scheduleTopResumedActivityStateLossIfNeeded(); + mTopResumedActivity = null; } if (mService.isSleepingLocked()) { // There won't be a next resumed activity. The top process should still be updated @@ -2339,25 +2342,27 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { /** Schedule current top resumed activity state loss */ private void scheduleTopResumedActivityStateLossIfNeeded() { - if (mTopResumedActivity == null) { + if (mLastReportedTopResumedActivity == null) { return; } // mTopResumedActivityWaitingForPrev == true at this point would mean that an activity // before the prevTopActivity one hasn't reported back yet. So server never sent the top // resumed state change message to prevTopActivity. - if (!mTopResumedActivityWaitingForPrev - && mTopResumedActivity.scheduleTopResumedActivityChanged(false /* onTop */)) { - scheduleTopResumedStateLossTimeout(mTopResumedActivity); + if (!mTopResumedActivityWaitingForPrev && readyToResume() + && mLastReportedTopResumedActivity.scheduleTopResumedActivityChanged( + false /* onTop */)) { + scheduleTopResumedStateLossTimeout(mLastReportedTopResumedActivity); mTopResumedActivityWaitingForPrev = true; + mLastReportedTopResumedActivity = null; } - mTopResumedActivity = null; } /** Schedule top resumed state change if previous top activity already reported back. */ private void scheduleTopResumedActivityStateIfNeeded() { - if (mTopResumedActivity != null && !mTopResumedActivityWaitingForPrev) { + if (mTopResumedActivity != null && !mTopResumedActivityWaitingForPrev && readyToResume()) { mTopResumedActivity.scheduleTopResumedActivityChanged(true /* onTop */); + mLastReportedTopResumedActivity = mTopResumedActivity; } } @@ -2611,6 +2616,10 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { */ void endDeferResume() { mDeferResumeCount--; + if (readyToResume() && mLastReportedTopResumedActivity != null + && mTopResumedActivity != mLastReportedTopResumedActivity) { + scheduleTopResumedActivityStateLossIfNeeded(); + } } /** @return True if resume can be called. */ diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index 81a04af17703..f0e12fec3107 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -2025,22 +2025,9 @@ class RecentTasks { // Fill in some deprecated values. rti.id = rti.isRunning ? rti.taskId : INVALID_TASK_ID; rti.persistentId = rti.taskId; - rti.lastSnapshotData.set(tr.mLastTaskSnapshotData); if (!getTasksAllowed) { Task.trimIneffectiveInfo(tr, rti); } - - // Fill in organized child task info for the task created by organizer. - if (tr.mCreatedByOrganizer) { - for (int i = tr.getChildCount() - 1; i >= 0; i--) { - final Task childTask = tr.getChildAt(i).asTask(); - if (childTask != null && childTask.isOrganized()) { - final ActivityManager.RecentTaskInfo cti = new ActivityManager.RecentTaskInfo(); - childTask.fillTaskInfo(cti, true /* stripExtras */, tda); - rti.childrenTaskInfos.add(cti); - } - } - } return rti; } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index e8ae28008c89..9504fc7d4d30 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -128,7 +128,6 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.Activity; import android.app.ActivityManager; -import android.app.ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData; import android.app.ActivityManager.TaskDescription; import android.app.ActivityOptions; import android.app.ActivityTaskManager; @@ -256,9 +255,6 @@ class Task extends TaskFragment { private static final String ATTR_MIN_HEIGHT = "min_height"; private static final String ATTR_PERSIST_TASK_VERSION = "persist_task_version"; private static final String ATTR_WINDOW_LAYOUT_AFFINITY = "window_layout_affinity"; - private static final String ATTR_LAST_SNAPSHOT_TASK_SIZE = "last_snapshot_task_size"; - private static final String ATTR_LAST_SNAPSHOT_CONTENT_INSETS = "last_snapshot_content_insets"; - private static final String ATTR_LAST_SNAPSHOT_BUFFER_SIZE = "last_snapshot_buffer_size"; // How long to wait for all background Activities to redraw following a call to // convertToTranslucent(). @@ -472,10 +468,6 @@ class Task extends TaskFragment { // NOTE: This value needs to be persisted with each task private TaskDescription mTaskDescription; - // Information about the last snapshot that should be persisted with the task to allow SystemUI - // to layout without loading all the task snapshots - final PersistedTaskSnapshotData mLastTaskSnapshotData; - /** @see #setCanAffectSystemUiFlags */ private boolean mCanAffectSystemUiFlags = true; @@ -635,14 +627,13 @@ class Task extends TaskFragment { ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset, boolean _autoRemoveRecents, int _userId, int _effectiveUid, String _lastDescription, long lastTimeMoved, boolean neverRelinquishIdentity, - TaskDescription _lastTaskDescription, PersistedTaskSnapshotData _lastSnapshotData, - int taskAffiliation, int prevTaskId, int nextTaskId, int callingUid, - String callingPackage, @Nullable String callingFeatureId, int resizeMode, - boolean supportsPictureInPicture, boolean _realActivitySuspended, - boolean userSetupComplete, int minWidth, int minHeight, ActivityInfo info, - IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor, - boolean _createdByOrganizer, IBinder _launchCookie, boolean _deferTaskAppear, - boolean _removeWithTaskOrganizer) { + TaskDescription _lastTaskDescription, int taskAffiliation, int prevTaskId, + int nextTaskId, int callingUid, String callingPackage, + @Nullable String callingFeatureId, int resizeMode, boolean supportsPictureInPicture, + boolean _realActivitySuspended, boolean userSetupComplete, int minWidth, int minHeight, + ActivityInfo info, IVoiceInteractionSession _voiceSession, + IVoiceInteractor _voiceInteractor, boolean _createdByOrganizer, IBinder _launchCookie, + boolean _deferTaskAppear, boolean _removeWithTaskOrganizer) { super(atmService, null /* fragmentToken */, _createdByOrganizer, false /* isEmbedded */); mTaskId = _taskId; @@ -652,9 +643,6 @@ class Task extends TaskFragment { mTaskDescription = _lastTaskDescription != null ? _lastTaskDescription : new TaskDescription(); - mLastTaskSnapshotData = _lastSnapshotData != null - ? _lastSnapshotData - : new PersistedTaskSnapshotData(); affinityIntent = _affinityIntent; affinity = _affinity; rootAffinity = _rootAffinity; @@ -3111,7 +3099,6 @@ class Task extends TaskFragment { } void onSnapshotChanged(TaskSnapshot snapshot) { - mLastTaskSnapshotData.set(snapshot); mAtmService.getTaskChangeNotificationController().notifyTaskSnapshotChanged( mTaskId, snapshot); } @@ -3990,19 +3977,6 @@ class Task extends TaskFragment { out.attributeInt(null, ATTR_MIN_HEIGHT, mMinHeight); out.attributeInt(null, ATTR_PERSIST_TASK_VERSION, PERSIST_TASK_VERSION); - if (mLastTaskSnapshotData.taskSize != null) { - out.attribute(null, ATTR_LAST_SNAPSHOT_TASK_SIZE, - mLastTaskSnapshotData.taskSize.flattenToString()); - } - if (mLastTaskSnapshotData.contentInsets != null) { - out.attribute(null, ATTR_LAST_SNAPSHOT_CONTENT_INSETS, - mLastTaskSnapshotData.contentInsets.flattenToString()); - } - if (mLastTaskSnapshotData.bufferSize != null) { - out.attribute(null, ATTR_LAST_SNAPSHOT_BUFFER_SIZE, - mLastTaskSnapshotData.bufferSize.flattenToString()); - } - if (affinityIntent != null) { out.startTag(null, TAG_AFFINITYINTENT); affinityIntent.saveToXml(out); @@ -4069,7 +4043,6 @@ class Task extends TaskFragment { int taskId = INVALID_TASK_ID; final int outerDepth = in.getDepth(); TaskDescription taskDescription = new TaskDescription(); - PersistedTaskSnapshotData lastSnapshotData = new PersistedTaskSnapshotData(); int taskAffiliation = INVALID_TASK_ID; int prevTaskId = INVALID_TASK_ID; int nextTaskId = INVALID_TASK_ID; @@ -4176,15 +4149,6 @@ class Task extends TaskFragment { case ATTR_PERSIST_TASK_VERSION: persistTaskVersion = Integer.parseInt(attrValue); break; - case ATTR_LAST_SNAPSHOT_TASK_SIZE: - lastSnapshotData.taskSize = Point.unflattenFromString(attrValue); - break; - case ATTR_LAST_SNAPSHOT_CONTENT_INSETS: - lastSnapshotData.contentInsets = Rect.unflattenFromString(attrValue); - break; - case ATTR_LAST_SNAPSHOT_BUFFER_SIZE: - lastSnapshotData.bufferSize = Point.unflattenFromString(attrValue); - break; default: if (!attrName.startsWith(TaskDescription.ATTR_TASKDESCRIPTION_PREFIX)) { Slog.w(TAG, "Task: Unknown attribute=" + attrName); @@ -4278,7 +4242,6 @@ class Task extends TaskFragment { .setLastTimeMoved(lastTimeOnTop) .setNeverRelinquishIdentity(neverRelinquishIdentity) .setLastTaskDescription(taskDescription) - .setLastSnapshotData(lastSnapshotData) .setTaskAffiliation(taskAffiliation) .setPrevAffiliateTaskId(prevTaskId) .setNextAffiliateTaskId(nextTaskId) @@ -6444,7 +6407,6 @@ class Task extends TaskFragment { private long mLastTimeMoved; private boolean mNeverRelinquishIdentity; private TaskDescription mLastTaskDescription; - private PersistedTaskSnapshotData mLastSnapshotData; private int mTaskAffiliation; private int mPrevAffiliateTaskId = INVALID_TASK_ID; private int mNextAffiliateTaskId = INVALID_TASK_ID; @@ -6672,11 +6634,6 @@ class Task extends TaskFragment { return this; } - private Builder setLastSnapshotData(PersistedTaskSnapshotData lastSnapshotData) { - mLastSnapshotData = lastSnapshotData; - return this; - } - private Builder setOrigActivity(ComponentName origActivity) { mOrigActivity = origActivity; return this; @@ -6825,7 +6782,7 @@ class Task extends TaskFragment { return new Task(mAtmService, mTaskId, mIntent, mAffinityIntent, mAffinity, mRootAffinity, mRealActivity, mOrigActivity, mRootWasReset, mAutoRemoveRecents, mUserId, mEffectiveUid, mLastDescription, mLastTimeMoved, - mNeverRelinquishIdentity, mLastTaskDescription, mLastSnapshotData, + mNeverRelinquishIdentity, mLastTaskDescription, mTaskAffiliation, mPrevAffiliateTaskId, mNextAffiliateTaskId, mCallingUid, mCallingPackage, mCallingFeatureId, mResizeMode, mSupportsPictureInPicture, mRealActivitySuspended, mUserSetupComplete, mMinWidth, mMinHeight, diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 091896590b6b..c42aa37d847b 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -788,9 +788,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub deferResume = false; // Already calls ensureActivityConfig mService.mRootWindowContainer.ensureActivitiesVisible(); - if (!mService.mRootWindowContainer.resumeFocusedTasksTopActivities()) { - mService.mTaskSupervisor.updateTopResumedActivityIfNeeded("endWCT-effects"); - } + mService.mRootWindowContainer.resumeFocusedTasksTopActivities(); } else if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) { for (int i = haveConfigChanges.size() - 1; i >= 0; --i) { haveConfigChanges.valueAt(i).forAllActivities(r -> { @@ -816,10 +814,6 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */); if (deferResume) { mService.mTaskSupervisor.endDeferResume(); - // Transient launching the Recents via HIERARCHY_OP_TYPE_PENDING_INTENT directly - // resume the Recents activity with no TRANSACT_EFFECTS_LIFECYCLE. Explicitly - // checks if the top resumed activity should be updated after defer-resume ended. - mService.mTaskSupervisor.updateTopResumedActivityIfNeeded("endWCT"); } mService.continueWindowLayout(); } diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java index 5bb6b19cd63e..d08715586580 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java @@ -194,7 +194,13 @@ public class InputMethodServiceTest { () -> assertThat(mActivity.hideImeWithWindowInsetsController()).isTrue(), true /* expected */, false /* inputViewStarted */); - assertThat(mInputMethodService.isInputViewShown()).isFalse(); + if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) { + // The IME visibility is only sent at the end of the animation. Therefore, we have to + // wait until the visibility was sent to the server and the IME window hidden. + eventually(() -> assertThat(mInputMethodService.isInputViewShown()).isFalse()); + } else { + assertThat(mInputMethodService.isInputViewShown()).isFalse(); + } } /** diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockPowerStatsCollectorTest.java index 0d5d277b00ef..ed927c6ab699 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockPowerStatsCollectorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockPowerStatsCollectorTest.java @@ -26,8 +26,7 @@ import android.content.Context; import android.os.Process; import android.platform.test.annotations.DisableFlags; import android.platform.test.flag.junit.SetFlagsRule; -import android.platform.test.ravenwood.RavenwoodConfig; -import android.platform.test.ravenwood.RavenwoodConfig.Config; +import android.platform.test.ravenwood.RavenwoodRule; import com.android.internal.os.PowerStats; import com.android.server.power.feature.flags.Flags; @@ -39,10 +38,9 @@ import org.junit.Test; public class WakelockPowerStatsCollectorTest { - @Config - public static final RavenwoodConfig sConfig = - new RavenwoodConfig.Builder() - .setProvideMainThread(true) + @Rule + public final RavenwoodRule mRule = + new RavenwoodRule.Builder() .setSystemPropertyImmutable( "persist.sys.com.android.server.power.feature.flags." + "framework_wakelock_info-override", 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 704c1b858b8d..e6b4bc98ea4c 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -17178,8 +17178,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING) public void testSetCanBePromoted_granted() throws Exception { - mContext.getTestablePermissions().setPermission( - android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED); // qualifying posted notification Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId()) .setSmallIcon(android.R.drawable.sym_def_app_icon) @@ -17254,8 +17252,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING) public void testSetCanBePromoted_granted_onlyNotifiesOnce() throws Exception { - mContext.getTestablePermissions().setPermission( - android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED); // qualifying posted notification Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId()) .setSmallIcon(android.R.drawable.sym_def_app_icon) @@ -17285,8 +17281,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING) public void testSetCanBePromoted_revoked() throws Exception { - mContext.getTestablePermissions().setPermission( - android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED); // start from true state mBinderService.setCanBePromoted(mPkg, mUid, true, true); @@ -17350,8 +17344,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING) public void testSetCanBePromoted_revoked_onlyNotifiesOnce() throws Exception { - mContext.getTestablePermissions().setPermission( - android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED); // start from true state mBinderService.setCanBePromoted(mPkg, mUid, true, true); @@ -17387,8 +17379,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testPostPromotableNotification() throws Exception { mBinderService.setCanBePromoted(mPkg, mUid, true, true); assertThat(mBinderService.appCanBePromoted(mPkg, mUid)).isTrue(); - mContext.getTestablePermissions().setPermission( - android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED); Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId()) .setSmallIcon(android.R.drawable.sym_def_app_icon) @@ -17415,8 +17405,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING) public void testPostPromotableNotification_noPermission() throws Exception { - mContext.getTestablePermissions().setPermission( - android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED); Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId()) .setSmallIcon(android.R.drawable.sym_def_app_icon) .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) @@ -17444,8 +17432,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING) public void testPostPromotableNotification_unimportantNotification() throws Exception { mBinderService.setCanBePromoted(mPkg, mUid, true, true); - mContext.getTestablePermissions().setPermission( - android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED); Notification n = new Notification.Builder(mContext, mMinChannel.getId()) .setSmallIcon(android.R.drawable.sym_def_app_icon) .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java index 7f260f85a755..70f57eb40385 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java @@ -357,6 +357,25 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase { assertEquals(activity1.app, mAtm.mTopApp); } + @Test + public void testTopResumedActivity_deferResume() { + final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true).build(); + final ActivityRecord activity2 = new ActivityBuilder(mAtm).setCreateTask(true).build(); + activity2.setState(ActivityRecord.State.RESUMED, "test"); + assertEquals(activity2.app, mAtm.mTopApp); + reset(activity2); + + // Verify that no top-resumed activity changes to the client while defer-resume enabled. + mSupervisor.beginDeferResume(); + activity1.getTask().moveToFront("test"); + activity1.setState(ActivityRecord.State.RESUMED, "test"); + verify(activity2, never()).scheduleTopResumedActivityChanged(eq(false)); + + // Verify that the change is scheduled to the client after defer-resumed disabled + mSupervisor.endDeferResume(); + verify(activity2).scheduleTopResumedActivityChanged(eq(false)); + } + /** * We need to launch home again after user unlocked for those displays that do not have * encryption aware home app. diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java index df17cd1d24b7..7ed8283efffd 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -496,24 +496,6 @@ public class RecentTasksTest extends WindowTestsBase { } @Test - public void testAppendOrganizedChildTaskInfo() { - final Task root = createTaskBuilder(".CreatedByOrganizerRoot").build(); - root.mCreatedByOrganizer = true; - // Add organized and non-organized child. - final Task child1 = createTaskBuilder(".Task1").setParentTask(root).build(); - final Task child2 = createTaskBuilder(".Task2").setParentTask(root).build(); - doReturn(true).when(child1).isOrganized(); - doReturn(false).when(child2).isOrganized(); - mRecentTasks.add(root); - - // Make sure only organized child will be appended. - final List<RecentTaskInfo> infos = getRecentTasks(0 /* flags */); - final List<RecentTaskInfo> childrenTaskInfos = infos.get(0).childrenTaskInfos; - assertEquals(childrenTaskInfos.size(), 1); - assertEquals(childrenTaskInfos.get(0).taskId, child1.mTaskId); - } - - @Test public void testAddTasksHomeClearUntrackedTasks_expectFinish() { // There may be multiple tasks with the same base intent by flags (FLAG_ACTIVITY_NEW_TASK | // FLAG_ACTIVITY_MULTIPLE_TASK). If the previous task is still active, it should be removed @@ -1420,46 +1402,6 @@ public class RecentTasksTest extends WindowTestsBase { } @Test - public void testLastSnapshotData_snapshotSaved() { - final TaskSnapshot snapshot = createSnapshot(new Point(100, 100), new Point(80, 80)); - final Task task1 = createTaskBuilder(".Task").build(); - task1.onSnapshotChanged(snapshot); - - mRecentTasks.add(task1); - final List<RecentTaskInfo> infos = getRecentTasks(0 /* flags */); - final RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData = - infos.get(0).lastSnapshotData; - assertTrue(lastSnapshotData.taskSize.equals(100, 100)); - assertTrue(lastSnapshotData.bufferSize.equals(80, 80)); - } - - @Test - public void testLastSnapshotData_noBuffer() { - final Task task1 = createTaskBuilder(".Task").build(); - final TaskSnapshot snapshot = createSnapshot(new Point(100, 100), null); - task1.onSnapshotChanged(snapshot); - - mRecentTasks.add(task1); - final List<RecentTaskInfo> infos = getRecentTasks(0 /* flags */); - final RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData = - infos.get(0).lastSnapshotData; - assertTrue(lastSnapshotData.taskSize.equals(100, 100)); - assertNull(lastSnapshotData.bufferSize); - } - - @Test - public void testLastSnapshotData_notSet() { - final Task task1 = createTaskBuilder(".Task").build(); - - mRecentTasks.add(task1); - final List<RecentTaskInfo> infos = getRecentTasks(0 /* flags */); - final RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData = - infos.get(0).lastSnapshotData; - assertNull(lastSnapshotData.taskSize); - assertNull(lastSnapshotData.bufferSize); - } - - @Test public void testCreateRecentTaskInfo_detachedTask() { final Task task = createTaskBuilder(".Task").build(); final ComponentName componentName = getUniqueComponentName(); diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 6490cbe3e31a..7cfdec664a92 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -9734,6 +9734,35 @@ public class CarrierConfigManager { "carrier_supported_satellite_services_per_provider_bundle"; /** + * A PersistableBundle that contains a list of key-value pairs, where the values are integer + * arrays. + * <p> + * Keys are the IDs of regional satellite configs as strings and values are + * integer arrays of earfcns in the corresponding regions. + * + * An example config for two regions "1" and "2": + * <pre>{@code + * <carrier_config> + * <pbundle_as_map name="regional_satellite_earfcn_bundle"> + * <int-array name = "1" num = "2"> + * <item value = "100"/> + * <item value = "200"/> + * </int-array> + * <int-array name = "2" num = "1"> + * <item value = "200"/> + * </int-array> + * </pbundle_as_map> + * </carrier_config> + * }</pre> + * <p> + * This config is empty by default. + * @hide + */ + @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) + public static final String KEY_REGIONAL_SATELLITE_EARFCN_BUNDLE = + "regional_satellite_earfcn_bundle"; + + /** * This config enables modem to scan satellite PLMNs specified as per * {@link #KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE} and attach to same * in case cellular networks are not enabled. This will need specific agreement between @@ -11264,6 +11293,9 @@ public class CarrierConfigManager { sDefaults.putPersistableBundle( KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE, PersistableBundle.EMPTY); + sDefaults.putPersistableBundle( + KEY_REGIONAL_SATELLITE_EARFCN_BUNDLE, + PersistableBundle.EMPTY); sDefaults.putBoolean(KEY_SATELLITE_ATTACH_SUPPORTED_BOOL, false); sDefaults.putInt(KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT, 180); sDefaults.putIntArray(KEY_NTN_LTE_RSRP_THRESHOLDS_INT_ARRAY, diff --git a/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java b/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java index 2cd625eec032..4d495adf727b 100644 --- a/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java +++ b/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java @@ -18,7 +18,9 @@ package android.app.jank.tests; import static org.junit.Assert.assertEquals; +import android.app.jank.AppJankStats; import android.app.jank.Flags; +import android.app.jank.FrameOverrunHistogram; import android.app.jank.JankDataProcessor; import android.app.jank.StateTracker; import android.platform.test.annotations.RequiresFlagsEnabled; @@ -39,6 +41,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; @RunWith(AndroidJUnit4.class) @@ -154,6 +157,73 @@ public class JankDataProcessorTest { assertEquals(totalFrames, histogramFrames); } + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void mergeAppJankStats_confirmStatAddedToPendingStats() { + HashMap<String, JankDataProcessor.PendingJankStat> pendingStats = + mJankDataProcessor.getPendingJankStats(); + + assertEquals(pendingStats.size(), 0); + + AppJankStats jankStats = getAppJankStats(); + mJankDataProcessor.mergeJankStats(jankStats, sActivityName); + + pendingStats = mJankDataProcessor.getPendingJankStats(); + + assertEquals(pendingStats.size(), 1); + } + + /** + * This test confirms matching states are combined into one pending stat. When JankStats are + * merged from outside the platform they will contain widget category, widget id and widget + * state. If an incoming JankStats matches a pending stat on all those fields the incoming + * JankStat will be merged into the existing stat. + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void mergeAppJankStats_confirmStatsWithMatchingStatesAreCombinedIntoOnePendingStat() { + AppJankStats jankStats = getAppJankStats(); + mJankDataProcessor.mergeJankStats(jankStats, sActivityName); + + HashMap<String, JankDataProcessor.PendingJankStat> pendingStats = + mJankDataProcessor.getPendingJankStats(); + assertEquals(pendingStats.size(), 1); + + AppJankStats secondJankStat = getAppJankStats(); + mJankDataProcessor.mergeJankStats(secondJankStat, sActivityName); + + pendingStats = mJankDataProcessor.getPendingJankStats(); + + assertEquals(pendingStats.size(), 1); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void mergeAppJankStats_whenStatsWithMatchingStatesMerge_confirmFrameCountsAdded() { + AppJankStats jankStats = getAppJankStats(); + mJankDataProcessor.mergeJankStats(jankStats, sActivityName); + mJankDataProcessor.mergeJankStats(jankStats, sActivityName); + + HashMap<String, JankDataProcessor.PendingJankStat> pendingStats = + mJankDataProcessor.getPendingJankStats(); + + String statKey = pendingStats.keySet().iterator().next(); + JankDataProcessor.PendingJankStat pendingStat = pendingStats.get(statKey); + + assertEquals(pendingStats.size(), 1); + // The same jankStats objects are merged twice, this should result in the frame counts being + // doubled. + assertEquals(jankStats.getJankyFrameCount() * 2, pendingStat.getJankyFrames()); + assertEquals(jankStats.getTotalFrameCount() * 2, pendingStat.getTotalFrames()); + + int[] originalHistogramBuckets = jankStats.getFrameOverrunHistogram().getBucketCounters(); + int[] frameOverrunBuckets = pendingStat.getFrameOverrunBuckets(); + + for (int i = 0; i < frameOverrunBuckets.length; i++) { + assertEquals(originalHistogramBuckets[i] * 2, frameOverrunBuckets[i]); + } + } + // TODO b/375005277 add tests that cover logging and releasing resources back to pool. private long getTotalFramesCounted() { @@ -276,4 +346,26 @@ public class JankDataProcessorTest { return mockData; } + private AppJankStats getAppJankStats() { + AppJankStats jankStats = new AppJankStats( + /*App Uid*/APP_ID, + /*Widget Id*/"test widget id", + /*Widget Category*/AppJankStats.SCROLL, + /*Widget State*/AppJankStats.SCROLLING, + /*Total Frames*/100, + /*Janky Frames*/25, + getOverrunHistogram() + ); + return jankStats; + } + + private FrameOverrunHistogram getOverrunHistogram() { + FrameOverrunHistogram overrunHistogram = new FrameOverrunHistogram(); + overrunHistogram.addFrameOverrunMillis(-2); + overrunHistogram.addFrameOverrunMillis(1); + overrunHistogram.addFrameOverrunMillis(5); + overrunHistogram.addFrameOverrunMillis(25); + return overrunHistogram; + } + } diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/StartMediaProjectionAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/StartMediaProjectionAppHelper.kt index 69fde0168b14..9e488486e16a 100644 --- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/StartMediaProjectionAppHelper.kt +++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/StartMediaProjectionAppHelper.kt @@ -65,10 +65,45 @@ constructor( .waitForAndVerify() } + fun startSingleAppMediaProjectionWithExtraIntent( + wmHelper: WindowManagerStateHelper, + targetApp: StandardAppHelper + ) { + clickStartMediaProjectionWithExtraIntentButton() + chooseSingleAppOption() + startScreenSharing() + selectTargetApp(targetApp.appName) + wmHelper + .StateSyncBuilder() + .withAppTransitionIdle() + .withHomeActivityVisible() + .waitForAndVerify() + } + + fun startSingleAppMediaProjectionFromRecents( + wmHelper: WindowManagerStateHelper, + targetApp: StandardAppHelper, + recentTasksIndex: Int = 0, + ) { + clickStartMediaProjectionButton() + chooseSingleAppOption() + startScreenSharing() + selectTargetAppRecent(recentTasksIndex) + wmHelper + .StateSyncBuilder() + .withAppTransitionIdle() + .withWindowSurfaceAppeared(targetApp) + .waitForAndVerify() + } + private fun clickStartMediaProjectionButton() { findObject(By.res(packageName, START_MEDIA_PROJECTION_BUTTON_ID)).also { it.click() } } + private fun clickStartMediaProjectionWithExtraIntentButton() { + findObject(By.res(packageName, START_MEDIA_PROJECTION_NEW_INTENT_BUTTON_ID)).also { it.click() } + } + private fun chooseEntireScreenOption() { findObject(By.res(SCREEN_SHARE_OPTIONS_PATTERN)).also { it.click() } @@ -92,6 +127,13 @@ constructor( findObject(By.text(targetAppName)).also { it.click() } } + private fun selectTargetAppRecent(recentTasksIndex: Int) { + // Scroll to to find target app to launch then click app icon it to start capture + val recentsTasksRecycler = + findObject(By.res(SYSTEMUI_PACKAGE, MEDIA_PROJECTION_RECENT_TASKS)) + recentsTasksRecycler.children[recentTasksIndex].also{ it.click() } + } + private fun chooseSingleAppOption() { findObject(By.res(SCREEN_SHARE_OPTIONS_PATTERN)).also { it.click() } @@ -116,8 +158,10 @@ constructor( const val TIMEOUT: Long = 5000L const val ACCEPT_RESOURCE_ID: String = "android:id/button1" const val START_MEDIA_PROJECTION_BUTTON_ID: String = "button_start_mp" + const val START_MEDIA_PROJECTION_NEW_INTENT_BUTTON_ID: String = "button_start_mp_new_intent" val SCREEN_SHARE_OPTIONS_PATTERN: Pattern = Pattern.compile("$SYSTEMUI_PACKAGE:id/screen_share_mode_(options|spinner)") + const val MEDIA_PROJECTION_RECENT_TASKS: String = "media_projection_recent_tasks_recycler" const val ENTIRE_SCREEN_STRING_RES_NAME: String = "screen_share_permission_dialog_option_entire_screen" const val SINGLE_APP_STRING_RES_NAME: String = diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml index 46f01e6c9752..c34d2003ef42 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml @@ -16,17 +16,27 @@ --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center" android:orientation="vertical" android:background="@android:color/holo_orange_light"> <Button android:id="@+id/button_start_mp" - android:layout_width="500dp" - android:layout_height="500dp" + android:layout_margin="16dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" android:gravity="center_vertical|center_horizontal" android:text="Start Media Projection" android:textAppearance="?android:attr/textAppearanceLarge"/> + <Button + android:id="@+id/button_start_mp_new_intent" + android:layout_margin="16dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center_vertical|center_horizontal" + android:text="Start Media Projection with extra intent" + android:textAppearance="?android:attr/textAppearanceLarge"/> </LinearLayout>
\ No newline at end of file diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/StartMediaProjectionActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/StartMediaProjectionActivity.java index a24a48269d7c..b29b87450197 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/StartMediaProjectionActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/StartMediaProjectionActivity.java @@ -19,7 +19,8 @@ package com.android.server.wm.flicker.testapp; import static com.android.wm.shell.flicker.utils.MediaProjectionUtils.EXTRA_MESSENGER; import static com.android.wm.shell.flicker.utils.MediaProjectionUtils.MSG_SERVICE_DESTROYED; import static com.android.wm.shell.flicker.utils.MediaProjectionUtils.MSG_START_FOREGROUND_DONE; -import static com.android.wm.shell.flicker.utils.MediaProjectionUtils.REQUEST_CODE; +import static com.android.wm.shell.flicker.utils.MediaProjectionUtils.REQUEST_CODE_NORMAL; +import static com.android.wm.shell.flicker.utils.MediaProjectionUtils.REQUEST_CODE_EXTRA_INTENT; import android.app.Activity; import android.content.ComponentName; @@ -71,13 +72,17 @@ public class StartMediaProjectionActivity extends Activity { setContentView(R.layout.activity_start_media_projection); Button startMediaProjectionButton = findViewById(R.id.button_start_mp); + Button startMediaProjectionButton2 = findViewById(R.id.button_start_mp_new_intent); startMediaProjectionButton.setOnClickListener(v -> - startActivityForResult(mService.createScreenCaptureIntent(), REQUEST_CODE)); + startActivityForResult(mService.createScreenCaptureIntent(), REQUEST_CODE_NORMAL)); + startMediaProjectionButton2.setOnClickListener(v -> + startActivityForResult(mService.createScreenCaptureIntent(), + REQUEST_CODE_EXTRA_INTENT)); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode != REQUEST_CODE) { + if (requestCode != REQUEST_CODE_NORMAL && requestCode != REQUEST_CODE_EXTRA_INTENT) { throw new IllegalStateException("Unknown request code: " + requestCode); } if (resultCode != RESULT_OK) { @@ -85,6 +90,11 @@ public class StartMediaProjectionActivity extends Activity { } Log.d(TAG, "onActivityResult"); startMediaProjectionService(resultCode, data); + if (requestCode == REQUEST_CODE_EXTRA_INTENT) { + Intent startMain = new Intent(Intent.ACTION_MAIN); + startMain.addCategory(Intent.CATEGORY_HOME); + startActivity(startMain); + } } private void startMediaProjectionService(int resultCode, Intent resultData) { @@ -122,7 +132,7 @@ public class StartMediaProjectionActivity extends Activity { displayBounds.width(), displayBounds.height(), PixelFormat.RGBA_8888, 1); mVirtualDisplay = mMediaProjection.createVirtualDisplay( - "DanielDisplay", + "TestDisplay", displayBounds.width(), displayBounds.height(), DisplayMetrics.DENSITY_HIGH, |