diff options
87 files changed, 2717 insertions, 612 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..e03548e715f5 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); } 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/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/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/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/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/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/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/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/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/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/res/xml/network_security_config.xml b/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/res/xml/network_security_config.xml deleted file mode 100644 index 4bd9ca049f55..000000000000 --- a/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/res/xml/network_security_config.xml +++ /dev/null @@ -1,22 +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. - --> - -<network-security-config> - <domain-config cleartextTrafficPermitted="true"> - <domain includeSubdomains="true">localhost</domain> - </domain-config> -</network-security-config> diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/trace_config/trace_config.textproto 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/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/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/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/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/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/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 813bb9c52aeb..8cab15506d20 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -2056,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/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/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/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/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/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/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/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, |