diff options
267 files changed, 10708 insertions, 3591 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java index 4de8ec8d1241..dd102bdd726e 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java @@ -892,7 +892,8 @@ public class AppIdleHistory { } if (history.bucketExpiryTimesMs != null) { xml.startTag(null, TAG_BUCKET_EXPIRY_TIMES); - for (int j = 0; j < history.bucketExpiryTimesMs.size(); ++j) { + final int size = history.bucketExpiryTimesMs.size(); + for (int j = 0; j < size; ++j) { final long expiryTimeMs = history.bucketExpiryTimesMs.valueAt(j); // Skip writing to disk if the expiry time already elapsed. if (expiryTimeMs < elapsedTimeMs) { @@ -994,7 +995,8 @@ public class AppIdleHistory { return; } idpw.print("("); - for (int i = 0; i < appUsageHistory.bucketExpiryTimesMs.size(); ++i) { + final int size = appUsageHistory.bucketExpiryTimesMs.size(); + for (int i = 0; i < size; ++i) { final int bucket = appUsageHistory.bucketExpiryTimesMs.keyAt(i); final long expiryTimeMs = appUsageHistory.bucketExpiryTimesMs.valueAt(i); if (i != 0) { diff --git a/apex/media/framework/java/android/media/MediaSession2Service.java b/apex/media/framework/java/android/media/MediaSession2Service.java index f6fd509fd245..9f80c433d580 100644 --- a/apex/media/framework/java/android/media/MediaSession2Service.java +++ b/apex/media/framework/java/android/media/MediaSession2Service.java @@ -161,19 +161,19 @@ public abstract class MediaSession2Service extends Service { public abstract MediaSession2 onGetSession(@NonNull ControllerInfo controllerInfo); /** - * Called when notification UI needs update. Override this method to show or cancel your own - * notification UI. + * Called to update the media notification when the playback state changes. * <p> - * This would be called on {@link MediaSession2}'s callback executor when playback state is - * changed. + * If playback is active and a notification is returned, the service uses it to become a + * foreground service. If playback is not active then the notification is still posted, but the + * service does not become a foreground service. * <p> - * With the notification returned here, the service becomes foreground service when the playback - * is started. Apps must request the permission - * {@link android.Manifest.permission#FOREGROUND_SERVICE} in order to use this API. It becomes - * background service after the playback is stopped. + * Apps must request the {@link android.Manifest.permission#FOREGROUND_SERVICE} permission + * in order to use this API. For apps targeting {@link android.os.Build.VERSION_CODES#TIRAMISU} + * or later, notifications will only be posted if the app has also been granted the + * {@link android.Manifest.permission#POST_NOTIFICATIONS} permission. * - * @param session a session that needs notification update. - * @return a {@link MediaNotification}. Can be {@code null}. + * @param session the session for which an updated media notification is required. + * @return the {@link MediaNotification}. Can be {@code null}. */ @Nullable public abstract MediaNotification onUpdateNotification(@NonNull MediaSession2 session); diff --git a/api/Android.bp b/api/Android.bp index 362f39f2beaf..a22c2f6af35a 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -133,6 +133,7 @@ combined_apis { system_server_classpath: [ "service-media-s", "service-permission", + "service-supplementalprocess", ], } diff --git a/core/api/current.txt b/core/api/current.txt index 713875415c68..22d1c3aa090f 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -125,11 +125,13 @@ package android { field public static final String POST_NOTIFICATIONS = "android.permission.POST_NOTIFICATIONS"; field @Deprecated public static final String PROCESS_OUTGOING_CALLS = "android.permission.PROCESS_OUTGOING_CALLS"; field public static final String QUERY_ALL_PACKAGES = "android.permission.QUERY_ALL_PACKAGES"; + field public static final String READ_ASSISTANT_APP_SEARCH_DATA = "android.permission.READ_ASSISTANT_APP_SEARCH_DATA"; field public static final String READ_BASIC_PHONE_STATE = "android.permission.READ_BASIC_PHONE_STATE"; field public static final String READ_CALENDAR = "android.permission.READ_CALENDAR"; field public static final String READ_CALL_LOG = "android.permission.READ_CALL_LOG"; field public static final String READ_CONTACTS = "android.permission.READ_CONTACTS"; field public static final String READ_EXTERNAL_STORAGE = "android.permission.READ_EXTERNAL_STORAGE"; + field public static final String READ_HOME_APP_SEARCH_DATA = "android.permission.READ_HOME_APP_SEARCH_DATA"; field @Deprecated public static final String READ_INPUT_STATE = "android.permission.READ_INPUT_STATE"; field public static final String READ_LOGS = "android.permission.READ_LOGS"; field public static final String READ_NEARBY_STREAMING_POLICY = "android.permission.READ_NEARBY_STREAMING_POLICY"; @@ -7291,10 +7293,10 @@ package android.app.admin { method @Nullable public java.util.List<java.lang.String> getDelegatePackages(@NonNull android.content.ComponentName, @NonNull String); method @NonNull public java.util.List<java.lang.String> getDelegatedScopes(@Nullable android.content.ComponentName, @NonNull String); method public CharSequence getDeviceOwnerLockScreenInfo(); - method @Nullable public android.graphics.drawable.Drawable getDrawable(int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>); - method @Nullable public android.graphics.drawable.Drawable getDrawable(int, int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>); - method @Nullable public android.graphics.drawable.Drawable getDrawableForDensity(int, int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>); - method @Nullable public android.graphics.drawable.Drawable getDrawableForDensity(int, int, int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>); + method @NonNull public android.graphics.drawable.Drawable getDrawable(int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>); + method @NonNull public android.graphics.drawable.Drawable getDrawable(int, int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>); + method @NonNull public android.graphics.drawable.Drawable getDrawableForDensity(int, int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>); + method @NonNull public android.graphics.drawable.Drawable getDrawableForDensity(int, int, int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>); method public CharSequence getEndUserSessionMessage(@NonNull android.content.ComponentName); method @NonNull public String getEnrollmentSpecificId(); method @Nullable public android.app.admin.FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(@Nullable android.content.ComponentName); @@ -7598,6 +7600,7 @@ package android.app.admin { field public static final String EXTRA_PROVISIONING_WIFI_USER_CERTIFICATE = "android.app.extra.PROVISIONING_WIFI_USER_CERTIFICATE"; field public static final String EXTRA_RESOURCE_ID = "android.app.extra.RESOURCE_ID"; field public static final String EXTRA_RESOURCE_TYPE_DRAWABLE = "android.app.extra.RESOURCE_TYPE_DRAWABLE"; + field public static final String EXTRA_RESOURCE_TYPE_STRING = "android.app.extra.RESOURCE_TYPE_STRING"; field public static final int FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY = 1; // 0x1 field public static final int FLAG_MANAGED_CAN_ACCESS_PARENT = 2; // 0x2 field public static final int FLAG_PARENT_CAN_ACCESS_MANAGED = 1; // 0x1 @@ -10932,10 +10935,10 @@ package android.content { method @Nullable public abstract android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, @Nullable String, @Nullable android.os.Handler, int); method @Deprecated @RequiresPermission(android.Manifest.permission.BROADCAST_STICKY) public abstract void removeStickyBroadcast(@RequiresPermission android.content.Intent); method @Deprecated @RequiresPermission(allOf={"android.permission.INTERACT_ACROSS_USERS", android.Manifest.permission.BROADCAST_STICKY}) public abstract void removeStickyBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle); + method public void revokeOwnPermissionOnKill(@NonNull String); + method public void revokeOwnPermissionsOnKill(@NonNull java.util.Collection<java.lang.String>); method public abstract void revokeUriPermission(android.net.Uri, int); method public abstract void revokeUriPermission(String, android.net.Uri, int); - method public void selfRevokePermission(@NonNull String); - method public void selfRevokePermissions(@NonNull java.util.Collection<java.lang.String>); method public abstract void sendBroadcast(@RequiresPermission android.content.Intent); method public abstract void sendBroadcast(@RequiresPermission android.content.Intent, @Nullable String); method @RequiresPermission("android.permission.INTERACT_ACROSS_USERS") public abstract void sendBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle); @@ -13475,6 +13478,7 @@ package android.content.pm { public final class ShortcutInfo implements android.os.Parcelable { method public int describeContents(); method @Nullable public android.content.ComponentName getActivity(); + method @NonNull public java.util.List<java.lang.String> getCapabilityParameterValues(@NonNull String, @NonNull String); method @Nullable public java.util.Set<java.lang.String> getCategories(); method @Nullable public CharSequence getDisabledMessage(); method public int getDisabledReason(); @@ -13489,6 +13493,7 @@ package android.content.pm { method public int getRank(); method @Nullable public CharSequence getShortLabel(); method public android.os.UserHandle getUserHandle(); + method public boolean hasCapability(@NonNull String); method public boolean hasKeyFieldsOnly(); method public boolean isCached(); method public boolean isDeclaredInManifest(); @@ -13513,6 +13518,7 @@ package android.content.pm { public static class ShortcutInfo.Builder { ctor public ShortcutInfo.Builder(android.content.Context, String); + method @NonNull public android.content.pm.ShortcutInfo.Builder addCapabilityBinding(@NonNull String, @Nullable String, @Nullable java.util.List<java.lang.String>); method @NonNull public android.content.pm.ShortcutInfo build(); method @NonNull public android.content.pm.ShortcutInfo.Builder setActivity(@NonNull android.content.ComponentName); method @NonNull public android.content.pm.ShortcutInfo.Builder setCategories(java.util.Set<java.lang.String>); @@ -18047,6 +18053,7 @@ package android.hardware { field public static final long USAGE_CPU_READ_RARELY = 2L; // 0x2L field public static final long USAGE_CPU_WRITE_OFTEN = 48L; // 0x30L field public static final long USAGE_CPU_WRITE_RARELY = 32L; // 0x20L + field public static final long USAGE_FRONT_BUFFER = 1L; // 0x1L field public static final long USAGE_GPU_COLOR_OUTPUT = 512L; // 0x200L field public static final long USAGE_GPU_CUBE_MAP = 33554432L; // 0x2000000L field public static final long USAGE_GPU_DATA_BUFFER = 16777216L; // 0x1000000L @@ -22073,14 +22080,30 @@ package android.media { public class ImageWriter implements java.lang.AutoCloseable { method public void close(); method public android.media.Image dequeueInputImage(); + method public long getDataSpace(); method public int getFormat(); + method public int getHardwareBufferFormat(); + method public int getHeight(); method public int getMaxImages(); + method public long getUsage(); + method public int getWidth(); method @NonNull public static android.media.ImageWriter newInstance(@NonNull android.view.Surface, @IntRange(from=1) int); method @NonNull public static android.media.ImageWriter newInstance(@NonNull android.view.Surface, @IntRange(from=1) int, int); method public void queueInputImage(android.media.Image); method public void setOnImageReleasedListener(android.media.ImageWriter.OnImageReleasedListener, android.os.Handler); } + public static final class ImageWriter.Builder { + ctor public ImageWriter.Builder(@NonNull android.view.Surface); + method @NonNull public android.media.ImageWriter build(); + method @NonNull public android.media.ImageWriter.Builder setDataSpace(long); + method @NonNull public android.media.ImageWriter.Builder setHardwareBufferFormat(int); + method @NonNull public android.media.ImageWriter.Builder setImageFormat(int); + method @NonNull public android.media.ImageWriter.Builder setMaxImages(@IntRange(from=1) int); + method @NonNull public android.media.ImageWriter.Builder setUsage(long); + method @NonNull public android.media.ImageWriter.Builder setWidthAndHeight(@IntRange(from=1) int, @IntRange(from=1) int); + } + public static interface ImageWriter.OnImageReleasedListener { method public void onImageReleased(android.media.ImageWriter); } @@ -32539,6 +32562,7 @@ package android.os { method public static final boolean is64Bit(); method public static boolean isApplicationUid(int); method public static final boolean isIsolated(); + method public static final boolean isSupplemental(); method public static final void killProcess(int); method public static final int myPid(); method @NonNull public static String myProcessName(); @@ -42288,7 +42312,6 @@ package android.telephony { field public static final String KEY_CHILD_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY = "iwlan.child_session_aes_ctr_key_size_int_array"; field public static final String KEY_DIFFIE_HELLMAN_GROUPS_INT_ARRAY = "iwlan.diffie_hellman_groups_int_array"; field public static final String KEY_DPD_TIMER_SEC_INT = "iwlan.dpd_timer_sec_int"; - field public static final String KEY_ENABLE_SUPPORT_FOR_EAP_AKA_FAST_REAUTH_BOOL = "iwlan.enable_support_for_eap_aka_fast_reauth_bool"; field public static final String KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY = "iwlan.epdg_address_priority_int_array"; field public static final String KEY_EPDG_AUTHENTICATION_METHOD_INT = "iwlan.epdg_authentication_method_int"; field public static final String KEY_EPDG_PCO_ID_IPV4_INT = "iwlan.epdg_pco_id_ipv4_int"; diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 2be0c5b1e36b..b082629ce99c 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -73,6 +73,11 @@ package android.app.usage { public class NetworkStatsManager { method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void forceUpdate(); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void notifyNetworkStatus(@NonNull java.util.List<android.net.Network>, @NonNull java.util.List<android.net.NetworkStateSnapshot>, @Nullable String, @NonNull java.util.List<android.net.UnderlyingNetworkInfo>); + method @NonNull @WorkerThread public android.app.usage.NetworkStats queryDetailsForDevice(@NonNull android.net.NetworkTemplate, long, long); + method @NonNull @WorkerThread public android.app.usage.NetworkStats queryDetailsForUidTagState(@NonNull android.net.NetworkTemplate, long, long, int, int, int) throws java.lang.SecurityException; + method @NonNull @WorkerThread public android.app.usage.NetworkStats querySummary(@NonNull android.net.NetworkTemplate, long, long) throws java.lang.SecurityException; + method @NonNull @WorkerThread public android.app.usage.NetworkStats.Bucket querySummaryForDevice(@NonNull android.net.NetworkTemplate, long, long); + method @NonNull @WorkerThread public android.app.usage.NetworkStats queryTaggedSummary(@NonNull android.net.NetworkTemplate, long, long) throws java.lang.SecurityException; method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void setDefaultGlobalAlert(long); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void setPollOnOpen(boolean); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void setStatsProviderWarningAndLimitAsync(@NonNull String, long, long); @@ -81,6 +86,14 @@ package android.app.usage { } +package android.bluetooth { + + public final class BluetoothPan implements android.bluetooth.BluetoothProfile { + method @Nullable @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.TETHER_PRIVILEGED}) public android.net.TetheringManager.TetheredInterfaceRequest requestTetheredInterface(@NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.TetheredInterfaceCallback); + } + +} + package android.content { public abstract class ContentProvider implements android.content.ComponentCallbacks2 { @@ -376,6 +389,10 @@ package android.os { } public class Process { + method public static final boolean isSupplemental(int); + method public static final int toAppUid(int); + method public static final int toSupplementalUid(int); + field public static final int NFC_UID = 1027; // 0x403 field public static final int VPN_UID = 1016; // 0x3f8 } @@ -407,6 +424,16 @@ package android.os { method @NonNull public java.util.List<android.content.ComponentName> getEnabledComponentOverrides(@NonNull String); } + public final class Trace { + method public static void asyncTraceBegin(long, @NonNull String, int); + method public static void asyncTraceEnd(long, @NonNull String, int); + method public static boolean isTagEnabled(long); + method public static void traceBegin(long, @NonNull String); + method public static void traceCounter(long, @NonNull String, int); + method public static void traceEnd(long); + field public static final long TRACE_TAG_NETWORK = 2097152L; // 0x200000L + } + } package android.os.storage { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index dea3ed55401f..445a01235fb9 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -139,6 +139,7 @@ package android { field public static final String INTERNAL_SYSTEM_WINDOW = "android.permission.INTERNAL_SYSTEM_WINDOW"; field public static final String INVOKE_CARRIER_SETUP = "android.permission.INVOKE_CARRIER_SETUP"; field public static final String KILL_UID = "android.permission.KILL_UID"; + field public static final String LAUNCH_DEVICE_MANAGER_SETUP = "android.permission.LAUNCH_DEVICE_MANAGER_SETUP"; field public static final String LOCAL_MAC_ADDRESS = "android.permission.LOCAL_MAC_ADDRESS"; field public static final String LOCK_DEVICE = "android.permission.LOCK_DEVICE"; field public static final String LOOP_RADIO = "android.permission.LOOP_RADIO"; @@ -366,6 +367,7 @@ package android { } public static final class R.bool { + field public static final int config_enableQrCodeScannerOnLockScreen; field public static final int config_sendPackageName = 17891328; // 0x1110000 field public static final int config_showDefaultAssistant = 17891329; // 0x1110001 field public static final int config_showDefaultEmergency = 17891330; // 0x1110002 @@ -1037,6 +1039,8 @@ package android.app.admin { method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_ADMIN_POLICY}) public java.util.List<java.lang.String> getPermittedInputMethodsForCurrentUser(); method @Nullable public android.content.ComponentName getProfileOwner() throws java.lang.IllegalArgumentException; method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public String getProfileOwnerNameAsUser(int) throws java.lang.IllegalArgumentException; + method @NonNull public String getString(@NonNull String, @NonNull java.util.concurrent.Callable<java.lang.String>); + method @NonNull public String getString(@NonNull String, @NonNull java.util.concurrent.Callable<java.lang.String>, @NonNull java.lang.Object...); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public int getUserProvisioningState(); method public boolean isDeviceManaged(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioned(); @@ -1049,25 +1053,28 @@ package android.app.admin { method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean packageHasActiveAdmins(String); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException; method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) public void resetDrawables(@NonNull int[]); + method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) public void resetStrings(@NonNull String[]); method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException; method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied(); method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) public void setDrawables(@NonNull java.util.Set<android.app.admin.DevicePolicyDrawableResource>); method @Deprecated @RequiresPermission(value=android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS, conditional=true) public void setProfileOwnerCanAccessDeviceIds(@NonNull android.content.ComponentName); method public void setSecondaryLockscreenEnabled(@NonNull android.content.ComponentName, boolean); + method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) public void setStrings(@NonNull java.util.Set<android.app.admin.DevicePolicyStringResource>); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setUserProvisioningState(int, @NonNull android.os.UserHandle); field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED"; field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED"; field public static final String ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE = "android.app.action.BIND_SECONDARY_LOCKSCREEN_SERVICE"; + field @RequiresPermission(android.Manifest.permission.DISPATCH_PROVISIONING_MESSAGE) public static final String ACTION_ESTABLISH_NETWORK_CONNECTION = "android.app.action.ESTABLISH_NETWORK_CONNECTION"; field public static final String ACTION_PROVISION_FINALIZATION = "android.app.action.PROVISION_FINALIZATION"; field public static final String ACTION_PROVISION_FINANCED_DEVICE = "android.app.action.PROVISION_FINANCED_DEVICE"; field public static final String ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE"; field @RequiresPermission(android.Manifest.permission.MANAGE_FACTORY_RESET_PROTECTION) public static final String ACTION_RESET_PROTECTION_POLICY_CHANGED = "android.app.action.RESET_PROTECTION_POLICY_CHANGED"; - field @RequiresPermission("android.permission.LAUNCH_DEVICE_MANAGER_SETUP") public static final String ACTION_ROLE_HOLDER_PROVISION_FINALIZATION = "android.app.action.ROLE_HOLDER_PROVISION_FINALIZATION"; - field @RequiresPermission("android.permission.LAUNCH_DEVICE_MANAGER_SETUP") public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE"; - field @RequiresPermission("android.permission.LAUNCH_DEVICE_MANAGER_SETUP") public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_PROFILE"; + field @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP) public static final String ACTION_ROLE_HOLDER_PROVISION_FINALIZATION = "android.app.action.ROLE_HOLDER_PROVISION_FINALIZATION"; + field @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP) public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE"; + field @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP) public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_PROFILE"; field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER"; field @Deprecated public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE"; - field @RequiresPermission("android.permission.LAUNCH_DEVICE_MANAGER_SETUP") public static final String ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER = "android.app.action.UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER"; + field @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP) public static final String ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER = "android.app.action.UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER"; field public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; // 0x6 field public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11; // 0xb field public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13; // 0xd @@ -1121,6 +1128,19 @@ package android.app.admin { field public static final int STATE_USER_UNMANAGED = 0; // 0x0 } + public static final class DevicePolicyResources.Strings { + field public static final String INVALID_ID = "INVALID_ID"; + } + + public final class DevicePolicyStringResource implements android.os.Parcelable { + ctor public DevicePolicyStringResource(@NonNull android.content.Context, @NonNull String, @StringRes int); + method public int describeContents(); + method public int getCallingPackageResourceId(); + method @NonNull public String getStringId(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DevicePolicyStringResource> CREATOR; + } + public final class FullyManagedDeviceProvisioningParams implements android.os.Parcelable { method public boolean canDeviceOwnerGrantSensorsPermissions(); method public int describeContents(); @@ -2041,6 +2061,8 @@ package android.app.usage { } public class NetworkStatsManager { + method @NonNull @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public android.net.NetworkStats getMobileUidStats(); + method @NonNull @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public android.net.NetworkStats getWifiUidStats(); method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STATS_PROVIDER, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void registerNetworkStatsProvider(@NonNull String, @NonNull android.net.netstats.provider.NetworkStatsProvider); method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STATS_PROVIDER, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void unregisterNetworkStatsProvider(@NonNull android.net.netstats.provider.NetworkStatsProvider); } @@ -2304,7 +2326,7 @@ package android.bluetooth { method @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isTetheringOn(); - method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.TETHER_PRIVILEGED}) public void setBluetoothTethering(boolean); + method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.TETHER_PRIVILEGED}) public void setBluetoothTethering(boolean); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int); field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED"; field public static final String ACTION_TETHERING_STATE_CHANGED = "android.bluetooth.action.TETHERING_STATE_CHANGED"; @@ -2582,10 +2604,32 @@ package android.companion { package android.companion.virtual { public final class VirtualDeviceManager { + method @Nullable @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.VirtualDeviceManager.VirtualDevice createVirtualDevice(int, @NonNull android.companion.virtual.VirtualDeviceParams); } public static class VirtualDeviceManager.VirtualDevice implements java.lang.AutoCloseable { method public void close(); + method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(int, int, int, @Nullable android.view.Surface, int, @Nullable android.os.Handler, @Nullable android.hardware.display.VirtualDisplay.Callback); + method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualKeyboard createVirtualKeyboard(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int); + method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int); + method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int); + } + + public final class VirtualDeviceParams implements android.os.Parcelable { + method public int describeContents(); + method public int getLockState(); + method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.VirtualDeviceParams> CREATOR; + field public static final int LOCK_STATE_ALWAYS_LOCKED = 0; // 0x0 + field public static final int LOCK_STATE_ALWAYS_UNLOCKED = 1; // 0x1 + } + + public static final class VirtualDeviceParams.Builder { + ctor public VirtualDeviceParams.Builder(); + method @NonNull public android.companion.virtual.VirtualDeviceParams build(); + method @NonNull @RequiresPermission(value="android.permission.ADD_ALWAYS_UNLOCKED_DISPLAY", conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int); + method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setUsersWithMatchingAccounts(@NonNull java.util.Set<android.os.UserHandle>); } } @@ -3639,6 +3683,7 @@ package android.hardware.display { method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration); method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfigurationForDisplay(@NonNull android.hardware.display.BrightnessConfiguration, @NonNull String); method @Deprecated @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_SATURATION) public void setSaturationLevel(float); + field public static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1024; // 0x400 } } @@ -7713,6 +7758,7 @@ package android.media.tv.tuner.frontend { public class FrontendStatus { method public int getAgc(); + method @NonNull public android.media.tv.tuner.frontend.Atsc3PlpInfo[] getAllAtsc3PlpInfo(); method @NonNull public android.media.tv.tuner.frontend.FrontendStatus.Atsc3PlpTuningInfo[] getAtsc3PlpTuningInfo(); method public int getBandwidth(); method public int getBer(); @@ -7755,6 +7801,7 @@ package android.media.tv.tuner.frontend { method public boolean isRfLocked(); method public boolean isShortFramesEnabled(); field public static final int FRONTEND_STATUS_TYPE_AGC = 14; // 0xe + field public static final int FRONTEND_STATUS_TYPE_ATSC3_ALL_PLP_INFO = 41; // 0x29 field public static final int FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO = 21; // 0x15 field public static final int FRONTEND_STATUS_TYPE_BANDWIDTH = 25; // 0x19 field public static final int FRONTEND_STATUS_TYPE_BER = 2; // 0x2 @@ -9674,9 +9721,9 @@ package android.permission { method @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String); method @Deprecated @BinderThread public void onRestoreDelayedRuntimePermissionsBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @Deprecated @BinderThread public void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable); + method @BinderThread public void onRevokeOwnPermissionsOnKill(@NonNull String, @NonNull java.util.List<java.lang.String>, @NonNull Runnable); method @BinderThread public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String, @NonNull Runnable); method @BinderThread public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.util.List<java.lang.String>>>); - method @BinderThread public void onSelfRevokePermissions(@NonNull String, @NonNull java.util.List<java.lang.String>, @NonNull Runnable); method @Deprecated @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @BinderThread public void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull android.permission.AdminPermissionControlParams, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @BinderThread public void onStageAndApplyRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable); @@ -10905,6 +10952,7 @@ package android.service.games { ctor public GameSession(); method public void onCreate(); method public void onDestroy(); + method public void setTaskOverlayView(@NonNull android.view.View, @NonNull android.view.ViewGroup.LayoutParams); } public abstract class GameSessionService extends android.app.Service { @@ -11045,6 +11093,7 @@ package android.service.persistentdata { method @android.service.persistentdata.PersistentDataBlockManager.FlashLockState @RequiresPermission(anyOf={android.Manifest.permission.READ_OEM_UNLOCK_STATE, "android.permission.OEM_UNLOCK_STATE"}) public int getFlashLockState(); method public long getMaximumDataBlockSize(); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_OEM_UNLOCK_STATE, "android.permission.OEM_UNLOCK_STATE"}) public boolean getOemUnlockEnabled(); + method @NonNull public String getPersistentDataPackageName(); method public byte[] read(); method @Deprecated @RequiresPermission("android.permission.OEM_UNLOCK_STATE") public void setOemUnlockEnabled(boolean); method @RequiresPermission("android.permission.OEM_UNLOCK_STATE") public void wipe(); diff --git a/core/java/android/accessibilityservice/TouchInteractionController.java b/core/java/android/accessibilityservice/TouchInteractionController.java index a8ba1d33f3b4..bb2b8d45fd61 100644 --- a/core/java/android/accessibilityservice/TouchInteractionController.java +++ b/core/java/android/accessibilityservice/TouchInteractionController.java @@ -24,6 +24,8 @@ import android.util.ArrayMap; import android.view.MotionEvent; import android.view.accessibility.AccessibilityInteractionClient; +import java.util.LinkedList; +import java.util.Queue; import java.util.concurrent.Executor; /** @@ -102,6 +104,11 @@ public final class TouchInteractionController { private boolean mServiceDetectsGestures; /** Map of callbacks to executors. Lazily created when adding the first callback. */ private ArrayMap<Callback, Executor> mCallbacks; + // A list of motion events that should be queued until a pending transition has taken place. + private Queue<MotionEvent> mQueuedMotionEvents = new LinkedList<>(); + // Whether this controller is waiting for a state transition. + // Motion events will be queued and sent to listeners after the transition has taken place. + private boolean mStateChangeRequested = false; // The current state of the display. private int mState = STATE_CLEAR; @@ -169,6 +176,14 @@ public final class TouchInteractionController { * main thread. */ void onMotionEvent(MotionEvent event) { + if (mStateChangeRequested) { + mQueuedMotionEvents.add(event); + } else { + sendEventToAllListeners(event); + } + } + + private void sendEventToAllListeners(MotionEvent event) { final ArrayMap<Callback, Executor> entries; synchronized (mLock) { // callbacks may remove themselves. Perform a shallow copy to avoid concurrent @@ -209,6 +224,10 @@ public final class TouchInteractionController { callback.onStateChanged(state); } } + mStateChangeRequested = false; + while (mQueuedMotionEvents.size() > 0) { + sendEventToAllListeners(mQueuedMotionEvents.poll()); + } } /** @@ -253,6 +272,7 @@ public final class TouchInteractionController { } catch (RemoteException re) { throw new RuntimeException(re); } + mStateChangeRequested = true; } } @@ -281,6 +301,7 @@ public final class TouchInteractionController { } catch (RemoteException re) { throw new RuntimeException(re); } + mStateChangeRequested = true; } } @@ -302,6 +323,7 @@ public final class TouchInteractionController { } catch (RemoteException re) { throw new RuntimeException(re); } + mStateChangeRequested = true; } } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 1e0b143749f5..a7b96a6f136d 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -1524,7 +1524,10 @@ public class Activity extends ContextThemeWrapper } private void dispatchActivityConfigurationChanged() { - getApplication().dispatchActivityConfigurationChanged(this); + // In case the new config comes before mApplication is assigned. + if (getApplication() != null) { + getApplication().dispatchActivityConfigurationChanged(this); + } Object[] callbacks = collectActivityLifecycleCallbacks(); if (callbacks != null) { for (int i = 0; i < callbacks.length; i++) { diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index fa48730d4950..f3315a8dc089 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -2179,8 +2179,8 @@ class ContextImpl extends Context { } @Override - public void selfRevokePermissions(@NonNull Collection<String> permissions) { - getSystemService(PermissionManager.class).selfRevokePermissions(permissions); + public void revokeOwnPermissionsOnKill(@NonNull Collection<String> permissions) { + getSystemService(PermissionManager.class).revokeOwnPermissionsOnKill(permissions); } @Override diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index a9ec11edd680..0801b2481f0c 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -72,6 +72,7 @@ import android.view.IRemoteAnimationRunner; import android.view.RemoteAnimationDefinition; import android.view.RemoteAnimationAdapter; import android.window.IWindowOrganizerController; +import android.window.BackNavigationInfo; import android.window.SplashScreenView; import com.android.internal.app.IVoiceInteractor; import com.android.internal.os.IResultReceiver; @@ -346,7 +347,8 @@ interface IActivityTaskManager { void setRunningRemoteTransitionDelegate(in IApplicationThread caller); /** - * Prepare the back preview in the server + * Prepare the back navigation in the server. This setups the leashed for sysui to animate + * the back gesture and returns the data needed for the animation. */ - void startBackPreview(IRemoteAnimationRunner runner); + android.window.BackNavigationInfo startBackNavigation(); } diff --git a/core/java/android/app/ServiceStartNotAllowedException.java b/core/java/android/app/ServiceStartNotAllowedException.java index 33285b2190eb..b1f47eee4bfd 100644 --- a/core/java/android/app/ServiceStartNotAllowedException.java +++ b/core/java/android/app/ServiceStartNotAllowedException.java @@ -40,4 +40,11 @@ public abstract class ServiceStartNotAllowedException extends IllegalStateExcept return new BackgroundServiceStartNotAllowedException(message); } } + + @Override + public synchronized Throwable getCause() { + // "Cause" is often used for clustering exceptions, and developers don't want to have it + // for this exception. b/210890426 + return null; + } } diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 4af4c5de8a48..63c1fd8f2a4a 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -1024,19 +1024,21 @@ public final class SystemServiceRegistry { }}); registerService(Context.PERSISTENT_DATA_BLOCK_SERVICE, PersistentDataBlockManager.class, - new StaticServiceFetcher<PersistentDataBlockManager>() { + new CachedServiceFetcher<PersistentDataBlockManager>() { @Override - public PersistentDataBlockManager createService() throws ServiceNotFoundException { + public PersistentDataBlockManager createService(ContextImpl ctx) + throws ServiceNotFoundException { IBinder b = ServiceManager.getServiceOrThrow(Context.PERSISTENT_DATA_BLOCK_SERVICE); IPersistentDataBlockService persistentDataBlockService = IPersistentDataBlockService.Stub.asInterface(b); if (persistentDataBlockService != null) { - return new PersistentDataBlockManager(persistentDataBlockService); + return new PersistentDataBlockManager(ctx, persistentDataBlockService); } else { // not supported return null; } - }}); + } + }); registerService(Context.OEM_LOCK_SERVICE, OemLockManager.class, new StaticServiceFetcher<OemLockManager>() { diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 658dcf393d5d..cefd25ac7ac5 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -3012,6 +3012,54 @@ public class DevicePolicyManager { "android.app.extra.PROVISIONING_ROLE_HOLDER_CUSTOM_USER_CONSENT_INTENT"; /** + * Activity action: attempts to establish network connection + * + * <p>This intent can be accompanied by any of the relevant provisioning extras related to + * network connectivity, such as: + * <ul> + * <li>{@link #EXTRA_PROVISIONING_WIFI_SSID}</li> + * <li>{@link #EXTRA_PROVISIONING_WIFI_HIDDEN}</li> + * <li>{@link #EXTRA_PROVISIONING_WIFI_SECURITY_TYPE}</li> + * <li>{@link #EXTRA_PROVISIONING_WIFI_PASSWORD}</li> + * <li>{@link #EXTRA_PROVISIONING_WIFI_PROXY_HOST}</li> + * <li>{@link #EXTRA_PROVISIONING_WIFI_PROXY_PORT}</li> + * <li>{@link #EXTRA_PROVISIONING_WIFI_PROXY_BYPASS}</li> + * <li>{@link #EXTRA_PROVISIONING_WIFI_PAC_URL}</li> + * <li>{@code #EXTRA_PROVISIONING_WIFI_EAP_METHOD}</li> + * <li>{@code #EXTRA_PROVISIONING_WIFI_PHASE2_AUTH}</li> + * <li>{@code #EXTRA_PROVISIONING_WIFI_CA_CERTIFICATE}</li> + * <li>{@code #EXTRA_PROVISIONING_WIFI_USER_CERTIFICATE}</li> + * <li>{@code #EXTRA_PROVISIONING_WIFI_IDENTITY}</li> + * <li>{@code #EXTRA_PROVISIONING_WIFI_ANONYMOUS_IDENTITY}</li> + * <li>{@code #EXTRA_PROVISIONING_WIFI_DOMAIN}</li> + * </ul> + * + * <p>If there are provisioning extras related to network connectivity, this activity + * attempts to connect to the specified network. Otherwise it prompts the end-user to connect. + * + * <p>This activity is meant to be started by the provisioning initiator prior to starting + * {@link #ACTION_PROVISION_MANAGED_PROFILE} or {@link + * #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}. + * + * <p>Note that network connectivity is still also handled when provisioning via {@link + * #ACTION_PROVISION_MANAGED_PROFILE} or {@link + * #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}. {@link + * #ACTION_ESTABLISH_NETWORK_CONNECTION} should only be used in cases when the provisioning + * initiator would like to do some additional logic after the network connectivity step and + * before the start of provisioning. + * + * If network connection is established, {@link Activity#RESULT_OK} will be returned. Otherwise + * the result will be {@link Activity#RESULT_CANCELED}. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.DISPATCH_PROVISIONING_MESSAGE) + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + @SystemApi + public static final String ACTION_ESTABLISH_NETWORK_CONNECTION = + "android.app.action.ESTABLISH_NETWORK_CONNECTION"; + + /** * Maximum supported password length. Kind-of arbitrary. * @hide */ @@ -3282,14 +3330,15 @@ public class DevicePolicyManager { /** * Broadcast action: notify system apps (e.g. settings, SysUI, etc) that the device management - * resources with IDs {@link #EXTRA_RESOURCE_ID} has been updated using, the updated resources - * can be retrieved using {@link #getDrawable}. + * resources with IDs {@link #EXTRA_RESOURCE_ID} has been updated, the updated resources can be + * retrieved using {@link #getDrawable} and {@code #getString}. * * <p>This broadcast is sent to registered receivers only. * * <p> The following extras will be included to identify the type of resource being updated: * <ul> * <li>{@link #EXTRA_RESOURCE_TYPE_DRAWABLE} for drawable resources</li> + * <li>{@link #EXTRA_RESOURCE_TYPE_STRING} for string resources</li> * </ul> */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) @@ -3304,8 +3353,16 @@ public class DevicePolicyManager { "android.app.extra.RESOURCE_TYPE_DRAWABLE"; /** + * A boolean extra for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to indicate that a + * resource of type {@link String} is being updated. + */ + public static final String EXTRA_RESOURCE_TYPE_STRING = + "android.app.extra.RESOURCE_TYPE_STRING"; + + /** * An integer array extra for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to indicate which - * drawable IDs (see {@link DevicePolicyResources.UpdatableDrawableId}) have been updated. + * resource IDs (see {@link DevicePolicyResources.UpdatableDrawableId} and + * {@link DevicePolicyResources.UpdatableStringId}) have been updated. */ public static final String EXTRA_RESOURCE_ID = "android.app.extra.RESOURCE_ID"; @@ -14535,11 +14592,6 @@ public class DevicePolicyManager { * * @param drawables The list of {@link DevicePolicyDrawableResource} to update. * - * @throws IllegalArgumentException if {@link DevicePolicyDrawableResource#getDrawableId()}, - * {@link DevicePolicyDrawableResource#getDrawableStyle()}, or - * {@link DevicePolicyDrawableResource#getDrawableSource()} aren't defined in - * {@link DevicePolicyResources.Drawable}. - * * @hide */ @SystemApi @@ -14567,9 +14619,6 @@ public class DevicePolicyManager { * * @param drawableIds The list of IDs to remove. * - * @throws IllegalArgumentException if IDs are not defined in - * {@link DevicePolicyResources.Drawable} - * * @hide */ @SystemApi @@ -14593,6 +14642,9 @@ public class DevicePolicyManager { * <p>Also returns the drawable from {@code defaultDrawableLoader} if * {@link DevicePolicyResources.Drawable#INVALID_ID} was passed. * + * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a + * {@link NullPointerException} is thrown. + * * <p>This API uses the screen density returned from {@link Resources#getConfiguration()}, to * set a different value use * {@link #getDrawableForDensity(int, int, int, Callable)}. @@ -14608,7 +14660,7 @@ public class DevicePolicyManager { * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for * the provided params. */ - @Nullable + @NonNull public Drawable getDrawable( @DevicePolicyResources.UpdatableDrawableId int drawableId, @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle, @@ -14622,6 +14674,9 @@ public class DevicePolicyManager { * could result in returning a different drawable than {@link #getDrawable(int, int, Callable)} * if an override was set for that specific source. * + * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a + * {@link NullPointerException} is thrown. + * * <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get * notified when a resource has been updated. * @@ -14631,7 +14686,7 @@ public class DevicePolicyManager { * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for * the provided params. */ - @Nullable + @NonNull public Drawable getDrawable( @DevicePolicyResources.UpdatableDrawableId int drawableId, @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle, @@ -14669,6 +14724,9 @@ public class DevicePolicyManager { * Similar to {@link #getDrawable(int, int, Callable)}, but also accepts * {@code density}. See {@link Resources#getDrawableForDensity(int, int, Resources.Theme)}. * + * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a + * {@link NullPointerException} is thrown. + * * <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get * notified when a resource has been updated. * @@ -14680,7 +14738,7 @@ public class DevicePolicyManager { * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for * the provided params. */ - @Nullable + @NonNull public Drawable getDrawableForDensity( @DevicePolicyResources.UpdatableDrawableId int drawableId, @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle, @@ -14698,6 +14756,9 @@ public class DevicePolicyManager { * Similar to {@link #getDrawable(int, int, int, Callable)}, but also accepts * {@code density}. See {@link Resources#getDrawableForDensity(int, int, Resources.Theme)}. * + * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a + * {@link NullPointerException} is thrown. + * * <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get * notified when a resource has been updated. * @@ -14710,7 +14771,7 @@ public class DevicePolicyManager { * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for * the provided params. */ - @Nullable + @NonNull public Drawable getDrawableForDensity( @DevicePolicyResources.UpdatableDrawableId int drawableId, @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle, @@ -14740,4 +14801,167 @@ public class DevicePolicyManager { } return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader); } + + /** + * For each {@link DevicePolicyStringResource} item in {@code strings}, it updates the string + * resource for {@link DevicePolicyStringResource#getStringId()} to the string with ID + * {@code callingPackageResourceId} (see {@link DevicePolicyResources.String}), meaning any + * system UI surface calling {@link #getString} with {@code stringId} will get + * the new resource after this API is called. + * + * <p>Sends a broadcast with action {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to + * registered receivers when a resource has been updated successfully. + * + * <p>Important notes to consider when using this API: + * <ul> + * <li> {@link #getString} references the resource + * {@code callingPackageResourceId} in the calling package each time it gets called. You have to + * ensure that the resource is always available in the calling package as long as it is used as + * an updated resource. + * <li> You still have to re-call {@code setStrings} even if you only make changes to the + * content of the resource with ID {@code callingPackageResourceId} as the content might be + * cached and would need updating. + * </ul> + * + * @param strings The list of {@link DevicePolicyStringResource} to update. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) + public void setStrings(@NonNull Set<DevicePolicyStringResource> strings) { + if (mService != null) { + try { + mService.setStrings(new ArrayList<>(strings)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Removes the updated strings for the list of {@code stringIds} (see + * {@link DevicePolicyResources.String}) that was previously set by calling {@link #setStrings}, + * meaning any subsequent calls to {@link #getString} for the provided IDs will + * return the default string from {@code defaultStringLoader}. + * + * <p>Sends a broadcast with action {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to + * registered receivers when a resource has been reset successfully. + * + * @param stringIds The list of IDs to remove the updated resources for. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) + public void resetStrings(@NonNull String[] stringIds) { + if (mService != null) { + try { + mService.resetStrings(stringIds); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Returns the appropriate updated string for the {@code stringId} (see + * {@link DevicePolicyResources.String}) if one was set using + * {@link #setStrings}, otherwise returns the string from {@code defaultStringLoader}. + * + * <p>Also returns the string from {@code defaultStringLoader} if + * {@link DevicePolicyResources.String#INVALID_ID} was passed. + * + * <p>{@code defaultStringLoader} must return a non {@code null} {@link String}, otherwise a + * {@link NullPointerException} is thrown. + * + * <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get + * notified when a resource has been updated. + * + * <p>Note that each call to this API loads the resource from the package that called + * {@link #setStrings} to set the updated resource. + * + * @param stringId The IDs to get the updated resource for. + * @param defaultStringLoader To get the default string if no updated string was set for + * {@code stringId}. + * + * @hide + */ + @SystemApi + @NonNull + public String getString( + @NonNull @DevicePolicyResources.UpdatableStringId String stringId, + @NonNull Callable<String> defaultStringLoader) { + + Objects.requireNonNull(stringId, "stringId can't be null"); + Objects.requireNonNull(defaultStringLoader, "defaultStringLoader can't be null"); + + if (stringId.equals(DevicePolicyResources.Strings.INVALID_ID)) { + return ParcelableResource.loadDefaultString(defaultStringLoader); + } + if (mService != null) { + try { + ParcelableResource resource = mService.getString(stringId); + if (resource == null) { + return ParcelableResource.loadDefaultString(defaultStringLoader); + } + return resource.getString(mContext, defaultStringLoader); + } catch (RemoteException e) { + Log.e( + TAG, + "Error getting the updated string from DevicePolicyManagerService.", + e); + return ParcelableResource.loadDefaultString(defaultStringLoader); + } + } + return ParcelableResource.loadDefaultString(defaultStringLoader); + } + + /** + * Similar to {@link #getString(String, Callable)} but accepts {@code formatArgs} and returns a + * localized formatted string, substituting the format arguments as defined in + * {@link java.util.Formatter} and {@link java.lang.String#format}, (see + * {@link Resources#getString(int, Object...)}). + * + * <p>{@code defaultStringLoader} must return a non {@code null} {@link String}, otherwise a + * {@link NullPointerException} is thrown. + * + * @param stringId The IDs to get the updated resource for. + * @param defaultStringLoader To get the default string if no updated string was set for + * {@code stringId}. + * @param formatArgs The format arguments that will be used for substitution. + * + * @hide + */ + @SystemApi + @NonNull + @SuppressLint("SamShouldBeLast") + public String getString( + @NonNull @DevicePolicyResources.UpdatableStringId String stringId, + @NonNull Callable<String> defaultStringLoader, + @NonNull Object... formatArgs) { + + Objects.requireNonNull(stringId, "stringId can't be null"); + Objects.requireNonNull(defaultStringLoader, "defaultStringLoader can't be null"); + + if (stringId.equals(DevicePolicyResources.Strings.INVALID_ID)) { + return ParcelableResource.loadDefaultString(defaultStringLoader); + } + if (mService != null) { + try { + ParcelableResource resource = mService.getString(stringId); + if (resource == null) { + return ParcelableResource.loadDefaultString(defaultStringLoader); + } + return resource.getString(mContext, defaultStringLoader, formatArgs); + } catch (RemoteException e) { + Log.e( + TAG, + "Error getting the updated string from DevicePolicyManagerService.", + e); + return ParcelableResource.loadDefaultString(defaultStringLoader); + } + } + return ParcelableResource.loadDefaultString(defaultStringLoader); + } } diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java index 5133f26f8111..21e20cd0d90e 100644 --- a/core/java/android/app/admin/DevicePolicyResources.java +++ b/core/java/android/app/admin/DevicePolicyResources.java @@ -16,8 +16,60 @@ package android.app.admin; +import static android.app.admin.DevicePolicyResources.Strings.Launcher.ALL_APPS_PERSONAL_TAB; +import static android.app.admin.DevicePolicyResources.Strings.Launcher.ALL_APPS_PERSONAL_TAB_ACCESSIBILITY; +import static android.app.admin.DevicePolicyResources.Strings.Launcher.ALL_APPS_WORK_TAB; +import static android.app.admin.DevicePolicyResources.Strings.Launcher.ALL_APPS_WORK_TAB_ACCESSIBILITY; +import static android.app.admin.DevicePolicyResources.Strings.Launcher.DISABLED_BY_ADMIN_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Launcher.WIDGETS_PERSONAL_TAB; +import static android.app.admin.DevicePolicyResources.Strings.Launcher.WIDGETS_WORK_TAB; +import static android.app.admin.DevicePolicyResources.Strings.Launcher.WORK_FOLDER_NAME; +import static android.app.admin.DevicePolicyResources.Strings.Launcher.WORK_PROFILE_EDU; +import static android.app.admin.DevicePolicyResources.Strings.Launcher.WORK_PROFILE_EDU_ACCEPT; +import static android.app.admin.DevicePolicyResources.Strings.Launcher.WORK_PROFILE_ENABLE_BUTTON; +import static android.app.admin.DevicePolicyResources.Strings.Launcher.WORK_PROFILE_PAUSED_DESCRIPTION; +import static android.app.admin.DevicePolicyResources.Strings.Launcher.WORK_PROFILE_PAUSED_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Launcher.WORK_PROFILE_PAUSE_BUTTON; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PIN_LAST_ATTEMPT; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_MANAGEMENT_DISCLOSURE; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.ONGOING_PRIVACY_DIALOG_WORK; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_CA_CERT; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_NAMED_VPN; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_NETWORK; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_TWO_NAMED_VPN; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_NAMED_MANAGEMENT; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_PERSONAL_PROFILE_NAMED_VPN; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_VIEW_POLICIES; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_WORK_PROFILE_CA_CERT; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_WORK_PROFILE_NAMED_VPN; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_WORK_PROFILE_NETWORK; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_MANAGEMENT; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_MANAGEMENT_MONITORING; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_MANAGEMENT_MULTIPLE_VPNS; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_MANAGEMENT_NAMED_VPN; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_MANAGEMENT; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_MANAGEMENT_MONITORING; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_MANAGEMENT_MULTIPLE_VPNS; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_MANAGEMENT_NAMED_VPN; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_WORK_PROFILE_MONITORING; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_PERSONAL_PROFILE_NAMED_VPN; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_WORK_PROFILE_MONITORING; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_WORK_PROFILE_NAMED_VPN; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_WORK_PROFILE_NETWORK; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.STATUS_BAR_WORK_ICON_ACCESSIBILITY; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.WORK_LOCK_ACCESSIBILITY; + + import android.annotation.IntDef; +import android.annotation.StringDef; import android.annotation.SuppressLint; +import android.annotation.SystemApi; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -27,8 +79,8 @@ import java.util.Set; /** * Class containing the required identifiers to update device management resources. * - * <p>See {@link DevicePolicyManager#getDrawable}. - * + * <p>See {@link DevicePolicyManager#getDrawable} and + * {@code DevicePolicyManager#getString}. */ public final class DevicePolicyResources { @@ -78,6 +130,40 @@ public final class DevicePolicyResources { }) public @interface UpdatableDrawableSource {} + /** + * Resource identifiers used to update device management-related string resources. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @StringDef({ + // Launcher Strings + WORK_PROFILE_EDU, WORK_PROFILE_EDU_ACCEPT, WORK_PROFILE_PAUSED_TITLE, + WORK_PROFILE_PAUSED_DESCRIPTION, WORK_PROFILE_PAUSE_BUTTON, WORK_PROFILE_ENABLE_BUTTON, + ALL_APPS_WORK_TAB, ALL_APPS_PERSONAL_TAB, ALL_APPS_WORK_TAB_ACCESSIBILITY, + ALL_APPS_PERSONAL_TAB_ACCESSIBILITY, WORK_FOLDER_NAME, WIDGETS_WORK_TAB, + WIDGETS_PERSONAL_TAB, DISABLED_BY_ADMIN_MESSAGE, + + // SysUI Strings + QS_MSG_MANAGEMENT, QS_MSG_NAMED_MANAGEMENT, QS_MSG_MANAGEMENT_MONITORING, + QS_MSG_NAMED_MANAGEMENT_MONITORING, QS_MSG_MANAGEMENT_NAMED_VPN, + QS_MSG_NAMED_MANAGEMENT_NAMED_VPN, QS_MSG_MANAGEMENT_MULTIPLE_VPNS, + QS_MSG_NAMED_MANAGEMENT_MULTIPLE_VPNS, QS_MSG_WORK_PROFILE_MONITORING, + QS_MSG_NAMED_WORK_PROFILE_MONITORING, QS_MSG_WORK_PROFILE_NETWORK, + QS_MSG_WORK_PROFILE_NAMED_VPN, QS_MSG_PERSONAL_PROFILE_NAMED_VPN, + QS_DIALOG_MANAGEMENT_TITLE, QS_DIALOG_VIEW_POLICIES, QS_DIALOG_MANAGEMENT, + QS_DIALOG_NAMED_MANAGEMENT, QS_DIALOG_MANAGEMENT_CA_CERT, + QS_DIALOG_WORK_PROFILE_CA_CERT, QS_DIALOG_MANAGEMENT_NETWORK, + QS_DIALOG_WORK_PROFILE_NETWORK, QS_DIALOG_MANAGEMENT_NAMED_VPN, + QS_DIALOG_MANAGEMENT_TWO_NAMED_VPN, QS_DIALOG_WORK_PROFILE_NAMED_VPN, + QS_DIALOG_PERSONAL_PROFILE_NAMED_VPN, BIOMETRIC_DIALOG_WORK_PIN_LAST_ATTEMPT, + BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT, BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT, + BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS, STATUS_BAR_WORK_ICON_ACCESSIBILITY, + ONGOING_PRIVACY_DIALOG_WORK, KEYGUARD_MANAGEMENT_DISCLOSURE, + KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE, WORK_LOCK_ACCESSIBILITY + }) + public @interface UpdatableStringId { + } /** * Class containing the identifiers used to update device management-related system drawable. @@ -240,4 +326,418 @@ public final class DevicePolicyResources { } } } + + /** + * Class containing the identifiers used to update device management-related system strings. + * + * @hide + */ + @SystemApi + public static final class Strings { + + private Strings() {} + + /** + * An ID for any string that can't be updated. + */ + public static final String INVALID_ID = "INVALID_ID"; + + /** + * @hide + */ + public static final Set<String> UPDATABLE_STRING_IDS = buildStringsSet(); + + private static Set<String> buildStringsSet() { + Set<String> strings = new HashSet<>(); + strings.addAll(Launcher.buildStringsSet()); + strings.addAll(SystemUi.buildStringsSet()); + return strings; + } + + /** + * Class containing the identifiers used to update device management-related system strings + * in the Launcher package. + * + * @hide + */ + public static final class Launcher { + + private Launcher(){} + + /** + * User on-boarding title for work profile apps. + */ + public static final String WORK_PROFILE_EDU = "WORK_PROFILE_EDU"; + + /** + * Action label to finish work profile edu. + */ + public static final String WORK_PROFILE_EDU_ACCEPT = "WORK_PROFILE_EDU_ACCEPT"; + + /** + * Title shown when user opens work apps tab while work profile is paused. + */ + public static final String WORK_PROFILE_PAUSED_TITLE = "WORK_PROFILE_PAUSED_TITLE"; + + /** + * Description shown when user opens work apps tab while work profile is paused. + */ + public static final String WORK_PROFILE_PAUSED_DESCRIPTION = + "WORK_PROFILE_PAUSED_DESCRIPTION"; + + /** + * Shown on the button to pause work profile. + */ + public static final String WORK_PROFILE_PAUSE_BUTTON = "WORK_PROFILE_PAUSE_BUTTON"; + + /** + * Shown on the button to enable work profile. + */ + public static final String WORK_PROFILE_ENABLE_BUTTON = "WORK_PROFILE_ENABLE_BUTTON"; + + /** + * Label on launcher tab to indicate work apps. + */ + public static final String ALL_APPS_WORK_TAB = "ALL_APPS_WORK_TAB"; + + /** + * Label on launcher tab to indicate personal apps. + */ + public static final String ALL_APPS_PERSONAL_TAB = "ALL_APPS_PERSONAL_TAB"; + + /** + * Accessibility description for launcher tab to indicate work apps. + */ + public static final String ALL_APPS_WORK_TAB_ACCESSIBILITY = + "ALL_APPS_WORK_TAB_ACCESSIBILITY"; + + /** + * Accessibility description for launcher tab to indicate personal apps. + */ + public static final String ALL_APPS_PERSONAL_TAB_ACCESSIBILITY = + "ALL_APPS_PERSONAL_TAB_ACCESSIBILITY"; + + /** + * Work folder name. + */ + public static final String WORK_FOLDER_NAME = "WORK_FOLDER_NAME"; + + /** + * Label on widget tab to indicate work app widgets. + */ + public static final String WIDGETS_WORK_TAB = "WIDGETS_WORK_TAB"; + + /** + * Label on widget tab to indicate personal app widgets. + */ + public static final String WIDGETS_PERSONAL_TAB = "WIDGETS_PERSONAL_TAB"; + + /** + * Message shown when a feature is disabled by the admin (e.g. changing wallpaper). + */ + public static final String DISABLED_BY_ADMIN_MESSAGE = "DISABLED_BY_ADMIN_MESSAGE"; + + /** + * @hide + */ + static Set<String> buildStringsSet() { + Set<String> strings = new HashSet<>(); + strings.add(WORK_PROFILE_EDU); + strings.add(WORK_PROFILE_EDU_ACCEPT); + strings.add(WORK_PROFILE_PAUSED_TITLE); + strings.add(WORK_PROFILE_PAUSED_DESCRIPTION); + strings.add(WORK_PROFILE_PAUSE_BUTTON); + strings.add(WORK_PROFILE_ENABLE_BUTTON); + strings.add(ALL_APPS_WORK_TAB); + strings.add(ALL_APPS_PERSONAL_TAB); + strings.add(ALL_APPS_PERSONAL_TAB_ACCESSIBILITY); + strings.add(ALL_APPS_WORK_TAB_ACCESSIBILITY); + strings.add(WORK_FOLDER_NAME); + strings.add(WIDGETS_WORK_TAB); + strings.add(WIDGETS_PERSONAL_TAB); + strings.add(DISABLED_BY_ADMIN_MESSAGE); + return strings; + } + } + + /** + * Class containing the identifiers used to update device management-related system strings + * in the SystemUi package. + * + * @hide + */ + public static final class SystemUi { + + private SystemUi() { + } + + /** + * Label in quick settings for toggling work profile on/off. + */ + public static final String QS_WORK_PROFILE_LABEL = "QS_WORK_PROFILE_LABEL"; + + /** + * Disclosure at the bottom of Quick Settings to indicate device management. + */ + public static final String QS_MSG_MANAGEMENT = "QS_MSG_MANAGEMENT"; + + /** + * Similar to {@link #QS_MSG_MANAGEMENT} but accepts the organization name as a + * param. + */ + public static final String QS_MSG_NAMED_MANAGEMENT = "QS_MSG_NAMED_MANAGEMENT"; + + /** + * Disclosure at the bottom of Quick Settings to indicate device management monitoring. + */ + public static final String QS_MSG_MANAGEMENT_MONITORING = + "QS_MSG_MANAGEMENT_MONITORING"; + + /** + * Similar to {@link #QS_MSG_MANAGEMENT_MONITORING} but accepts the + * organization name as a param. + */ + public static final String QS_MSG_NAMED_MANAGEMENT_MONITORING = + "QS_MSG_NAMED_MANAGEMENT_MONITORING"; + + /** + * Disclosure at the bottom of Quick Settings to indicate device management and the + * device is connected to a VPN, accepts VPN name as a param. + */ + public static final String QS_MSG_MANAGEMENT_NAMED_VPN = + "QS_MSG_MANAGEMENT_NAMED_VPN"; + + /** + * Similar to {@link #QS_MSG_MANAGEMENT_NAMED_VPN} but also accepts the + * organization name as a param. + */ + public static final String QS_MSG_NAMED_MANAGEMENT_NAMED_VPN = + "QS_MSG_NAMED_MANAGEMENT_NAMED_VPN"; + + /** + * Disclosure at the bottom of Quick Settings to indicate device management and the + * device is connected to multiple VPNs. + */ + public static final String QS_MSG_MANAGEMENT_MULTIPLE_VPNS = + "QS_MSG_MANAGEMENT_MULTIPLE_VPNS"; + + /** + * Similar to {@link #QS_MSG_MANAGEMENT_MULTIPLE_VPNS} but also accepts the + * organization name as a param. + */ + public static final String QS_MSG_NAMED_MANAGEMENT_MULTIPLE_VPNS = + "QS_MSG_NAMED_MANAGEMENT_MULTIPLE_VPNS"; + + /** + * Disclosure at the bottom of Quick Settings to indicate work profile monitoring. + */ + public static final String QS_MSG_WORK_PROFILE_MONITORING = + "QS_MSG_WORK_PROFILE_MONITORING"; + + /** + * Similar to {@link #QS_MSG_WORK_PROFILE_MONITORING} but accepts the + * organization name as a param. + */ + public static final String QS_MSG_NAMED_WORK_PROFILE_MONITORING = + "QS_MSG_NAMED_WORK_PROFILE_MONITORING"; + + /** + * Disclosure at the bottom of Quick Settings to indicate network activity is visible to + * admin. + */ + public static final String QS_MSG_WORK_PROFILE_NETWORK = "QS_MSG_WORK_PROFILE_NETWORK"; + + /** + * Disclosure at the bottom of Quick Settings to indicate work profile is connected to a + * VPN, accepts VPN name as a param. + */ + public static final String QS_MSG_WORK_PROFILE_NAMED_VPN = + "QS_MSG_WORK_PROFILE_NAMED_VPN"; + + /** + * Disclosure at the bottom of Quick Settings to indicate personal profile is connected + * to a VPN, accepts VPN name as a param. + */ + public static final String QS_MSG_PERSONAL_PROFILE_NAMED_VPN = + "QS_MSG_PERSONAL_PROFILE_NAMED_VPN"; + + /** + * Title for dialog to indicate device management. + */ + public static final String QS_DIALOG_MANAGEMENT_TITLE = "QS_DIALOG_MANAGEMENT_TITLE"; + + /** + * Label for button in the device management dialog to open a page with more information + * on the admin's abilities. + */ + public static final String QS_DIALOG_VIEW_POLICIES = "QS_DIALOG_VIEW_POLICIES"; + + /** + * Description for device management dialog to indicate admin abilities. + */ + public static final String QS_DIALOG_MANAGEMENT = "QS_DIALOG_MANAGEMENT"; + + /** + * Similar to {@link #QS_DIALOG_MANAGEMENT} but accepts the organization name as a + * param. + */ + public static final String QS_DIALOG_NAMED_MANAGEMENT = "QS_DIALOG_NAMED_MANAGEMENT"; + + /** + * Description for the managed device certificate authorities in the device management + * dialog. + */ + public static final String QS_DIALOG_MANAGEMENT_CA_CERT = + "QS_DIALOG_MANAGEMENT_CA_CERT"; + + /** + * Description for the work profile certificate authorities in the device management + * dialog. + */ + public static final String QS_DIALOG_WORK_PROFILE_CA_CERT = + "QS_DIALOG_WORK_PROFILE_CA_CERT"; + + /** + * Description for the managed device network logging in the device management dialog. + */ + public static final String QS_DIALOG_MANAGEMENT_NETWORK = + "QS_DIALOG_MANAGEMENT_NETWORK"; + + /** + * Description for the work profile network logging in the device management dialog. + */ + public static final String QS_DIALOG_WORK_PROFILE_NETWORK = + "QS_DIALOG_WORK_PROFILE_NETWORK"; + + /** + * Description for an active VPN in the device management dialog, accepts VPN name as a + * param. + */ + public static final String QS_DIALOG_MANAGEMENT_NAMED_VPN = + "QS_DIALOG_MANAGEMENT_NAMED_VPN"; + + /** + * Description for two active VPN in the device management dialog, accepts two VPN names + * as params. + */ + public static final String QS_DIALOG_MANAGEMENT_TWO_NAMED_VPN = + "QS_DIALOG_MANAGEMENT_TWO_NAMED_VPN"; + + /** + * Description for an active work profile VPN in the device management dialog, accepts + * VPN name as a param. + */ + public static final String QS_DIALOG_WORK_PROFILE_NAMED_VPN = + "QS_DIALOG_WORK_PROFILE_NAMED_VPN"; + + /** + * Description for an active personal profile VPN in the device management dialog, + * accepts VPN name as a param. + */ + public static final String QS_DIALOG_PERSONAL_PROFILE_NAMED_VPN = + "QS_DIALOG_PERSONAL_PROFILE_NAMED_VPN"; + + /** + * Content of a dialog shown when the user only has one attempt left to provide the + * correct pin before the work profile is removed. + */ + public static final String BIOMETRIC_DIALOG_WORK_PIN_LAST_ATTEMPT = + "BIOMETRIC_DIALOG_WORK_PIN_LAST_ATTEMPT"; + + /** + * Content of a dialog shown when the user only has one attempt left to provide the + * correct pattern before the work profile is removed. + */ + public static final String BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT = + "BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT"; + + /** + * Content of a dialog shown when the user only has one attempt left to provide the + * correct password before the work profile is removed. + */ + public static final String BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT = + "BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT"; + + /** + * Content of a dialog shown when the user has failed to provide the work lock too many + * times and the work profile is removed. + */ + public static final String BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS = + "BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS"; + + /** + * Accessibility label for managed profile icon in the status bar + */ + public static final String STATUS_BAR_WORK_ICON_ACCESSIBILITY = + "STATUS_BAR_WORK_ICON_ACCESSIBILITY"; + + /** + * Text appended to privacy dialog, indicating that the application is in the work + * profile. + */ + public static final String ONGOING_PRIVACY_DIALOG_WORK = + "ONGOING_PRIVACY_DIALOG_WORK"; + + /** + * Text on keyguard screen indicating device management. + */ + public static final String KEYGUARD_MANAGEMENT_DISCLOSURE = + "KEYGUARD_MANAGEMENT_DISCLOSURE"; + + /** + * Similar to {@link #KEYGUARD_MANAGEMENT_DISCLOSURE} but also accepts organization name + * as a param. + */ + public static final String KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE = + "KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE"; + + /** + * Content description for the work profile lock screen. + */ + public static final String WORK_LOCK_ACCESSIBILITY = "WORK_LOCK_ACCESSIBILITY"; + + /** + * @hide + */ + static Set<String> buildStringsSet() { + Set<String> strings = new HashSet<>(); + strings.add(QS_WORK_PROFILE_LABEL); + strings.add(QS_MSG_MANAGEMENT); + strings.add(QS_MSG_NAMED_MANAGEMENT); + strings.add(QS_MSG_MANAGEMENT_MONITORING); + strings.add(QS_MSG_NAMED_MANAGEMENT_MONITORING); + strings.add(QS_MSG_MANAGEMENT_NAMED_VPN); + strings.add(QS_MSG_NAMED_MANAGEMENT_NAMED_VPN); + strings.add(QS_MSG_MANAGEMENT_MULTIPLE_VPNS); + strings.add(QS_MSG_NAMED_MANAGEMENT_MULTIPLE_VPNS); + strings.add(QS_MSG_WORK_PROFILE_MONITORING); + strings.add(QS_MSG_NAMED_WORK_PROFILE_MONITORING); + strings.add(QS_MSG_WORK_PROFILE_NETWORK); + strings.add(QS_MSG_WORK_PROFILE_NAMED_VPN); + strings.add(QS_MSG_PERSONAL_PROFILE_NAMED_VPN); + strings.add(QS_DIALOG_MANAGEMENT_TITLE); + strings.add(QS_DIALOG_VIEW_POLICIES); + strings.add(QS_DIALOG_MANAGEMENT); + strings.add(QS_DIALOG_NAMED_MANAGEMENT); + strings.add(QS_DIALOG_MANAGEMENT_CA_CERT); + strings.add(QS_DIALOG_WORK_PROFILE_CA_CERT); + strings.add(QS_DIALOG_MANAGEMENT_NETWORK); + strings.add(QS_DIALOG_WORK_PROFILE_NETWORK); + strings.add(QS_DIALOG_MANAGEMENT_NAMED_VPN); + strings.add(QS_DIALOG_MANAGEMENT_TWO_NAMED_VPN); + strings.add(QS_DIALOG_WORK_PROFILE_NAMED_VPN); + strings.add(QS_DIALOG_PERSONAL_PROFILE_NAMED_VPN); + strings.add(BIOMETRIC_DIALOG_WORK_PIN_LAST_ATTEMPT); + strings.add(BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT); + strings.add(BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT); + strings.add(BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS); + strings.add(STATUS_BAR_WORK_ICON_ACCESSIBILITY); + strings.add(ONGOING_PRIVACY_DIALOG_WORK); + strings.add(KEYGUARD_MANAGEMENT_DISCLOSURE); + strings.add(KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE); + strings.add(WORK_LOCK_ACCESSIBILITY); + return strings; + } + } + } } diff --git a/core/java/android/app/admin/DevicePolicyStringResource.aidl b/core/java/android/app/admin/DevicePolicyStringResource.aidl new file mode 100644 index 000000000000..13b0b958027b --- /dev/null +++ b/core/java/android/app/admin/DevicePolicyStringResource.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.admin; + +parcelable DevicePolicyStringResource; diff --git a/core/java/android/app/admin/DevicePolicyStringResource.java b/core/java/android/app/admin/DevicePolicyStringResource.java new file mode 100644 index 000000000000..5f09bfdcf331 --- /dev/null +++ b/core/java/android/app/admin/DevicePolicyStringResource.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.admin; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.StringRes; +import android.annotation.SystemApi; +import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Used to pass in the required information for updating an enterprise string resource using + * {@link DevicePolicyManager#setStrings}. + * + * @hide + */ +@SystemApi +public final class DevicePolicyStringResource implements Parcelable { + @NonNull private final @DevicePolicyResources.UpdatableStringId String mStringId; + private final @StringRes int mCallingPackageResourceId; + @NonNull private ParcelableResource mResource; + + /** + * Creates an object containing the required information for updating an enterprise string + * resource using {@link DevicePolicyManager#setStrings}. + * + * <p>It will be used to update the string defined by {@code stringId} to the string with ID + * {@code callingPackageResourceId} in the calling package</p> + * + * @param stringId The ID of the string to update. + * @param callingPackageResourceId The ID of the {@link StringRes} in the calling package to + * use as an updated resource. + * + * @throws IllegalStateException if the resource with ID {@code callingPackageResourceId} + * doesn't exist in the {@code context} package. + */ + public DevicePolicyStringResource( + @NonNull Context context, + @NonNull @DevicePolicyResources.UpdatableStringId String stringId, + @StringRes int callingPackageResourceId) { + this(stringId, callingPackageResourceId, new ParcelableResource( + context, callingPackageResourceId, ParcelableResource.RESOURCE_TYPE_STRING)); + } + + private DevicePolicyStringResource( + @NonNull @DevicePolicyResources.UpdatableStringId String stringId, + @StringRes int callingPackageResourceId, + @NonNull ParcelableResource resource) { + Objects.requireNonNull(stringId, "stringId must be provided."); + Objects.requireNonNull(resource, "ParcelableResource must be provided."); + + this.mStringId = stringId; + this.mCallingPackageResourceId = callingPackageResourceId; + this.mResource = resource; + } + + /** + * Returns the ID of the string to update. + */ + @DevicePolicyResources.UpdatableStringId + @NonNull + public String getStringId() { + return mStringId; + } + + /** + * Returns the ID of the {@link StringRes} in the calling package to use as an updated + * resource. + */ + public int getCallingPackageResourceId() { + return mCallingPackageResourceId; + } + + /** + * Returns the {@link ParcelableResource} of the string. + * + * @hide + */ + @NonNull + public ParcelableResource getResource() { + return mResource; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + DevicePolicyStringResource other = (DevicePolicyStringResource) o; + return mStringId == other.mStringId + && mCallingPackageResourceId == other.mCallingPackageResourceId + && mResource.equals(other.mResource); + } + + @Override + public int hashCode() { + return Objects.hash(mStringId, mCallingPackageResourceId, mResource); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(mStringId); + dest.writeInt(mCallingPackageResourceId); + dest.writeTypedObject(mResource, flags); + } + + public static final @NonNull Creator<DevicePolicyStringResource> CREATOR = + new Creator<DevicePolicyStringResource>() { + @Override + public DevicePolicyStringResource createFromParcel(Parcel in) { + String stringId = in.readString(); + int callingPackageResourceId = in.readInt(); + ParcelableResource resource = in.readTypedObject(ParcelableResource.CREATOR); + + return new DevicePolicyStringResource(stringId, callingPackageResourceId, resource); + } + + @Override + public DevicePolicyStringResource[] newArray(int size) { + return new DevicePolicyStringResource[size]; + } + }; +} diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 832008755451..fae64d735b17 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -18,6 +18,7 @@ package android.app.admin; import android.app.admin.DevicePolicyDrawableResource; +import android.app.admin.DevicePolicyStringResource; import android.app.admin.ParcelableResource; import android.app.admin.NetworkEvent; import android.app.IApplicationThread; @@ -533,7 +534,11 @@ interface IDevicePolicyManager { boolean canUsbDataSignalingBeDisabled(); List<UserHandle> listForegroundAffiliatedUsers(); - void setDrawables(in List<DevicePolicyDrawableResource> resource); + void setDrawables(in List<DevicePolicyDrawableResource> drawables); void resetDrawables(in int[] drawableIds); ParcelableResource getDrawable(int drawableId, int drawableStyle, int drawableSource); + + void setStrings(in List<DevicePolicyStringResource> strings); + void resetStrings(in String[] stringIds); + ParcelableResource getString(String stringId); } diff --git a/core/java/android/app/admin/ParcelableResource.java b/core/java/android/app/admin/ParcelableResource.java index e5171622be4a..dba362820b1d 100644 --- a/core/java/android/app/admin/ParcelableResource.java +++ b/core/java/android/app/admin/ParcelableResource.java @@ -43,7 +43,7 @@ import java.util.concurrent.Callable; /** * Used to store the required information to load a resource that was updated using - * {@link DevicePolicyManager#setDrawables}. + * {@link DevicePolicyManager#setDrawables} and {@link DevicePolicyManager#setStrings}. * * @hide */ @@ -57,10 +57,13 @@ public final class ParcelableResource implements Parcelable { private static final String ATTR_RESOURCE_TYPE = "resource-type"; public static final int RESOURCE_TYPE_DRAWABLE = 1; + public static final int RESOURCE_TYPE_STRING = 2; + @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "RESOURCE_TYPE_" }, value = { - RESOURCE_TYPE_DRAWABLE + RESOURCE_TYPE_DRAWABLE, + RESOURCE_TYPE_STRING }) public @interface ResourceType {} @@ -78,13 +81,11 @@ public final class ParcelableResource implements Parcelable { * resource * @param resourceId of the resource to use as an updated resource * @param resourceType see {@link ResourceType} - * @throws IllegalArgumentException if the given {@code resourceId} doesn't exist in the - * {@link Context#getResources()} of the given {@code context} */ - public ParcelableResource(@NonNull Context context, @AnyRes int resourceId, - @ResourceType int resourceType) throws IllegalArgumentException { + public ParcelableResource( + @NonNull Context context, @AnyRes int resourceId, @ResourceType int resourceType) + throws IllegalStateException, IllegalArgumentException { Objects.requireNonNull(context, "context must be provided"); - verifyResourceExistsInCallingPackage(context, resourceId, resourceType); this.mResourceId = resourceId; @@ -108,25 +109,41 @@ public final class ParcelableResource implements Parcelable { private static void verifyResourceExistsInCallingPackage( Context context, @AnyRes int resourceId, @ResourceType int resourceType) - throws IllegalArgumentException { + throws IllegalStateException, IllegalArgumentException { switch (resourceType) { case RESOURCE_TYPE_DRAWABLE: if (!hasDrawableInCallingPackage(context, resourceId)) { - throw new IllegalArgumentException(String.format( + throw new IllegalStateException(String.format( "Drawable with id %d doesn't exist in the calling package %s", resourceId, context.getPackageName())); } break; + case RESOURCE_TYPE_STRING: + if (!hasStringInCallingPackage(context, resourceId)) { + throw new IllegalStateException(String.format( + "String with id %d doesn't exist in the calling package %s", + resourceId, + context.getPackageName())); + } + break; default: throw new IllegalArgumentException( - "Unknown ParcelableDevicePolicyResourceType: " + resourceType); + "Unknown ResourceType: " + resourceType); } } private static boolean hasDrawableInCallingPackage(Context context, @AnyRes int resourceId) { try { - return context.getDrawable(resourceId) != null; + return "drawable".equals(context.getResources().getResourceTypeName(resourceId)); + } catch (Resources.NotFoundException e) { + return false; + } + } + + private static boolean hasStringInCallingPackage(Context context, @AnyRes int resourceId) { + try { + return "string".equals(context.getResources().getResourceTypeName(resourceId)); } catch (Resources.NotFoundException e) { return false; } @@ -158,7 +175,7 @@ public final class ParcelableResource implements Parcelable { * <p>Returns the default drawable by calling the {@code defaultDrawableLoader} if the updated * drawable was not found or could not be loaded.</p> */ - @Nullable + @NonNull public Drawable getDrawable( Context context, int density, @@ -175,6 +192,59 @@ public final class ParcelableResource implements Parcelable { } } + /** + * Loads the string with id {@code mResourceId} from {@code mPackageName} using the + * configuration returned from {@link Resources#getConfiguration} of the provided + * {@code context}. + * + * <p>Returns the default string by calling {@code defaultStringLoader} if the updated + * string was not found or could not be loaded.</p> + */ + @NonNull + public String getString( + Context context, + @NonNull Callable<String> defaultStringLoader) { + // TODO(b/203548565): properly handle edge case when the device manager role holder is + // unavailable because it's being updated. + try { + Resources resources = getAppResourcesWithCallersConfiguration(context); + verifyResourceName(resources); + return resources.getString(mResourceId); + } catch (PackageManager.NameNotFoundException | RuntimeException e) { + Slog.e(TAG, "Unable to load string resource " + mResourceName, e); + return loadDefaultString(defaultStringLoader); + } + } + + /** + * Loads the string with id {@code mResourceId} from {@code mPackageName} using the + * configuration returned from {@link Resources#getConfiguration} of the provided + * {@code context}. + * + * <p>Returns the default string by calling {@code defaultStringLoader} if the updated + * string was not found or could not be loaded.</p> + */ + @Nullable + public String getString( + Context context, + @NonNull Callable<String> defaultStringLoader, + @NonNull Object... formatArgs) { + // TODO(b/203548565): properly handle edge case when the device manager role holder is + // unavailable because it's being updated. + try { + Resources resources = getAppResourcesWithCallersConfiguration(context); + verifyResourceName(resources); + String rawString = resources.getString(mResourceId); + return String.format( + context.getResources().getConfiguration().getLocales().get(0), + rawString, + formatArgs); + } catch (PackageManager.NameNotFoundException | RuntimeException e) { + Slog.e(TAG, "Unable to load string resource " + mResourceName, e); + return loadDefaultString(defaultStringLoader); + } + } + private Resources getAppResourcesWithCallersConfiguration(Context context) throws PackageManager.NameNotFoundException { PackageManager pm = context.getPackageManager(); @@ -195,15 +265,40 @@ public final class ParcelableResource implements Parcelable { } /** - * returns the {@link Drawable} loaded from calling - * {@code defaultDrawableLoader}. + * returns the {@link Drawable} loaded from calling {@code defaultDrawableLoader}. */ - public static Drawable loadDefaultDrawable( - @NonNull Callable<Drawable> defaultDrawableLoader) { + @NonNull + public static Drawable loadDefaultDrawable(@NonNull Callable<Drawable> defaultDrawableLoader) { + try { + Objects.requireNonNull(defaultDrawableLoader, "defaultDrawableLoader can't be null"); + + Drawable drawable = defaultDrawableLoader.call(); + Objects.requireNonNull(drawable, "defaultDrawable can't be null"); + + return drawable; + } catch (NullPointerException rethrown) { + throw rethrown; + } catch (Exception e) { + throw new RuntimeException("Couldn't load default drawable: ", e); + } + } + + /** + * returns the {@link String} loaded from calling {@code defaultStringLoader}. + */ + @NonNull + public static String loadDefaultString(@NonNull Callable<String> defaultStringLoader) { try { - return defaultDrawableLoader.call(); + Objects.requireNonNull(defaultStringLoader, "defaultStringLoader can't be null"); + + String string = defaultStringLoader.call(); + Objects.requireNonNull(string, "defaultString can't be null"); + + return string; + } catch (NullPointerException rethrown) { + throw rethrown; } catch (Exception e) { - throw new RuntimeException("Couldn't load default drawable", e); + throw new RuntimeException("Couldn't load default string: ", e); } } diff --git a/core/java/android/app/trust/ITrustManager.aidl b/core/java/android/app/trust/ITrustManager.aidl index 1291766c00d8..edabccf23c2c 100644 --- a/core/java/android/app/trust/ITrustManager.aidl +++ b/core/java/android/app/trust/ITrustManager.aidl @@ -26,6 +26,7 @@ import android.hardware.biometrics.BiometricSourceType; */ interface ITrustManager { void reportUnlockAttempt(boolean successful, int userId); + void reportUserRequestedUnlock(int userId); void reportUnlockLockout(int timeoutMs, int userId); void reportEnabledTrustAgentsChanged(int userId); void registerTrustListener(in ITrustListener trustListener); diff --git a/core/java/android/app/trust/OWNERS b/core/java/android/app/trust/OWNERS new file mode 100644 index 000000000000..e2c6ce15b51e --- /dev/null +++ b/core/java/android/app/trust/OWNERS @@ -0,0 +1 @@ +include /core/java/android/service/trust/OWNERS diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java index e214007c1850..937fcf0c0709 100644 --- a/core/java/android/app/trust/TrustManager.java +++ b/core/java/android/app/trust/TrustManager.java @@ -85,6 +85,19 @@ public class TrustManager { } /** + * Reports that the user {@code userId} is likely interested in unlocking the device. + * + * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission. + */ + public void reportUserRequestedUnlock(int userId) { + try { + mService.reportUserRequestedUnlock(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Reports that user {@param userId} has entered a temporary device lockout. * * This generally occurs when the user has unsuccessfully tried to unlock the device too many diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index 8ab668873f33..a97b7b3ae81c 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -16,7 +16,6 @@ package android.companion.virtual; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -43,10 +42,6 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.view.Surface; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import java.util.concurrent.Executor; /** @@ -61,23 +56,6 @@ public final class VirtualDeviceManager { private static final boolean DEBUG = false; private static final String LOG_TAG = "VirtualDeviceManager"; - /** @hide */ - @IntDef(prefix = "DISPLAY_FLAG_", - flag = true, - value = {DISPLAY_FLAG_TRUSTED}) - @Retention(RetentionPolicy.SOURCE) - @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) - public @interface DisplayFlags {} - - /** - * Indicates that the display is trusted to show system decorations and receive inputs without - * users' touch. - * - * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED - * @hide // TODO(b/194949534): Unhide this API - */ - public static final int DISPLAY_FLAG_TRUSTED = 1; - private static final int DEFAULT_VIRTUAL_DISPLAY_FLAGS = DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC | DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT @@ -102,12 +80,12 @@ public final class VirtualDeviceManager { * @param associationId The association ID as returned by {@link AssociationInfo#getId()} from * Companion Device Manager. Virtual devices must have a corresponding association with CDM in * order to be created. - * @hide */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @Nullable - public VirtualDevice createVirtualDevice(int associationId, VirtualDeviceParams params) { - // TODO(b/194949534): Unhide this API + public VirtualDevice createVirtualDevice( + int associationId, + @NonNull VirtualDeviceParams params) { try { IVirtualDevice virtualDevice = mService.createVirtualDevice( new Binder(), mContext.getPackageName(), associationId, params); @@ -187,7 +165,12 @@ public final class VirtualDeviceManager { * @param surface The surface to which the content of the virtual display should * be rendered, or null if there is none initially. The surface can also be set later using * {@link VirtualDisplay#setSurface(Surface)}. - * @param flags Either 0, or {@link #DISPLAY_FLAG_TRUSTED}. + * @param flags A combination of virtual display flags accepted by + * {@link DisplayManager#createVirtualDisplay}. In addition, the following flags are + * automatically set for all virtual devices: + * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC VIRTUAL_DISPLAY_FLAG_PUBLIC} and + * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY + * VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}. * @param callback Callback to call when the state of the {@link VirtualDisplay} changes * @param handler The handler on which the listener should be invoked, or null * if the listener should be invoked on the calling thread's looper. @@ -195,9 +178,7 @@ public final class VirtualDeviceManager { * not create the virtual display. * * @see DisplayManager#createVirtualDisplay - * @hide */ - // TODO(b/194949534): Unhide this API // Suppress "ExecutorRegistration" because DisplayManager.createVirtualDisplay takes a // handler @SuppressLint("ExecutorRegistration") @@ -207,7 +188,7 @@ public final class VirtualDeviceManager { int height, int densityDpi, @Nullable Surface surface, - @DisplayFlags int flags, + int flags, @Nullable Handler handler, @Nullable VirtualDisplay.Callback callback) { // TODO(b/205343547): Handle display groups properly instead of creating a new display @@ -246,7 +227,6 @@ public final class VirtualDeviceManager { * @param inputDeviceName the name to call this input device * @param vendorId the vendor id * @param productId the product id - * @hide */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @NonNull @@ -273,7 +253,6 @@ public final class VirtualDeviceManager { * @param inputDeviceName the name to call this input device * @param vendorId the vendor id * @param productId the product id - * @hide */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @NonNull @@ -300,7 +279,6 @@ public final class VirtualDeviceManager { * @param inputDeviceName the name to call this input device * @param vendorId the vendor id * @param productId the product id - * @hide */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @NonNull @@ -328,12 +306,8 @@ public final class VirtualDeviceManager { * com.android.server.companion.virtual.VirtualDeviceImpl#getBaseVirtualDisplayFlags()} will * be added by DisplayManagerService. */ - private int getVirtualDisplayFlags(@DisplayFlags int flags) { - int virtualDisplayFlags = DEFAULT_VIRTUAL_DISPLAY_FLAGS; - if ((flags & DISPLAY_FLAG_TRUSTED) != 0) { - virtualDisplayFlags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED; - } - return virtualDisplayFlags; + private int getVirtualDisplayFlags(int flags) { + return DEFAULT_VIRTUAL_DISPLAY_FLAGS | flags; } private String getVirtualDisplayName() { diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java index d61d4741637a..2ddfeb4c8ab5 100644 --- a/core/java/android/companion/virtual/VirtualDeviceParams.java +++ b/core/java/android/companion/virtual/VirtualDeviceParams.java @@ -20,7 +20,10 @@ import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.content.ComponentName; import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; @@ -39,7 +42,7 @@ import java.util.Set; * * @hide */ -// TODO(b/194949534): Unhide this API +@SystemApi public final class VirtualDeviceParams implements Parcelable { /** @hide */ @@ -51,32 +54,36 @@ public final class VirtualDeviceParams implements Parcelable { /** * Indicates that the lock state of the virtual device should be always locked. - * - * @hide // TODO(b/194949534): Unhide this API */ public static final int LOCK_STATE_ALWAYS_LOCKED = 0; /** * Indicates that the lock state of the virtual device should be always unlocked. - * - * @hide // TODO(b/194949534): Unhide this API */ public static final int LOCK_STATE_ALWAYS_UNLOCKED = 1; private final int mLockState; private final ArraySet<UserHandle> mUsersWithMatchingAccounts; + @Nullable private final ArraySet<ComponentName> mAllowedActivities; + @Nullable private final ArraySet<ComponentName> mBlockedActivities; private VirtualDeviceParams( @LockState int lockState, - @NonNull Set<UserHandle> usersWithMatchingAccounts) { + @NonNull Set<UserHandle> usersWithMatchingAccounts, + @Nullable Set<ComponentName> allowedActivities, + @Nullable Set<ComponentName> blockedActivities) { mLockState = lockState; mUsersWithMatchingAccounts = new ArraySet<>(usersWithMatchingAccounts); + mAllowedActivities = allowedActivities == null ? null : new ArraySet<>(allowedActivities); + mBlockedActivities = blockedActivities == null ? null : new ArraySet<>(blockedActivities); } @SuppressWarnings("unchecked") private VirtualDeviceParams(Parcel parcel) { mLockState = parcel.readInt(); mUsersWithMatchingAccounts = (ArraySet<UserHandle>) parcel.readArraySet(null); + mAllowedActivities = (ArraySet<ComponentName>) parcel.readArraySet(null); + mBlockedActivities = (ArraySet<ComponentName>) parcel.readArraySet(null); } /** @@ -98,6 +105,35 @@ public final class VirtualDeviceParams implements Parcelable { return Collections.unmodifiableSet(mUsersWithMatchingAccounts); } + /** + * Returns the set of activities allowed to be streamed, or {@code null} if this is not set. + * + * @see Builder#setAllowedActivities(Set) + * @hide // TODO(b/194949534): Unhide this API + */ + @Nullable + public Set<ComponentName> getAllowedActivities() { + if (mAllowedActivities == null) { + return null; + } + return Collections.unmodifiableSet(mAllowedActivities); + } + + /** + * Returns the set of activities that are blocked from streaming, or {@code null} if this is not + * set. + * + * @see Builder#setBlockedActivities(Set) + * @hide // TODO(b/194949534): Unhide this API + */ + @Nullable + public Set<ComponentName> getBlockedActivities() { + if (mBlockedActivities == null) { + return null; + } + return Collections.unmodifiableSet(mBlockedActivities); + } + @Override public int describeContents() { return 0; @@ -107,6 +143,8 @@ public final class VirtualDeviceParams implements Parcelable { public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(mLockState); dest.writeArraySet(mUsersWithMatchingAccounts); + dest.writeArraySet(mAllowedActivities); + dest.writeArraySet(mBlockedActivities); } @Override @@ -118,8 +156,10 @@ public final class VirtualDeviceParams implements Parcelable { return false; } VirtualDeviceParams that = (VirtualDeviceParams) o; - return mLockState == that.mLockState && mUsersWithMatchingAccounts.equals( - that.mUsersWithMatchingAccounts); + return mLockState == that.mLockState + && mUsersWithMatchingAccounts.equals(that.mUsersWithMatchingAccounts) + && Objects.equals(mAllowedActivities, that.mAllowedActivities) + && Objects.equals(mBlockedActivities, that.mBlockedActivities); } @Override @@ -128,13 +168,17 @@ public final class VirtualDeviceParams implements Parcelable { } @Override + @NonNull public String toString() { return "VirtualDeviceParams(" + " mLockState=" + mLockState + " mUsersWithMatchingAccounts=" + mUsersWithMatchingAccounts + + " mAllowedActivities=" + mAllowedActivities + + " mBlockedActivities=" + mBlockedActivities + ")"; } + @NonNull public static final Parcelable.Creator<VirtualDeviceParams> CREATOR = new Parcelable.Creator<VirtualDeviceParams>() { public VirtualDeviceParams createFromParcel(Parcel in) { @@ -153,6 +197,8 @@ public final class VirtualDeviceParams implements Parcelable { private @LockState int mLockState = LOCK_STATE_ALWAYS_LOCKED; private Set<UserHandle> mUsersWithMatchingAccounts; + @Nullable private Set<ComponentName> mBlockedActivities; + @Nullable private Set<ComponentName> mAllowedActivities; /** * Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY} @@ -171,12 +217,25 @@ public final class VirtualDeviceParams implements Parcelable { /** * Sets the user handles with matching managed accounts on the remote device to which - * this virtual device is streaming. + * this virtual device is streaming. The caller is responsible for verifying the presence + * and legitimacy of a matching managed account on the remote device. + * + * <p>If the app streaming policy is + * {@link android.app.admin.DevicePolicyManager#NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY + * NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY}, activities not in + * {@code usersWithMatchingAccounts} will be blocked from starting. + * + * <p> If {@code usersWithMatchingAccounts} is empty (the default), streaming is allowed + * only if there is no device policy, or if the nearby streaming policy is + * {@link android.app.admin.DevicePolicyManager#NEARBY_STREAMING_ENABLED + * NEARBY_STREAMING_ENABLED}. * * @param usersWithMatchingAccounts A set of user handles with matching managed * accounts on the remote device this is streaming to. + * * @see android.app.admin.DevicePolicyManager#NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY */ + @NonNull public Builder setUsersWithMatchingAccounts( @NonNull Set<UserHandle> usersWithMatchingAccounts) { mUsersWithMatchingAccounts = usersWithMatchingAccounts; @@ -184,6 +243,54 @@ public final class VirtualDeviceParams implements Parcelable { } /** + * Sets the activities allowed to be launched in the virtual device. If + * {@code allowedActivities} is non-null, all activities other than the ones in the set will + * be blocked from launching. + * + * <p>{@code allowedActivities} and the set in {@link #setBlockedActivities(Set)} cannot + * both be non-null at the same time. + * + * @throws IllegalArgumentException if {@link #setBlockedActivities(Set)} has been set to a + * non-null value. + * + * @param allowedActivities A set of activity {@link ComponentName} allowed to be launched + * in the virtual device. + * @hide // TODO(b/194949534): Unhide this API + */ + public Builder setAllowedActivities(@Nullable Set<ComponentName> allowedActivities) { + if (mBlockedActivities != null && allowedActivities != null) { + throw new IllegalArgumentException( + "Allowed activities and Blocked activities cannot both be set."); + } + mAllowedActivities = allowedActivities; + return this; + } + + /** + * Sets the activities blocked from launching in the virtual device. If the {@code + * blockedActivities} is non-null, activities in the set are blocked from launching in the + * virtual device. + * + * {@code blockedActivities} and the set in {@link #setAllowedActivities(Set)} cannot both + * be non-null at the same time. + * + * @throws IllegalArgumentException if {@link #setAllowedActivities(Set)} has been set to a + * non-null value. + * + * @param blockedActivities A set of {@link ComponentName} to be blocked launching from + * virtual device. + * @hide // TODO(b/194949534): Unhide this API + */ + public Builder setBlockedActivities(@Nullable Set<ComponentName> blockedActivities) { + if (mAllowedActivities != null && blockedActivities != null) { + throw new IllegalArgumentException( + "Allowed activities and Blocked activities cannot both be set."); + } + mBlockedActivities = blockedActivities; + return this; + } + + /** * Builds the {@link VirtualDeviceParams} instance. */ @NonNull @@ -191,7 +298,13 @@ public final class VirtualDeviceParams implements Parcelable { if (mUsersWithMatchingAccounts == null) { mUsersWithMatchingAccounts = Collections.emptySet(); } - return new VirtualDeviceParams(mLockState, mUsersWithMatchingAccounts); + if (mAllowedActivities != null && mBlockedActivities != null) { + // Should never reach here because the setters block this as well. + throw new IllegalStateException( + "Allowed activities and Blocked activities cannot both be set."); + } + return new VirtualDeviceParams(mLockState, mUsersWithMatchingAccounts, + mAllowedActivities, mBlockedActivities); } } } diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index c714f507242e..b4f23028325b 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -48,10 +48,13 @@ import android.os.RemoteCallback; import android.os.RemoteException; import android.os.Trace; import android.os.UserHandle; +import android.os.UserManager; import android.os.storage.StorageManager; import android.permission.PermissionCheckerManager; +import android.provider.MediaStore; import android.text.TextUtils; import android.util.Log; +import android.util.SparseBooleanArray; import com.android.internal.annotations.VisibleForTesting; @@ -134,9 +137,18 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall private boolean mExported; private boolean mNoPerms; private boolean mSingleUser; + private SparseBooleanArray mUsersRedirectedToOwner = new SparseBooleanArray(); private ThreadLocal<AttributionSource> mCallingAttributionSource; + /** + * @hide + */ + public static boolean isAuthorityRedirectedForCloneProfile(String authority) { + // For now, only MediaProvider gets redirected. + return MediaStore.AUTHORITY.equals(authority); + } + private Transport mTransport = new Transport(); /** @@ -726,13 +738,47 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } boolean checkUser(int pid, int uid, Context context) { - if (UserHandle.getUserId(uid) == context.getUserId() || mSingleUser) { + int callingUserId = UserHandle.getUserId(uid); + + if (callingUserId == context.getUserId() || mSingleUser) { return true; } - return context.checkPermission(INTERACT_ACROSS_USERS, pid, uid) - == PackageManager.PERMISSION_GRANTED + if (context.checkPermission(INTERACT_ACROSS_USERS, pid, uid) + == PackageManager.PERMISSION_GRANTED || context.checkPermission(INTERACT_ACROSS_USERS_FULL, pid, uid) - == PackageManager.PERMISSION_GRANTED; + == PackageManager.PERMISSION_GRANTED) { + return true; + } + + if (isAuthorityRedirectedForCloneProfile(mAuthority)) { + if (mUsersRedirectedToOwner.indexOfKey(callingUserId) >= 0) { + return mUsersRedirectedToOwner.get(callingUserId); + } + + // Haven't seen this user yet, look it up + try { + UserHandle callingUser = UserHandle.getUserHandleForUid(uid); + Context callingUserContext = mContext.createPackageContextAsUser("system", + 0, callingUser); + UserManager um = callingUserContext.getSystemService(UserManager.class); + + if (um != null && um.isCloneProfile()) { + UserHandle parent = um.getProfileParent(callingUser); + + if (parent != null && parent.equals(context.getUser())) { + mUsersRedirectedToOwner.put(callingUser.getIdentifier(), true); + return true; + } + } + } catch (PackageManager.NameNotFoundException e) { + // ignore + } + + mUsersRedirectedToOwner.put(UserHandle.getUserId(uid), false); + return false; + } + + return false; } /** diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index b52bff62af3b..845d23c199a3 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -6417,10 +6417,10 @@ public abstract class Context { * Triggers the asynchronous revocation of a permission. * * @param permName The name of the permission to be revoked. - * @see #selfRevokePermissions(Collection) + * @see #revokeOwnPermissionsOnKill(Collection) */ - public void selfRevokePermission(@NonNull String permName) { - selfRevokePermissions(Collections.singletonList(permName)); + public void revokeOwnPermissionOnKill(@NonNull String permName) { + revokeOwnPermissionsOnKill(Collections.singletonList(permName)); } /** @@ -6445,7 +6445,7 @@ public abstract class Context { * @see PackageManager#getGroupOfPlatformPermission(String, Executor, Consumer) * @see PackageManager#getPlatformPermissionsForGroup(String, Executor, Consumer) */ - public void selfRevokePermissions(@NonNull Collection<String> permissions) { + public void revokeOwnPermissionsOnKill(@NonNull Collection<String> permissions) { throw new AbstractMethodError("Must be overridden in implementing class"); } diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 805e499bba46..6ae768a44078 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -1016,8 +1016,8 @@ public class ContextWrapper extends Context { } @Override - public void selfRevokePermissions(@NonNull Collection<String> permissions) { - mBase.selfRevokePermissions(permissions); + public void revokeOwnPermissionsOnKill(@NonNull Collection<String> permissions) { + mBase.revokeOwnPermissionsOnKill(permissions); } @Override diff --git a/core/java/android/content/pm/AppSearchShortcutInfo.java b/core/java/android/content/pm/AppSearchShortcutInfo.java index 806091e2158d..8d9ef8530bfc 100644 --- a/core/java/android/content/pm/AppSearchShortcutInfo.java +++ b/core/java/android/content/pm/AppSearchShortcutInfo.java @@ -423,7 +423,7 @@ public class AppSearchShortcutInfo extends GenericDocument { shortLabelResName, longLabel, longLabelResId, longLabelResName, disabledMessage, disabledMessageResId, disabledMessageResName, categoriesSet, intents, rank, extras, getCreationTimestampMillis(), flags, iconResId, iconResName, bitmapPath, iconUri, - disabledReason, persons, locusId, null); + disabledReason, persons, locusId, null, null); si.setImplicitRank(implicitRank); if ((implicitRank & ShortcutInfo.RANK_CHANGED_BIT) != 0) { si.setRankChanged(); diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java index 7d4f7ecef29c..ab827aacbdc1 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -41,6 +41,7 @@ import android.os.Parcelable; import android.os.PersistableBundle; import android.os.UserHandle; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import android.view.contentcapture.ContentCaptureContext; @@ -50,9 +51,15 @@ import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * Represents a shortcut that can be published via {@link ShortcutManager}. @@ -463,6 +470,9 @@ public final class ShortcutInfo implements Parcelable { private int mExcludedSurfaces; + @Nullable + private Map<String, Map<String, List<String>>> mCapabilityBindings; + private ShortcutInfo(Builder b) { mUserId = b.mContext.getUserId(); @@ -490,7 +500,7 @@ public final class ShortcutInfo implements Parcelable { mRank = b.mRank; mExtras = b.mExtras; mLocusId = b.mLocusId; - + mCapabilityBindings = b.mCapabilityBindings; mStartingThemeResName = b.mStartingThemeResId != 0 ? b.mContext.getResources().getResourceName(b.mStartingThemeResId) : null; updateTimestamp(); @@ -602,7 +612,7 @@ public final class ShortcutInfo implements Parcelable { mLocusId = source.mLocusId; mExcludedSurfaces = source.mExcludedSurfaces; - // Just always keep it since it's cheep. + // Just always keep it since it's cheap. mIconResId = source.mIconResId; if ((cloneFlags & CLONE_REMOVE_NON_KEY_INFO) == 0) { @@ -641,6 +651,7 @@ public final class ShortcutInfo implements Parcelable { // Set this bit. mFlags |= FLAG_KEY_FIELDS_ONLY; } + mCapabilityBindings = source.mCapabilityBindings; mStartingThemeResName = source.mStartingThemeResName; } @@ -968,6 +979,9 @@ public final class ShortcutInfo implements Parcelable { if (source.mStartingThemeResName != null && !source.mStartingThemeResName.isEmpty()) { mStartingThemeResName = source.mStartingThemeResName; } + if (source.mCapabilityBindings != null) { + mCapabilityBindings = source.mCapabilityBindings; + } } /** @@ -1039,6 +1053,9 @@ public final class ShortcutInfo implements Parcelable { private int mStartingThemeResId; + @Nullable + private Map<String, Map<String, List<String>>> mCapabilityBindings; + private int mExcludedSurfaces; /** @@ -1401,6 +1418,53 @@ public final class ShortcutInfo implements Parcelable { } /** + * Associates a shortcut with a capability, and a parameter of that capability. Used when + * the shortcut is an instance of a capability. + * + * <P>This method can be called multiple times to add multiple parameters to the same + * capability. + * + * @param capability capability associated with the shortcut. e.g. actions.intent + * .START_EXERCISE. + * @param parameterName name of the parameter associated with given capability. + * e.g. exercise.name. + * @param parameterValues a list of values for that parameters. The first value will be + * the primary name, while the rest will be alternative names. If + * the values are empty, then the parameter will not be saved in + * the shortcut. + */ + @NonNull + public Builder addCapabilityBinding(@NonNull String capability, + @Nullable String parameterName, @Nullable List<String> parameterValues) { + Objects.requireNonNull(capability); + if (capability.contains("/")) { + throw new IllegalArgumentException("Illegal character '/' is found in capability"); + } + if (mCapabilityBindings == null) { + mCapabilityBindings = new ArrayMap<>(1); + } + if (!mCapabilityBindings.containsKey(capability)) { + mCapabilityBindings.put(capability, new ArrayMap<>(0)); + } + if (parameterName == null || parameterValues == null || parameterValues.isEmpty()) { + return this; + } + if (parameterName.contains("/")) { + throw new IllegalArgumentException( + "Illegal character '/' is found in parameter name"); + } + final Map<String, List<String>> params = mCapabilityBindings.get(capability); + if (!params.containsKey(parameterName)) { + params.put(parameterName, parameterValues); + return this; + } + params.put(parameterName, + Stream.of(params.get(parameterName), parameterValues) + .flatMap(Collection::stream).collect(Collectors.toList())); + return this; + } + + /** * Sets which surfaces a shortcut will be excluded from. * * If the shortcut is set to be excluded from {@link #SURFACE_LAUNCHER}, shortcuts will be @@ -2176,6 +2240,44 @@ public final class ShortcutInfo implements Parcelable { return (mExcludedSurfaces & surface) == 0; } + /** + * @hide + */ + public Map<String, Map<String, List<String>>> getCapabilityBindings() { + return mCapabilityBindings; + } + + /** + * Return true if the shortcut is or can be used in specified capability. + */ + public boolean hasCapability(@NonNull String capability) { + Objects.requireNonNull(capability); + return mCapabilityBindings != null && mCapabilityBindings.containsKey(capability); + } + + /** + * Returns the values of specified parameter in associated with given capability. + * + * @param capability capability associated with the shortcut. e.g. actions.intent + * .START_EXERCISE. + * @param parameterName name of the parameter associated with given capability. + * e.g. exercise.name. + */ + @NonNull + public List<String> getCapabilityParameterValues( + @NonNull String capability, @NonNull String parameterName) { + Objects.requireNonNull(capability); + Objects.requireNonNull(parameterName); + if (mCapabilityBindings == null) { + return Collections.emptyList(); + } + final Map<String, List<String>> param = mCapabilityBindings.get(capability); + if (param == null || !param.containsKey(parameterName)) { + return Collections.emptyList(); + } + return param.get(parameterName); + } + private ShortcutInfo(Parcel source) { final ClassLoader cl = getClass().getClassLoader(); @@ -2225,6 +2327,15 @@ public final class ShortcutInfo implements Parcelable { mIconUri = source.readString8(); mStartingThemeResName = source.readString8(); mExcludedSurfaces = source.readInt(); + + final Map<String, Map> rawCapabilityBindings = source.readHashMap( + /*loader*/ null, /*clazzKey*/ String.class, /*clazzValue*/ HashMap.class); + if (rawCapabilityBindings != null && !rawCapabilityBindings.isEmpty()) { + final Map<String, Map<String, List<String>>> capabilityBindings = + new ArrayMap<>(rawCapabilityBindings.size()); + rawCapabilityBindings.forEach(capabilityBindings::put); + mCapabilityBindings = capabilityBindings; + } } @Override @@ -2278,6 +2389,7 @@ public final class ShortcutInfo implements Parcelable { dest.writeString8(mIconUri); dest.writeString8(mStartingThemeResName); dest.writeInt(mExcludedSurfaces); + dest.writeMap(mCapabilityBindings); } public static final @NonNull Creator<ShortcutInfo> CREATOR = @@ -2529,7 +2641,8 @@ public final class ShortcutInfo implements Parcelable { long lastChangedTimestamp, int flags, int iconResId, String iconResName, String bitmapPath, String iconUri, int disabledReason, Person[] persons, LocusId locusId, - @Nullable String startingThemeResName) { + @Nullable String startingThemeResName, + @Nullable Map<String, Map<String, List<String>>> capabilityBindings) { mUserId = userId; mId = id; mPackageName = packageName; @@ -2559,5 +2672,6 @@ public final class ShortcutInfo implements Parcelable { mPersons = persons; mLocusId = locusId; mStartingThemeResName = startingThemeResName; + mCapabilityBindings = capabilityBindings; } } diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java index 4683d252b68a..acceb654a959 100644 --- a/core/java/android/hardware/HardwareBuffer.java +++ b/core/java/android/hardware/HardwareBuffer.java @@ -110,9 +110,9 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable { @Retention(RetentionPolicy.SOURCE) @LongDef(flag = true, value = {USAGE_CPU_READ_RARELY, USAGE_CPU_READ_OFTEN, USAGE_CPU_WRITE_RARELY, USAGE_CPU_WRITE_OFTEN, USAGE_GPU_SAMPLED_IMAGE, - USAGE_GPU_COLOR_OUTPUT, USAGE_PROTECTED_CONTENT, USAGE_VIDEO_ENCODE, - USAGE_GPU_DATA_BUFFER, USAGE_SENSOR_DIRECT_DATA, USAGE_GPU_CUBE_MAP, - USAGE_GPU_MIPMAP_COMPLETE}) + USAGE_GPU_COLOR_OUTPUT, USAGE_COMPOSER_OVERLAY, USAGE_PROTECTED_CONTENT, + USAGE_VIDEO_ENCODE, USAGE_GPU_DATA_BUFFER, USAGE_SENSOR_DIRECT_DATA, + USAGE_GPU_CUBE_MAP, USAGE_GPU_MIPMAP_COMPLETE, USAGE_FRONT_BUFFER}) public @interface Usage {}; @Usage @@ -151,6 +151,12 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable { public static final long USAGE_GPU_CUBE_MAP = 1 << 25; /** Usage: The buffer contains a complete mipmap hierarchy */ public static final long USAGE_GPU_MIPMAP_COMPLETE = 1 << 26; + /** Usage: The buffer is used for front-buffer rendering. When front-buffering rendering is + * specified, different usages may adjust their behavior as a result. For example, when + * used as USAGE_GPU_COLOR_OUTPUT the buffer will behave similar to a single-buffered window. + * When used with USAGE_COMPOSER_OVERLAY, the system will try to prioritize the buffer + * receiving an overlay plane & avoid caching it in intermediate composition buffers. */ + public static final long USAGE_FRONT_BUFFER = 1 << 32; /** * Creates a new <code>HardwareBuffer</code> instance. diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 89ac8bf2d7bc..eefa1d3279e3 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -334,6 +334,7 @@ public final class DisplayManager { * @hide */ @TestApi + @SystemApi public static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1 << 10; /** diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java index d2f788f4d0fb..47a272c2337f 100644 --- a/core/java/android/os/BatteryConsumer.java +++ b/core/java/android/os/BatteryConsumer.java @@ -192,6 +192,7 @@ public abstract class BatteryConsumer { POWER_COMPONENT_CPU, POWER_COMPONENT_MOBILE_RADIO, POWER_COMPONENT_WIFI, + POWER_COMPONENT_BLUETOOTH, }; static final int COLUMN_INDEX_BATTERY_CONSUMER_TYPE = 0; diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index a4538876c60f..4666c5c12189 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -1037,6 +1037,16 @@ public abstract class BatteryStats implements Parcelable { public abstract long getBluetoothMeasuredBatteryConsumptionUC(); /** + * Returns the battery consumption (in microcoulombs) of the uid's bluetooth usage + * when in the specified process state. + * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable. + * + * {@hide} + */ + public abstract long getBluetoothMeasuredBatteryConsumptionUC( + @BatteryConsumer.ProcessState int processState); + + /** * Returns the battery consumption (in microcoulombs) of the uid's cpu usage, derived from * on device power measurement data. * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable. diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 742a542efdd1..2fe062268112 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -132,6 +132,7 @@ public class Process { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @TestApi + @SystemApi(client = MODULE_LIBRARIES) public static final int NFC_UID = 1027; /** @@ -279,6 +280,26 @@ public class Process { public static final int LAST_APPLICATION_UID = 19999; /** + * Defines the start of a range of UIDs going from this number to + * {@link #LAST_SUPPLEMENTAL_UID} that are reserved for assigning to + * supplemental processes. There is a 1-1 mapping between a supplemental + * process UID and the app that it belongs to, which can be computed by + * subtracting (FIRST_SUPPLEMENTAL_UID - FIRST_APPLICATION_UID) from the + * uid of a supplemental process. + * + * Note that there are no GIDs associated with these processes; storage + * attribution for them will be done using project IDs. + * @hide + */ + public static final int FIRST_SUPPLEMENTAL_UID = 20000; + + /** + * Last UID that is used for supplemental processes. + * @hide + */ + public static final int LAST_SUPPLEMENTAL_UID = 29999; + + /** * First uid used for fully isolated sandboxed processes spawned from an app zygote * @hide */ @@ -880,6 +901,46 @@ public class Process { } /** + * Returns whether the provided UID belongs to a supplemental process. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final boolean isSupplemental(int uid) { + uid = UserHandle.getAppId(uid); + return (uid >= FIRST_SUPPLEMENTAL_UID && uid <= LAST_SUPPLEMENTAL_UID); + } + + /** + * + * Returns the app process corresponding to a supplemental process. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final int toAppUid(int uid) { + return uid - (FIRST_SUPPLEMENTAL_UID - FIRST_APPLICATION_UID); + } + + /** + * + * Returns the supplemental process corresponding to an app process. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final int toSupplementalUid(int uid) { + return uid + (FIRST_SUPPLEMENTAL_UID - FIRST_APPLICATION_UID); + } + + /** + * Returns whether the current process is a supplemental process. + */ + public static final boolean isSupplemental() { + return isSupplemental(myUid()); + } + + /** * Returns the UID assigned to a particular user name, or -1 if there is * none. If the given string consists of only numbers, it is converted * directly to a uid. diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java index d974e0c0713a..6b869f13059d 100644 --- a/core/java/android/os/Trace.java +++ b/core/java/android/os/Trace.java @@ -16,7 +16,10 @@ package android.os; +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + import android.annotation.NonNull; +import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import dalvik.annotation.optimization.CriticalNative; @@ -90,6 +93,7 @@ public final class Trace { /** @hide */ public static final long TRACE_TAG_DATABASE = 1L << 20; /** @hide */ + @SystemApi(client = MODULE_LIBRARIES) public static final long TRACE_TAG_NETWORK = 1L << 21; /** @hide */ public static final long TRACE_TAG_ADB = 1L << 22; @@ -148,6 +152,7 @@ public final class Trace { * @hide */ @UnsupportedAppUsage + @SystemApi(client = MODULE_LIBRARIES) public static boolean isTagEnabled(long traceTag) { long tags = nativeGetEnabledTags(); return (tags & traceTag) != 0; @@ -163,7 +168,8 @@ public final class Trace { * @hide */ @UnsupportedAppUsage - public static void traceCounter(long traceTag, String counterName, int counterValue) { + @SystemApi(client = MODULE_LIBRARIES) + public static void traceCounter(long traceTag, @NonNull String counterName, int counterValue) { if (isTagEnabled(traceTag)) { nativeTraceCounter(traceTag, counterName, counterValue); } @@ -202,7 +208,8 @@ public final class Trace { * @hide */ @UnsupportedAppUsage - public static void traceBegin(long traceTag, String methodName) { + @SystemApi(client = MODULE_LIBRARIES) + public static void traceBegin(long traceTag, @NonNull String methodName) { if (isTagEnabled(traceTag)) { nativeTraceBegin(traceTag, methodName); } @@ -217,6 +224,7 @@ public final class Trace { * @hide */ @UnsupportedAppUsage + @SystemApi(client = MODULE_LIBRARIES) public static void traceEnd(long traceTag) { if (isTagEnabled(traceTag)) { nativeTraceEnd(traceTag); @@ -237,7 +245,8 @@ public final class Trace { * @hide */ @UnsupportedAppUsage - public static void asyncTraceBegin(long traceTag, String methodName, int cookie) { + @SystemApi(client = MODULE_LIBRARIES) + public static void asyncTraceBegin(long traceTag, @NonNull String methodName, int cookie) { if (isTagEnabled(traceTag)) { nativeAsyncTraceBegin(traceTag, methodName, cookie); } @@ -255,7 +264,8 @@ public final class Trace { * @hide */ @UnsupportedAppUsage - public static void asyncTraceEnd(long traceTag, String methodName, int cookie) { + @SystemApi(client = MODULE_LIBRARIES) + public static void asyncTraceEnd(long traceTag, @NonNull String methodName, int cookie) { if (isTagEnabled(traceTag)) { nativeAsyncTraceEnd(traceTag, methodName, cookie); } diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl index 5814bac06f59..0894e372efc2 100644 --- a/core/java/android/permission/IPermissionController.aidl +++ b/core/java/android/permission/IPermissionController.aidl @@ -56,6 +56,6 @@ oneway interface IPermissionController { in AndroidFuture<String> callback); void getUnusedAppCount( in AndroidFuture callback); - void selfRevokePermissions(in String packageName, in List<String> permissions, + void revokeOwnPermissionsOnKill(in String packageName, in List<String> permissions, in AndroidFuture callback); } diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl index 8e5581b1b80e..1c0320e9a86e 100644 --- a/core/java/android/permission/IPermissionManager.aidl +++ b/core/java/android/permission/IPermissionManager.aidl @@ -76,7 +76,7 @@ interface IPermissionManager { List<SplitPermissionInfoParcelable> getSplitPermissions(); - void selfRevokePermissions(String packageName, in List<String> permissions); + void revokeOwnPermissionsOnKill(String packageName, in List<String> permissions); void startOneTimePermissionSession(String packageName, int userId, long timeout, int importanceToResetTimer, int importanceToKeepSessionAlive); diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java index 47cd10765da0..8733ac44af98 100644 --- a/core/java/android/permission/PermissionControllerManager.java +++ b/core/java/android/permission/PermissionControllerManager.java @@ -836,15 +836,15 @@ public final class PermissionControllerManager { * @param packageName The name of the package for which the permissions will be revoked. * @param permissions List of permissions to be revoked. * - * @see Context#selfRevokePermissions(Collection) + * @see Context#revokeOwnPermissionsOnKill(Collection) * * @hide */ - public void selfRevokePermissions(@NonNull String packageName, + public void revokeOwnPermissionsOnKill(@NonNull String packageName, @NonNull List<String> permissions) { mRemoteService.postAsync(service -> { AndroidFuture<Void> future = new AndroidFuture<>(); - service.selfRevokePermissions(packageName, permissions, future); + service.revokeOwnPermissionsOnKill(packageName, permissions, future); return future; }).whenComplete((result, err) -> { if (err != null) { diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java index dcbab62530b1..b1e3cfc161de 100644 --- a/core/java/android/permission/PermissionControllerService.java +++ b/core/java/android/permission/PermissionControllerService.java @@ -337,10 +337,10 @@ public abstract class PermissionControllerService extends Service { * @param permissions List of permissions to be revoked. * @param callback Callback waiting for operation to be complete. * - * @see PermissionManager#selfRevokePermissions(java.util.Collection) + * @see PermissionManager#revokeOwnPermissionsOnKill(java.util.Collection) */ @BinderThread - public void onSelfRevokePermissions(@NonNull String packageName, + public void onRevokeOwnPermissionsOnKill(@NonNull String packageName, @NonNull List<String> permissions, @NonNull Runnable callback) { throw new AbstractMethodError("Must be overridden in implementing class"); } @@ -669,13 +669,13 @@ public abstract class PermissionControllerService extends Service { } @Override - public void selfRevokePermissions(@NonNull String packageName, + public void revokeOwnPermissionsOnKill(@NonNull String packageName, @NonNull List<String> permissions, @NonNull AndroidFuture callback) { try { enforceSomePermissionsGrantedToCaller( Manifest.permission.REVOKE_RUNTIME_PERMISSIONS); Objects.requireNonNull(callback); - onSelfRevokePermissions(packageName, permissions, + onRevokeOwnPermissionsOnKill(packageName, permissions, () -> callback.complete(null)); } catch (Throwable t) { callback.completeExceptionally(t); diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 13941dc5ef82..e4aee7619250 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -562,12 +562,12 @@ public final class PermissionManager { } /** - * @see Context#selfRevokePermissions(Collection) + * @see Context#revokeOwnPermissionsOnKill(Collection) * @hide */ - public void selfRevokePermissions(@NonNull Collection<String> permissions) { + public void revokeOwnPermissionsOnKill(@NonNull Collection<String> permissions) { try { - mPermissionManager.selfRevokePermissions(mContext.getPackageName(), + mPermissionManager.revokeOwnPermissionsOnKill(mContext.getPackageName(), new ArrayList<String>(permissions)); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index bca028668942..66debb9a4ae3 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -7168,6 +7168,12 @@ public final class Settings { public static final String LOCATION_COARSE_ACCURACY_M = "locationCoarseAccuracy"; /** + * Whether or not to show display system location accesses. + * @hide + */ + public static final String LOCATION_SHOW_SYSTEM_OPS = "locationShowSystemOps"; + + /** * A flag containing settings used for biometric weak * @hide */ @@ -9057,6 +9063,16 @@ public final class Settings { public static final String SCREENSAVER_DEFAULT_COMPONENT = "screensaver_default_component"; /** + * The complications that are enabled to be shown over the screensaver by the user. Holds + * a comma separated list of + * {@link com.android.settingslib.dream.DreamBackend.ComplicationType}. + * + * @hide + */ + public static final String SCREENSAVER_ENABLED_COMPLICATIONS = + "screensaver_enabled_complications"; + + /** * The default NFC payment component * @hide */ diff --git a/core/java/android/service/games/CreateGameSessionResult.aidl b/core/java/android/service/games/CreateGameSessionResult.aidl new file mode 100644 index 000000000000..b7c5e1602891 --- /dev/null +++ b/core/java/android/service/games/CreateGameSessionResult.aidl @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.games; + + +/** + * @hide + */ +parcelable CreateGameSessionResult; diff --git a/core/java/android/service/games/CreateGameSessionResult.java b/core/java/android/service/games/CreateGameSessionResult.java new file mode 100644 index 000000000000..8448b0f433b2 --- /dev/null +++ b/core/java/android/service/games/CreateGameSessionResult.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.games; + + +import android.annotation.Hide; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; +import android.view.SurfaceControlViewHost; + +/** + * Internal result object that contains the successful creation of a game session. + * + * @see IGameSessionService#create(CreateGameSessionRequest, GameSessionViewHostConfiguration, + * com.android.internal.infra.AndroidFuture) + * @hide + */ +@Hide +public final class CreateGameSessionResult implements Parcelable { + + @NonNull + public static final Parcelable.Creator<CreateGameSessionResult> CREATOR = + new Parcelable.Creator<CreateGameSessionResult>() { + @Override + public CreateGameSessionResult createFromParcel(Parcel source) { + return new CreateGameSessionResult( + IGameSession.Stub.asInterface(source.readStrongBinder()), + source.readParcelable( + SurfaceControlViewHost.SurfacePackage.class.getClassLoader(), + SurfaceControlViewHost.SurfacePackage.class)); + } + + @Override + public CreateGameSessionResult[] newArray(int size) { + return new CreateGameSessionResult[0]; + } + }; + + private final IGameSession mGameSession; + private final SurfaceControlViewHost.SurfacePackage mSurfacePackage; + + public CreateGameSessionResult( + @NonNull IGameSession gameSession, + @NonNull SurfaceControlViewHost.SurfacePackage surfacePackage) { + mGameSession = gameSession; + mSurfacePackage = surfacePackage; + } + + @NonNull + public IGameSession getGameSession() { + return mGameSession; + } + + @NonNull + public SurfaceControlViewHost.SurfacePackage getSurfacePackage() { + return mSurfacePackage; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeStrongBinder(mGameSession.asBinder()); + dest.writeParcelable(mSurfacePackage, flags); + } +} diff --git a/core/java/android/service/games/GameSession.java b/core/java/android/service/games/GameSession.java index 0ff08c08932b..1a5331f10525 100644 --- a/core/java/android/service/games/GameSession.java +++ b/core/java/android/service/games/GameSession.java @@ -16,8 +16,17 @@ package android.service.games; +import android.annotation.Hide; +import android.annotation.NonNull; import android.annotation.SystemApi; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Rect; import android.os.Handler; +import android.view.SurfaceControlViewHost; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; import com.android.internal.util.function.pooled.PooledLambda; @@ -41,12 +50,29 @@ public abstract class GameSession { } }; + private GameSessionRootView mGameSessionRootView; + private SurfaceControlViewHost mSurfaceControlViewHost; + + @Hide + void attach( + @NonNull Context context, + @NonNull SurfaceControlViewHost surfaceControlViewHost, + int widthPx, + int heightPx) { + mSurfaceControlViewHost = surfaceControlViewHost; + mGameSessionRootView = new GameSessionRootView(context, mSurfaceControlViewHost); + surfaceControlViewHost.setView(mGameSessionRootView, widthPx, heightPx); + } + + @Hide void doCreate() { onCreate(); } + @Hide void doDestroy() { onDestroy(); + mSurfaceControlViewHost.release(); } /** @@ -54,12 +80,57 @@ public abstract class GameSession { * * This should be used perform any setup required now that the game session is created. */ - public void onCreate() {} + public void onCreate() { + } /** * Finalizer called when the game session is ending. * * This should be used to perform any cleanup before the game session is destroyed. */ - public void onDestroy() {} + public void onDestroy() { + } + + + /** + * Sets the task overlay content to an explicit view. This view is placed directly into the game + * session's task overlay view hierarchy. It can itself be a complex view hierarchy. The size + * the task overlay view will always match the dimensions of the associated task's window. The + * {@code View} may not be cleared once set, but may be replaced by invoking + * {@link #setTaskOverlayView(View, ViewGroup.LayoutParams)} again. + * + * @param view The desired content to display. + * @param layoutParams Layout parameters for the view. + */ + public void setTaskOverlayView( + @NonNull View view, + @NonNull ViewGroup.LayoutParams layoutParams) { + mGameSessionRootView.removeAllViews(); + mGameSessionRootView.addView(view, layoutParams); + } + + /** + * Root view of the {@link SurfaceControlViewHost} associated with the {@link GameSession} + * instance. It is responsible for observing changes in the size of the window and resizing + * itself to match. + */ + private static final class GameSessionRootView extends FrameLayout { + private final SurfaceControlViewHost mSurfaceControlViewHost; + + GameSessionRootView(@NonNull Context context, + SurfaceControlViewHost surfaceControlViewHost) { + super(context); + mSurfaceControlViewHost = surfaceControlViewHost; + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + // TODO(b/204504596): Investigate skipping the relayout in cases where the size has + // not changed. + Rect bounds = newConfig.windowConfiguration.getBounds(); + mSurfaceControlViewHost.relayout(bounds.width(), bounds.height()); + } + } } diff --git a/core/java/android/service/games/GameSessionService.java b/core/java/android/service/games/GameSessionService.java index c1a3eb5286c4..195a0f233307 100644 --- a/core/java/android/service/games/GameSessionService.java +++ b/core/java/android/service/games/GameSessionService.java @@ -22,8 +22,12 @@ import android.annotation.SdkConstant; import android.annotation.SystemApi; import android.app.Service; import android.content.Intent; +import android.hardware.display.DisplayManager; +import android.os.Binder; import android.os.Handler; import android.os.IBinder; +import android.view.Display; +import android.view.SurfaceControlViewHost; import com.android.internal.infra.AndroidFuture; import com.android.internal.util.function.pooled.PooledLambda; @@ -62,15 +66,26 @@ public abstract class GameSessionService extends Service { private final IGameSessionService mInterface = new IGameSessionService.Stub() { @Override - public void create(CreateGameSessionRequest createGameSessionRequest, + public void create( + CreateGameSessionRequest createGameSessionRequest, + GameSessionViewHostConfiguration gameSessionViewHostConfiguration, AndroidFuture gameSessionFuture) { Handler.getMain().post(PooledLambda.obtainRunnable( GameSessionService::doCreate, GameSessionService.this, createGameSessionRequest, + gameSessionViewHostConfiguration, gameSessionFuture)); } }; + private DisplayManager mDisplayManager; + + @Override + public void onCreate() { + super.onCreate(); + mDisplayManager = this.getSystemService(DisplayManager.class); + } + @Override @Nullable public final IBinder onBind(@Nullable Intent intent) { @@ -85,12 +100,36 @@ public abstract class GameSessionService extends Service { return mInterface.asBinder(); } - private void doCreate(CreateGameSessionRequest createGameSessionRequest, - AndroidFuture<IBinder> gameSessionFuture) { + private void doCreate( + CreateGameSessionRequest createGameSessionRequest, + GameSessionViewHostConfiguration gameSessionViewHostConfiguration, + AndroidFuture<CreateGameSessionResult> createGameSessionResultFuture) { GameSession gameSession = onNewSession(createGameSessionRequest); Objects.requireNonNull(gameSession); - gameSessionFuture.complete(gameSession.mInterface.asBinder()); + Display display = mDisplayManager.getDisplay(gameSessionViewHostConfiguration.mDisplayId); + if (display == null) { + createGameSessionResultFuture.completeExceptionally( + new IllegalStateException("No display found for id: " + + gameSessionViewHostConfiguration.mDisplayId)); + return; + } + + IBinder hostToken = new Binder(); + SurfaceControlViewHost surfaceControlViewHost = + new SurfaceControlViewHost(this, display, hostToken); + + gameSession.attach(this, + surfaceControlViewHost, + gameSessionViewHostConfiguration.mWidthPx, + gameSessionViewHostConfiguration.mHeightPx); + + CreateGameSessionResult createGameSessionResult = + new CreateGameSessionResult(gameSession.mInterface, + surfaceControlViewHost.getSurfacePackage()); + + createGameSessionResultFuture.complete(createGameSessionResult); + gameSession.doCreate(); } diff --git a/core/java/android/service/games/GameSessionViewHostConfiguration.aidl b/core/java/android/service/games/GameSessionViewHostConfiguration.aidl new file mode 100644 index 000000000000..b900b9d09b07 --- /dev/null +++ b/core/java/android/service/games/GameSessionViewHostConfiguration.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.games; + +/** + * @hide + */ +parcelable GameSessionViewHostConfiguration; diff --git a/core/java/android/service/games/GameSessionViewHostConfiguration.java b/core/java/android/service/games/GameSessionViewHostConfiguration.java new file mode 100644 index 000000000000..53db0dfae8b2 --- /dev/null +++ b/core/java/android/service/games/GameSessionViewHostConfiguration.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.games; + +import android.annotation.Hide; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Represents the configuration of the {@link android.view.SurfaceControlViewHost} used to render + * the overlay for a game session. + * + * @hide + */ +@Hide +public final class GameSessionViewHostConfiguration implements Parcelable { + + @NonNull + public static final Creator<GameSessionViewHostConfiguration> CREATOR = + new Creator<GameSessionViewHostConfiguration>() { + @Override + public GameSessionViewHostConfiguration createFromParcel(Parcel source) { + return new GameSessionViewHostConfiguration( + source.readInt(), + source.readInt(), + source.readInt()); + } + + @Override + public GameSessionViewHostConfiguration[] newArray(int size) { + return new GameSessionViewHostConfiguration[0]; + } + }; + + final int mDisplayId; + final int mWidthPx; + final int mHeightPx; + + public GameSessionViewHostConfiguration(int displayId, int widthPx, int heightPx) { + this.mDisplayId = displayId; + this.mWidthPx = widthPx; + this.mHeightPx = heightPx; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mDisplayId); + dest.writeInt(mWidthPx); + dest.writeInt(mHeightPx); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof GameSessionViewHostConfiguration)) return false; + GameSessionViewHostConfiguration that = (GameSessionViewHostConfiguration) o; + return mDisplayId == that.mDisplayId && mWidthPx == that.mWidthPx + && mHeightPx == that.mHeightPx; + } + + @Override + public int hashCode() { + return Objects.hash(mDisplayId, mWidthPx, mHeightPx); + } + + @Override + public String toString() { + return "GameSessionViewHostConfiguration{" + + "mDisplayId=" + mDisplayId + + ", mWidthPx=" + mWidthPx + + ", mHeightPx=" + mHeightPx + + '}'; + } +} diff --git a/core/java/android/service/games/IGameSessionService.aidl b/core/java/android/service/games/IGameSessionService.aidl index 2a53ea7f8e4a..dcbcbc16a374 100644 --- a/core/java/android/service/games/IGameSessionService.aidl +++ b/core/java/android/service/games/IGameSessionService.aidl @@ -18,6 +18,7 @@ package android.service.games; import android.service.games.IGameSession; import android.service.games.CreateGameSessionRequest; +import android.service.games.GameSessionViewHostConfiguration; import com.android.internal.infra.AndroidFuture; @@ -28,5 +29,6 @@ import com.android.internal.infra.AndroidFuture; oneway interface IGameSessionService { void create( in CreateGameSessionRequest createGameSessionRequest, - in AndroidFuture /* T=IBinder for IGameSession */ gameSessionFuture); + in GameSessionViewHostConfiguration gameSessionViewHostConfiguration, + in AndroidFuture /* T=CreateGameSessionResult */ createGameSessionResultFuture); } diff --git a/core/java/android/service/persistentdata/PersistentDataBlockManager.java b/core/java/android/service/persistentdata/PersistentDataBlockManager.java index 8242f4e2c9dc..44a886257d5a 100644 --- a/core/java/android/service/persistentdata/PersistentDataBlockManager.java +++ b/core/java/android/service/persistentdata/PersistentDataBlockManager.java @@ -17,6 +17,7 @@ package android.service.persistentdata; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; @@ -25,6 +26,8 @@ import android.content.Context; import android.os.RemoteException; import android.service.oemlock.OemLockManager; +import com.android.internal.R; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -50,6 +53,7 @@ import java.lang.annotation.RetentionPolicy; @SystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE) public class PersistentDataBlockManager { private static final String TAG = PersistentDataBlockManager.class.getSimpleName(); + private final Context mContext; private IPersistentDataBlockService sService; /** @@ -74,7 +78,10 @@ public class PersistentDataBlockManager { public @interface FlashLockState {} /** @hide */ - public PersistentDataBlockManager(IPersistentDataBlockService service) { + public PersistentDataBlockManager( + Context context, + IPersistentDataBlockService service) { + mContext = context; sService = service; } @@ -204,4 +211,15 @@ public class PersistentDataBlockManager { throw e.rethrowFromSystemServer(); } } + + /** + * Returns the package name which can access the persistent data partition. + * + * @hide + */ + @SystemApi + @NonNull + public String getPersistentDataPackageName() { + return mContext.getString(R.string.config_persistentDataPackageName); + } } diff --git a/core/java/android/service/security/attestationverification/OWNERS b/core/java/android/service/security/attestationverification/OWNERS new file mode 100644 index 000000000000..12c997868f3c --- /dev/null +++ b/core/java/android/service/security/attestationverification/OWNERS @@ -0,0 +1 @@ +include platform/frameworks/base:/core/java/android/security/attestationverification/OWNERS diff --git a/core/java/android/service/trust/ITrustAgentService.aidl b/core/java/android/service/trust/ITrustAgentService.aidl index 21661db0606a..ec3b8575ed36 100644 --- a/core/java/android/service/trust/ITrustAgentService.aidl +++ b/core/java/android/service/trust/ITrustAgentService.aidl @@ -25,6 +25,7 @@ import android.service.trust.ITrustAgentServiceCallback; */ interface ITrustAgentService { oneway void onUnlockAttempt(boolean successful); + oneway void onUserRequestedUnlock(); oneway void onUnlockLockout(int timeoutMs); oneway void onTrustTimeout(); oneway void onDeviceLocked(); diff --git a/core/java/android/service/trust/TrustAgentService.java b/core/java/android/service/trust/TrustAgentService.java index 22ed1b8138b9..fba61cfd801e 100644 --- a/core/java/android/service/trust/TrustAgentService.java +++ b/core/java/android/service/trust/TrustAgentService.java @@ -186,6 +186,7 @@ public class TrustAgentService extends Service { private static final int MSG_ESCROW_TOKEN_ADDED = 7; private static final int MSG_ESCROW_TOKEN_STATE_RECEIVED = 8; private static final int MSG_ESCROW_TOKEN_REMOVED = 9; + private static final int MSG_USER_REQUESTED_UNLOCK = 10; private static final String EXTRA_TOKEN = "token"; private static final String EXTRA_TOKEN_HANDLE = "token_handle"; @@ -219,6 +220,9 @@ public class TrustAgentService extends Service { case MSG_UNLOCK_ATTEMPT: onUnlockAttempt(msg.arg1 != 0); break; + case MSG_USER_REQUESTED_UNLOCK: + onUserRequestedUnlock(); + break; case MSG_UNLOCK_LOCKOUT: onDeviceUnlockLockout(msg.arg1); break; @@ -306,7 +310,7 @@ public class TrustAgentService extends Service { * * @see #FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE * - * TODO(b/213631672): Hook up call from system server & SystemUI, then un-hide + * TODO(b/213631672): Add CTS tests * @hide */ public void onUserRequestedUnlock() { @@ -665,6 +669,11 @@ public class TrustAgentService extends Service { } @Override + public void onUserRequestedUnlock() { + mHandler.obtainMessage(MSG_USER_REQUESTED_UNLOCK).sendToTarget(); + } + + @Override public void onUnlockLockout(int timeoutMs) { mHandler.obtainMessage(MSG_UNLOCK_LOCKOUT, timeoutMs, 0).sendToTarget(); } diff --git a/core/java/android/util/SparseDoubleArray.java b/core/java/android/util/SparseDoubleArray.java index dc93a473fe44..e8d96d8e26e4 100644 --- a/core/java/android/util/SparseDoubleArray.java +++ b/core/java/android/util/SparseDoubleArray.java @@ -81,9 +81,17 @@ public class SparseDoubleArray implements Cloneable { * if no such mapping has been made. */ public double get(int key) { + return get(key, 0); + } + + /** + * Gets the double mapped from the specified key, or the specified value + * if no such mapping has been made. + */ + public double get(int key, double valueIfKeyNotFound) { final int index = mValues.indexOfKey(key); if (index < 0) { - return 0.0d; + return valueIfKeyNotFound; } return valueAt(index); } @@ -105,7 +113,7 @@ public class SparseDoubleArray implements Cloneable { * <p>This differs from {@link #put} because instead of replacing any previous value, it adds * (in the numerical sense) to it. */ - public void add(int key, double summand) { + public void incrementValue(int key, double summand) { final double oldValue = get(key); put(key, oldValue + summand); } @@ -138,6 +146,13 @@ public class SparseDoubleArray implements Cloneable { } /** + * Removes all key-value mappings from this SparseDoubleArray. + */ + public void clear() { + mValues.clear(); + } + + /** * {@inheritDoc} * * <p>This implementation composes a string by iterating over its mappings. diff --git a/core/java/android/util/SparseLongArray.java b/core/java/android/util/SparseLongArray.java index f2bc0c5a34d6..7185972b85bf 100644 --- a/core/java/android/util/SparseLongArray.java +++ b/core/java/android/util/SparseLongArray.java @@ -164,6 +164,30 @@ public class SparseLongArray implements Cloneable { } /** + * Adds a mapping from the specified key to the specified value, + * <b>adding</b> its value to the previous mapping from the specified key if there + * was one. + * + * <p>This differs from {@link #put} because instead of replacing any previous value, it adds + * (in the numerical sense) to it. + * + * @hide + */ + public void incrementValue(int key, long summand) { + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); + + if (i >= 0) { + mValues[i] += summand; + } else { + i = ~i; + + mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key); + mValues = GrowingArrayUtils.insert(mValues, mSize, i, summand); + mSize++; + } + } + + /** * Returns the number of key-value mappings that this SparseLongArray * currently stores. */ diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index 9b8523f9b006..b8eb6027b09c 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -779,7 +779,7 @@ public final class Choreographer { + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past."); } frameTimeNanos = startNanos - lastFrameOffset; - frameData.setFrameTimeNanos(-lastFrameOffset); + frameData.setFrameTimeNanos(frameTimeNanos); } if (frameTimeNanos < mLastFrameTimeNanos) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackPreviewHandler.java b/core/java/android/window/BackNavigationInfo.aidl index dc20f7b72a2b..1529902b9c20 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackPreviewHandler.java +++ b/core/java/android/window/BackNavigationInfo.aidl @@ -14,28 +14,9 @@ * limitations under the License. */ -package com.android.wm.shell.back; - -import android.os.SystemProperties; -import android.view.IWindowManager; - -import javax.inject.Inject; +package android.window; /** - * Handle the preview of what a back gesture will lead to. + * @hide */ -public class BackPreviewHandler { - - private static final String BACK_PREDICTABILITY_PROP = "persist.debug.back_predictability"; - - public static boolean isEnabled() { - return SystemProperties.getInt(BACK_PREDICTABILITY_PROP, 0) > 0; - } - - private final IWindowManager mWmService; - - @Inject - public BackPreviewHandler(IWindowManager windowManagerService) { - mWmService = windowManagerService; - } -} +parcelable BackNavigationInfo;
\ No newline at end of file diff --git a/core/java/android/window/BackNavigationInfo.java b/core/java/android/window/BackNavigationInfo.java new file mode 100644 index 000000000000..571714cc05d5 --- /dev/null +++ b/core/java/android/window/BackNavigationInfo.java @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import static java.util.Objects.requireNonNull; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.WindowConfiguration; +import android.hardware.HardwareBuffer; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.RemoteCallback; +import android.view.SurfaceControl; + +/** + * Information to be sent to SysUI about a back event. + * + * @hide + */ +public final class BackNavigationInfo implements Parcelable { + + /** + * The target of the back navigation is undefined. + */ + public static final int TYPE_UNDEFINED = -1; + + /** + * Navigating back will close the currently visible dialog + */ + public static final int TYPE_DIALOG_CLOSE = 0; + + /** + * Navigating back will bring the user back to the home screen + */ + public static final int TYPE_RETURN_TO_HOME = 1; + + /** + * Navigating back will bring the user to the previous activity in the same Task + */ + public static final int TYPE_CROSS_ACTIVITY = 2; + + /** + * Navigating back will bring the user to the previous activity in the previous Task + */ + public static final int TYPE_CROSS_TASK = 3; + + /** + * Defines the type of back destinations a back even can lead to. This is used to define the + * type of animation that need to be run on SystemUI. + */ + @IntDef(prefix = "TYPE_", value = { + TYPE_UNDEFINED, + TYPE_DIALOG_CLOSE, + TYPE_RETURN_TO_HOME, + TYPE_CROSS_ACTIVITY, + TYPE_CROSS_TASK}) + @interface BackTargetType { + } + + private final int mType; + @Nullable + private final SurfaceControl mDepartingWindowContainer; + @Nullable + private final SurfaceControl mScreenshotSurface; + @Nullable + private final HardwareBuffer mScreenshotBuffer; + @Nullable + private final RemoteCallback mRemoteCallback; + @Nullable + private final WindowConfiguration mTaskWindowConfiguration; + + /** + * Create a new {@link BackNavigationInfo} instance. + * + * @param type The {@link BackTargetType} of the destination (what will be displayed after + * the back action) + * @param topWindowLeash The leash to animate away the current topWindow. The consumer + * of the leash is responsible for removing it. + * @param screenshotSurface The screenshot of the previous activity to be displayed. + * @param screenshotBuffer A buffer containing a screenshot used to display the activity. + * See {@link #getScreenshotHardwareBuffer()} for information + * about nullity. + * @param taskWindowConfiguration The window configuration of the Task being animated + * beneath. + * @param onBackNavigationDone The callback to be called once the client is done with the back + * preview. + */ + public BackNavigationInfo(@BackTargetType int type, + @Nullable SurfaceControl topWindowLeash, + @Nullable SurfaceControl screenshotSurface, + @Nullable HardwareBuffer screenshotBuffer, + @Nullable WindowConfiguration taskWindowConfiguration, + @NonNull RemoteCallback onBackNavigationDone) { + mType = type; + mDepartingWindowContainer = topWindowLeash; + mScreenshotSurface = screenshotSurface; + mScreenshotBuffer = screenshotBuffer; + mTaskWindowConfiguration = taskWindowConfiguration; + mRemoteCallback = onBackNavigationDone; + } + + private BackNavigationInfo(@NonNull Parcel in) { + mType = in.readInt(); + mDepartingWindowContainer = in.readTypedObject(SurfaceControl.CREATOR); + mScreenshotSurface = in.readTypedObject(SurfaceControl.CREATOR); + mScreenshotBuffer = in.readTypedObject(HardwareBuffer.CREATOR); + mTaskWindowConfiguration = in.readTypedObject(WindowConfiguration.CREATOR); + mRemoteCallback = requireNonNull(in.readTypedObject(RemoteCallback.CREATOR)); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mType); + dest.writeTypedObject(mDepartingWindowContainer, flags); + dest.writeTypedObject(mScreenshotSurface, flags); + dest.writeTypedObject(mScreenshotBuffer, flags); + dest.writeTypedObject(mTaskWindowConfiguration, flags); + dest.writeTypedObject(mRemoteCallback, flags); + } + + /** + * Returns the type of back navigation that is about to happen. + * @see BackTargetType + */ + public @BackTargetType int getType() { + return mType; + } + + /** + * Returns a leash to the top window container that needs to be animated. This can be null if + * the back animation is controlled by the application. + */ + @Nullable + public SurfaceControl getDepartingWindowContainer() { + return mDepartingWindowContainer; + } + + /** + * Returns the {@link SurfaceControl} that should be used to display a screenshot of the + * previous activity. + */ + @Nullable + public SurfaceControl getScreenshotSurface() { + return mScreenshotSurface; + } + + /** + * Returns the {@link HardwareBuffer} containing the screenshot the activity about to be + * shown. This can be null if one of the following conditions is met: + * <ul> + * <li>The screenshot is not available + * <li> The previous activity is the home screen ( {@link #TYPE_RETURN_TO_HOME} + * <li> The current window is a dialog ({@link #TYPE_DIALOG_CLOSE} + * <li> The back animation is controlled by the application + * </ul> + */ + @Nullable + public HardwareBuffer getScreenshotHardwareBuffer() { + return mScreenshotBuffer; + } + + /** + * Returns the {@link WindowConfiguration} of the current task. This is null when the top + * application is controlling the back animation. + */ + @Nullable + public WindowConfiguration getTaskWindowConfiguration() { + return mTaskWindowConfiguration; + } + + /** + * Callback to be called when the back preview is finished in order to notify the server that + * it can clean up the resources created for the animation. + */ + public void onBackNavigationFinished() { + mRemoteCallback.sendResult(null); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator<BackNavigationInfo> CREATOR = new Creator<BackNavigationInfo>() { + @Override + public BackNavigationInfo createFromParcel(Parcel in) { + return new BackNavigationInfo(in); + } + + @Override + public BackNavigationInfo[] newArray(int size) { + return new BackNavigationInfo[size]; + } + }; + + @Override + public String toString() { + return "BackNavigationInfo{" + + "mType=" + typeToString(mType) + " (" + mType + ")" + + ", mDepartingWindowContainer=" + mDepartingWindowContainer + + ", mScreenshotSurface=" + mScreenshotSurface + + ", mTaskWindowConfiguration= " + mTaskWindowConfiguration + + ", mScreenshotBuffer=" + mScreenshotBuffer + + ", mRemoteCallback=" + mRemoteCallback + + '}'; + } + + /** + * Translates the {@link BackNavigationInfo} integer type to its String representation + */ + public static String typeToString(@BackTargetType int type) { + switch (type) { + case TYPE_UNDEFINED: + return "TYPE_UNDEFINED"; + case TYPE_DIALOG_CLOSE: + return "TYPE_DIALOG_CLOSE"; + case TYPE_RETURN_TO_HOME: + return "TYPE_RETURN_TO_HOME"; + case TYPE_CROSS_ACTIVITY: + return "TYPE_CROSS_ACTIVITY"; + case TYPE_CROSS_TASK: + return "TYPE_CROSS_TASK"; + } + return String.valueOf(type); + } +} diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index 13a39de3e365..0ada13a73ad2 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -163,6 +163,12 @@ public final class SystemUiDeviceConfigFlags { public static final String PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED = "location_indicators_small_enabled"; + /** + * Whether to show the location indicator for system apps. + */ + public static final String PROPERTY_LOCATION_INDICATORS_SHOW_SYSTEM = + "location_indicators_show_system"; + // Flags related to Assistant /** diff --git a/core/java/com/android/internal/midi/MidiFramer.java b/core/java/com/android/internal/midi/MidiFramer.java index 62517fa82953..bf23ad190ef1 100644 --- a/core/java/com/android/internal/midi/MidiFramer.java +++ b/core/java/com/android/internal/midi/MidiFramer.java @@ -99,6 +99,12 @@ public class MidiFramer extends MidiReceiver { } } else { // data byte if (!mInSysEx) { + // Hack to avoid crashing if we start parsing in the middle + // of a data stream + if (mNeeded <= 0) { + break; + } + mBuffer[mCount++] = currentByte; if (--mNeeded == 0) { if (mRunningStatus != 0) { diff --git a/core/java/com/android/internal/midi/OWNERS b/core/java/com/android/internal/midi/OWNERS new file mode 100644 index 000000000000..af273a6f50e0 --- /dev/null +++ b/core/java/com/android/internal/midi/OWNERS @@ -0,0 +1 @@ +include /services/midi/OWNERS diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index a4183ca8f163..451eec0c18df 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -84,7 +84,6 @@ import android.util.Log; import android.util.LongSparseArray; import android.util.LongSparseLongArray; import android.util.MutableInt; -import android.util.Pools; import android.util.PrintWriterPrinter; import android.util.Printer; import android.util.Slog; @@ -137,9 +136,7 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Queue; -import java.util.Set; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantLock; @@ -265,6 +262,7 @@ public class BatteryStatsImpl extends BatteryStats { MeasuredEnergyStats.POWER_BUCKET_CPU, MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO, MeasuredEnergyStats.POWER_BUCKET_WIFI, + MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH, }; // TimeInState counters need NUM_PROCESS_STATE states in order to accommodate @@ -8371,6 +8369,11 @@ public class BatteryStatsImpl extends BatteryStats { if (wifiControllerActivity != null) { wifiControllerActivity.setState(batteryConsumerProcessState, elapsedTimeMs); } + final ControllerActivityCounterImpl bluetoothControllerActivity = + getBluetoothControllerActivity(); + if (bluetoothControllerActivity != null) { + bluetoothControllerActivity.setState(batteryConsumerProcessState, elapsedTimeMs); + } final MeasuredEnergyStats energyStats = getOrCreateMeasuredEnergyStatsIfSupportedLocked(); if (energyStats != null) { @@ -8718,7 +8721,7 @@ public class BatteryStatsImpl extends BatteryStats { } @Override - public ControllerActivityCounter getBluetoothControllerActivity() { + public ControllerActivityCounterImpl getBluetoothControllerActivity() { return mBluetoothControllerActivity; } @@ -8839,6 +8842,14 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("mBsi") @Override + public long getBluetoothMeasuredBatteryConsumptionUC( + @BatteryConsumer.ProcessState int processState) { + return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH, + processState); + } + + @GuardedBy("mBsi") + @Override public long getCpuMeasuredBatteryConsumptionUC() { return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_CPU); } @@ -11424,6 +11435,13 @@ public class BatteryStatsImpl extends BatteryStats { wifiControllerActivity.setState(batteryConsumerProcessState, elapsedRealtimeMs); } + final ControllerActivityCounterImpl bluetoothControllerActivity = + getBluetoothControllerActivity(); + if (bluetoothControllerActivity != null) { + bluetoothControllerActivity.setState(batteryConsumerProcessState, + elapsedRealtimeMs); + } + final MeasuredEnergyStats energyStats = getOrCreateMeasuredEnergyStatsIfSupportedLocked(); if (energyStats != null) { @@ -12669,8 +12687,6 @@ public class BatteryStatsImpl extends BatteryStats { } } - private final Pools.Pool<NetworkStats> mNetworkStatsPool = new Pools.SynchronizedPool<>(6); - private final Object mWifiNetworkLock = new Object(); @GuardedBy("mWifiNetworkLock") @@ -12688,13 +12704,15 @@ public class BatteryStatsImpl extends BatteryStats { private NetworkStats mLastModemNetworkStats = new NetworkStats(0, -1); @VisibleForTesting - protected NetworkStats readNetworkStatsLocked(@NonNull NetworkStatsManager networkStatsManager, - String[] ifaces) { - Objects.requireNonNull(networkStatsManager); - if (!ArrayUtils.isEmpty(ifaces)) { - return networkStatsManager.getDetailedUidStats(Set.of(ifaces)); - } - return null; + protected NetworkStats readMobileNetworkStatsLocked( + @NonNull NetworkStatsManager networkStatsManager) { + return networkStatsManager.getMobileUidStats(); + } + + @VisibleForTesting + protected NetworkStats readWifiNetworkStatsLocked( + @NonNull NetworkStatsManager networkStatsManager) { + return networkStatsManager.getWifiUidStats(); } /** @@ -12714,21 +12732,15 @@ public class BatteryStatsImpl extends BatteryStats { // Grab a separate lock to acquire the network stats, which may do I/O. NetworkStats delta = null; synchronized (mWifiNetworkLock) { - final NetworkStats latestStats = readNetworkStatsLocked(networkStatsManager, - mWifiIfaces); + final NetworkStats latestStats = readWifiNetworkStatsLocked(networkStatsManager); if (latestStats != null) { - delta = NetworkStats.subtract(latestStats, mLastWifiNetworkStats, null, null, - mNetworkStatsPool.acquire()); - mNetworkStatsPool.release(mLastWifiNetworkStats); + delta = latestStats.subtract(mLastWifiNetworkStats); mLastWifiNetworkStats = latestStats; } } synchronized (this) { if (!mOnBatteryInternal || mIgnoreNextExternalStats) { - if (delta != null) { - mNetworkStatsPool.release(delta); - } if (mIgnoreNextExternalStats) { // TODO: Strictly speaking, we should re-mark all 5 timers for each uid (and the // global one) here like we do for display. But I'm not sure it's worth the @@ -12828,13 +12840,12 @@ public class BatteryStatsImpl extends BatteryStats { } } - uidEstimatedConsumptionMah.add(u.getUid(), + uidEstimatedConsumptionMah.incrementValue(u.getUid(), mWifiPowerCalculator.calcPowerWithoutControllerDataMah( entry.rxPackets, entry.txPackets, uidRunningMs, uidScanMs, uidBatchScanMs)); } } - mNetworkStatsPool.release(delta); delta = null; } @@ -12955,7 +12966,7 @@ public class BatteryStatsImpl extends BatteryStats { if (uidEstimatedConsumptionMah != null) { double uidEstMah = mWifiPowerCalculator.calcPowerFromControllerDataMah( scanRxTimeSinceMarkMs, scanTxTimeSinceMarkMs, myIdleTimeMs); - uidEstimatedConsumptionMah.add(uid.getUid(), uidEstMah); + uidEstimatedConsumptionMah.incrementValue(uid.getUid(), uidEstMah); } } @@ -12977,7 +12988,7 @@ public class BatteryStatsImpl extends BatteryStats { uid.getOrCreateWifiControllerActivityLocked().getOrCreateTxTimeCounters()[0] .increment(myTxTimeMs, elapsedRealtimeMs); if (uidEstimatedConsumptionMah != null) { - uidEstimatedConsumptionMah.add(uid.getUid(), + uidEstimatedConsumptionMah.incrementValue(uid.getUid(), mWifiPowerCalculator.calcPowerFromControllerDataMah( 0, myTxTimeMs, 0)); } @@ -12996,7 +13007,7 @@ public class BatteryStatsImpl extends BatteryStats { uid.getOrCreateWifiControllerActivityLocked().getOrCreateRxTimeCounter() .increment(myRxTimeMs, elapsedRealtimeMs); if (uidEstimatedConsumptionMah != null) { - uidEstimatedConsumptionMah.add(uid.getUid(), + uidEstimatedConsumptionMah.incrementValue(uid.getUid(), mWifiPowerCalculator.calcPowerFromControllerDataMah( myRxTimeMs, 0, 0)); } @@ -13083,21 +13094,15 @@ public class BatteryStatsImpl extends BatteryStats { // Grab a separate lock to acquire the network stats, which may do I/O. NetworkStats delta = null; synchronized (mModemNetworkLock) { - final NetworkStats latestStats = readNetworkStatsLocked(networkStatsManager, - mModemIfaces); + final NetworkStats latestStats = readMobileNetworkStatsLocked(networkStatsManager); if (latestStats != null) { - delta = NetworkStats.subtract(latestStats, mLastModemNetworkStats, null, null, - mNetworkStatsPool.acquire()); - mNetworkStatsPool.release(mLastModemNetworkStats); + delta = latestStats.subtract(mLastModemNetworkStats); mLastModemNetworkStats = latestStats; } } synchronized (this) { if (!mOnBatteryInternal || mIgnoreNextExternalStats) { - if (delta != null) { - mNetworkStatsPool.release(delta); - } return; } @@ -13224,7 +13229,7 @@ public class BatteryStatsImpl extends BatteryStats { // Distribute measured mobile radio charge consumption based on app radio // active time if (uidEstimatedConsumptionMah != null) { - uidEstimatedConsumptionMah.add(u.getUid(), + uidEstimatedConsumptionMah.incrementValue(u.getUid(), mMobileRadioPowerCalculator.calcPowerFromRadioActiveDurationMah( appRadioTimeUs / 1000)); } @@ -13303,7 +13308,6 @@ public class BatteryStatsImpl extends BatteryStats { totalEstimatedConsumptionMah, elapsedRealtimeMs); } - mNetworkStatsPool.release(delta); delta = null; } } @@ -13479,7 +13483,7 @@ public class BatteryStatsImpl extends BatteryStats { .increment(scanTimeTxSinceMarkMs, elapsedRealtimeMs); if (uidEstimatedConsumptionMah != null) { - uidEstimatedConsumptionMah.add(u.getUid(), + uidEstimatedConsumptionMah.incrementValue(u.getUid(), mBluetoothPowerCalculator.calculatePowerMah( scanTimeRxSinceMarkMs, scanTimeTxSinceMarkMs, 0)); } @@ -13546,7 +13550,7 @@ public class BatteryStatsImpl extends BatteryStats { counter.getOrCreateRxTimeCounter().increment(timeRxMs, elapsedRealtimeMs); if (uidEstimatedConsumptionMah != null) { - uidEstimatedConsumptionMah.add(u.getUid(), + uidEstimatedConsumptionMah.incrementValue(u.getUid(), mBluetoothPowerCalculator.calculatePowerMah(timeRxMs, 0, 0)); } } @@ -13560,7 +13564,7 @@ public class BatteryStatsImpl extends BatteryStats { .increment(timeTxMs, elapsedRealtimeMs); if (uidEstimatedConsumptionMah != null) { - uidEstimatedConsumptionMah.add(u.getUid(), + uidEstimatedConsumptionMah.incrementValue(u.getUid(), mBluetoothPowerCalculator.calculatePowerMah(0, timeTxMs, 0)); } } @@ -16183,6 +16187,18 @@ public class BatteryStatsImpl extends BatteryStats { iPw.decreaseIndent(); } + /** + * Dump Power Profile + */ + @GuardedBy("this") + public void dumpPowerProfileLocked(PrintWriter pw) { + final IndentingPrintWriter iPw = new IndentingPrintWriter(pw, " "); + iPw.printf("Power Profile: \n"); + iPw.increaseIndent(); + mPowerProfile.dump(iPw); + iPw.decreaseIndent(); + } + final ReentrantLock mWriteLock = new ReentrantLock(); @GuardedBy("this") diff --git a/core/java/com/android/internal/os/BluetoothPowerCalculator.java b/core/java/com/android/internal/os/BluetoothPowerCalculator.java index c322258eda85..20535d29afcd 100644 --- a/core/java/com/android/internal/os/BluetoothPowerCalculator.java +++ b/core/java/com/android/internal/os/BluetoothPowerCalculator.java @@ -15,6 +15,7 @@ */ package com.android.internal.os; +import android.annotation.Nullable; import android.os.BatteryConsumer; import android.os.BatteryStats; import android.os.BatteryStats.ControllerActivityCounter; @@ -26,19 +27,33 @@ import android.os.UserHandle; import android.util.Log; import android.util.SparseArray; +import java.util.Arrays; import java.util.List; public class BluetoothPowerCalculator extends PowerCalculator { private static final String TAG = "BluetoothPowerCalc"; private static final boolean DEBUG = BatteryStatsHelper.DEBUG; + + private static final BatteryConsumer.Key[] UNINITIALIZED_KEYS = new BatteryConsumer.Key[0]; + private final double mIdleMa; private final double mRxMa; private final double mTxMa; private final boolean mHasBluetoothPowerController; private static class PowerAndDuration { + // Return value of BT duration per app public long durationMs; + // Return value of BT power per app public double powerMah; + + public BatteryConsumer.Key[] keys; + public double[] powerPerKeyMah; + + // Aggregated BT duration across all apps + public long totalDurationMs; + // Aggregated BT power across all apps + public double totalPowerMah; } public BluetoothPowerCalculator(PowerProfile profile) { @@ -55,59 +70,88 @@ public class BluetoothPowerCalculator extends PowerCalculator { return; } - final PowerAndDuration total = new PowerAndDuration(); + BatteryConsumer.Key[] keys = UNINITIALIZED_KEYS; + final PowerAndDuration powerAndDuration = new PowerAndDuration(); final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders = builder.getUidBatteryConsumerBuilders(); for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) { final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i); - calculateApp(app, total, query); + if (keys == UNINITIALIZED_KEYS) { + if (query.isProcessStateDataNeeded()) { + keys = app.getKeys(BatteryConsumer.POWER_COMPONENT_BLUETOOTH); + powerAndDuration.keys = keys; + powerAndDuration.powerPerKeyMah = new double[keys.length]; + } else { + keys = null; + } + } + calculateApp(app, powerAndDuration, query); } final long measuredChargeUC = batteryStats.getBluetoothMeasuredBatteryConsumptionUC(); final int powerModel = getPowerModel(measuredChargeUC, query); final ControllerActivityCounter activityCounter = batteryStats.getBluetoothControllerActivity(); - final long systemDurationMs = calculateDuration(activityCounter); - final double systemPowerMah = calculatePowerMah(powerModel, measuredChargeUC, - activityCounter, query.shouldForceUsePowerProfileModel()); + calculatePowerAndDuration(null, powerModel, measuredChargeUC, + activityCounter, query.shouldForceUsePowerProfileModel(), powerAndDuration); // Subtract what the apps used, but clamp to 0. - final long systemComponentDurationMs = Math.max(0, systemDurationMs - total.durationMs); + final long systemComponentDurationMs = Math.max(0, + powerAndDuration.durationMs - powerAndDuration.totalDurationMs); if (DEBUG) { Log.d(TAG, "Bluetooth active: time=" + (systemComponentDurationMs) - + " power=" + formatCharge(systemPowerMah)); + + " power=" + formatCharge(powerAndDuration.powerMah)); } builder.getAggregateBatteryConsumerBuilder( - BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) - .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, systemDurationMs) + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) + .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, + powerAndDuration.durationMs) .setConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, - Math.max(systemPowerMah, total.powerMah), powerModel); + Math.max(powerAndDuration.powerMah, powerAndDuration.totalPowerMah), + powerModel); builder.getAggregateBatteryConsumerBuilder( - BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS) - .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, total.durationMs) - .setConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, total.powerMah, + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS) + .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, + powerAndDuration.totalDurationMs) + .setConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, + powerAndDuration.totalPowerMah, powerModel); } - private void calculateApp(UidBatteryConsumer.Builder app, PowerAndDuration total, + private void calculateApp(UidBatteryConsumer.Builder app, PowerAndDuration powerAndDuration, BatteryUsageStatsQuery query) { final long measuredChargeUC = app.getBatteryStatsUid().getBluetoothMeasuredBatteryConsumptionUC(); final int powerModel = getPowerModel(measuredChargeUC, query); final ControllerActivityCounter activityCounter = app.getBatteryStatsUid().getBluetoothControllerActivity(); - final long durationMs = calculateDuration(activityCounter); - final double powerMah = calculatePowerMah(powerModel, measuredChargeUC, activityCounter, - query.shouldForceUsePowerProfileModel()); + calculatePowerAndDuration(app.getBatteryStatsUid(), powerModel, measuredChargeUC, + activityCounter, query.shouldForceUsePowerProfileModel(), powerAndDuration); - app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, durationMs) - .setConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, powerMah, powerModel); + app.setUsageDurationMillis( + BatteryConsumer.POWER_COMPONENT_BLUETOOTH, powerAndDuration.durationMs) + .setConsumedPower( + BatteryConsumer.POWER_COMPONENT_BLUETOOTH, powerAndDuration.powerMah, + powerModel); + + powerAndDuration.totalDurationMs += powerAndDuration.durationMs; + powerAndDuration.totalPowerMah += powerAndDuration.powerMah; - total.durationMs += durationMs; - total.powerMah += powerMah; + if (query.isProcessStateDataNeeded() && powerAndDuration.keys != null) { + for (int j = 0; j < powerAndDuration.keys.length; j++) { + BatteryConsumer.Key key = powerAndDuration.keys[j]; + final int processState = key.processState; + if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) { + // Already populated with the powerAndDuration across all process states + continue; + } + + app.setConsumedPower(key, powerAndDuration.powerPerKeyMah[j], powerModel); + } + } } @Override @@ -117,12 +161,12 @@ public class BluetoothPowerCalculator extends PowerCalculator { return; } - PowerAndDuration total = new PowerAndDuration(); + PowerAndDuration powerAndDuration = new PowerAndDuration(); for (int i = sippers.size() - 1; i >= 0; i--) { final BatterySipper app = sippers.get(i); if (app.drainType == BatterySipper.DrainType.APP) { - calculateApp(app, app.uidObj, statsType, total); + calculateApp(app, app.uidObj, statsType, powerAndDuration); } } @@ -131,13 +175,14 @@ public class BluetoothPowerCalculator extends PowerCalculator { final int powerModel = getPowerModel(measuredChargeUC); final ControllerActivityCounter activityCounter = batteryStats.getBluetoothControllerActivity(); - final long systemDurationMs = calculateDuration(activityCounter); - final double systemPowerMah = - calculatePowerMah(powerModel, measuredChargeUC, activityCounter, false); + calculatePowerAndDuration(null, powerModel, measuredChargeUC, activityCounter, false, + powerAndDuration); // Subtract what the apps used, but clamp to 0. - final double powerMah = Math.max(0, systemPowerMah - total.powerMah); - final long durationMs = Math.max(0, systemDurationMs - total.durationMs); + final double powerMah = Math.max(0, + powerAndDuration.powerMah - powerAndDuration.totalPowerMah); + final long durationMs = Math.max(0, + powerAndDuration.durationMs - powerAndDuration.totalDurationMs); if (DEBUG && powerMah != 0) { Log.d(TAG, "Bluetooth active: time=" + (durationMs) + " power=" + formatCharge(powerMah)); @@ -160,65 +205,102 @@ public class BluetoothPowerCalculator extends PowerCalculator { } private void calculateApp(BatterySipper app, BatteryStats.Uid u, int statsType, - PowerAndDuration total) { - + PowerAndDuration powerAndDuration) { final long measuredChargeUC = u.getBluetoothMeasuredBatteryConsumptionUC(); final int powerModel = getPowerModel(measuredChargeUC); final ControllerActivityCounter activityCounter = u.getBluetoothControllerActivity(); - final long durationMs = calculateDuration(activityCounter); - final double powerMah = calculatePowerMah(powerModel, measuredChargeUC, activityCounter, - false); + calculatePowerAndDuration(u, powerModel, measuredChargeUC, activityCounter, + false, powerAndDuration); - app.bluetoothRunningTimeMs = durationMs; - app.bluetoothPowerMah = powerMah; + app.bluetoothRunningTimeMs = powerAndDuration.durationMs; + app.bluetoothPowerMah = powerAndDuration.powerMah; app.btRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_BT_RX_DATA, statsType); app.btTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_BT_TX_DATA, statsType); - total.durationMs += durationMs; - total.powerMah += powerMah; + powerAndDuration.totalDurationMs += powerAndDuration.durationMs; + powerAndDuration.totalPowerMah += powerAndDuration.powerMah; } - private long calculateDuration(ControllerActivityCounter counter) { + /** Returns bluetooth power usage based on the best data available. */ + private void calculatePowerAndDuration(@Nullable BatteryStats.Uid uid, + @BatteryConsumer.PowerModel int powerModel, + long measuredChargeUC, ControllerActivityCounter counter, boolean ignoreReportedPower, + PowerAndDuration powerAndDuration) { if (counter == null) { - return 0; + powerAndDuration.durationMs = 0; + powerAndDuration.powerMah = 0; + if (powerAndDuration.powerPerKeyMah != null) { + Arrays.fill(powerAndDuration.powerPerKeyMah, 0); + } + return; } - return counter.getIdleTimeCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED) - + counter.getRxTimeCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED) - + counter.getTxTimeCounters()[0].getCountLocked(BatteryStats.STATS_SINCE_CHARGED); - } - - /** Returns bluetooth power usage based on the best data available. */ - private double calculatePowerMah(@BatteryConsumer.PowerModel int powerModel, - long measuredChargeUC, ControllerActivityCounter counter, boolean ignoreReportedPower) { - if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) { - return uCtoMah(measuredChargeUC); - } + final BatteryStats.LongCounter idleTimeCounter = counter.getIdleTimeCounter(); + final BatteryStats.LongCounter rxTimeCounter = counter.getRxTimeCounter(); + final BatteryStats.LongCounter txTimeCounter = counter.getTxTimeCounters()[0]; + final long idleTimeMs = idleTimeCounter.getCountLocked(BatteryStats.STATS_SINCE_CHARGED); + final long rxTimeMs = rxTimeCounter.getCountLocked(BatteryStats.STATS_SINCE_CHARGED); + final long txTimeMs = txTimeCounter.getCountLocked(BatteryStats.STATS_SINCE_CHARGED); - if (counter == null) { - return 0; - } + powerAndDuration.durationMs = idleTimeMs + rxTimeMs + txTimeMs; - if (!ignoreReportedPower) { - final double powerMah = - counter.getPowerCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED) - / (double) (1000 * 60 * 60); - if (powerMah != 0) { - return powerMah; + if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) { + powerAndDuration.powerMah = uCtoMah(measuredChargeUC); + if (uid != null && powerAndDuration.keys != null) { + for (int i = 0; i < powerAndDuration.keys.length; i++) { + BatteryConsumer.Key key = powerAndDuration.keys[i]; + final int processState = key.processState; + if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) { + // Already populated with the powerAndDuration across all process states + continue; + } + + powerAndDuration.powerPerKeyMah[i] = + uCtoMah(uid.getBluetoothMeasuredBatteryConsumptionUC(processState)); + } + } + } else { + if (!ignoreReportedPower) { + final double powerMah = + counter.getPowerCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED) + / (double) (1000 * 60 * 60); + if (powerMah != 0) { + powerAndDuration.powerMah = powerMah; + if (powerAndDuration.powerPerKeyMah != null) { + // Leave this use case unsupported: used energy is reported + // via BluetoothActivityEnergyInfo rather than PowerStats HAL. + Arrays.fill(powerAndDuration.powerPerKeyMah, 0); + } + return; + } } - } - if (!mHasBluetoothPowerController) { - return 0; + if (mHasBluetoothPowerController) { + powerAndDuration.powerMah = calculatePowerMah(rxTimeMs, txTimeMs, idleTimeMs); + + if (powerAndDuration.keys != null) { + for (int i = 0; i < powerAndDuration.keys.length; i++) { + BatteryConsumer.Key key = powerAndDuration.keys[i]; + final int processState = key.processState; + if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) { + // Already populated with the powerAndDuration across all process states + continue; + } + + powerAndDuration.powerPerKeyMah[i] = + calculatePowerMah( + rxTimeCounter.getCountForProcessState(processState), + txTimeCounter.getCountForProcessState(processState), + idleTimeCounter.getCountForProcessState(processState)); + } + } + } else { + powerAndDuration.powerMah = 0; + if (powerAndDuration.powerPerKeyMah != null) { + Arrays.fill(powerAndDuration.powerPerKeyMah, 0); + } + } } - - final long idleTimeMs = - counter.getIdleTimeCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED); - final long rxTimeMs = - counter.getRxTimeCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED); - final long txTimeMs = - counter.getTxTimeCounters()[0].getCountLocked(BatteryStats.STATS_SINCE_CHARGED); - return calculatePowerMah(rxTimeMs, txTimeMs, idleTimeMs); } /** Returns estimated bluetooth power usage based on usage times. */ diff --git a/core/java/com/android/internal/os/OWNERS b/core/java/com/android/internal/os/OWNERS index 7766b77ab3c6..fd1d86b27834 100644 --- a/core/java/com/android/internal/os/OWNERS +++ b/core/java/com/android/internal/os/OWNERS @@ -12,4 +12,5 @@ per-file *PowerCalculator* = file:/BATTERY_STATS_OWNERS per-file *PowerEstimator* = file:/BATTERY_STATS_OWNERS per-file *Kernel* = file:/BATTERY_STATS_OWNERS per-file *MultiState* = file:/BATTERY_STATS_OWNERS +per-file *PowerProfile* = file:/BATTERY_STATS_OWNERS diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java index 4d19b35b1e16..7f8acccd72bb 100644 --- a/core/java/com/android/internal/os/PowerProfile.java +++ b/core/java/com/android/internal/os/PowerProfile.java @@ -17,24 +17,31 @@ package com.android.internal.os; +import android.annotation.LongDef; import android.annotation.StringDef; +import android.annotation.XmlRes; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.Resources; import android.content.res.XmlResourceParser; +import android.util.IndentingPrintWriter; import android.util.Slog; import android.util.proto.ProtoOutputStream; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.power.ModemPowerProfile; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; /** @@ -259,6 +266,35 @@ public class PowerProfile { public @interface PowerGroup {} /** + * Constants for generating a 64bit power constant key. + * + * The bitfields of a key describes what its corresponding power constant represents: + * [63:40] - RESERVED + * [39:32] - {@link Subsystem} (max count = 16). + * [31:0] - per Subsystem fields, see {@link ModemPowerProfile}. + * + */ + private static final int SUBSYSTEM_SHIFT = 32; + private static final long SUBSYSTEM_MASK = 0xF << SUBSYSTEM_SHIFT; + /** + * Power constant not associated with a subsystem. + */ + public static final long SUBSYSTEM_NONE = 0 << SUBSYSTEM_SHIFT; + /** + * Modem power constant. + */ + public static final long SUBSYSTEM_MODEM = 1 << SUBSYSTEM_SHIFT; + + @LongDef(prefix = { "SUBSYSTEM_" }, value = { + SUBSYSTEM_NONE, + SUBSYSTEM_MODEM, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Subsystem {} + + private static final long SUBSYSTEM_FIELDS_MASK = 0xFFFFFFFF; + + /** * A map from Power Use Item to its power consumption. */ static final HashMap<String, Double> sPowerItemMap = new HashMap<>(); @@ -268,12 +304,16 @@ public class PowerProfile { */ static final HashMap<String, Double[]> sPowerArrayMap = new HashMap<>(); + static final ModemPowerProfile sModemPowerProfile = new ModemPowerProfile(); + private static final String TAG_DEVICE = "device"; private static final String TAG_ITEM = "item"; private static final String TAG_ARRAY = "array"; private static final String TAG_ARRAYITEM = "value"; private static final String ATTR_NAME = "name"; + private static final String TAG_MODEM = "modem"; + private static final Object sLock = new Object(); @VisibleForTesting @@ -289,19 +329,40 @@ public class PowerProfile { public PowerProfile(Context context, boolean forTest) { // Read the XML file for the given profile (normally only one per device) synchronized (sLock) { - if (sPowerItemMap.size() == 0 && sPowerArrayMap.size() == 0) { - readPowerValuesFromXml(context, forTest); - } - initCpuClusters(); - initDisplays(); + final int xmlId = forTest ? com.android.internal.R.xml.power_profile_test + : com.android.internal.R.xml.power_profile; + initLocked(context, xmlId); + } + } + + /** + * Reinitialize the PowerProfile with the provided XML. + * WARNING: use only for testing! + */ + @VisibleForTesting + public void forceInitForTesting(Context context, @XmlRes int xmlId) { + synchronized (sLock) { + sPowerItemMap.clear(); + sPowerArrayMap.clear(); + sModemPowerProfile.clear(); + initLocked(context, xmlId); + } + + } + + @GuardedBy("sLock") + private void initLocked(Context context, @XmlRes int xmlId) { + if (sPowerItemMap.size() == 0 && sPowerArrayMap.size() == 0) { + readPowerValuesFromXml(context, xmlId); } + initCpuClusters(); + initDisplays(); + initModem(); } - private void readPowerValuesFromXml(Context context, boolean forTest) { - final int id = forTest ? com.android.internal.R.xml.power_profile_test : - com.android.internal.R.xml.power_profile; + private void readPowerValuesFromXml(Context context, @XmlRes int xmlId) { final Resources resources = context.getResources(); - XmlResourceParser parser = resources.getXml(id); + XmlResourceParser parser = resources.getXml(xmlId); boolean parsingArray = false; ArrayList<Double> array = new ArrayList<>(); String arrayName = null; @@ -340,6 +401,8 @@ public class PowerProfile { array.add(value); } } + } else if (element.equals(TAG_MODEM)) { + sModemPowerProfile.parseFromXml(parser); } } if (parsingArray) { @@ -515,6 +578,39 @@ public class PowerProfile { return mNumDisplays; } + private void initModem() { + handleDeprecatedModemConstant(ModemPowerProfile.MODEM_DRAIN_TYPE_SLEEP, + POWER_MODEM_CONTROLLER_SLEEP, 0); + handleDeprecatedModemConstant(ModemPowerProfile.MODEM_DRAIN_TYPE_IDLE, + POWER_MODEM_CONTROLLER_SLEEP, 0); + handleDeprecatedModemConstant( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_RX, + POWER_MODEM_CONTROLLER_RX, 0); + handleDeprecatedModemConstant( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_0, POWER_MODEM_CONTROLLER_TX, 0); + handleDeprecatedModemConstant( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_1, POWER_MODEM_CONTROLLER_TX, 1); + handleDeprecatedModemConstant( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_2, POWER_MODEM_CONTROLLER_TX, 2); + handleDeprecatedModemConstant( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_3, POWER_MODEM_CONTROLLER_TX, 3); + handleDeprecatedModemConstant( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_4, POWER_MODEM_CONTROLLER_TX, 4); + } + + private void handleDeprecatedModemConstant(int key, String deprecatedKey, int level) { + final double drain = sModemPowerProfile.getAverageBatteryDrainMa(key); + if (!Double.isNaN(drain)) return; // Value already set, don't overwrite it. + + final double deprecatedDrain = getAveragePower(deprecatedKey, level); + sModemPowerProfile.setPowerConstant(key, Double.toString(deprecatedDrain)); + } + /** * Returns the number of memory bandwidth buckets defined in power_profile.xml, or a * default value if the subsystem has no recorded value. @@ -560,6 +656,43 @@ public class PowerProfile { } /** + * Returns the average current in mA consumed by a subsystem's specified operation, or the given + * default value if the subsystem has no recorded value. + * + * @param key that describes a subsystem's battery draining operation + * The key is built from multiple constant, see {@link Subsystem} and + * {@link ModemPowerProfile}. + * @param defaultValue the value to return if the subsystem has no recorded value. + * @return the average current in milliAmps. + */ + public double getAverageBatteryDrainOrDefaultMa(long key, double defaultValue) { + final long subsystemType = key & SUBSYSTEM_MASK; + final int subsystemFields = (int) (key & SUBSYSTEM_FIELDS_MASK); + + final double value; + if (subsystemType == SUBSYSTEM_MODEM) { + value = sModemPowerProfile.getAverageBatteryDrainMa(subsystemFields); + } else { + value = Double.NaN; + } + + if (Double.isNaN(value)) return defaultValue; + return value; + } + + /** + * Returns the average current in mA consumed by a subsystem's specified operation. + * + * @param key that describes a subsystem's battery draining operation + * The key is built from multiple constant, see {@link Subsystem} and + * {@link ModemPowerProfile}. + * @return the average current in milliAmps. + */ + public double getAverageBatteryDrainMa(long key) { + return getAverageBatteryDrainOrDefaultMa(key, 0); + } + + /** * Returns the average current in mA consumed by the subsystem for the given level. * * @param type the subsystem type @@ -784,6 +917,25 @@ public class PowerProfile { PowerProfileProto.BATTERY_CAPACITY); } + /** + * Dump the PowerProfile values. + */ + public void dump(PrintWriter pw) { + final IndentingPrintWriter ipw = new IndentingPrintWriter(pw); + sPowerItemMap.forEach((key, value) -> { + ipw.print(key, value); + ipw.println(); + }); + sPowerArrayMap.forEach((key, value) -> { + ipw.print(key, Arrays.toString(value)); + ipw.println(); + }); + ipw.println("Modem values:"); + ipw.increaseIndent(); + sModemPowerProfile.dump(ipw); + ipw.decreaseIndent(); + } + // Writes items in sPowerItemMap to proto if exists. private void writePowerConstantToProto(ProtoOutputStream proto, String key, long fieldId) { if (sPowerItemMap.containsKey(key)) { diff --git a/core/java/com/android/internal/power/ModemPowerProfile.java b/core/java/com/android/internal/power/ModemPowerProfile.java new file mode 100644 index 000000000000..456ff4ba8a26 --- /dev/null +++ b/core/java/com/android/internal/power/ModemPowerProfile.java @@ -0,0 +1,442 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.power; + +import android.annotation.IntDef; +import android.content.res.XmlResourceParser; +import android.telephony.ModemActivityInfo; +import android.telephony.ServiceState; +import android.telephony.TelephonyManager; +import android.util.Slog; +import android.util.SparseDoubleArray; + +import com.android.internal.telephony.util.ArrayUtils; +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; + +/** + * ModemPowerProfile for handling the modem element in the power_profile.xml + */ +public class ModemPowerProfile { + private static final String TAG = "ModemPowerProfile"; + + private static final String TAG_SLEEP = "sleep"; + private static final String TAG_IDLE = "idle"; + private static final String TAG_ACTIVE = "active"; + private static final String TAG_RECEIVE = "receive"; + private static final String TAG_TRANSMIT = "transmit"; + private static final String ATTR_RAT = "rat"; + private static final String ATTR_NR_FREQUENCY = "nrFrequency"; + private static final String ATTR_LEVEL = "level"; + + /** + * A flattened list of the modem power constant extracted from the given XML parser. + * + * The bitfields of a key describes what its corresponding power constant represents: + * [31:28] - {@link ModemDrainType} (max count = 16). + * [27:24] - {@link ModemTxLevel} (only for {@link MODEM_DRAIN_TYPE_TX}) (max count = 16). + * [23:20] - {@link ModemRatType} (max count = 16). + * [19:16] - {@link ModemNrFrequencyRange} (only for {@link MODEM_RAT_TYPE_NR}) + * (max count = 16). + * [15:0] - RESERVED + */ + private final SparseDoubleArray mPowerConstants = new SparseDoubleArray(); + + private static final int MODEM_DRAIN_TYPE_SHIFT = 28; + private static final int MODEM_DRAIN_TYPE_MASK = 0xF << MODEM_DRAIN_TYPE_SHIFT; + + private static final int MODEM_TX_LEVEL_SHIFT = 24; + private static final int MODEM_TX_LEVEL_MASK = 0xF << MODEM_TX_LEVEL_SHIFT; + + private static final int MODEM_RAT_TYPE_SHIFT = 20; + private static final int MODEM_RAT_TYPE_MASK = 0xF << MODEM_RAT_TYPE_SHIFT; + + private static final int MODEM_NR_FREQUENCY_RANGE_SHIFT = 16; + private static final int MODEM_NR_FREQUENCY_RANGE_MASK = 0xF << MODEM_NR_FREQUENCY_RANGE_SHIFT; + + /** + * Corresponds to the overall modem battery drain while asleep. + */ + public static final int MODEM_DRAIN_TYPE_SLEEP = 0 << MODEM_DRAIN_TYPE_SHIFT; + + /** + * Corresponds to the overall modem battery drain while idle. + */ + public static final int MODEM_DRAIN_TYPE_IDLE = 1 << MODEM_DRAIN_TYPE_SHIFT; + + /** + * Corresponds to the modem battery drain while receiving data. A specific Rx battery drain + * power constant can be selected using a bitwise OR (|) with {@link ModemRatType} and + * {@link ModemNrFrequencyRange} (when applicable). + */ + public static final int MODEM_DRAIN_TYPE_RX = 2 << MODEM_DRAIN_TYPE_SHIFT; + + /** + * Corresponds to the modem battery drain while receiving data. + * {@link ModemTxLevel} must be specified with this drain type. + * Specific Tx battery drain power constanta can be selected using a bitwise OR (|) with + * {@link ModemRatType} and {@link ModemNrFrequencyRange} (when applicable). + */ + public static final int MODEM_DRAIN_TYPE_TX = 3 << MODEM_DRAIN_TYPE_SHIFT; + + @IntDef(prefix = {"MODEM_DRAIN_TYPE_"}, value = { + MODEM_DRAIN_TYPE_SLEEP, + MODEM_DRAIN_TYPE_IDLE, + MODEM_DRAIN_TYPE_RX, + MODEM_DRAIN_TYPE_TX, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ModemDrainType { + } + + private static final String[] MODEM_DRAIN_TYPE_NAMES = + new String[]{"SLEEP", "IDLE", "RX", "TX"}; + + /** + * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_0}. + */ + public static final int MODEM_TX_LEVEL_0 = 0 << MODEM_TX_LEVEL_SHIFT; + + /** + * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_1}. + */ + public static final int MODEM_TX_LEVEL_1 = 1 << MODEM_TX_LEVEL_SHIFT; + + /** + * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_2}. + */ + public static final int MODEM_TX_LEVEL_2 = 2 << MODEM_TX_LEVEL_SHIFT; + + /** + * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_3}. + */ + public static final int MODEM_TX_LEVEL_3 = 3 << MODEM_TX_LEVEL_SHIFT; + + /** + * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_4}. + */ + public static final int MODEM_TX_LEVEL_4 = 4 << MODEM_TX_LEVEL_SHIFT; + + private static final int MODEM_TX_LEVEL_COUNT = 5; + + @IntDef(prefix = {"MODEM_TX_LEVEL_"}, value = { + MODEM_TX_LEVEL_0, + MODEM_TX_LEVEL_1, + MODEM_TX_LEVEL_2, + MODEM_TX_LEVEL_3, + MODEM_TX_LEVEL_4, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ModemTxLevel { + } + + /** + * Fallback for any active modem usage that does not match specified Radio Access Technology + * (RAT) power constants. + */ + public static final int MODEM_RAT_TYPE_DEFAULT = 0 << MODEM_RAT_TYPE_SHIFT; + + /** + * Corresponds to active modem usage on 4G {@link TelephonyManager#NETWORK_TYPE_LTE} RAT. + */ + public static final int MODEM_RAT_TYPE_LTE = 1 << MODEM_RAT_TYPE_SHIFT; + + /** + * Corresponds to active modem usage on 5G {@link TelephonyManager#NETWORK_TYPE_NR} RAT. + */ + public static final int MODEM_RAT_TYPE_NR = 2 << MODEM_RAT_TYPE_SHIFT; + + @IntDef(prefix = {"MODEM_RAT_TYPE_"}, value = { + MODEM_RAT_TYPE_DEFAULT, + MODEM_RAT_TYPE_LTE, + MODEM_RAT_TYPE_NR, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ModemRatType { + } + + private static final String[] MODEM_RAT_TYPE_NAMES = new String[]{"DEFAULT", "LTE", "NR"}; + + /** + * Fallback for any active 5G modem usage that does not match specified NR frequency power + * constants. + */ + public static final int MODEM_NR_FREQUENCY_RANGE_DEFAULT = 0 << MODEM_NR_FREQUENCY_RANGE_SHIFT; + + /** + * Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_LOW}. + */ + public static final int MODEM_NR_FREQUENCY_RANGE_LOW = 1 << MODEM_NR_FREQUENCY_RANGE_SHIFT; + + /** + * Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_MID}. + */ + public static final int MODEM_NR_FREQUENCY_RANGE_MID = 2 << MODEM_NR_FREQUENCY_RANGE_SHIFT; + + /** + * Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_HIGH}. + */ + public static final int MODEM_NR_FREQUENCY_RANGE_HIGH = 3 << MODEM_NR_FREQUENCY_RANGE_SHIFT; + + /** + * Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_MMWAVE}. + */ + public static final int MODEM_NR_FREQUENCY_RANGE_MMWAVE = 4 << MODEM_NR_FREQUENCY_RANGE_SHIFT; + + @IntDef(prefix = {"MODEM_NR_FREQUENCY_RANGE_"}, value = { + MODEM_RAT_TYPE_DEFAULT, + MODEM_NR_FREQUENCY_RANGE_LOW, + MODEM_NR_FREQUENCY_RANGE_MID, + MODEM_NR_FREQUENCY_RANGE_HIGH, + MODEM_NR_FREQUENCY_RANGE_MMWAVE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ModemNrFrequencyRange { + } + + private static final String[] MODEM_NR_FREQUENCY_RANGE_NAMES = + new String[]{"DEFAULT", "LOW", "MID", "HIGH", "MMWAVE"}; + + public ModemPowerProfile() { + } + + /** + * Generates a ModemPowerProfile object from the <modem /> element of a power_profile.xml + */ + public void parseFromXml(XmlResourceParser parser) throws IOException, + XmlPullParserException { + final int depth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, depth)) { + final String name = parser.getName(); + switch (name) { + case TAG_SLEEP: + if (parser.next() != XmlPullParser.TEXT) { + continue; + } + final String sleepDrain = parser.getText(); + setPowerConstant(MODEM_DRAIN_TYPE_SLEEP, sleepDrain); + break; + case TAG_IDLE: + if (parser.next() != XmlPullParser.TEXT) { + continue; + } + final String idleDrain = parser.getText(); + setPowerConstant(MODEM_DRAIN_TYPE_IDLE, idleDrain); + break; + case TAG_ACTIVE: + parseActivePowerConstantsFromXml(parser); + break; + default: + Slog.e(TAG, "Unexpected element parsed: " + name); + } + } + } + + /** Parse the <active /> XML element */ + private void parseActivePowerConstantsFromXml(XmlResourceParser parser) + throws IOException, XmlPullParserException { + // Parse attributes to get the type of active modem usage the power constants are for. + final int ratType; + final int nrfType; + try { + ratType = getTypeFromAttribute(parser, ATTR_RAT, MODEM_RAT_TYPE_SHIFT, + MODEM_RAT_TYPE_NAMES); + if (ratType == MODEM_RAT_TYPE_NR) { + nrfType = getTypeFromAttribute(parser, ATTR_NR_FREQUENCY, + MODEM_NR_FREQUENCY_RANGE_SHIFT, MODEM_NR_FREQUENCY_RANGE_NAMES); + } else { + nrfType = 0; + } + } catch (IllegalArgumentException iae) { + Slog.e(TAG, "Failed parse to active modem power constants", iae); + return; + } + + // Parse and populate the active modem use power constants. + final int depth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, depth)) { + final String name = parser.getName(); + switch (name) { + case TAG_RECEIVE: + if (parser.next() != XmlPullParser.TEXT) { + continue; + } + final String rxDrain = parser.getText(); + final int rxKey = MODEM_DRAIN_TYPE_RX | ratType | nrfType; + setPowerConstant(rxKey, rxDrain); + break; + case TAG_TRANSMIT: + final int level = XmlUtils.readIntAttribute(parser, ATTR_LEVEL, -1); + if (parser.next() != XmlPullParser.TEXT) { + continue; + } + final String txDrain = parser.getText(); + if (level < 0 || level >= MODEM_TX_LEVEL_COUNT) { + Slog.e(TAG, + "Unexpected tx level: " + level + ". Must be between 0 and " + ( + MODEM_TX_LEVEL_COUNT - 1)); + continue; + } + final int modemTxLevel = level << MODEM_TX_LEVEL_SHIFT; + Slog.d("MWACHENS", + "parsing tx at level:" + level + ", aka 0x" + Integer.toHexString( + modemTxLevel)); + final int txKey = MODEM_DRAIN_TYPE_TX | modemTxLevel | ratType | nrfType; + setPowerConstant(txKey, txDrain); + break; + default: + Slog.e(TAG, "Unexpected element parsed: " + name); + } + } + } + + private static int getTypeFromAttribute(XmlResourceParser parser, String attr, int shift, + String[] names) { + final String value = XmlUtils.readStringAttribute(parser, attr); + final int index = ArrayUtils.indexOf(names, value); + if (value == null) { + // Attribute was not specified, just use the default. + return 0; + } + if (index < 0) { + throw new IllegalArgumentException( + "Unexpected " + attr + " value : " + value + ". Acceptable values are " + + Arrays.toString(names)); + } + return index << shift; + } + + /** + * Set the average battery drain in milli-amps of the modem for a given drain type. + * + * @param key a key built from the union of {@link ModemDrainType}, {@link ModemTxLevel}, + * {@link ModemRatType}, and {@link ModemNrFrequencyRange}.key + * @param value the battery dram in milli-amps for the given key. + */ + public void setPowerConstant(int key, String value) { + try { + mPowerConstants.put(key, Double.valueOf(value)); + } catch (Exception e) { + Slog.e(TAG, "Failed to set power constant 0x" + Integer.toHexString( + key) + "(" + keyToString(key) + ") to " + value, e); + } + } + + /** + * Returns the average battery drain in milli-amps of the modem for a given drain type. + * Returns {@link Double.NaN} if a suitable value is not found for the given key. + * + * @param key a key built from the union of {@link ModemDrainType}, {@link ModemTxLevel}, + * {@link ModemRatType}, and {@link ModemNrFrequencyRange}. + */ + public double getAverageBatteryDrainMa(int key) { + int bestKey = key; + double value; + value = mPowerConstants.get(bestKey, Double.NaN); + if (!Double.isNaN(value)) return value; + // The power constant for given key was not explicitly set. Try to fallback to possible + // defaults. + + if ((bestKey & MODEM_NR_FREQUENCY_RANGE_MASK) != MODEM_NR_FREQUENCY_RANGE_DEFAULT) { + // Fallback to NR Frequency default value + bestKey &= ~MODEM_NR_FREQUENCY_RANGE_MASK; + bestKey |= MODEM_NR_FREQUENCY_RANGE_DEFAULT; + value = mPowerConstants.get(bestKey, Double.NaN); + if (!Double.isNaN(value)) return value; + } + + if ((bestKey & MODEM_RAT_TYPE_MASK) != MODEM_RAT_TYPE_DEFAULT) { + // Fallback to RAT default value + bestKey &= ~MODEM_RAT_TYPE_MASK; + bestKey |= MODEM_RAT_TYPE_DEFAULT; + value = mPowerConstants.get(bestKey, Double.NaN); + if (!Double.isNaN(value)) return value; + } + + Slog.w(TAG, + "getAverageBatteryDrainMaH called with unexpected key: 0x" + Integer.toHexString( + key) + ", " + keyToString(key)); + return Double.NaN; + } + + private static String keyToString(int key) { + StringBuilder sb = new StringBuilder(); + final int drainType = key & MODEM_DRAIN_TYPE_MASK; + appendFieldToString(sb, "drain", MODEM_DRAIN_TYPE_NAMES, + drainType >> MODEM_DRAIN_TYPE_SHIFT); + sb.append(","); + + if (drainType == MODEM_DRAIN_TYPE_TX) { + final int txLevel = (key & MODEM_TX_LEVEL_MASK) >> MODEM_TX_LEVEL_SHIFT; + sb.append("level:"); + sb.append(txLevel); + sb.append(","); + } + + final int ratType = key & MODEM_RAT_TYPE_MASK; + appendFieldToString(sb, "RAT", MODEM_RAT_TYPE_NAMES, ratType >> MODEM_RAT_TYPE_SHIFT); + + if (ratType == MODEM_RAT_TYPE_NR) { + sb.append(","); + final int nrFreq = key & MODEM_NR_FREQUENCY_RANGE_MASK; + appendFieldToString(sb, "nrFreq", MODEM_NR_FREQUENCY_RANGE_NAMES, + nrFreq >> MODEM_NR_FREQUENCY_RANGE_SHIFT); + } + return sb.toString(); + } + + private static void appendFieldToString(StringBuilder sb, String fieldName, String[] names, + int index) { + sb.append(fieldName); + sb.append(":"); + if (index < 0 || index >= names.length) { + sb.append("UNKNOWN("); + sb.append(index); + sb.append(")"); + } else { + sb.append(names[index]); + } + } + + /** + * Clear this ModemPowerProfile power constants. + */ + public void clear() { + mPowerConstants.clear(); + } + + + /** + * Dump this ModemPowerProfile power constants. + */ + public void dump(PrintWriter pw) { + final int size = mPowerConstants.size(); + for (int i = 0; i < size; i++) { + pw.print(keyToString(mPowerConstants.keyAt(i))); + pw.print("="); + pw.println(mPowerConstants.valueAt(i)); + } + } +} diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java index 5ac493637822..def598ca8724 100644 --- a/core/java/com/android/internal/protolog/ProtoLogGroup.java +++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java @@ -85,6 +85,8 @@ public enum ProtoLogGroup implements IProtoLogGroup { WM_DEBUG_LAYER_MIRRORING(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM), WM_DEBUG_WALLPAPER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM), + WM_DEBUG_BACK_PREVIEW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, + "CoreBackPreview"), TEST_GROUP(true, true, false, "WindowManagerProtoLogTest"); private final boolean mEnabled; diff --git a/core/proto/OWNERS b/core/proto/OWNERS index 78650ed34813..a4463e4e96c5 100644 --- a/core/proto/OWNERS +++ b/core/proto/OWNERS @@ -18,7 +18,8 @@ per-file usagestatsservice.proto, usagestatsservice_v2.proto = file:/core/java/a per-file apphibernationservice.proto = file:/core/java/android/apphibernation/OWNERS # Biometrics -kchyn@google.com +jaggies@google.com +jbolinger@google.com # Launcher hyunyoungs@google.com diff --git a/core/proto/android/server/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto index fbe2170ea51c..2f2158d4d5a0 100644 --- a/core/proto/android/server/vibrator/vibratormanagerservice.proto +++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto @@ -97,7 +97,7 @@ message VibrationProto { optional int32 status = 6; } -// Next id: 24 +// Next id: 25 message VibratorManagerServiceDumpProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; repeated int32 vibrator_ids = 1; @@ -106,6 +106,7 @@ message VibratorManagerServiceDumpProto { optional VibrationProto current_external_vibration = 4; optional bool vibrator_under_external_control = 5; optional bool low_power_mode = 6; + optional bool vibrate_on = 24; optional int32 alarm_intensity = 18; optional int32 alarm_default_intensity = 19; optional int32 haptic_feedback_intensity = 7; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 0fd46295c8e8..9ed11375c7fe 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -6105,11 +6105,21 @@ <permission android:name="android.permission.CAPTURE_BLACKOUT_CONTENT" android:protectionLevel="signature" /> - <!-- @SystemApi Allows an application to query over global data in AppSearch. + <!-- @SystemApi Allows an application to query over global data in AppSearch. @hide --> <permission android:name="android.permission.READ_GLOBAL_APP_SEARCH_DATA" android:protectionLevel="internal|role" /> + <!-- Allows an application to query over global data in AppSearch that's visible to the + ASSISTANT role. --> + <permission android:name="android.permission.READ_ASSISTANT_APP_SEARCH_DATA" + android:protectionLevel="internal|role" /> + + <!-- Allows an application to query over global data in AppSearch that's visible to the + HOME role. --> + <permission android:name="android.permission.READ_HOME_APP_SEARCH_DATA" + android:protectionLevel="internal|role" /> + <!-- @SystemApi Allows an application to create virtual devices in VirtualDeviceManager. @hide --> <permission android:name="android.permission.CREATE_VIRTUAL_DEVICE" @@ -6123,7 +6133,7 @@ <permission android:name="android.permission.SEND_SAFETY_CENTER_UPDATE" android:protectionLevel="signature|privileged" /> - <!-- Allows an application to launch device manager setup screens. + <!-- @SystemApi Allows an application to launch device manager setup screens. <p>Not for use by third-party applications. @hide --> diff --git a/core/res/OWNERS b/core/res/OWNERS index b18a9896cb2a..4bea4d55e5ef 100644 --- a/core/res/OWNERS +++ b/core/res/OWNERS @@ -34,3 +34,7 @@ per-file res/values/dimens_car.xml = file:/platform/packages/services/Car:/OWNER # Wear per-file res/*-watch/* = file:/platform/frameworks/opt/wear:/OWNERS + +# PowerProfile +per-file res/xml/power_profile.xml = file:/BATTERY_STATS_OWNERS +per-file res/xml/power_profile_test.xml = file:/BATTERY_STATS_OWNERS diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 4a5df99b6377..d8b3785d7f4e 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4247,7 +4247,7 @@ <string translatable="false" name="config_defaultRingtoneVibrationSound"></string> <!-- Default number of notifications from the same app before they are automatically grouped by the OS --> - <integer translatable="false" name="config_autoGroupAtCount">4</integer> + <integer translatable="false" name="config_autoGroupAtCount">2</integer> <!-- The OEM specified sensor type for the lift trigger to launch the camera app. --> <integer name="config_cameraLiftTriggerSensorType">-1</integer> @@ -5612,4 +5612,7 @@ <!-- Flag indicating if help links for Settings app should be enabled. --> <bool name="config_settingsHelpLinksEnabled">false</bool> + + <!-- Whether or not to enable the lock screen entry point for the QR code scanner. --> + <bool name="config_enableQrCodeScannerOnLockScreen">false</bool> </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index d2ac8a3c5fbc..cfe65eb00cea 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3324,6 +3324,8 @@ <staging-public-group type="bool" first-id="0x01cf0000"> <!-- @hide @TestApi --> <public name="config_preventImeStartupUnlessTextEditor" /> + <!-- @hide @SystemApi --> + <public name="config_enableQrCodeScannerOnLockScreen" /> </staging-public-group> <staging-public-group type="fraction" first-id="0x01ce0000"> diff --git a/core/res/res/xml/power_profile.xml b/core/res/res/xml/power_profile.xml index d310736ae121..fc63657f04d0 100644 --- a/core/res/res/xml/power_profile.xml +++ b/core/res/res/xml/power_profile.xml @@ -144,17 +144,49 @@ <value>2</value> <!-- 4097-/hr --> </array> - <!-- Cellular modem related values. Default is 0.--> - <item name="modem.controller.sleep">0</item> - <item name="modem.controller.idle">0</item> - <item name="modem.controller.rx">0</item> - <array name="modem.controller.tx"> <!-- Strength 0 to 4 --> - <value>0</value> - <value>0</value> - <value>0</value> - <value>0</value> - <value>0</value> - </array> + <!-- Cellular modem related values.--> + <modem> + <!-- Modem sleep drain current value in mA. --> + <sleep>0</sleep> + <!-- Modem idle drain current value in mA. --> + <idle>0</idle> + <!-- Modem active drain current values. + Multiple <active /> can be defined to specify current drain for different modes of + operation. + Available attributes: + rat - Specify the current drain for a Radio Access Technology. + Available options are "LTE", "NR" and "DEFAULT". + <active rat="default" /> will be used for any usage that does not match any other + defined <active /> rat. + + nrFrequency - Specify the current drain for a frequency level while NR is active. + Available options are "LOW", "MID", "HIGH", "MMWAVE", and "DEFAULT", + where, + "LOW" indicated <1GHz frequencies, + "MID" indicates 1GHz to 3GHz frequencies, + "HIGH" indicates 3GHz to 6GHz frequencies, + "MMWAVE"indicates >6GHz frequencies. + <active rat="NR" nrFrequency="default"/> will be used for any usage that + does not match any other defined <active rat="NR" /> nrFrequency. + --> + <active rat="DEFAULT"> + <!-- Transmit current drain in mA. --> + <receive>0</receive> + + <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) --> + <transmit level="0">0</transmit> + <transmit level="1">0</transmit> + <transmit level="2">0</transmit> + <transmit level="3">0</transmit> + <transmit level="4">0</transmit> + </active> + <!-- Additional <active /> may be defined. + Example: + <active rat="LTE"> ... </active> + <active rat="NR" nrFrequency="MMWAVE"> ... </active> + <active rat="NR" nrFrequency="DEFAULT"> ... </active> + --> + </modem> <item name="modem.controller.voltage">0</item> <!-- GPS related values. Default is 0.--> @@ -163,5 +195,4 @@ <value>0</value> </array> <item name="gps.voltage">0</item> - </device> diff --git a/core/tests/coretests/res/xml/power_profile_test_modem.xml b/core/tests/coretests/res/xml/power_profile_test_modem.xml new file mode 100644 index 000000000000..ff36a9c94e0b --- /dev/null +++ b/core/tests/coretests/res/xml/power_profile_test_modem.xml @@ -0,0 +1,151 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2021, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License") +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<device name="test"> + <test-modem name="testModemPowerProfile_defaultRat"> + <!-- Modem sleep drain current value in mA. --> + <sleep>10</sleep> + <!-- Modem idle drain current value in mA. --> + <idle>20</idle> + <active rat="DEFAULT"> + <!-- Transmit current drain in mA. --> + <receive>30</receive> + <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) --> + <transmit level="0">40</transmit> + <transmit level="1">50</transmit> + <transmit level="2">60</transmit> + <transmit level="3">70</transmit> + <transmit level="4">80</transmit> + </active> + </test-modem> + + <test-modem name="testModemPowerProfile_partiallyDefined"> + <!-- Modem sleep drain current value in mA. --> + <sleep>1</sleep> + <!-- Modem idle drain current value in mA. --> + <idle>2</idle> + <active rat="DEFAULT"> + <!-- Transmit current drain in mA. --> + <receive>3</receive> + <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) --> + <transmit level="0">4</transmit> + <transmit level="1">5</transmit> + <transmit level="2">6</transmit> + <transmit level="3">7</transmit> + <transmit level="4">8</transmit> + </active> + <active rat="NR" nrFrequency="DEFAULT"> + <!-- Transmit current drain in mA. --> + <receive>13</receive> + <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) --> + <transmit level="0">14</transmit> + <transmit level="1">15</transmit> + <transmit level="2">16</transmit> + <transmit level="3">17</transmit> + <transmit level="4">18</transmit> + </active> + <active rat="NR" nrFrequency="MMWAVE"> + <!-- Transmit current drain in mA. --> + <receive>53</receive> + <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) --> + <transmit level="0">54</transmit> + <transmit level="1">55</transmit> + <transmit level="2">56</transmit> + <transmit level="3">57</transmit> + <transmit level="4">58</transmit> + </active> + </test-modem> + + <test-modem name="testModemPowerProfile_fullyDefined"> + <!-- Modem sleep drain current value in mA. --> + <sleep>1</sleep> + <!-- Modem idle drain current value in mA. --> + <idle>2</idle> + <active rat="DEFAULT"> + <!-- Transmit current drain in mA. --> + <receive>3</receive> + <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) --> + <transmit level="0">4</transmit> + <transmit level="1">5</transmit> + <transmit level="2">6</transmit> + <transmit level="3">7</transmit> + <transmit level="4">8</transmit> + </active> + <active rat="LTE"> + <!-- Transmit current drain in mA. --> + <receive>10</receive> + <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) --> + <transmit level="0">20</transmit> + <transmit level="1">30</transmit> + <transmit level="2">40</transmit> + <transmit level="3">50</transmit> + <transmit level="4">60</transmit> + </active> + <active rat="NR" nrFrequency="DEFAULT"> + <!-- Transmit current drain in mA. --> + <receive>13</receive> + <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) --> + <transmit level="0">14</transmit> + <transmit level="1">15</transmit> + <transmit level="2">16</transmit> + <transmit level="3">17</transmit> + <transmit level="4">18</transmit> + </active> + <active rat="NR" nrFrequency="LOW"> + <!-- Transmit current drain in mA. --> + <receive>23</receive> + <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) --> + <transmit level="0">24</transmit> + <transmit level="1">25</transmit> + <transmit level="2">26</transmit> + <transmit level="3">27</transmit> + <transmit level="4">28</transmit> + </active> + <active rat="NR" nrFrequency="MID"> + <!-- Transmit current drain in mA. --> + <receive>33</receive> + <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) --> + <transmit level="0">34</transmit> + <transmit level="1">35</transmit> + <transmit level="2">36</transmit> + <transmit level="3">37</transmit> + <transmit level="4">38</transmit> + </active> + <active rat="NR" nrFrequency="HIGH"> + <!-- Transmit current drain in mA. --> + <receive>43</receive> + <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) --> + <transmit level="0">44</transmit> + <transmit level="1">45</transmit> + <transmit level="2">46</transmit> + <transmit level="3">47</transmit> + <transmit level="4">48</transmit> + </active> + <active rat="NR" nrFrequency="MMWAVE"> + <!-- Transmit current drain in mA. --> + <receive>53</receive> + <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) --> + <transmit level="0">54</transmit> + <transmit level="1">55</transmit> + <transmit level="2">56</transmit> + <transmit level="3">57</transmit> + <transmit level="4">58</transmit> + </active> + </test-modem> +</device> diff --git a/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java b/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java index 2dd3f69852c1..ba9c8d92e173 100644 --- a/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java +++ b/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java @@ -64,12 +64,12 @@ public class SparseDoubleArrayTest { } @Test - public void testAdd() { + public void testIncrementValue() { final SparseDoubleArray sda = new SparseDoubleArray(); sda.put(4, 6.1); - sda.add(4, -1.2); - sda.add(2, -1.2); + sda.incrementValue(4, -1.2); + sda.incrementValue(2, -1.2); assertEquals(6.1 - 1.2, sda.get(4), PRECISION); assertEquals(-1.2, sda.get(2), PRECISION); diff --git a/core/tests/coretests/src/android/util/SparseLongArrayTest.java b/core/tests/coretests/src/android/util/SparseLongArrayTest.java index df2d752e04b9..b29b6f1f8e9d 100644 --- a/core/tests/coretests/src/android/util/SparseLongArrayTest.java +++ b/core/tests/coretests/src/android/util/SparseLongArrayTest.java @@ -154,4 +154,16 @@ public class SparseLongArrayTest { assertRemoved(startIndex, endIndex); assertTrue(isSame(sparseLongArray2, mSparseLongArray)); } + + @Test + public void testIncrementValue() { + final SparseLongArray sla = new SparseLongArray(); + + sla.put(4, 6); + sla.incrementValue(4, 4); + sla.incrementValue(2, 5); + + assertEquals(6 + 4, sla.get(4)); + assertEquals(5, sla.get(2)); + } } diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java index 9699275d3037..8cc4c348111c 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java @@ -83,7 +83,7 @@ public class BatteryUsageStatsTest { final Parcel parcel = Parcel.obtain(); parcel.writeParcelable(outBatteryUsageStats, 0); - assertThat(parcel.dataSize()).isLessThan(6000); + assertThat(parcel.dataSize()).isLessThan(7000); parcel.setDataPosition(0); diff --git a/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java index d361da95a1b9..d0a13fc79ce2 100644 --- a/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java @@ -25,18 +25,20 @@ import android.os.BatteryConsumer; import android.os.BatteryStats; import android.os.BatteryUsageStatsQuery; import android.os.Process; +import android.os.UidBatteryConsumer; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.google.common.collect.ImmutableList; + import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.ArrayList; - @RunWith(AndroidJUnit4.class) @SmallTest +@SuppressWarnings("GuardedBy") public class BluetoothPowerCalculatorTest { private static final double PRECISION = 0.00001; private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42; @@ -62,6 +64,69 @@ public class BluetoothPowerCalculatorTest { } @Test + public void testTimerBasedModel_byProcessState() { + mStatsRule.setTime(1000, 1000); + + BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); + + BatteryStatsImpl.Uid uid = batteryStats.getUidStatsLocked(APP_UID); + uid.setProcessStateForTest( + BatteryStats.Uid.PROCESS_STATE_FOREGROUND, 1000); + + BluetoothActivityEnergyInfo info1 = new BluetoothActivityEnergyInfo(2000, + BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 1000, 2000, 3000, 4000); + info1.setUidTraffic(ImmutableList.of( + new UidTraffic(Process.BLUETOOTH_UID, 1000, 2000), + new UidTraffic(APP_UID, 3000, 4000))); + + batteryStats.updateBluetoothStateLocked(info1, + 0/*1_000_000*/, 2000, 2000); + + uid.setProcessStateForTest( + BatteryStats.Uid.PROCESS_STATE_BACKGROUND, 3000); + + BluetoothActivityEnergyInfo info2 = new BluetoothActivityEnergyInfo(4000, + BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 5000, 6000, 7000, 8000); + info2.setUidTraffic(ImmutableList.of( + new UidTraffic(Process.BLUETOOTH_UID, 5000, 6000), + new UidTraffic(APP_UID, 7000, 8000))); + + batteryStats.updateBluetoothStateLocked(info2, + 0 /*5_000_000 */, 4000, 4000); + + BluetoothPowerCalculator calculator = + new BluetoothPowerCalculator(mStatsRule.getPowerProfile()); + + mStatsRule.apply(new BatteryUsageStatsQuery.Builder() + .powerProfileModeledOnly() + .includePowerModels() + .includeProcessStateData() + .build(), calculator); + + UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID); + assertThat(uidConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH)) + .isEqualTo(6166); + assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH)) + .isWithin(PRECISION).of(0.1226666); + assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_BLUETOOTH)) + .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE); + + final BatteryConsumer.Key foreground = uidConsumer.getKey( + BatteryConsumer.POWER_COMPONENT_BLUETOOTH, + BatteryConsumer.PROCESS_STATE_FOREGROUND); + final BatteryConsumer.Key background = uidConsumer.getKey( + BatteryConsumer.POWER_COMPONENT_BLUETOOTH, + BatteryConsumer.PROCESS_STATE_BACKGROUND); + final BatteryConsumer.Key fgs = uidConsumer.getKey( + BatteryConsumer.POWER_COMPONENT_BLUETOOTH, + BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE); + + assertThat(uidConsumer.getConsumedPower(foreground)).isWithin(PRECISION).of(0.081); + assertThat(uidConsumer.getConsumedPower(background)).isWithin(PRECISION).of(0.0416666); + assertThat(uidConsumer.getConsumedPower(fgs)).isWithin(PRECISION).of(0); + } + + @Test public void testReportedEnergyBasedModel() { setupBluetoothEnergyInfo(4000000, BatteryStats.POWER_DATA_UNAVAILABLE); @@ -90,6 +155,70 @@ public class BluetoothPowerCalculatorTest { } @Test + public void testMeasuredEnergyBasedModel_byProcessState() { + mStatsRule.initMeasuredEnergyStatsLocked(); + mStatsRule.setTime(1000, 1000); + + BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); + + BatteryStatsImpl.Uid uid = batteryStats.getUidStatsLocked(APP_UID); + uid.setProcessStateForTest( + BatteryStats.Uid.PROCESS_STATE_FOREGROUND, 1000); + + BluetoothActivityEnergyInfo info1 = new BluetoothActivityEnergyInfo(2000, + BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 1000, 2000, 3000, 4000); + info1.setUidTraffic(ImmutableList.of( + new UidTraffic(Process.BLUETOOTH_UID, 1000, 2000), + new UidTraffic(APP_UID, 3000, 4000))); + + batteryStats.updateBluetoothStateLocked(info1, + 1_000_000, 2000, 2000); + + uid.setProcessStateForTest( + BatteryStats.Uid.PROCESS_STATE_BACKGROUND, 3000); + + BluetoothActivityEnergyInfo info2 = new BluetoothActivityEnergyInfo(4000, + BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 5000, 6000, 7000, 8000); + info2.setUidTraffic(ImmutableList.of( + new UidTraffic(Process.BLUETOOTH_UID, 5000, 6000), + new UidTraffic(APP_UID, 7000, 8000))); + + batteryStats.updateBluetoothStateLocked(info2, + 5_000_000, 4000, 4000); + + BluetoothPowerCalculator calculator = + new BluetoothPowerCalculator(mStatsRule.getPowerProfile()); + + mStatsRule.apply(new BatteryUsageStatsQuery.Builder() + .includePowerModels() + .includeProcessStateData() + .build(), calculator); + + UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID); + assertThat(uidConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH)) + .isEqualTo(6166); + assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH)) + .isWithin(PRECISION).of(0.8220561); + assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_BLUETOOTH)) + .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY); + + final BatteryConsumer.Key foreground = uidConsumer.getKey( + BatteryConsumer.POWER_COMPONENT_BLUETOOTH, + BatteryConsumer.PROCESS_STATE_FOREGROUND); + final BatteryConsumer.Key background = uidConsumer.getKey( + BatteryConsumer.POWER_COMPONENT_BLUETOOTH, + BatteryConsumer.PROCESS_STATE_BACKGROUND); + final BatteryConsumer.Key fgs = uidConsumer.getKey( + BatteryConsumer.POWER_COMPONENT_BLUETOOTH, + BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE); + + assertThat(uidConsumer.getConsumedPower(foreground)).isWithin(PRECISION).of(0.4965352); + assertThat(uidConsumer.getConsumedPower(background)).isWithin(PRECISION).of(0.3255208); + assertThat(uidConsumer.getConsumedPower(fgs)).isWithin(PRECISION).of(0); + } + + + @Test public void testIgnoreMeasuredEnergyBasedModel() { mStatsRule.initMeasuredEnergyStatsLocked(); setupBluetoothEnergyInfo(4000000, 1200000); @@ -107,10 +236,9 @@ public class BluetoothPowerCalculatorTest { final BluetoothActivityEnergyInfo info = new BluetoothActivityEnergyInfo(1000, BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 7000, 5000, 0, reportedEnergyUc); - info.setUidTraffic(new ArrayList<UidTraffic>(){{ - add(new UidTraffic(Process.BLUETOOTH_UID, 1000, 2000)); - add(new UidTraffic(APP_UID, 3000, 4000)); - }}); + info.setUidTraffic(ImmutableList.of( + new UidTraffic(Process.BLUETOOTH_UID, 1000, 2000), + new UidTraffic(APP_UID, 3000, 4000))); mStatsRule.getBatteryStats().updateBluetoothStateLocked(info, consumedEnergyUc, 1000, 1000); } diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java index bddb3a1906fd..1bb41a8cfffd 100644 --- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java +++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java @@ -43,6 +43,7 @@ import java.util.concurrent.Future; */ public class MockBatteryStatsImpl extends BatteryStatsImpl { public boolean mForceOnBattery; + // The mNetworkStats will be used for both wifi and mobile categories private NetworkStats mNetworkStats; private DummyExternalStatsSync mExternalStatsSync = new DummyExternalStatsSync(); @@ -118,11 +119,16 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { } @Override - protected NetworkStats readNetworkStatsLocked(@NonNull NetworkStatsManager networkStatsManager, - String[] ifaces) { + protected NetworkStats readMobileNetworkStatsLocked( + @NonNull NetworkStatsManager networkStatsManager) { return mNetworkStats; } + @Override + protected NetworkStats readWifiNetworkStatsLocked( + @NonNull NetworkStatsManager networkStatsManager) { + return mNetworkStats; + } public MockBatteryStatsImpl setPowerProfile(PowerProfile powerProfile) { mPowerProfile = powerProfile; return this; diff --git a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java index 88ee405483db..1efd78bc13fc 100644 --- a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java +++ b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java @@ -21,25 +21,43 @@ import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_AMBIENT; import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_FULL; import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_ON; +import android.annotation.XmlRes; +import android.content.Context; +import android.content.res.Resources; +import android.content.res.XmlResourceParser; + import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; +import com.android.frameworks.coretests.R; +import com.android.internal.power.ModemPowerProfile; +import com.android.internal.util.XmlUtils; + import junit.framework.TestCase; import org.junit.Before; import org.junit.Test; /* - * Keep this file in sync with frameworks/base/core/res/res/xml/power_profile_test.xml + * Keep this file in sync with frameworks/base/core/res/res/xml/power_profile_test.xml and + * frameworks/base/core/tests/coretests/res/xml/power_profile_test_modem.xml + * + * Run with: + * atest com.android.internal.os.PowerProfileTest */ @SmallTest public class PowerProfileTest extends TestCase { + static final String TAG_TEST_MODEM = "test-modem"; + static final String ATTR_NAME = "name"; + private PowerProfile mProfile; + private Context mContext; @Before public void setUp() { - mProfile = new PowerProfile(InstrumentationRegistry.getContext(), true); + mContext = InstrumentationRegistry.getContext(); + mProfile = new PowerProfile(mContext, true); } @Test @@ -67,4 +85,396 @@ public class PowerProfileTest extends TestCase { assertEquals(150.0, mProfile.getAveragePower(PowerProfile.POWER_VIDEO)); } + @Test + public void testModemPowerProfile_defaultRat() throws Exception { + final XmlResourceParser parser = getTestModemElement(R.xml.power_profile_test_modem, + "testModemPowerProfile_defaultRat"); + ModemPowerProfile mpp = new ModemPowerProfile(); + mpp.parseFromXml(parser); + assertEquals(10.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_DRAIN_TYPE_SLEEP)); + assertEquals(20.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_DRAIN_TYPE_IDLE)); + + // Only default RAT was defined, all other RAT's should fallback to the default value. + assertEquals(30.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(30.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(30.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + + assertEquals(40.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(40.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(40.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_0)); + + assertEquals(50.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(50.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(50.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_1)); + + assertEquals(60.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(60.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(60.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_2)); + + assertEquals(70.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(70.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(70.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_3)); + + assertEquals(80.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_4)); + assertEquals(80.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_4)); + assertEquals(80.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_4)); + } + + @Test + public void testModemPowerProfile_partiallyDefined() throws Exception { + final XmlResourceParser parser = getTestModemElement(R.xml.power_profile_test_modem, + "testModemPowerProfile_partiallyDefined"); + ModemPowerProfile mpp = new ModemPowerProfile(); + mpp.parseFromXml(parser); + assertEquals(1.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_DRAIN_TYPE_SLEEP)); + assertEquals(2.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_DRAIN_TYPE_IDLE)); + + assertEquals(3.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(4.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(5.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(6.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(7.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(8.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_4)); + + // LTE RAT power constants were not defined, fallback to defaults + assertEquals(3.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(4.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(5.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(6.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(7.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(8.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_4)); + + assertEquals(13.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(14.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(15.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(16.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(17.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(18.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_4)); + + // Non-mmwave NR frequency power constants were not defined, fallback to defaults + assertEquals(13.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW + | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(14.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(15.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(16.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(17.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(18.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_4)); + + assertEquals(13.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID + | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(14.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(15.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(16.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(17.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(18.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_4)); + + assertEquals(13.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH + | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(14.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(15.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(16.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(17.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(18.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_4)); + + assertEquals(53.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE + | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(54.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(55.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(56.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(57.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(58.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_4)); + } + + @Test + public void testModemPowerProfile_fullyDefined() throws Exception { + final XmlResourceParser parser = getTestModemElement(R.xml.power_profile_test_modem, + "testModemPowerProfile_fullyDefined"); + ModemPowerProfile mpp = new ModemPowerProfile(); + mpp.parseFromXml(parser); + assertEquals(1.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_DRAIN_TYPE_SLEEP)); + assertEquals(2.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_DRAIN_TYPE_IDLE)); + + // Only default RAT was defined, all other RAT's should fallback to the default value. + assertEquals(3.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(4.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(5.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(6.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(7.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(8.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_4)); + + assertEquals(10.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(20.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(30.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(40.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(50.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(60.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_4)); + + assertEquals(13.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(14.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(15.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(16.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(17.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(18.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_4)); + + assertEquals(23.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW + | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(24.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(25.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(26.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(27.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(28.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_4)); + + assertEquals(33.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID + | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(34.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(35.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(36.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(37.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(38.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_4)); + + assertEquals(43.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH + | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(44.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(45.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(46.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(47.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(48.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_4)); + + assertEquals(53.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE + | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(54.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(55.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(56.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(57.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(58.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_4)); + } + + private XmlResourceParser getTestModemElement(@XmlRes int xmlId, String elementName) + throws Exception { + final String element = TAG_TEST_MODEM; + final Resources resources = mContext.getResources(); + XmlResourceParser parser = resources.getXml(xmlId); + while (true) { + XmlUtils.nextElement(parser); + final String e = parser.getName(); + if (e == null) break; + if (!e.equals(element)) continue; + + final String name = parser.getAttributeValue(null, ATTR_NAME); + if (!name.equals(elementName)) continue; + + return parser; + } + fail("Unanable to find element " + element + " with name " + elementName); + return null; + } } diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml index f2a33de008d6..d0bb4dc4d185 100644 --- a/data/etc/com.android.systemui.xml +++ b/data/etc/com.android.systemui.xml @@ -50,6 +50,7 @@ <permission name="android.permission.REAL_GET_TASKS"/> <permission name="android.permission.REQUEST_NETWORK_SCORES"/> <permission name="android.permission.RECEIVE_MEDIA_RESOURCE_USAGE"/> + <permission name="android.permission.SET_WALLPAPER_DIM_AMOUNT"/> <permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND" /> <permission name="android.permission.START_ACTIVITY_AS_CALLER"/> <permission name="android.permission.START_TASKS_FROM_RECENTS"/> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 6f5951bdaca6..1068c2712fc8 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -394,6 +394,9 @@ applications that come with the platform <permission name="android.permission.SET_WALLPAPER_COMPONENT" /> <permission name="android.permission.SET_WALLPAPER_DIM_AMOUNT" /> <permission name="android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE" /> + <!-- Permission required for CTS test - TrustTestCases --> + <permission name="android.permission.PROVIDE_TRUST_AGENT" /> + <permission name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" /> <!-- Permissions required for Incremental CTS tests --> <permission name="com.android.permission.USE_INSTALLER_V2"/> <permission name="android.permission.LOADER_USAGE_STATS"/> diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 535d656462f4..9b67cfc3b83b 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -103,18 +103,6 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/TaskFragment.java" }, - "-2002500255": { - "message": "Defer removing snapshot surface in %dms", - "level": "VERBOSE", - "group": "WM_DEBUG_STARTING_WINDOW", - "at": "com\/android\/server\/wm\/TaskSnapshotSurface.java" - }, - "-1991255017": { - "message": "Drawing snapshot surface sizeMismatch=%b", - "level": "VERBOSE", - "group": "WM_DEBUG_STARTING_WINDOW", - "at": "com\/android\/server\/wm\/TaskSnapshotSurface.java" - }, "-1980468143": { "message": "DisplayArea appeared name=%s", "level": "VERBOSE", @@ -745,6 +733,12 @@ "group": "WM_DEBUG_BOOT", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "-1343787701": { + "message": "startBackNavigation task=%s, topRunningActivity=%s", + "level": "DEBUG", + "group": "WM_DEBUG_BACK_PREVIEW", + "at": "com\/android\/server\/wm\/BackNavigationController.java" + }, "-1340540100": { "message": "Creating SnapshotStartingData", "level": "VERBOSE", @@ -1597,12 +1591,6 @@ "group": "WM_DEBUG_FOCUS_LIGHT", "at": "com\/android\/server\/wm\/DisplayContent.java" }, - "-405536909": { - "message": "Removing snapshot surface", - "level": "VERBOSE", - "group": "WM_DEBUG_STARTING_WINDOW", - "at": "com\/android\/server\/wm\/TaskSnapshotSurface.java" - }, "-401282500": { "message": "destroyIfPossible: r=%s destroy returned removed=%s", "level": "DEBUG", @@ -1867,6 +1855,12 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/Task.java" }, + "-134091882": { + "message": "Screenshotting Activity %s", + "level": "VERBOSE", + "group": "WM_DEBUG_BACK_PREVIEW", + "at": "com\/android\/server\/wm\/TaskFragment.java" + }, "-124316973": { "message": "Translucent=%s Floating=%s ShowWallpaper=%s Disable=%s", "level": "VERBOSE", @@ -1951,6 +1945,12 @@ "group": "WM_DEBUG_APP_TRANSITIONS_ANIM", "at": "com\/android\/server\/wm\/WindowContainer.java" }, + "-23020844": { + "message": "Back: Reset surfaces", + "level": "DEBUG", + "group": "WM_DEBUG_BACK_PREVIEW", + "at": "com\/android\/server\/wm\/BackNavigationController.java" + }, "-21399771": { "message": "activity %s already destroying, skipping request with reason:%s", "level": "VERBOSE", @@ -2005,12 +2005,6 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, - "44438983": { - "message": "performLayout: Activity exiting now removed %s", - "level": "VERBOSE", - "group": "WM_DEBUG_ADD_REMOVE", - "at": "com\/android\/server\/wm\/DisplayContent.java" - }, "45285419": { "message": "startingWindow was set but startingSurface==null, couldn't remove", "level": "VERBOSE", @@ -3271,12 +3265,6 @@ "group": "WM_DEBUG_LAYER_MIRRORING", "at": "com\/android\/server\/wm\/DisplayContent.java" }, - "1417601133": { - "message": "Enqueueing ADD_STARTING", - "level": "VERBOSE", - "group": "WM_DEBUG_STARTING_WINDOW", - "at": "com\/android\/server\/wm\/ActivityRecord.java" - }, "1422781269": { "message": "Resuming rotation after re-position", "level": "DEBUG", @@ -3397,6 +3385,12 @@ "group": "WM_DEBUG_APP_TRANSITIONS", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "1554795024": { + "message": "Previous Activity is %s", + "level": "DEBUG", + "group": "WM_DEBUG_BACK_PREVIEW", + "at": "com\/android\/server\/wm\/BackNavigationController.java" + }, "1557732761": { "message": "For Intent %s bringing to top: %s", "level": "DEBUG", @@ -3924,6 +3918,9 @@ "WM_DEBUG_APP_TRANSITIONS_ANIM": { "tag": "WindowManager" }, + "WM_DEBUG_BACK_PREVIEW": { + "tag": "CoreBackPreview" + }, "WM_DEBUG_BOOT": { "tag": "WindowManager" }, diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml index 0cdaa206c156..1b8032b7077b 100644 --- a/libs/WindowManager/Shell/res/values/config.xml +++ b/libs/WindowManager/Shell/res/values/config.xml @@ -43,6 +43,9 @@ <!-- PiP minimum size, which is a % based off the shorter side of display width and height --> <fraction name="config_pipShortestEdgePercent">40%</fraction> + <!-- Show PiP enter split icon, which allows apps to directly enter splitscreen from PiP. --> + <bool name="config_pipEnableEnterSplitButton">false</bool> + <!-- Animation duration when using long press on recents to dock --> <integer name="long_press_dock_anim_duration">250</integer> diff --git a/services/core/java/com/android/server/wm/BackGestureController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java index f8f625486e56..b310dd638e6c 100644 --- a/services/core/java/com/android/server/wm/BackGestureController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java @@ -14,24 +14,22 @@ * limitations under the License. */ -package com.android.server.wm; +package com.android.wm.shell.back; -import android.os.SystemProperties; +import android.view.MotionEvent; /** - * Controller to handle actions related to the back gesture on the server side. + * Interface for SysUI to get access to the Back animation related methods. */ -public class BackGestureController { +public interface BackAnimation { - private static final String BACK_PREDICTABILITY_PROP = "persist.debug.back_predictability"; - - public static boolean isEnabled() { - return SystemProperties.getInt(BACK_PREDICTABILITY_PROP, 0) > 0; - } + /** + * Called when a {@link MotionEvent} is generated by a back gesture. + */ + void onBackMotion(MotionEvent event); /** - * Start a remote animation the back gesture. + * Sets whether the back gesture is past the trigger threshold or not. */ - public void startBackPreview() { - } + void setTriggerBack(boolean triggerBack); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java new file mode 100644 index 000000000000..229e8ee04982 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.back; + +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityTaskManager; +import android.app.IActivityTaskManager; +import android.app.WindowConfiguration; +import android.graphics.Point; +import android.graphics.PointF; +import android.hardware.HardwareBuffer; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.util.Log; +import android.view.MotionEvent; +import android.view.SurfaceControl; +import android.window.BackNavigationInfo; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.annotations.ShellMainThread; + +/** + * Controls the window animation run when a user initiates a back gesture. + */ +public class BackAnimationController { + + private static final String BACK_PREDICTABILITY_PROP = "persist.debug.back_predictability"; + public static final boolean IS_ENABLED = SystemProperties + .getInt(BACK_PREDICTABILITY_PROP, 0) > 0; + private static final String TAG = "BackAnimationController"; + + /** + * Location of the initial touch event of the back gesture. + */ + private final PointF mInitTouchLocation = new PointF(); + + /** + * Raw delta between {@link #mInitTouchLocation} and the last touch location. + */ + private final Point mTouchEventDelta = new Point(); + private final ShellExecutor mShellExecutor; + + /** True when a back gesture is ongoing */ + private boolean mBackGestureStarted = false; + + /** @see #setTriggerBack(boolean) */ + private boolean mTriggerBack; + + @Nullable + private BackNavigationInfo mBackNavigationInfo; + private final SurfaceControl.Transaction mTransaction; + private final IActivityTaskManager mActivityTaskManager; + + public BackAnimationController(@ShellMainThread ShellExecutor shellExecutor) { + this(shellExecutor, new SurfaceControl.Transaction(), ActivityTaskManager.getService()); + } + + @VisibleForTesting + BackAnimationController(@NonNull ShellExecutor shellExecutor, + @NonNull SurfaceControl.Transaction transaction, + @NonNull IActivityTaskManager activityTaskManager) { + mShellExecutor = shellExecutor; + mTransaction = transaction; + mActivityTaskManager = activityTaskManager; + } + + public BackAnimation getBackAnimationImpl() { + return mBackAnimation; + } + + private final BackAnimation mBackAnimation = new BackAnimationImpl(); + + private class BackAnimationImpl implements BackAnimation { + + @Override + public void onBackMotion(MotionEvent event) { + mShellExecutor.execute(() -> onMotionEvent(event)); + } + + @Override + public void setTriggerBack(boolean triggerBack) { + mShellExecutor.execute(() -> BackAnimationController.this.setTriggerBack(triggerBack)); + } + } + + /** + * Called when a new motion event needs to be transferred to this + * {@link BackAnimationController} + */ + public void onMotionEvent(MotionEvent event) { + int action = event.getActionMasked(); + if (action == MotionEvent.ACTION_DOWN) { + initAnimation(event); + } else if (action == MotionEvent.ACTION_MOVE) { + onMove(event); + } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { + onGestureFinished(); + } + } + + private void initAnimation(MotionEvent event) { + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "initAnimation mMotionStarted=%b", mBackGestureStarted); + if (mBackGestureStarted) { + Log.e(TAG, "Animation is being initialized but is already started."); + return; + } + + if (mBackNavigationInfo != null) { + finishAnimation(); + } + mInitTouchLocation.set(event.getX(), event.getY()); + mBackGestureStarted = true; + + try { + mBackNavigationInfo = mActivityTaskManager.startBackNavigation(); + onBackNavigationInfoReceived(mBackNavigationInfo); + } catch (RemoteException remoteException) { + Log.e(TAG, "Failed to initAnimation", remoteException); + finishAnimation(); + } + } + + private void onBackNavigationInfoReceived(@Nullable BackNavigationInfo backNavigationInfo) { + if (backNavigationInfo == null + || backNavigationInfo.getDepartingWindowContainer() == null) { + Log.e(TAG, "Received BackNavigationInfo is null."); + finishAnimation(); + return; + } + + HardwareBuffer hardwareBuffer = backNavigationInfo.getScreenshotHardwareBuffer(); + if (hardwareBuffer != null) { + displayTargetScreenshot(hardwareBuffer, + backNavigationInfo.getTaskWindowConfiguration()); + } + mTransaction.apply(); + } + + /** + * Display the screenshot of the activity beneath. + * + * @param hardwareBuffer The buffer containing the screenshot. + */ + private void displayTargetScreenshot(@NonNull HardwareBuffer hardwareBuffer, + WindowConfiguration taskWindowConfiguration) { + SurfaceControl screenshotSurface = + mBackNavigationInfo == null ? null : mBackNavigationInfo.getScreenshotSurface(); + if (screenshotSurface == null) { + Log.e(TAG, "BackNavigationInfo doesn't contain a surface for the screenshot. "); + return; + } + + // Scale the buffer to fill the whole Task + float sx = 1; + float sy = 1; + float w = taskWindowConfiguration.getBounds().width(); + float h = taskWindowConfiguration.getBounds().height(); + + if (w != hardwareBuffer.getWidth()) { + sx = w / hardwareBuffer.getWidth(); + } + + if (h != hardwareBuffer.getHeight()) { + sy = h / hardwareBuffer.getHeight(); + } + mTransaction.setScale(screenshotSurface, sx, sy); + mTransaction.setBuffer(screenshotSurface, hardwareBuffer); + mTransaction.setVisibility(screenshotSurface, true); + } + + private void onMove(MotionEvent event) { + if (!mBackGestureStarted || mBackNavigationInfo == null) { + return; + } + int deltaX = Math.round(event.getX() - mInitTouchLocation.x); + int deltaY = Math.round(event.getY() - mInitTouchLocation.y); + ProtoLog.v(WM_SHELL_BACK_PREVIEW, "Runner move: %d %d", deltaX, deltaY); + SurfaceControl topWindowLeash = mBackNavigationInfo.getDepartingWindowContainer(); + mTransaction.setPosition(topWindowLeash, deltaX, deltaY); + mTouchEventDelta.set(deltaX, deltaY); + mTransaction.apply(); + } + + private void onGestureFinished() { + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", mTriggerBack); + if (mBackGestureStarted) { + if (mTriggerBack) { + prepareTransition(); + } else { + resetPositionAnimated(); + } + } + mBackGestureStarted = false; + mTriggerBack = false; + } + + /** + * Animate the top window leash to its initial position. + */ + private void resetPositionAnimated() { + mBackGestureStarted = false; + // TODO(208786853) Handle overlap with a new coming gesture. + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Runner: Back not triggered, cancelling animation " + + "mLastPos=%s mInitTouch=%s", mTouchEventDelta, mInitTouchLocation); + + // TODO(208427216) : Replace placeholder animation with an actual one. + ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f).setDuration(200); + animation.addUpdateListener(animation1 -> { + if (mBackNavigationInfo == null) { + return; + } + float fraction = animation1.getAnimatedFraction(); + int deltaX = Math.round(mTouchEventDelta.x - (mTouchEventDelta.x * fraction)); + int deltaY = Math.round(mTouchEventDelta.y - (mTouchEventDelta.y * fraction)); + mTransaction.setPosition(mBackNavigationInfo.getDepartingWindowContainer(), + deltaX, deltaY); + mTransaction.apply(); + }); + + animation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: onAnimationEnd"); + finishAnimation(); + } + }); + animation.start(); + } + + private void prepareTransition() { + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "prepareTransition()"); + mTriggerBack = false; + mBackGestureStarted = false; + } + + /** + * Sets to true when the back gesture has passed the triggering threshold, false otherwise. + */ + public void setTriggerBack(boolean triggerBack) { + mTriggerBack = triggerBack; + } + + private void finishAnimation() { + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishAnimation()"); + mBackGestureStarted = false; + mTouchEventDelta.set(0, 0); + mInitTouchLocation.set(0, 0); + BackNavigationInfo backNavigationInfo = mBackNavigationInfo; + mBackNavigationInfo = null; + if (backNavigationInfo == null) { + return; + } + SurfaceControl topWindowLeash = backNavigationInfo.getDepartingWindowContainer(); + if (topWindowLeash != null && topWindowLeash.isValid()) { + mTransaction.remove(topWindowLeash); + } + SurfaceControl screenshotSurface = backNavigationInfo.getScreenshotSurface(); + if (screenshotSurface != null && screenshotSurface.isValid()) { + mTransaction.remove(screenshotSurface); + } + mTransaction.apply(); + backNavigationInfo.onBackNavigationFinished(); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 23d9b8b14159..f61e62444366 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -40,6 +40,8 @@ import com.android.wm.shell.TaskViewTransitions; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.apppairs.AppPairs; import com.android.wm.shell.apppairs.AppPairsController; +import com.android.wm.shell.back.BackAnimation; +import com.android.wm.shell.back.BackAnimationController; import com.android.wm.shell.bubbles.BubbleController; import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.common.DisplayController; @@ -238,6 +240,17 @@ public abstract class WMShellBaseModule { } // + // Back animation + // + + @WMSingleton + @Provides + static Optional<BackAnimation> provideBackAnimation( + Optional<BackAnimationController> backAnimationController) { + return backAnimationController.map(BackAnimationController::getBackAnimationImpl); + } + + // // Bubbles (optional feature) // @@ -678,4 +691,16 @@ public abstract class WMShellBaseModule { legacySplitScreenOptional, splitScreenOptional, pipOptional, oneHandedOptional, hideDisplayCutout, appPairsOptional, recentTasksOptional, mainExecutor); } + + @WMSingleton + @Provides + static Optional<BackAnimationController> provideBackAnimationController( + @ShellMainThread ShellExecutor shellExecutor + ) { + if (BackAnimationController.IS_ENABLED) { + return Optional.of( + new BackAnimationController(shellExecutor)); + } + return Optional.empty(); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java index 10aa8a018acc..225305bd5178 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java @@ -105,8 +105,6 @@ public class PipMenuView extends FrameLayout { private static final float MENU_BACKGROUND_ALPHA = 0.3f; private static final float DISABLED_ACTION_ALPHA = 0.54f; - private static final boolean ENABLE_ENTER_SPLIT = true; - private int mMenuState; private boolean mAllowMenuTimeout = true; private boolean mAllowTouches = true; @@ -281,6 +279,8 @@ public class PipMenuView extends FrameLayout { boolean resizeMenuOnShow, boolean withDelay, boolean showResizeHandle) { mAllowMenuTimeout = allowMenuTimeout; mDidLastShowMenuResize = resizeMenuOnShow; + final boolean enableEnterSplit = + mContext.getResources().getBoolean(R.bool.config_pipEnableEnterSplitButton); if (mMenuState != menuState) { // Disallow touches if the menu needs to resize while showing, and we are transitioning // to/from a full menu state. @@ -301,7 +301,7 @@ public class PipMenuView extends FrameLayout { mDismissButton.getAlpha(), 1f); ObjectAnimator enterSplitAnim = ObjectAnimator.ofFloat(mEnterSplitButton, View.ALPHA, mEnterSplitButton.getAlpha(), - ENABLE_ENTER_SPLIT && mFocusedTaskAllowSplitScreen ? 1f : 0f); + enableEnterSplit && mFocusedTaskAllowSplitScreen ? 1f : 0f); if (menuState == MENU_STATE_FULL) { mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim, enterSplitAnim); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java index 79c1df2174b9..20c4e21a811d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java @@ -34,6 +34,8 @@ public enum ShellProtoLogGroup implements IProtoLogGroup { Consts.TAG_WM_SHELL), WM_SHELL_STARTING_WINDOW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_STARTING_WINDOW), + WM_SHELL_BACK_PREVIEW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, + "ShellBackPreview"), TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest"); private final boolean mEnabled; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java index 8664d9be3340..d30d0cc95f46 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java @@ -70,7 +70,8 @@ class SplitScreenTransitions { IBinder mPendingRecent = null; private IBinder mAnimatingTransition = null; - private OneShotRemoteHandler mRemoteHandler = null; + private OneShotRemoteHandler mPendingRemoteHandler = null; + private OneShotRemoteHandler mActiveRemoteHandler = null; private final Transitions.TransitionFinishCallback mRemoteFinishCB = this::onFinish; @@ -96,10 +97,11 @@ class SplitScreenTransitions { @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot) { mFinishCallback = finishCallback; mAnimatingTransition = transition; - if (mRemoteHandler != null) { - mRemoteHandler.startAnimation(transition, info, startTransaction, finishTransaction, - mRemoteFinishCB); - mRemoteHandler = null; + if (mPendingRemoteHandler != null) { + mPendingRemoteHandler.startAnimation(transition, info, startTransaction, + finishTransaction, mRemoteFinishCB); + mActiveRemoteHandler = mPendingRemoteHandler; + mPendingRemoteHandler = null; return; } playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot); @@ -172,15 +174,14 @@ class SplitScreenTransitions { IBinder startEnterTransition(@WindowManager.TransitionType int transitType, @NonNull WindowContainerTransaction wct, @Nullable RemoteTransition remoteTransition, @NonNull Transitions.TransitionHandler handler) { + final IBinder transition = mTransitions.startTransition(transitType, wct, handler); + mPendingEnter = transition; + if (remoteTransition != null) { // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff) - mRemoteHandler = new OneShotRemoteHandler( + mPendingRemoteHandler = new OneShotRemoteHandler( mTransitions.getMainExecutor(), remoteTransition); - } - final IBinder transition = mTransitions.startTransition(transitType, wct, handler); - mPendingEnter = transition; - if (mRemoteHandler != null) { - mRemoteHandler.setTransition(transition); + mPendingRemoteHandler.setTransition(transition); } return transition; } @@ -211,9 +212,9 @@ class SplitScreenTransitions { if (remoteTransition != null) { // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff) - mRemoteHandler = new OneShotRemoteHandler( + mPendingRemoteHandler = new OneShotRemoteHandler( mTransitions.getMainExecutor(), remoteTransition); - mRemoteHandler.setTransition(transition); + mPendingRemoteHandler.setTransition(transition); } ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition " @@ -221,6 +222,13 @@ class SplitScreenTransitions { return transition; } + void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, + IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback) { + if (mergeTarget == mAnimatingTransition && mActiveRemoteHandler != null) { + mActiveRemoteHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); + } + } + void onFinish(WindowContainerTransaction wct, WindowContainerTransactionCallback wctCB) { if (!mAnimations.isEmpty()) return; mOnFinish.run(); @@ -241,11 +249,13 @@ class SplitScreenTransitions { } if (mAnimatingTransition == mPendingRecent) { // If the wct is not null while finishing recent transition, it indicates it's not - // returning to home and hence needing the wct to reorder tasks. - final boolean toHome = wct == null; - mStageCoordinator.finishRecentAnimation(toHome); + // dismissing split and thus need to reorder split task so they can be on top again. + final boolean dismissSplit = wct == null; + mStageCoordinator.finishRecentAnimation(dismissSplit); mPendingRecent = null; } + mPendingRemoteHandler = null; + mActiveRemoteHandler = null; mAnimatingTransition = null; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index c8120509f0db..e592101d2b20 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -166,17 +166,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, @StageType private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; - private final Runnable mOnTransitionAnimationComplete = () -> { - // If still playing, let it finish. - if (!isSplitScreenVisible()) { - // Update divider state after animation so that it is still around and positioned - // properly for the animation itself. - mSplitLayout.release(); - mSplitLayout.resetDividerPosition(); - mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; - } - }; - private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks = new SplitWindowManager.ParentContainerCallbacks() { @Override @@ -237,7 +226,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, deviceStateManager.registerCallback(taskOrganizer.getExecutor(), new DeviceStateManager.FoldStateListener(mContext, this::onFoldedStateChanged)); mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions, - mOnTransitionAnimationComplete, this); + this::onTransitionAnimationComplete, this); mDisplayController.addDisplayWindowListener(this); mDisplayLayout = new DisplayLayout(displayController.getDisplayLayout(displayId)); transitions.addHandler(this); @@ -267,7 +256,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mRootTDAOrganizer.registerListener(displayId, this); mSplitLayout = splitLayout; mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions, - mOnTransitionAnimationComplete, this); + this::onTransitionAnimationComplete, this); mMainUnfoldController = unfoldControllerProvider.get().orElse(null); mSideUnfoldController = unfoldControllerProvider.get().orElse(null); mLogger = logger; @@ -1234,7 +1223,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask(); if (triggerTask == null) { // Still want to monitor everything while in split-screen, so return non-null. - return isSplitScreenVisible() ? new WindowContainerTransaction() : null; + return mMainStage.isActive() ? new WindowContainerTransaction() : null; } else if (triggerTask.displayId != mDisplayId) { // Skip handling task on the other display. return null; @@ -1250,7 +1239,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mRecentTasks.ifPresent(recentTasks -> recentTasks.removeSplitPair(triggerTask.taskId)); } - if (isSplitScreenVisible()) { + if (mMainStage.isActive()) { // Try to handle everything while in split-screen, so return a WCT even if it's empty. ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " split is active so using split" + "Transition to handle request. triggerTask=%d type=%s mainChildren=%d" @@ -1296,6 +1285,13 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, } @Override + public void mergeAnimation(IBinder transition, TransitionInfo info, + SurfaceControl.Transaction t, IBinder mergeTarget, + Transitions.TransitionFinishCallback finishCallback) { + mSplitTransitions.mergeAnimation(transition, info, t, mergeTarget, finishCallback); + } + + @Override public void onTransitionMerged(@NonNull IBinder transition) { // Once the pending enter transition got merged, make sure to bring divider bar visible and // clear the pending transition from cache to prevent mess-up the following state. @@ -1375,6 +1371,17 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, return true; } + void onTransitionAnimationComplete() { + // If still playing, let it finish. + if (!mMainStage.isActive()) { + // Update divider state after animation so that it is still around and positioned + // properly for the animation itself. + mSplitLayout.release(); + mSplitLayout.resetDividerPosition(); + mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; + } + } + private boolean startPendingEnterAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) { // First, verify that we actually have opened apps in both splits. @@ -1513,8 +1520,17 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, return true; } - void finishRecentAnimation(boolean toHome) { - if (toHome) { + void finishRecentAnimation(boolean dismissSplit) { + // Exclude the case that the split screen has been dismissed already. + if (!mMainStage.isActive()) { + // The latest split dismissing transition might be a no-op transition and thus won't + // callback startAnimation, update split visibility here to cover this kind of no-op + // transition case. + setSplitsVisible(false); + return; + } + + if (dismissSplit) { final WindowContainerTransaction wct = new WindowContainerTransaction(); prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct); mSplitTransitions.startDismissTransition(null /* transition */, wct, this, diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/OWNERS b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/OWNERS new file mode 100644 index 000000000000..8446b37dbf06 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/OWNERS @@ -0,0 +1,2 @@ +# window manager > wm shell > Split Screen +# Bug component: 928697 diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OWNERS b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OWNERS new file mode 100644 index 000000000000..566acc87e42d --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OWNERS @@ -0,0 +1,2 @@ +# window manager > wm shell > Bubbles +# Bug component: 555586 diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OWNERS b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OWNERS new file mode 100644 index 000000000000..8446b37dbf06 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OWNERS @@ -0,0 +1,2 @@ +# window manager > wm shell > Split Screen +# Bug component: 928697 diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/OWNERS b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/OWNERS new file mode 100644 index 000000000000..172e24bf4574 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/OWNERS @@ -0,0 +1,2 @@ +# window manager > wm shell > Picture-In-Picture +# Bug component: 316251 diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java new file mode 100644 index 000000000000..960c7ac4099a --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.back; + +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.app.IActivityTaskManager; +import android.app.WindowConfiguration; +import android.hardware.HardwareBuffer; +import android.os.RemoteCallback; +import android.os.RemoteException; +import android.testing.AndroidTestingRunner; +import android.view.MotionEvent; +import android.view.SurfaceControl; +import android.window.BackNavigationInfo; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.TestShellExecutor; +import com.android.wm.shell.common.ShellExecutor; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * atest WMShellUnitTests:BackAnimationControllerTest + */ +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class BackAnimationControllerTest { + + private final ShellExecutor mShellExecutor = new TestShellExecutor(); + + @Mock + private SurfaceControl.Transaction mTransaction; + + @Mock + private IActivityTaskManager mActivityTaskManager; + + private BackAnimationController mController; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mController = new BackAnimationController( + mShellExecutor, mTransaction, mActivityTaskManager); + } + + private void createNavigationInfo(SurfaceControl topWindowLeash, + SurfaceControl screenshotSurface, + HardwareBuffer hardwareBuffer) { + BackNavigationInfo navigationInfo = new BackNavigationInfo( + BackNavigationInfo.TYPE_RETURN_TO_HOME, + topWindowLeash, + screenshotSurface, + hardwareBuffer, + new WindowConfiguration(), + new RemoteCallback((bundle) -> {})); + try { + doReturn(navigationInfo).when(mActivityTaskManager).startBackNavigation(); + } catch (RemoteException ex) { + ex.rethrowFromSystemServer(); + } + } + + @Test + public void screenshotAttachedAndVisible() { + SurfaceControl topWindowLeash = new SurfaceControl(); + SurfaceControl screenshotSurface = new SurfaceControl(); + HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class); + createNavigationInfo(topWindowLeash, screenshotSurface, hardwareBuffer); + mController.onMotionEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0)); + verify(mTransaction).setBuffer(screenshotSurface, hardwareBuffer); + verify(mTransaction).setVisibility(screenshotSurface, true); + verify(mTransaction).apply(); + } + + @Test + public void surfaceMovesWithGesture() { + SurfaceControl topWindowLeash = new SurfaceControl(); + SurfaceControl screenshotSurface = new SurfaceControl(); + HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class); + createNavigationInfo(topWindowLeash, screenshotSurface, hardwareBuffer); + mController.onMotionEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0)); + mController.onMotionEvent(MotionEvent.obtain(10, 0, MotionEvent.ACTION_MOVE, 100, 100, 0)); + verify(mTransaction).setPosition(topWindowLeash, 100, 100); + verify(mTransaction, atLeastOnce()).apply(); + } +} diff --git a/libs/androidfw/Locale.cpp b/libs/androidfw/Locale.cpp index 3eedda88fdce..d87a3ce72177 100644 --- a/libs/androidfw/Locale.cpp +++ b/libs/androidfw/Locale.cpp @@ -29,40 +29,33 @@ using ::android::StringPiece; namespace android { -void LocaleValue::set_language(const char* language_chars) { +template <size_t N, class Transformer> +static void safe_transform_copy(const char* source, char (&dest)[N], Transformer t) { size_t i = 0; - while ((*language_chars) != '\0') { - language[i++] = ::tolower(*language_chars); - language_chars++; + while (i < N && (*source) != '\0') { + dest[i++] = t(i, *source); + source++; + } + while (i < N) { + dest[i++] = '\0'; } } +void LocaleValue::set_language(const char* language_chars) { + safe_transform_copy(language_chars, language, [](size_t, char c) { return ::tolower(c); }); +} + void LocaleValue::set_region(const char* region_chars) { - size_t i = 0; - while ((*region_chars) != '\0') { - region[i++] = ::toupper(*region_chars); - region_chars++; - } + safe_transform_copy(region_chars, region, [](size_t, char c) { return ::toupper(c); }); } void LocaleValue::set_script(const char* script_chars) { - size_t i = 0; - while ((*script_chars) != '\0') { - if (i == 0) { - script[i++] = ::toupper(*script_chars); - } else { - script[i++] = ::tolower(*script_chars); - } - script_chars++; - } + safe_transform_copy(script_chars, script, + [](size_t i, char c) { return i ? ::tolower(c) : ::toupper(c); }); } void LocaleValue::set_variant(const char* variant_chars) { - size_t i = 0; - while ((*variant_chars) != '\0') { - variant[i++] = *variant_chars; - variant_chars++; - } + safe_transform_copy(variant_chars, variant, [](size_t, char c) { return c; }); } static inline bool is_alpha(const std::string& str) { @@ -234,6 +227,10 @@ ssize_t LocaleValue::InitFromParts(std::vector<std::string>::iterator iter, return static_cast<ssize_t>(iter - start_iter); } +// Make sure the following memcpy's are properly sized. +static_assert(sizeof(ResTable_config::localeScript) == sizeof(LocaleValue::script)); +static_assert(sizeof(ResTable_config::localeVariant) == sizeof(LocaleValue::variant)); + void LocaleValue::InitFromResTable(const ResTable_config& config) { config.unpackLanguage(language); config.unpackRegion(region); diff --git a/media/java/android/media/ImageWriter.java b/media/java/android/media/ImageWriter.java index 1fc2cf9edc90..6168c221bf6e 100644 --- a/media/java/android/media/ImageWriter.java +++ b/media/java/android/media/ImageWriter.java @@ -18,12 +18,16 @@ package android.media; import android.annotation.IntRange; import android.annotation.NonNull; +import android.annotation.SuppressLint; import android.graphics.GraphicBuffer; import android.graphics.ImageFormat; import android.graphics.ImageFormat.Format; import android.graphics.PixelFormat; import android.graphics.Rect; +import android.hardware.DataSpace; +import android.hardware.DataSpace.NamedDataSpace; import android.hardware.HardwareBuffer; +import android.hardware.HardwareBuffer.Usage; import android.hardware.camera2.params.StreamConfigurationMap; import android.hardware.camera2.utils.SurfaceUtils; import android.os.Handler; @@ -95,10 +99,18 @@ public class ImageWriter implements AutoCloseable { private ListenerHandler mListenerHandler; private long mNativeContext; + private int mWidth; + private int mHeight; + private final int mMaxImages; + private @Usage long mUsage = HardwareBuffer.USAGE_CPU_WRITE_OFTEN; + private @HardwareBuffer.Format int mHardwareBufferFormat; + private @NamedDataSpace long mDataSpace; + private boolean mUseLegacyImageFormat; + private boolean mUseSurfaceImageFormatInfo; + // Field below is used by native code, do not access or modify. private int mWriterFormat; - private final int mMaxImages; // Keep track of the currently dequeued Image. This need to be thread safe as the images // could be closed by different threads (e.g., application thread and GC thread). private List<Image> mDequeuedImages = new CopyOnWriteArrayList<>(); @@ -131,7 +143,7 @@ public class ImageWriter implements AutoCloseable { */ public static @NonNull ImageWriter newInstance(@NonNull Surface surface, @IntRange(from = 1) int maxImages) { - return new ImageWriter(surface, maxImages, ImageFormat.UNKNOWN, -1 /*width*/, + return new ImageWriter(surface, maxImages, true, ImageFormat.UNKNOWN, -1 /*width*/, -1 /*height*/); } @@ -183,7 +195,7 @@ public class ImageWriter implements AutoCloseable { if (!ImageFormat.isPublicFormat(format) && !PixelFormat.isPublicFormat(format)) { throw new IllegalArgumentException("Invalid format is specified: " + format); } - return new ImageWriter(surface, maxImages, format, width, height); + return new ImageWriter(surface, maxImages, false, format, width, height); } /** @@ -232,48 +244,49 @@ public class ImageWriter implements AutoCloseable { if (!ImageFormat.isPublicFormat(format) && !PixelFormat.isPublicFormat(format)) { throw new IllegalArgumentException("Invalid format is specified: " + format); } - return new ImageWriter(surface, maxImages, format, -1 /*width*/, -1 /*height*/); + return new ImageWriter(surface, maxImages, false, format, -1 /*width*/, -1 /*height*/); } - /** - * @hide - */ - protected ImageWriter(Surface surface, int maxImages, int format, int width, int height) { + private void initializeImageWriter(Surface surface, int maxImages, + boolean useSurfaceImageFormatInfo, boolean useLegacyImageFormat, int imageFormat, + int hardwareBufferFormat, long dataSpace, int width, int height, long usage) { if (surface == null || maxImages < 1) { throw new IllegalArgumentException("Illegal input argument: surface " + surface - + ", maxImages: " + maxImages); + + ", maxImages: " + maxImages); } - mMaxImages = maxImages; - + mUseSurfaceImageFormatInfo = useSurfaceImageFormatInfo; + mUseLegacyImageFormat = useLegacyImageFormat; // Note that the underlying BufferQueue is working in synchronous mode // to avoid dropping any buffers. - mNativeContext = nativeInit(new WeakReference<>(this), surface, maxImages, format, width, - height); - - // nativeInit internally overrides UNKNOWN format. So does surface format query after - // nativeInit and before getEstimatedNativeAllocBytes(). - if (format == ImageFormat.UNKNOWN) { - format = SurfaceUtils.getSurfaceFormat(surface); - } - // Several public formats use the same native HAL_PIXEL_FORMAT_BLOB. The native - // allocation estimation sequence depends on the public formats values. To avoid - // possible errors, convert where necessary. - if (format == StreamConfigurationMap.HAL_PIXEL_FORMAT_BLOB) { - int surfaceDataspace = SurfaceUtils.getSurfaceDataspace(surface); - switch (surfaceDataspace) { - case StreamConfigurationMap.HAL_DATASPACE_DEPTH: - format = ImageFormat.DEPTH_POINT_CLOUD; - break; - case StreamConfigurationMap.HAL_DATASPACE_DYNAMIC_DEPTH: - format = ImageFormat.DEPTH_JPEG; - break; - case StreamConfigurationMap.HAL_DATASPACE_HEIF: - format = ImageFormat.HEIC; - break; - default: - format = ImageFormat.JPEG; + mNativeContext = nativeInit(new WeakReference<>(this), surface, maxImages, width, height, + useSurfaceImageFormatInfo, hardwareBufferFormat, dataSpace, usage); + + if (useSurfaceImageFormatInfo) { + // nativeInit internally overrides UNKNOWN format. So does surface format query after + // nativeInit and before getEstimatedNativeAllocBytes(). + imageFormat = SurfaceUtils.getSurfaceFormat(surface); + // Several public formats use the same native HAL_PIXEL_FORMAT_BLOB. The native + // allocation estimation sequence depends on the public formats values. To avoid + // possible errors, convert where necessary. + if (imageFormat == StreamConfigurationMap.HAL_PIXEL_FORMAT_BLOB) { + int surfaceDataspace = SurfaceUtils.getSurfaceDataspace(surface); + switch (surfaceDataspace) { + case StreamConfigurationMap.HAL_DATASPACE_DEPTH: + imageFormat = ImageFormat.DEPTH_POINT_CLOUD; + break; + case StreamConfigurationMap.HAL_DATASPACE_DYNAMIC_DEPTH: + imageFormat = ImageFormat.DEPTH_JPEG; + break; + case StreamConfigurationMap.HAL_DATASPACE_HEIF: + imageFormat = ImageFormat.HEIC; + break; + default: + imageFormat = ImageFormat.JPEG; + } } + mHardwareBufferFormat = PublicFormatUtils.getHalFormat(imageFormat); + mDataSpace = PublicFormatUtils.getHalDataspace(imageFormat); } // Estimate the native buffer allocation size and register it so it gets accounted for // during GC. Note that this doesn't include the buffers required by the buffer queue @@ -282,12 +295,49 @@ public class ImageWriter implements AutoCloseable { // complex, and 1 buffer is enough for the VM to treat the ImageWriter as being of some // size. Size surfSize = SurfaceUtils.getSurfaceSize(surface); + mWidth = width == -1 ? surfSize.getWidth() : width; + mHeight = height == -1 ? surfSize.getHeight() : height; + mEstimatedNativeAllocBytes = - ImageUtils.getEstimatedNativeAllocBytes(surfSize.getWidth(),surfSize.getHeight(), - format, /*buffer count*/ 1); + ImageUtils.getEstimatedNativeAllocBytes(mWidth, mHeight, + useLegacyImageFormat ? imageFormat : hardwareBufferFormat, /*buffer count*/ 1); VMRuntime.getRuntime().registerNativeAllocation(mEstimatedNativeAllocBytes); } + private ImageWriter(Surface surface, int maxImages, boolean useSurfaceImageFormatInfo, + int imageFormat, int width, int height) { + mMaxImages = maxImages; + // update hal format and dataspace only if image format is overridden by producer. + mHardwareBufferFormat = PublicFormatUtils.getHalFormat(imageFormat); + mDataSpace = PublicFormatUtils.getHalDataspace(imageFormat); + + initializeImageWriter(surface, maxImages, useSurfaceImageFormatInfo, true, + imageFormat, mHardwareBufferFormat, mDataSpace, width, height, mUsage); + } + + private ImageWriter(Surface surface, int maxImages, boolean useSurfaceImageFormatInfo, + int imageFormat, int width, int height, long usage) { + mMaxImages = maxImages; + mUsage = usage; + mHardwareBufferFormat = PublicFormatUtils.getHalFormat(imageFormat); + mDataSpace = PublicFormatUtils.getHalDataspace(imageFormat); + + initializeImageWriter(surface, maxImages, useSurfaceImageFormatInfo, true, + imageFormat, mHardwareBufferFormat, mDataSpace, width, height, usage); + } + + private ImageWriter(Surface surface, int maxImages, boolean useSurfaceImageFormatInfo, + int hardwareBufferFormat, long dataSpace, int width, int height, long usage) { + mMaxImages = maxImages; + mUsage = usage; + mHardwareBufferFormat = hardwareBufferFormat; + mDataSpace = dataSpace; + int publicFormat = PublicFormatUtils.getPublicFormat(hardwareBufferFormat, dataSpace); + + initializeImageWriter(surface, maxImages, useSurfaceImageFormatInfo, false, + publicFormat, hardwareBufferFormat, dataSpace, width, height, usage); + } + /** * <p> * Maximum number of Images that can be dequeued from the ImageWriter @@ -316,6 +366,30 @@ public class ImageWriter implements AutoCloseable { } /** + * The width of {@link Image Images}, in pixels. + * + * <p>If {@link Builder#setWidthAndHeight} is not called, the default width of the Image + * depends on the Surface provided by customer end-point.</p> + * + * @return the expected actual width of an Image. + */ + public int getWidth() { + return mWidth; + } + + /** + * The height of {@link Image Images}, in pixels. + * + * <p>If {@link Builder#setWidthAndHeight} is not called, the default height of the Image + * depends on the Surface provided by customer end-point.</p> + * + * @return the expected height of an Image. + */ + public int getHeight() { + return mHeight; + } + + /** * <p> * Dequeue the next available input Image for the application to produce * data into. @@ -490,6 +564,41 @@ public class ImageWriter implements AutoCloseable { } /** + * Get the ImageWriter usage flag. + * + * @return The ImageWriter usage flag. + */ + public @Usage long getUsage() { + return mUsage; + } + + /** + * Get the ImageWriter hardwareBuffer format. + * + * <p>Use this function if the ImageWriter instance is created by builder pattern + * {@code ImageWriter.Builder} and using {@link Builder#setHardwareBufferFormat} and + * {@link Builder#setDataSpace}.</p> + * + * @return The ImageWriter hardwareBuffer format. + */ + public @HardwareBuffer.Format int getHardwareBufferFormat() { + return mHardwareBufferFormat; + } + + /** + * Get the ImageWriter dataspace. + * + * <p>Use this function if the ImageWriter instance is created by builder pattern + * {@code ImageWriter.Builder} and {@link Builder#setDataSpace}.</p> + * + * @return The ImageWriter dataspace. + */ + @SuppressLint("MethodNameUnits") + public @NamedDataSpace long getDataSpace() { + return mDataSpace; + } + + /** * ImageWriter callback interface, used to to asynchronously notify the * application of various ImageWriter events. */ @@ -755,6 +864,155 @@ public class ImageWriter implements AutoCloseable { return true; } + /** + * Builder class for {@link ImageWriter} objects. + */ + public static final class Builder { + private Surface mSurface; + private int mWidth = -1; + private int mHeight = -1; + private int mMaxImages = 1; + private int mImageFormat = ImageFormat.UNKNOWN; + private @Usage long mUsage = HardwareBuffer.USAGE_CPU_WRITE_OFTEN; + private @HardwareBuffer.Format int mHardwareBufferFormat = HardwareBuffer.RGBA_8888; + private @NamedDataSpace long mDataSpace = DataSpace.DATASPACE_UNKNOWN; + private boolean mUseSurfaceImageFormatInfo = true; + // set this as true temporarily now as a workaround to get correct format + // when using surface format by default without overriding the image format + // in the builder pattern + private boolean mUseLegacyImageFormat = true; + + /** + * Constructs a new builder for {@link ImageWriter}. + * + * @param surface The destination Surface this writer produces Image data into. + */ + public Builder(@NonNull Surface surface) { + mSurface = surface; + } + + /** + * Set the width and height of images. Default size is dependent on the Surface that is + * provided by the downstream end-point. + * + * @param width The width in pixels that will be passed to the producer. + * @param height The height in pixels that will be passed to the producer. + * @return the Builder instance with customized width and height. + */ + @SuppressLint("MissingGetterMatchingBuilder") + public @NonNull Builder setWidthAndHeight(@IntRange(from = 1) int width, + @IntRange(from = 1) int height) { + mWidth = width; + mHeight = height; + return this; + } + + /** + * Set the maximum number of images. Default value is 1. + * + * @param maxImages The maximum number of Images the user will want to access simultaneously + * for producing Image data. + * @return the Builder instance with customized usage value. + */ + public @NonNull Builder setMaxImages(@IntRange(from = 1) int maxImages) { + mMaxImages = maxImages; + return this; + } + + /** + * Set the image format of this ImageWriter. + * Default format depends on the Surface provided. + * + * @param imageFormat The format of the {@link ImageWriter}. It can be any valid specified + * by {@link ImageFormat} or {@link PixelFormat}. + * @return the Builder instance with customized image format. + */ + @SuppressLint("MissingGetterMatchingBuilder") + public @NonNull Builder setImageFormat(@Format int imageFormat) { + if (!ImageFormat.isPublicFormat(imageFormat) + && !PixelFormat.isPublicFormat(imageFormat)) { + throw new IllegalArgumentException( + "Invalid imageFormat is specified: " + imageFormat); + } + mImageFormat = imageFormat; + mUseLegacyImageFormat = true; + mHardwareBufferFormat = HardwareBuffer.RGBA_8888; + mDataSpace = DataSpace.DATASPACE_UNKNOWN; + mUseSurfaceImageFormatInfo = false; + return this; + } + + /** + * Set the hardwareBuffer format of this ImageWriter. The default value is + * {@link HardwareBuffer#RGBA_8888 HardwareBuffer.RGBA_8888}. + * + * <p>This function works together with {@link #setDataSpace} for an + * {@link ImageWriter} instance. Setting at least one of these two replaces + * {@link #setImageFormat} function.</p> + * + * @param hardwareBufferFormat The HardwareBuffer format of the image that this writer + * will produce. + * @return the Builder instance with customized buffer format. + * + * @see #setDataSpace + * @see #setImageFormat + */ + public @NonNull Builder setHardwareBufferFormat( + @HardwareBuffer.Format int hardwareBufferFormat) { + mHardwareBufferFormat = hardwareBufferFormat; + mImageFormat = ImageFormat.UNKNOWN; + mUseLegacyImageFormat = false; + mUseSurfaceImageFormatInfo = false; + return this; + } + + /** + * Set the dataspace of this ImageWriter. + * The default value is {@link DataSpace#DATASPACE_UNKNOWN}. + * + * @param dataSpace The dataspace of the image that this writer will produce. + * @return the builder instance with customized dataspace value. + * + * @see #setHardwareBufferFormat + */ + public @NonNull Builder setDataSpace(@NamedDataSpace long dataSpace) { + mDataSpace = dataSpace; + mImageFormat = ImageFormat.UNKNOWN; + mUseLegacyImageFormat = false; + mUseSurfaceImageFormatInfo = false; + return this; + } + + /** + * Set the usage flag of this ImageWriter. + * Default value is {@link HardwareBuffer#USAGE_CPU_WRITE_OFTEN}. + * + * @param usage The intended usage of the images produced by this ImageWriter. + * @return the Builder instance with customized usage flag. + * + * @see HardwareBuffer + */ + public @NonNull Builder setUsage(@Usage long usage) { + mUsage = usage; + return this; + } + + /** + * Builds a new ImageWriter object. + * + * @return The new ImageWriter object. + */ + public @NonNull ImageWriter build() { + if (mUseLegacyImageFormat) { + return new ImageWriter(mSurface, mMaxImages, mUseSurfaceImageFormatInfo, + mImageFormat, mWidth, mHeight, mUsage); + } else { + return new ImageWriter(mSurface, mMaxImages, mUseSurfaceImageFormatInfo, + mHardwareBufferFormat, mDataSpace, mWidth, mHeight, mUsage); + } + } + } + private static class WriterSurfaceImage extends android.media.Image { private ImageWriter mOwner; // This field is used by native code, do not access or modify. @@ -774,6 +1032,13 @@ public class ImageWriter implements AutoCloseable { public WriterSurfaceImage(ImageWriter writer) { mOwner = writer; + mWidth = writer.mWidth; + mHeight = writer.mHeight; + + if (!writer.mUseLegacyImageFormat) { + mFormat = PublicFormatUtils.getPublicFormat( + writer.mHardwareBufferFormat, writer.mDataSpace); + } } @Override @@ -969,8 +1234,9 @@ public class ImageWriter implements AutoCloseable { } // Native implemented ImageWriter methods. - private synchronized native long nativeInit(Object weakSelf, Surface surface, int maxImgs, - int format, int width, int height); + private synchronized native long nativeInit(Object weakSelf, Surface surface, int maxImages, + int width, int height, boolean useSurfaceImageFormatInfo, int hardwareBufferFormat, + long dataSpace, long usage); private synchronized native void nativeClose(long nativeCtx); diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java index a049a8891f48..831649e5bae9 100644 --- a/media/java/android/media/midi/MidiManager.java +++ b/media/java/android/media/midi/MidiManager.java @@ -31,12 +31,15 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Log; +import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collection; import java.util.concurrent.ConcurrentHashMap; +// BLE-MIDI + /** * This class is the public application interface to the MIDI service. */ @@ -399,9 +402,11 @@ public final class MidiManager { final OnDeviceOpenedListener listenerF = listener; final Handler handlerF = handler; + Log.d(TAG, "openBluetoothDevice() " + bluetoothDevice); IMidiDeviceOpenCallback callback = new IMidiDeviceOpenCallback.Stub() { @Override public void onDeviceOpened(IMidiDeviceServer server, IBinder deviceToken) { + Log.d(TAG, "onDeviceOpened() server:" + server); MidiDevice device = null; if (server != null) { try { @@ -423,6 +428,15 @@ public final class MidiManager { } } + /** @hide */ // for now + public void closeBluetoothDevice(@NonNull MidiDevice midiDevice) { + try { + midiDevice.close(); + } catch (IOException ex) { + Log.e(TAG, "Exception closing BLE-MIDI device" + ex); + } + } + /** @hide */ public MidiDeviceServer createDeviceServer(MidiReceiver[] inputPortReceivers, int numOutputPorts, String[] inputPortNames, String[] outputPortNames, diff --git a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java index 8cedd04a8b89..c1e9b38a13b0 100644 --- a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java +++ b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java @@ -54,7 +54,7 @@ public class FrontendStatus { FRONTEND_STATUS_TYPE_IS_MISO_ENABLED, FRONTEND_STATUS_TYPE_IS_LINEAR, FRONTEND_STATUS_TYPE_IS_SHORT_FRAMES_ENABLED, FRONTEND_STATUS_TYPE_ISDBT_MODE, FRONTEND_STATUS_TYPE_ISDBT_PARTIAL_RECEPTION_FLAG, FRONTEND_STATUS_TYPE_STREAM_IDS, - FRONTEND_STATUS_TYPE_DVBT_CELL_IDS}) + FRONTEND_STATUS_TYPE_DVBT_CELL_IDS, FRONTEND_STATUS_TYPE_ATSC3_ALL_PLP_INFO}) @Retention(RetentionPolicy.SOURCE) public @interface FrontendStatusType {} @@ -165,7 +165,7 @@ public class FrontendStatus { public static final int FRONTEND_STATUS_TYPE_RF_LOCK = android.hardware.tv.tuner.FrontendStatusType.RF_LOCK; /** - * PLP information in a frequency band for ATSC-3.0 frontend. + * Current tuned PLP information in a frequency band for ATSC-3.0 frontend. */ public static final int FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO = android.hardware.tv.tuner.FrontendStatusType.ATSC3_PLP_INFO; @@ -267,6 +267,13 @@ public class FrontendStatus { public static final int FRONTEND_STATUS_TYPE_DVBT_CELL_IDS = android.hardware.tv.tuner.FrontendStatusType.DVBT_CELL_IDS; + /** + * All PLP information in a frequency band for ATSC-3.0 frontend, which includes both tuned and + * not tuned PLPs for currently watching service. + */ + public static final int FRONTEND_STATUS_TYPE_ATSC3_ALL_PLP_INFO = + android.hardware.tv.tuner.FrontendStatusType.ATSC3_ALL_PLP_INFO; + /** @hide */ @IntDef(value = { AtscFrontendSettings.MODULATION_UNDEFINED, @@ -508,6 +515,7 @@ public class FrontendStatus { private Integer mIsdbtPartialReceptionFlag; private int[] mStreamIds; private int[] mDvbtCellIds; + private Atsc3PlpInfo[] mAllPlpInfo; // Constructed and fields set by JNI code. private FrontendStatus() { @@ -1078,6 +1086,25 @@ public class FrontendStatus { } /** + * Gets an array of all PLPs information of ATSC3 frontend, which includes both tuned and not + * tuned PLPs for currently watching service. + * + * <p>This query is only supported by Tuner HAL 2.0 or higher. Unsupported version or if HAL + * doesn't return all PLPs information will throw IllegalStateException. Use + * {@link TunerVersionChecker#getTunerVersion()} to check the version. + */ + @SuppressLint("ArrayReturn") + @NonNull + public Atsc3PlpInfo[] getAllAtsc3PlpInfo() { + TunerVersionChecker.checkHigherOrEqualVersionTo( + TunerVersionChecker.TUNER_VERSION_2_0, "Atsc3PlpInfo all status"); + if (mAllPlpInfo == null) { + throw new IllegalStateException("Atsc3PlpInfo all status is empty"); + } + return mAllPlpInfo; + } + + /** * Information of each tuning Physical Layer Pipes. */ public static class Atsc3PlpTuningInfo { diff --git a/media/jni/android_media_ImageWriter.cpp b/media/jni/android_media_ImageWriter.cpp index 0a5490d33293..2e419a61de91 100644 --- a/media/jni/android_media_ImageWriter.cpp +++ b/media/jni/android_media_ImageWriter.cpp @@ -375,7 +375,8 @@ static void ImageWriter_classInit(JNIEnv* env, jclass clazz) { } static jlong ImageWriter_init(JNIEnv* env, jobject thiz, jobject weakThiz, jobject jsurface, - jint maxImages, jint userFormat, jint userWidth, jint userHeight) { + jint maxImages, jint userWidth, jint userHeight, jboolean useSurfaceImageFormatInfo, + jint hardwareBufferFormat, jlong dataSpace, jlong ndkUsage) { status_t res; ALOGV("%s: maxImages:%d", __FUNCTION__, maxImages); @@ -450,7 +451,7 @@ static jlong ImageWriter_init(JNIEnv* env, jobject thiz, jobject weakThiz, jobje // Query surface format if no valid user format is specified, otherwise, override surface format // with user format. - if (userFormat == IMAGE_FORMAT_UNKNOWN) { + if (useSurfaceImageFormatInfo) { if ((res = anw->query(anw.get(), NATIVE_WINDOW_FORMAT, &surfaceFormat)) != OK) { ALOGE("%s: Query Surface format failed: %s (%d)", __FUNCTION__, strerror(-res), res); jniThrowRuntimeException(env, "Failed to query Surface format"); @@ -458,13 +459,13 @@ static jlong ImageWriter_init(JNIEnv* env, jobject thiz, jobject weakThiz, jobje } } else { // Set consumer buffer format to user specified format - PublicFormat publicFormat = static_cast<PublicFormat>(userFormat); - int nativeFormat = mapPublicFormatToHalFormat(publicFormat); - android_dataspace nativeDataspace = mapPublicFormatToHalDataspace(publicFormat); - res = native_window_set_buffers_format(anw.get(), nativeFormat); + android_dataspace nativeDataspace = static_cast<android_dataspace>(dataSpace); + int userFormat = static_cast<int>(mapHalFormatDataspaceToPublicFormat( + hardwareBufferFormat, nativeDataspace)); + res = native_window_set_buffers_format(anw.get(), hardwareBufferFormat); if (res != OK) { ALOGE("%s: Unable to configure consumer native buffer format to %#x", - __FUNCTION__, nativeFormat); + __FUNCTION__, hardwareBufferFormat); jniThrowRuntimeException(env, "Failed to set Surface format"); return 0; } @@ -484,15 +485,13 @@ static jlong ImageWriter_init(JNIEnv* env, jobject thiz, jobject weakThiz, jobje env->SetIntField(thiz, gImageWriterClassInfo.mWriterFormat, reinterpret_cast<jint>(surfaceFormat)); - if (!isFormatOpaque(surfaceFormat)) { - res = native_window_set_usage(anw.get(), GRALLOC_USAGE_SW_WRITE_OFTEN); - if (res != OK) { - ALOGE("%s: Configure usage %08x for format %08x failed: %s (%d)", - __FUNCTION__, static_cast<unsigned int>(GRALLOC_USAGE_SW_WRITE_OFTEN), - surfaceFormat, strerror(-res), res); - jniThrowRuntimeException(env, "Failed to SW_WRITE_OFTEN configure usage"); - return 0; - } + res = native_window_set_usage(anw.get(), ndkUsage); + if (res != OK) { + ALOGE("%s: Configure usage %08x for format %08x failed: %s (%d)", + __FUNCTION__, static_cast<unsigned int>(ndkUsage), + surfaceFormat, strerror(-res), res); + jniThrowRuntimeException(env, "Failed to SW_WRITE_OFTEN configure usage"); + return 0; } int minUndequeuedBufferCount = 0; @@ -1093,7 +1092,7 @@ static jobjectArray Image_createSurfacePlanes(JNIEnv* env, jobject thiz, static JNINativeMethod gImageWriterMethods[] = { {"nativeClassInit", "()V", (void*)ImageWriter_classInit }, - {"nativeInit", "(Ljava/lang/Object;Landroid/view/Surface;IIII)J", + {"nativeInit", "(Ljava/lang/Object;Landroid/view/Surface;IIIZIJJ)J", (void*)ImageWriter_init }, {"nativeClose", "(J)V", (void*)ImageWriter_close }, {"nativeAttachAndQueueImage", diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index 1b41494814b7..e6a79796bef3 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -2610,6 +2610,24 @@ jobject JTuner::getFrontendStatus(jintArray types) { env->SetObjectField(statusObj, field, valObj); break; } + case FrontendStatus::Tag::allPlpInfo: { + jfieldID field = env->GetFieldID(clazz, "mAllPlpInfo", + "[Landroid/media/tv/tuner/frontend/Atsc3PlpInfo;"); + jclass plpClazz = env->FindClass("android/media/tv/tuner/frontend/Atsc3PlpInfo"); + jmethodID initPlp = env->GetMethodID(plpClazz, "<init>", "(IZ)V"); + + vector<FrontendScanAtsc3PlpInfo> plpInfos = + s.get<FrontendStatus::Tag::allPlpInfo>(); + jobjectArray valObj = env->NewObjectArray(plpInfos.size(), plpClazz, nullptr); + for (int i = 0; i < plpInfos.size(); i++) { + jobject plpObj = env->NewObject(plpClazz, initPlp, plpInfos[i].plpId, + plpInfos[i].bLlsFlag); + env->SetObjectArrayElement(valObj, i, plpObj); + } + + env->SetObjectField(statusObj, field, valObj); + break; + } } } return statusObj; diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java index 2dd9525867b6..4c3b68958c0f 100644 --- a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java +++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java @@ -24,6 +24,7 @@ import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothProfile; import android.content.Context; +import android.media.midi.MidiDevice; import android.media.midi.MidiDeviceInfo; import android.media.midi.MidiDeviceServer; import android.media.midi.MidiDeviceStatus; @@ -63,6 +64,7 @@ public final class BluetoothMidiDevice { "00002902-0000-1000-8000-00805f9b34fb"); private final BluetoothDevice mBluetoothDevice; + private final Context mContext; private final BluetoothMidiService mService; private final MidiManager mMidiManager; private MidiReceiver mOutputReceiver; @@ -136,6 +138,8 @@ public final class BluetoothMidiDevice { // switch to receiving notifications mBluetoothGatt.readCharacteristic(characteristic); } + + openBluetoothDevice(mBluetoothDevice); } } else { Log.e(TAG, "onServicesDiscovered received: " + status); @@ -249,6 +253,7 @@ public final class BluetoothMidiDevice { mBluetoothGatt = mBluetoothDevice.connectGatt(context, false, mGattCallback); + mContext = context; mMidiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE); Bundle properties = new Bundle(); @@ -310,6 +315,18 @@ public final class BluetoothMidiDevice { } } + void openBluetoothDevice(BluetoothDevice btDevice) { + Log.d(TAG, "openBluetoothDevice() device: " + btDevice); + + MidiManager midiManager = mContext.getSystemService(MidiManager.class); + midiManager.openBluetoothDevice(btDevice, + new MidiManager.OnDeviceOpenedListener() { + @Override + public void onDeviceOpened(MidiDevice device) { + } + }, null); + } + public IBinder getBinder() { return mDeviceServer.asBinder(); } diff --git a/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml b/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml new file mode 100644 index 000000000000..a1855fdda78d --- /dev/null +++ b/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml @@ -0,0 +1,73 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/activity_confirmation" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@drawable/dialog_background" + android:elevation="16dp" + android:maxHeight="400dp" + android:orientation="vertical" + android:padding="18dp" + android:layout_gravity="center"> + + <!-- Do NOT change the ID of the root LinearLayout above: it's referenced in CTS tests. --> + + <TextView + android:id="@+id/title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center" + android:paddingHorizontal="12dp" + style="@*android:style/TextAppearance.Widget.Toolbar.Title"/> + + <TextView + android:id="@+id/summary" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="12dp" + android:layout_marginBottom="12dp" + android:gravity="center" + android:textColor="?android:attr/textColorSecondary" + android:textSize="14sp" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:gravity="end"> + + <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. --> + + <Button + android:id="@+id/btn_negative" + style="@android:style/Widget.Material.Button.Borderless.Colored" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/consent_no" + android:textColor="?android:attr/textColorSecondary" /> + + <Button + android:id="@+id/btn_positive" + style="@android:style/Widget.Material.Button.Borderless.Colored" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/consent_yes" /> + + </LinearLayout> + +</LinearLayout>
\ No newline at end of file diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml index cb8b616ec009..25ec96065647 100644 --- a/packages/CompanionDeviceManager/res/values/strings.xml +++ b/packages/CompanionDeviceManager/res/values/strings.xml @@ -73,4 +73,15 @@ <!-- Negative button for the device-app association consent dialog [CHAR LIMIT=30] --> <string name="consent_no">Don\u2019t allow</string> + + <!-- ================== System data transfer ==================== --> + <!-- Title of the permission sync confirmation dialog. [CHAR LIMIT=60] --> + <string name="permission_sync_confirmation_title">Transfer app permissions to your + watch</string> + + <!-- Text of the permission sync explanation in the confirmation dialog. [CHAR LIMIT=400] --> + <string name="permission_sync_summary">To make it easier to set up your watch, + apps installed on your watch during setup will use the same permissions as your phone.\n\n + These permissions may include access to your watch\u2019s microphone and location.</string> + </resources> diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDataTransferActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDataTransferActivity.java new file mode 100644 index 000000000000..67efa03b645f --- /dev/null +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDataTransferActivity.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.companiondevicemanager; + +import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; + +import static java.util.Objects.requireNonNull; + +import android.app.Activity; +import android.companion.SystemDataTransferRequest; +import android.content.Intent; +import android.os.Bundle; +import android.os.ResultReceiver; +import android.text.Html; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.ListView; +import android.widget.TextView; + +/** + * This activity manages the UI of companion device data transfer. + */ +public class CompanionDeviceDataTransferActivity extends Activity { + + private static final String LOG_TAG = CompanionDeviceDataTransferActivity.class.getSimpleName(); + + // UI -> SystemDataTransferProcessor + private static final int RESULT_CODE_SYSTEM_DATA_TRANSFER_ALLOWED = 0; + private static final int RESULT_CODE_SYSTEM_DATA_TRANSFER_DISALLOWED = 1; + private static final String EXTRA_SYSTEM_DATA_TRANSFER_REQUEST = "system_data_transfer_request"; + private static final String EXTRA_SYSTEM_DATA_TRANSFER_RESULT_RECEIVER = + "system_data_transfer_result_receiver"; + + private SystemDataTransferRequest mRequest; + private ResultReceiver mCdmServiceReceiver; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Log.i(LOG_TAG, "Creating UI for data transfer confirmation."); + + getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); + + setContentView(R.layout.data_transfer_confirmation); + + TextView titleView = findViewById(R.id.title); + TextView summaryView = findViewById(R.id.summary); + ListView listView = findViewById(R.id.device_list); + listView.setVisibility(View.GONE); + Button allowButton = findViewById(R.id.btn_positive); + Button disallowButton = findViewById(R.id.btn_negative); + + final Intent intent = getIntent(); + mRequest = intent.getParcelableExtra(EXTRA_SYSTEM_DATA_TRANSFER_REQUEST); + mCdmServiceReceiver = intent.getParcelableExtra(EXTRA_SYSTEM_DATA_TRANSFER_RESULT_RECEIVER); + + requireNonNull(mRequest); + requireNonNull(mCdmServiceReceiver); + + if (mRequest.isPermissionSyncAllPackages() + || !mRequest.getPermissionSyncPackages().isEmpty()) { + titleView.setText(Html.fromHtml(getString( + R.string.permission_sync_confirmation_title), 0)); + summaryView.setText(getString(R.string.permission_sync_summary)); + allowButton.setOnClickListener(v -> allow()); + disallowButton.setOnClickListener(v -> disallow()); + } + } + + private void allow() { + Log.i(LOG_TAG, "allow()"); + + sendDataToReceiver(RESULT_CODE_SYSTEM_DATA_TRANSFER_ALLOWED); + + setResultAndFinish(RESULT_CODE_SYSTEM_DATA_TRANSFER_ALLOWED); + } + + private void disallow() { + Log.i(LOG_TAG, "disallow()"); + + sendDataToReceiver(RESULT_CODE_SYSTEM_DATA_TRANSFER_DISALLOWED); + + setResultAndFinish(RESULT_CODE_SYSTEM_DATA_TRANSFER_DISALLOWED); + } + + private void sendDataToReceiver(int cdmResultCode) { + Bundle data = new Bundle(); + data.putParcelable(EXTRA_SYSTEM_DATA_TRANSFER_REQUEST, mRequest); + mCdmServiceReceiver.send(cdmResultCode, data); + } + + private void setResultAndFinish(int cdmResultCode) { + setResult(cdmResultCode == RESULT_CODE_SYSTEM_DATA_TRANSFER_ALLOWED + ? RESULT_OK : RESULT_CANCELED); + finish(); + } +} diff --git a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java index d33666d744d1..2b6570a6ecb0 100644 --- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java +++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java @@ -556,7 +556,7 @@ public final class NetworkStats implements AutoCloseable { /** * Collects history results for uid and resets history enumeration index. */ - void startHistoryEnumeration(int uid, int tag, int state) { + void startHistoryUidEnumeration(int uid, int tag, int state) { mHistory = null; try { mHistory = mSession.getHistoryIntervalForUid(mTemplate, uid, @@ -571,6 +571,20 @@ public final class NetworkStats implements AutoCloseable { } /** + * Collects history results for network and resets history enumeration index. + */ + void startHistoryDeviceEnumeration() { + try { + mHistory = mSession.getHistoryIntervalForNetwork( + mTemplate, NetworkStatsHistory.FIELD_ALL, mStartTimeStamp, mEndTimeStamp); + } catch (RemoteException e) { + Log.w(TAG, e); + mHistory = null; + } + mEnumerationIndex = 0; + } + + /** * Starts uid enumeration for current user. * @throws RemoteException */ diff --git a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java index d00de3679e0b..683678ad4671 100644 --- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java +++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java @@ -17,6 +17,8 @@ package android.app.usage; import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import android.Manifest; import android.annotation.NonNull; @@ -55,7 +57,6 @@ import com.android.net.module.util.NetworkIdentityUtils; import java.util.List; import java.util.Objects; -import java.util.Set; /** * Provides access to network usage history and statistics. Usage data is collected in @@ -125,6 +126,19 @@ public class NetworkStatsManager { private final Context mContext; private final INetworkStatsService mService; + /** + * Type constants for reading different types of Data Usage. + * @hide + */ + // @SystemApi(client = MODULE_LIBRARIES) + public static final String PREFIX_DEV = "dev"; + /** @hide */ + public static final String PREFIX_XT = "xt"; + /** @hide */ + public static final String PREFIX_UID = "uid"; + /** @hide */ + public static final String PREFIX_UID_TAG = "uid_tag"; + /** @hide */ public static final int FLAG_POLL_ON_OPEN = 1 << 0; /** @hide */ @@ -211,9 +225,10 @@ public class NetworkStatsManager { */ @NonNull @WorkerThread - // @SystemApi(client = MODULE_LIBRARIES) + @SystemApi(client = MODULE_LIBRARIES) public Bucket querySummaryForDevice(@NonNull NetworkTemplate template, long startTime, long endTime) { + Objects.requireNonNull(template); try { NetworkStats stats = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService); @@ -385,10 +400,11 @@ public class NetworkStatsManager { * @hide */ @NonNull - // @SystemApi(client = MODULE_LIBRARIES) + @SystemApi(client = MODULE_LIBRARIES) @WorkerThread public NetworkStats querySummary(@NonNull NetworkTemplate template, long startTime, long endTime) throws SecurityException { + Objects.requireNonNull(template); try { NetworkStats result = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService); @@ -418,10 +434,11 @@ public class NetworkStatsManager { * @hide */ @NonNull - // @SystemApi(client = MODULE_LIBRARIES) + @SystemApi(client = MODULE_LIBRARIES) @WorkerThread public NetworkStats queryTaggedSummary(@NonNull NetworkTemplate template, long startTime, long endTime) throws SecurityException { + Objects.requireNonNull(template); try { NetworkStats result = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService); @@ -434,6 +451,43 @@ public class NetworkStatsManager { } /** + * Query usage statistics details for networks matching a given {@link NetworkTemplate}. + * + * Result is not aggregated over time. This means buckets' start and + * end timestamps will be between 'startTime' and 'endTime' parameters. + * <p>Only includes buckets whose entire time period is included between + * startTime and endTime. Doesn't interpolate or return partial buckets. + * Since bucket length is in the order of hours, this + * method cannot be used to measure data usage on a fine grained time scale. + * This may take a long time, and apps should avoid calling this on their main thread. + * + * @param template Template used to match networks. See {@link NetworkTemplate}. + * @param startTime Start of period, in milliseconds since the Unix epoch, see + * {@link java.lang.System#currentTimeMillis}. + * @param endTime End of period, in milliseconds since the Unix epoch, see + * {@link java.lang.System#currentTimeMillis}. + * @return Statistics which is described above. + * @hide + */ + @NonNull + @SystemApi(client = MODULE_LIBRARIES) + @WorkerThread + public NetworkStats queryDetailsForDevice(@NonNull NetworkTemplate template, + long startTime, long endTime) { + Objects.requireNonNull(template); + try { + final NetworkStats result = + new NetworkStats(mContext, template, mFlags, startTime, endTime, mService); + result.startHistoryDeviceEnumeration(); + return result; + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + + return null; // To make the compiler happy. + } + + /** * Query network usage statistics details for a given uid. * This may take a long time, and apps should avoid calling this on their main thread. * @@ -499,7 +553,8 @@ public class NetworkStatsManager { * @param endTime End of period. Defined in terms of "Unix time", see * {@link java.lang.System#currentTimeMillis}. * @param uid UID of app - * @param tag TAG of interest. Use {@link NetworkStats.Bucket#TAG_NONE} for no tags. + * @param tag TAG of interest. Use {@link NetworkStats.Bucket#TAG_NONE} for aggregated data + * across all the tags. * @param state state of interest. Use {@link NetworkStats.Bucket#STATE_ALL} to aggregate * traffic from all states. * @return Statistics object or null if an error happened during statistics collection. @@ -514,21 +569,52 @@ public class NetworkStatsManager { return queryDetailsForUidTagState(template, startTime, endTime, uid, tag, state); } - /** @hide */ - public NetworkStats queryDetailsForUidTagState(NetworkTemplate template, + /** + * Query network usage statistics details for a given template, uid, tag, and state. + * + * Only usable for uids belonging to calling user. Result is not aggregated over time. + * This means buckets' start and end timestamps are going to be between 'startTime' and + * 'endTime' parameters. The uid is going to be the same as the 'uid' parameter, the tag + * the same as the 'tag' parameter, and the state the same as the 'state' parameter. + * defaultNetwork is going to be {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL}, + * metered is going to be {@link NetworkStats.Bucket#METERED_ALL}, and + * roaming is going to be {@link NetworkStats.Bucket#ROAMING_ALL}. + * <p>Only includes buckets that atomically occur in the inclusive time range. Doesn't + * interpolate across partial buckets. Since bucket length is in the order of hours, this + * method cannot be used to measure data usage on a fine grained time scale. + * This may take a long time, and apps should avoid calling this on their main thread. + * + * @param template Template used to match networks. See {@link NetworkTemplate}. + * @param startTime Start of period, in milliseconds since the Unix epoch, see + * {@link java.lang.System#currentTimeMillis}. + * @param endTime End of period, in milliseconds since the Unix epoch, see + * {@link java.lang.System#currentTimeMillis}. + * @param uid UID of app + * @param tag TAG of interest. Use {@link NetworkStats.Bucket#TAG_NONE} for aggregated data + * across all the tags. + * @param state state of interest. Use {@link NetworkStats.Bucket#STATE_ALL} to aggregate + * traffic from all states. + * @return Statistics which is described above. + * @hide + */ + @NonNull + @SystemApi(client = MODULE_LIBRARIES) + @WorkerThread + public NetworkStats queryDetailsForUidTagState(@NonNull NetworkTemplate template, long startTime, long endTime, int uid, int tag, int state) throws SecurityException { - - NetworkStats result; + Objects.requireNonNull(template); try { - result = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService); - result.startHistoryEnumeration(uid, tag, state); + final NetworkStats result = new NetworkStats( + mContext, template, mFlags, startTime, endTime, mService); + result.startHistoryUidEnumeration(uid, tag, state); + return result; } catch (RemoteException e) { Log.e(TAG, "Error while querying stats for uid=" + uid + " tag=" + tag + " state=" + state, e); - return null; + e.rethrowFromSystemServer(); } - return result; + return null; // To make the compiler happy. } /** @@ -585,26 +671,49 @@ public class NetworkStatsManager { } /** - * Query realtime network usage statistics details with interfaces constrains. - * Return snapshot of current UID statistics, including any {@link TrafficStats#UID_TETHERING}, - * video calling data usage and count of network operations that set by - * {@link TrafficStats#incrementOperationCount}. The returned data doesn't include any - * statistics that is reported by {@link NetworkStatsProvider}. + * Query realtime mobile network usage statistics. + * + * Return a snapshot of current UID network statistics, as it applies + * to the mobile radios of the device. The snapshot will include any + * tethering traffic, video calling data usage and count of + * network operations set by {@link TrafficStats#incrementOperationCount} + * made over a mobile radio. + * The snapshot will not include any statistics that cannot be seen by + * the kernel, e.g. statistics reported by {@link NetworkStatsProvider}s. + * + * @hide + */ + @SystemApi + @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) + @NonNull public android.net.NetworkStats getMobileUidStats() { + try { + return mService.getUidStatsForTransport(TRANSPORT_CELLULAR); + } catch (RemoteException e) { + if (DBG) Log.d(TAG, "Remote exception when get Mobile uid stats"); + throw e.rethrowFromSystemServer(); + } + } + + /** + * Query realtime Wi-Fi network usage statistics. * - * @param requiredIfaces A list of interfaces the stats should be restricted to, or - * {@link NetworkStats#INTERFACES_ALL}. + * Return a snapshot of current UID network statistics, as it applies + * to the Wi-Fi radios of the device. The snapshot will include any + * tethering traffic, video calling data usage and count of + * network operations set by {@link TrafficStats#incrementOperationCount} + * made over a Wi-Fi radio. + * The snapshot will not include any statistics that cannot be seen by + * the kernel, e.g. statistics reported by {@link NetworkStatsProvider}s. * * @hide */ - //@SystemApi + @SystemApi @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) - @NonNull public android.net.NetworkStats getDetailedUidStats( - @NonNull Set<String> requiredIfaces) { - Objects.requireNonNull(requiredIfaces, "requiredIfaces cannot be null"); + @NonNull public android.net.NetworkStats getWifiUidStats() { try { - return mService.getDetailedUidStats(requiredIfaces.toArray(new String[0])); + return mService.getUidStatsForTransport(TRANSPORT_WIFI); } catch (RemoteException e) { - if (DBG) Log.d(TAG, "Remote exception when get detailed uid stats"); + if (DBG) Log.d(TAG, "Remote exception when get WiFi uid stats"); throw e.rethrowFromSystemServer(); } } diff --git a/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl index a4babb543dbd..da0aa99b9370 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl +++ b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl @@ -49,14 +49,8 @@ interface INetworkStatsService { @UnsupportedAppUsage NetworkStats getDataLayerSnapshotForUid(int uid); - /** Get a detailed snapshot of stats since boot for all UIDs. - * - * <p>Results will not always be limited to stats on requiredIfaces when specified: stats for - * interfaces stacked on the specified interfaces, or for interfaces on which the specified - * interfaces are stacked on, will also be included. - * @param requiredIfaces Interface names to get data for, or {@link NetworkStats#INTERFACES_ALL}. - */ - NetworkStats getDetailedUidStats(in String[] requiredIfaces); + /** Get the transport NetworkStats for all UIDs since boot. */ + NetworkStats getUidStatsForTransport(int transport); /** Return set of any ifaces associated with mobile networks since boot. */ @UnsupportedAppUsage diff --git a/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsSession.aidl b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsSession.aidl index babe0bfb9760..ab70be826f8e 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsSession.aidl +++ b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsSession.aidl @@ -32,6 +32,11 @@ interface INetworkStatsSession { /** Return historical network layer stats for traffic that matches template. */ @UnsupportedAppUsage NetworkStatsHistory getHistoryForNetwork(in NetworkTemplate template, int fields); + /** + * Return historical network layer stats for traffic that matches template, start and end + * timestamp. + */ + NetworkStatsHistory getHistoryIntervalForNetwork(in NetworkTemplate template, int fields, long start, long end); /** * Return network layer usage summary per UID for traffic that matches template. diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java index 04d1d6885186..9b9d38a36066 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java @@ -17,17 +17,24 @@ package android.net; import static android.net.ConnectivityManager.TYPE_WIFI; +import static android.net.NetworkTemplate.NETWORK_TYPE_ALL; +import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.content.Context; import android.net.wifi.WifiInfo; import android.service.NetworkIdentityProto; -import android.telephony.Annotation.NetworkType; +import android.telephony.Annotation; +import android.telephony.TelephonyManager; import android.util.proto.ProtoOutputStream; import com.android.net.module.util.NetworkCapabilitiesUtils; import com.android.net.module.util.NetworkIdentityUtils; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Objects; @@ -37,11 +44,24 @@ import java.util.Objects; * * @hide */ +// @SystemApi(client = MODULE_LIBRARIES) public class NetworkIdentity implements Comparable<NetworkIdentity> { private static final String TAG = "NetworkIdentity"; + /** @hide */ + // TODO: Remove this after migrating all callers to use + // {@link NetworkTemplate#NETWORK_TYPE_ALL} instead. public static final int SUBTYPE_COMBINED = -1; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "OEM_MANAGED_" }, value = { + NetworkTemplate.OEM_MANAGED_NO, + NetworkTemplate.OEM_MANAGED_PAID, + NetworkTemplate.OEM_MANAGED_PRIVATE + }) + public @interface OemManaged{} + /** * Network has no {@code NetworkCapabilities#NET_CAPABILITY_OEM_*}. * @hide @@ -59,21 +79,22 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { public static final int OEM_PRIVATE = 0x2; final int mType; - final int mSubType; + final int mRatType; final String mSubscriberId; - final String mNetworkId; + final String mWifiNetworkKey; final boolean mRoaming; final boolean mMetered; final boolean mDefaultNetwork; final int mOemManaged; + /** @hide */ public NetworkIdentity( - int type, int subType, String subscriberId, String networkId, boolean roaming, - boolean metered, boolean defaultNetwork, int oemManaged) { + int type, int ratType, @Nullable String subscriberId, @Nullable String wifiNetworkKey, + boolean roaming, boolean metered, boolean defaultNetwork, int oemManaged) { mType = type; - mSubType = subType; + mRatType = ratType; mSubscriberId = subscriberId; - mNetworkId = networkId; + mWifiNetworkKey = wifiNetworkKey; mRoaming = roaming; mMetered = metered; mDefaultNetwork = defaultNetwork; @@ -82,7 +103,7 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { @Override public int hashCode() { - return Objects.hash(mType, mSubType, mSubscriberId, mNetworkId, mRoaming, mMetered, + return Objects.hash(mType, mRatType, mSubscriberId, mWifiNetworkKey, mRoaming, mMetered, mDefaultNetwork, mOemManaged); } @@ -90,9 +111,9 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { public boolean equals(@Nullable Object obj) { if (obj instanceof NetworkIdentity) { final NetworkIdentity ident = (NetworkIdentity) obj; - return mType == ident.mType && mSubType == ident.mSubType && mRoaming == ident.mRoaming + return mType == ident.mType && mRatType == ident.mRatType && mRoaming == ident.mRoaming && Objects.equals(mSubscriberId, ident.mSubscriberId) - && Objects.equals(mNetworkId, ident.mNetworkId) + && Objects.equals(mWifiNetworkKey, ident.mWifiNetworkKey) && mMetered == ident.mMetered && mDefaultNetwork == ident.mDefaultNetwork && mOemManaged == ident.mOemManaged; @@ -104,18 +125,18 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { public String toString() { final StringBuilder builder = new StringBuilder("{"); builder.append("type=").append(mType); - builder.append(", subType="); - if (mSubType == SUBTYPE_COMBINED) { + builder.append(", ratType="); + if (mRatType == NETWORK_TYPE_ALL) { builder.append("COMBINED"); } else { - builder.append(mSubType); + builder.append(mRatType); } if (mSubscriberId != null) { builder.append(", subscriberId=") .append(NetworkIdentityUtils.scrubSubscriberId(mSubscriberId)); } - if (mNetworkId != null) { - builder.append(", networkId=").append(mNetworkId); + if (mWifiNetworkKey != null) { + builder.append(", wifiNetworkKey=").append(mWifiNetworkKey); } if (mRoaming) { builder.append(", ROAMING"); @@ -153,12 +174,13 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { } } + /** @hide */ public void dumpDebug(ProtoOutputStream proto, long tag) { final long start = proto.start(tag); proto.write(NetworkIdentityProto.TYPE, mType); - // Not dumping mSubType, subtypes are no longer supported. + // TODO: dump mRatType as well. proto.write(NetworkIdentityProto.ROAMING, mRoaming); proto.write(NetworkIdentityProto.METERED, mMetered); @@ -168,77 +190,95 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { proto.end(start); } + /** Get the network type of this instance. */ public int getType() { return mType; } - public int getSubType() { - return mSubType; + /** Get the Radio Access Technology(RAT) type of this instance. */ + public int getRatType() { + return mRatType; } + /** Get the Subscriber Id of this instance. */ + @Nullable public String getSubscriberId() { return mSubscriberId; } - public String getNetworkId() { - return mNetworkId; + /** Get the Wifi Network Key of this instance. See {@link WifiInfo#getCurrentNetworkKey()}. */ + @Nullable + public String getWifiNetworkKey() { + return mWifiNetworkKey; } + /** @hide */ + // TODO: Remove this function after all callers are removed. public boolean getRoaming() { return mRoaming; } + /** Return the roaming status of this instance. */ + public boolean isRoaming() { + return mRoaming; + } + + /** @hide */ + // TODO: Remove this function after all callers are removed. public boolean getMetered() { return mMetered; } + /** Return the meteredness of this instance. */ + public boolean isMetered() { + return mMetered; + } + + /** @hide */ + // TODO: Remove this function after all callers are removed. public boolean getDefaultNetwork() { return mDefaultNetwork; } + /** Return the default network status of this instance. */ + public boolean isDefaultNetwork() { + return mDefaultNetwork; + } + + /** Get the OEM managed type of this instance. */ public int getOemManaged() { return mOemManaged; } /** - * Build a {@link NetworkIdentity} from the given {@link NetworkStateSnapshot} and - * {@code subType}, assuming that any mobile networks are using the current IMSI. - * The subType if applicable, should be set as one of the TelephonyManager.NETWORK_TYPE_* - * constants, or {@link android.telephony.TelephonyManager#NETWORK_TYPE_UNKNOWN} if not. + * Assemble a {@link NetworkIdentity} from the passed arguments. + * + * This methods builds an identity based on the capabilities of the network in the + * snapshot and other passed arguments. The identity is used as a key to record data usage. + * + * @param snapshot the snapshot of network state. See {@link NetworkStateSnapshot}. + * @param defaultNetwork whether the network is a default network. + * @param ratType the Radio Access Technology(RAT) type of the network. Or + * {@link TelephonyManager#NETWORK_TYPE_UNKNOWN} if not applicable. + * See {@code TelephonyManager.NETWORK_TYPE_*}. + * @hide + * @deprecated See {@link NetworkIdentity#Builder}. */ + // TODO: Remove this after all callers are migrated to use new Api. + @Deprecated + @NonNull public static NetworkIdentity buildNetworkIdentity(Context context, - NetworkStateSnapshot snapshot, boolean defaultNetwork, @NetworkType int subType) { - final int legacyType = snapshot.getLegacyType(); - - final String subscriberId = snapshot.getSubscriberId(); - String networkId = null; - boolean roaming = !snapshot.getNetworkCapabilities().hasCapability( - NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING); - boolean metered = !(snapshot.getNetworkCapabilities().hasCapability( - NetworkCapabilities.NET_CAPABILITY_NOT_METERED) - || snapshot.getNetworkCapabilities().hasCapability( - NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED)); - - final int oemManaged = getOemBitfield(snapshot.getNetworkCapabilities()); - - if (legacyType == TYPE_WIFI) { - final TransportInfo transportInfo = snapshot.getNetworkCapabilities() - .getTransportInfo(); - if (transportInfo instanceof WifiInfo) { - final WifiInfo info = (WifiInfo) transportInfo; - networkId = info != null ? info.getCurrentNetworkKey() : null; - } - } - - return new NetworkIdentity(legacyType, subType, subscriberId, networkId, roaming, metered, - defaultNetwork, oemManaged); + @NonNull NetworkStateSnapshot snapshot, + boolean defaultNetwork, @Annotation.NetworkType int ratType) { + return new NetworkIdentity.Builder().setNetworkStateSnapshot(snapshot) + .setDefaultNetwork(defaultNetwork).setRatType(ratType).build(); } /** * Builds a bitfield of {@code NetworkIdentity.OEM_*} based on {@link NetworkCapabilities}. * @hide */ - public static int getOemBitfield(NetworkCapabilities nc) { + public static int getOemBitfield(@NonNull NetworkCapabilities nc) { int oemManaged = OEM_NONE; if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID)) { @@ -252,16 +292,17 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { } @Override - public int compareTo(NetworkIdentity another) { + public int compareTo(@NonNull NetworkIdentity another) { + Objects.requireNonNull(another); int res = Integer.compare(mType, another.mType); if (res == 0) { - res = Integer.compare(mSubType, another.mSubType); + res = Integer.compare(mRatType, another.mRatType); } if (res == 0 && mSubscriberId != null && another.mSubscriberId != null) { res = mSubscriberId.compareTo(another.mSubscriberId); } - if (res == 0 && mNetworkId != null && another.mNetworkId != null) { - res = mNetworkId.compareTo(another.mNetworkId); + if (res == 0 && mWifiNetworkKey != null && another.mWifiNetworkKey != null) { + res = mWifiNetworkKey.compareTo(another.mWifiNetworkKey); } if (res == 0) { res = Boolean.compare(mRoaming, another.mRoaming); @@ -277,4 +318,192 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { } return res; } + + /** + * Builder class for {@link NetworkIdentity}. + */ + public static final class Builder { + private int mType; + private int mRatType; + private String mSubscriberId; + private String mWifiNetworkKey; + private boolean mRoaming; + private boolean mMetered; + private boolean mDefaultNetwork; + private int mOemManaged; + + /** + * Creates a new Builder. + */ + public Builder() { + // Initialize with default values. Will be overwritten by setters. + mType = ConnectivityManager.TYPE_NONE; + mRatType = NetworkTemplate.NETWORK_TYPE_ALL; + mSubscriberId = null; + mWifiNetworkKey = null; + mRoaming = false; + mMetered = false; + mDefaultNetwork = false; + mOemManaged = NetworkTemplate.OEM_MANAGED_NO; + } + + /** + * Add an {@link NetworkStateSnapshot} into the {@link NetworkIdentity} instance. + * This is to read roaming, metered, wifikey... from the snapshot for convenience. + * + * @param snapshot The target {@link NetworkStateSnapshot} object. + * @return The builder object. + */ + @SuppressLint("MissingGetterMatchingBuilder") + @NonNull + public Builder setNetworkStateSnapshot(@NonNull NetworkStateSnapshot snapshot) { + setType(snapshot.getLegacyType()); + + setSubscriberId(snapshot.getSubscriberId()); + setRoaming(!snapshot.getNetworkCapabilities().hasCapability( + NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)); + setMetered(!(snapshot.getNetworkCapabilities().hasCapability( + NetworkCapabilities.NET_CAPABILITY_NOT_METERED) + || snapshot.getNetworkCapabilities().hasCapability( + NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED))); + + setOemManaged(getOemBitfield(snapshot.getNetworkCapabilities())); + + if (mType == TYPE_WIFI) { + final TransportInfo transportInfo = snapshot.getNetworkCapabilities() + .getTransportInfo(); + if (transportInfo instanceof WifiInfo) { + final WifiInfo info = (WifiInfo) transportInfo; + if (info != null) { + setWifiNetworkKey(info.getCurrentNetworkKey()); + } + } + } + return this; + } + + /** + * Set the network type of the network. + * + * @param type the network type. See {@link ConnectivityManager#TYPE_*}. + * + * @return this builder. + */ + @NonNull + public Builder setType(int type) { + mType = type; + return this; + } + + /** + * Set the Radio Access Technology(RAT) type of the network. + * + * @param ratType the Radio Access Technology(RAT) type if applicable. See + * {@code TelephonyManager.NETWORK_TYPE_*}. + * + * @return this builder. + */ + @NonNull + public Builder setRatType(@Annotation.NetworkType int ratType) { + mRatType = ratType; + return this; + } + + /** + * Clear the Radio Access Technology(RAT) type of the network. + * + * @return this builder. + */ + @NonNull + public Builder clearRatType() { + mRatType = NetworkTemplate.NETWORK_TYPE_ALL; + return this; + } + + /** + * Set the Subscriber Id. + * + * @param subscriberId the Subscriber Id of the network. Or null if not applicable. + * @return this builder. + */ + @NonNull + public Builder setSubscriberId(@Nullable String subscriberId) { + mSubscriberId = subscriberId; + return this; + } + + /** + * Set the Wifi Network Key. + * + * @param wifiNetworkKey Wifi Network Key of the network, + * see {@link WifiInfo#getCurrentNetworkKey()}. + * Or null if not applicable. + * @return this builder. + */ + @NonNull + public Builder setWifiNetworkKey(@Nullable String wifiNetworkKey) { + mWifiNetworkKey = wifiNetworkKey; + return this; + } + + /** + * Set the roaming. + * + * @param roaming the roaming status of the network. + * @return this builder. + */ + @NonNull + public Builder setRoaming(boolean roaming) { + mRoaming = roaming; + return this; + } + + /** + * Set the meteredness. + * + * @param metered the meteredness of the network. + * @return this builder. + */ + @NonNull + public Builder setMetered(boolean metered) { + mMetered = metered; + return this; + } + + /** + * Set the default network status. + * + * @param defaultNetwork the default network status of the network. + * @return this builder. + */ + @NonNull + public Builder setDefaultNetwork(boolean defaultNetwork) { + mDefaultNetwork = defaultNetwork; + return this; + } + + /** + * Set the OEM managed type. + * + * @param oemManaged Type of OEM managed network or unmanaged networks. + * See {@code NetworkTemplate#OEM_MANAGED_*}. + * @return this builder. + */ + @NonNull + public Builder setOemManaged(@OemManaged int oemManaged) { + mOemManaged = oemManaged; + return this; + } + + /** + * Builds the instance of the {@link NetworkIdentity}. + * + * @return the built instance of {@link NetworkIdentity}. + */ + @NonNull + public NetworkIdentity build() { + return new NetworkIdentity(mType, mRatType, mSubscriberId, mWifiNetworkKey, + mRoaming, mMetered, mDefaultNetwork, mOemManaged); + } + } } diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentitySet.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentitySet.java index abbebef85c8f..041f070512b0 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentitySet.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentitySet.java @@ -18,6 +18,7 @@ package android.net; import static android.net.ConnectivityManager.TYPE_MOBILE; +import android.annotation.NonNull; import android.service.NetworkIdentitySetProto; import android.util.proto.ProtoOutputStream; @@ -25,6 +26,7 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.util.HashSet; +import java.util.Objects; /** * Identity of a {@code iface}, defined by the set of {@link NetworkIdentity} @@ -32,6 +34,7 @@ import java.util.HashSet; * * @hide */ +// @SystemApi(client = MODULE_LIBRARIES) public class NetworkIdentitySet extends HashSet<NetworkIdentity> implements Comparable<NetworkIdentitySet> { private static final int VERSION_INIT = 1; @@ -41,9 +44,14 @@ public class NetworkIdentitySet extends HashSet<NetworkIdentity> implements private static final int VERSION_ADD_DEFAULT_NETWORK = 5; private static final int VERSION_ADD_OEM_MANAGED_NETWORK = 6; + /** + * Construct a {@link NetworkIdentitySet} object. + */ public NetworkIdentitySet() { + super(); } + /** @hide */ public NetworkIdentitySet(DataInput in) throws IOException { final int version = in.readInt(); final int size = in.readInt(); @@ -52,7 +60,7 @@ public class NetworkIdentitySet extends HashSet<NetworkIdentity> implements final int ignored = in.readInt(); } final int type = in.readInt(); - final int subType = in.readInt(); + final int ratType = in.readInt(); final String subscriberId = readOptionalString(in); final String networkId; if (version >= VERSION_ADD_NETWORK_ID) { @@ -91,63 +99,73 @@ public class NetworkIdentitySet extends HashSet<NetworkIdentity> implements oemNetCapabilities = NetworkIdentity.OEM_NONE; } - add(new NetworkIdentity(type, subType, subscriberId, networkId, roaming, metered, + add(new NetworkIdentity(type, ratType, subscriberId, networkId, roaming, metered, defaultNetwork, oemNetCapabilities)); } } /** * Method to serialize this object into a {@code DataOutput}. + * @hide */ public void writeToStream(DataOutput out) throws IOException { out.writeInt(VERSION_ADD_OEM_MANAGED_NETWORK); out.writeInt(size()); for (NetworkIdentity ident : this) { out.writeInt(ident.getType()); - out.writeInt(ident.getSubType()); + out.writeInt(ident.getRatType()); writeOptionalString(out, ident.getSubscriberId()); - writeOptionalString(out, ident.getNetworkId()); - out.writeBoolean(ident.getRoaming()); - out.writeBoolean(ident.getMetered()); - out.writeBoolean(ident.getDefaultNetwork()); + writeOptionalString(out, ident.getWifiNetworkKey()); + out.writeBoolean(ident.isRoaming()); + out.writeBoolean(ident.isMetered()); + out.writeBoolean(ident.isDefaultNetwork()); out.writeInt(ident.getOemManaged()); } } - /** @return whether any {@link NetworkIdentity} in this set is considered metered. */ + /** + * @return whether any {@link NetworkIdentity} in this set is considered metered. + * @hide + */ public boolean isAnyMemberMetered() { if (isEmpty()) { return false; } for (NetworkIdentity ident : this) { - if (ident.getMetered()) { + if (ident.isMetered()) { return true; } } return false; } - /** @return whether any {@link NetworkIdentity} in this set is considered roaming. */ + /** + * @return whether any {@link NetworkIdentity} in this set is considered roaming. + * @hide + */ public boolean isAnyMemberRoaming() { if (isEmpty()) { return false; } for (NetworkIdentity ident : this) { - if (ident.getRoaming()) { + if (ident.isRoaming()) { return true; } } return false; } - /** @return whether any {@link NetworkIdentity} in this set is considered on the default - network. */ + /** + * @return whether any {@link NetworkIdentity} in this set is considered on the default + * network. + * @hide + */ public boolean areAllMembersOnDefaultNetwork() { if (isEmpty()) { return true; } for (NetworkIdentity ident : this) { - if (!ident.getDefaultNetwork()) { + if (!ident.isDefaultNetwork()) { return false; } } @@ -172,7 +190,8 @@ public class NetworkIdentitySet extends HashSet<NetworkIdentity> implements } @Override - public int compareTo(NetworkIdentitySet another) { + public int compareTo(@NonNull NetworkIdentitySet another) { + Objects.requireNonNull(another); if (isEmpty()) return -1; if (another.isEmpty()) return 1; @@ -183,6 +202,7 @@ public class NetworkIdentitySet extends HashSet<NetworkIdentity> implements /** * Method to dump this object into proto debug file. + * @hide */ public void dumpDebug(ProtoOutputStream proto, long tag) { final long start = proto.start(tag); diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java index 9f9d73f88851..f169fed6b9b3 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java @@ -32,6 +32,8 @@ import static android.text.format.DateUtils.WEEK_IN_MILLIS; import static com.android.net.module.util.NetworkStatsUtils.multiplySafeByRational; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Binder; import android.service.NetworkStatsCollectionKeyProto; import android.service.NetworkStatsCollectionProto; @@ -77,6 +79,7 @@ import java.util.Objects; * * @hide */ +// @SystemApi(client = MODULE_LIBRARIES) public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.Writer { private static final String TAG = NetworkStatsCollection.class.getSimpleName(); /** File header magic number: "ANET" */ @@ -100,15 +103,23 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W private long mTotalBytes; private boolean mDirty; + /** + * Construct a {@link NetworkStatsCollection} object. + * + * @param bucketDuration duration of the buckets in this object, in milliseconds. + * @hide + */ public NetworkStatsCollection(long bucketDuration) { mBucketDuration = bucketDuration; reset(); } + /** @hide */ public void clear() { reset(); } + /** @hide */ public void reset() { mStats.clear(); mStartMillis = Long.MAX_VALUE; @@ -117,6 +128,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W mDirty = false; } + /** @hide */ public long getStartMillis() { return mStartMillis; } @@ -124,6 +136,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W /** * Return first atomic bucket in this collection, which is more conservative * than {@link #mStartMillis}. + * @hide */ public long getFirstAtomicBucketMillis() { if (mStartMillis == Long.MAX_VALUE) { @@ -133,26 +146,32 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W } } + /** @hide */ public long getEndMillis() { return mEndMillis; } + /** @hide */ public long getTotalBytes() { return mTotalBytes; } + /** @hide */ public boolean isDirty() { return mDirty; } + /** @hide */ public void clearDirty() { mDirty = false; } + /** @hide */ public boolean isEmpty() { return mStartMillis == Long.MAX_VALUE && mEndMillis == Long.MIN_VALUE; } + /** @hide */ @VisibleForTesting public long roundUp(long time) { if (time == Long.MIN_VALUE || time == Long.MAX_VALUE @@ -168,6 +187,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W } } + /** @hide */ @VisibleForTesting public long roundDown(long time) { if (time == Long.MIN_VALUE || time == Long.MAX_VALUE @@ -182,10 +202,12 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W } } + /** @hide */ public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel) { return getRelevantUids(accessLevel, Binder.getCallingUid()); } + /** @hide */ public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel, final int callerUid) { final ArrayList<Integer> uids = new ArrayList<>(); @@ -206,6 +228,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W /** * Combine all {@link NetworkStatsHistory} in this collection which match * the requested parameters. + * @hide */ public NetworkStatsHistory getHistory(NetworkTemplate template, SubscriptionPlan augmentPlan, int uid, int set, int tag, int fields, long start, long end, @@ -331,6 +354,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W * @param end - end of the range, timestamp in milliseconds since the epoch. * @param accessLevel - caller access level. * @param callerUid - caller UID. + * @hide */ public NetworkStats getSummary(NetworkTemplate template, long start, long end, @NetworkStatsAccess.Level int accessLevel, int callerUid) { @@ -377,6 +401,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W /** * Record given {@link android.net.NetworkStats.Entry} into this collection. + * @hide */ public void recordData(NetworkIdentitySet ident, int uid, int set, int tag, long start, long end, NetworkStats.Entry entry) { @@ -387,8 +412,12 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W /** * Record given {@link NetworkStatsHistory} into this collection. + * + * @hide */ - private void recordHistory(Key key, NetworkStatsHistory history) { + public void recordHistory(@NonNull Key key, @NonNull NetworkStatsHistory history) { + Objects.requireNonNull(key); + Objects.requireNonNull(history); if (history.size() == 0) return; noteRecordedHistory(history.getStart(), history.getEnd(), history.getTotalBytes()); @@ -403,8 +432,11 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W /** * Record all {@link NetworkStatsHistory} contained in the given collection * into this collection. + * + * @hide */ - public void recordCollection(NetworkStatsCollection another) { + public void recordCollection(@NonNull NetworkStatsCollection another) { + Objects.requireNonNull(another); for (int i = 0; i < another.mStats.size(); i++) { final Key key = another.mStats.keyAt(i); final NetworkStatsHistory value = another.mStats.valueAt(i); @@ -433,6 +465,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W } } + /** @hide */ @Override public void read(InputStream in) throws IOException { read((DataInput) new DataInputStream(in)); @@ -472,6 +505,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W } } + /** @hide */ @Override public void write(OutputStream out) throws IOException { write((DataOutput) new DataOutputStream(out)); @@ -514,6 +548,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W * See {@code NetworkStatsService#maybeUpgradeLegacyStatsLocked}. * * @deprecated + * @hide */ @Deprecated public void readLegacyNetwork(File file) throws IOException { @@ -559,6 +594,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W * See {@code NetworkStatsService#maybeUpgradeLegacyStatsLocked}. * * @deprecated + * @hide */ @Deprecated public void readLegacyUid(File file, boolean onlyTags) throws IOException { @@ -629,6 +665,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W * Remove any {@link NetworkStatsHistory} attributed to the requested UID, * moving any {@link NetworkStats#TAG_NONE} series to * {@link TrafficStats#UID_REMOVED}. + * @hide */ public void removeUids(int[] uids) { final ArrayList<Key> knownKeys = new ArrayList<>(); @@ -669,6 +706,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W return keys; } + /** @hide */ public void dump(IndentingPrintWriter pw) { for (Key key : getSortedKeys()) { pw.print("ident="); pw.print(key.ident.toString()); @@ -683,6 +721,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W } } + /** @hide */ public void dumpDebug(ProtoOutputStream proto, long tag) { final long start = proto.start(tag); @@ -706,6 +745,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W proto.end(start); } + /** @hide */ public void dumpCheckin(PrintWriter pw, long start, long end) { dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateMobileWildcard(), "cell"); dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateWifiWildcard(), "wifi"); @@ -768,16 +808,32 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W return false; } - private static class Key implements Comparable<Key> { + /** + * the identifier that associate with the {@link NetworkStatsHistory} object to identify + * a certain record in the {@link NetworkStatsCollection} object. + */ + public static class Key implements Comparable<Key> { + /** @hide */ public final NetworkIdentitySet ident; + /** @hide */ public final int uid; + /** @hide */ public final int set; + /** @hide */ public final int tag; private final int mHashCode; - Key(NetworkIdentitySet ident, int uid, int set, int tag) { - this.ident = ident; + /** + * Construct a {@link Key} object. + * + * @param ident a Set of {@link NetworkIdentity} that associated with the record. + * @param uid Uid of the record. + * @param set Set of the record, see {@code NetworkStats#SET_*}. + * @param tag Tag of the record, see {@link TrafficStats#setThreadStatsTag(int)}. + */ + public Key(@NonNull NetworkIdentitySet ident, int uid, int set, int tag) { + this.ident = Objects.requireNonNull(ident); this.uid = uid; this.set = set; this.tag = tag; @@ -790,7 +846,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof Key) { final Key key = (Key) obj; return uid == key.uid && set == key.set && tag == key.tag @@ -800,7 +856,8 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W } @Override - public int compareTo(Key another) { + public int compareTo(@NonNull Key another) { + Objects.requireNonNull(another); int res = 0; if (ident != null && another.ident != null) { res = ident.compareTo(another.ident); diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.java index 428bc6df266a..90054c683de5 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.java @@ -30,6 +30,7 @@ import static android.text.format.DateUtils.SECOND_IN_MILLIS; import static com.android.net.module.util.NetworkStatsUtils.multiplySafeByRational; +import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Parcel; @@ -64,18 +65,25 @@ import java.util.Random; * * @hide */ -public class NetworkStatsHistory implements Parcelable { +// @SystemApi(client = MODULE_LIBRARIES) +public final class NetworkStatsHistory implements Parcelable { private static final int VERSION_INIT = 1; private static final int VERSION_ADD_PACKETS = 2; private static final int VERSION_ADD_ACTIVE = 3; + /** @hide */ public static final int FIELD_ACTIVE_TIME = 0x01; + /** @hide */ public static final int FIELD_RX_BYTES = 0x02; + /** @hide */ public static final int FIELD_RX_PACKETS = 0x04; + /** @hide */ public static final int FIELD_TX_BYTES = 0x08; + /** @hide */ public static final int FIELD_TX_PACKETS = 0x10; + /** @hide */ public static final int FIELD_OPERATIONS = 0x20; - + /** @hide */ public static final int FIELD_ALL = 0xFFFFFFFF; private long bucketDuration; @@ -108,15 +116,18 @@ public class NetworkStatsHistory implements Parcelable { public long operations; } + /** @hide */ @UnsupportedAppUsage public NetworkStatsHistory(long bucketDuration) { this(bucketDuration, 10, FIELD_ALL); } + /** @hide */ public NetworkStatsHistory(long bucketDuration, int initialSize) { this(bucketDuration, initialSize, FIELD_ALL); } + /** @hide */ public NetworkStatsHistory(long bucketDuration, int initialSize, int fields) { this.bucketDuration = bucketDuration; bucketStart = new long[initialSize]; @@ -130,11 +141,13 @@ public class NetworkStatsHistory implements Parcelable { totalBytes = 0; } + /** @hide */ public NetworkStatsHistory(NetworkStatsHistory existing, long bucketDuration) { this(bucketDuration, existing.estimateResizeBuckets(bucketDuration)); recordEntireHistory(existing); } + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public NetworkStatsHistory(Parcel in) { bucketDuration = in.readLong(); @@ -150,7 +163,7 @@ public class NetworkStatsHistory implements Parcelable { } @Override - public void writeToParcel(Parcel out, int flags) { + public void writeToParcel(@NonNull Parcel out, int flags) { out.writeLong(bucketDuration); writeLongArray(out, bucketStart, bucketCount); writeLongArray(out, activeTime, bucketCount); @@ -162,6 +175,7 @@ public class NetworkStatsHistory implements Parcelable { out.writeLong(totalBytes); } + /** @hide */ public NetworkStatsHistory(DataInput in) throws IOException { final int version = in.readInt(); switch (version) { @@ -204,6 +218,7 @@ public class NetworkStatsHistory implements Parcelable { } } + /** @hide */ public void writeToStream(DataOutput out) throws IOException { out.writeInt(VERSION_ADD_ACTIVE); out.writeLong(bucketDuration); @@ -221,15 +236,18 @@ public class NetworkStatsHistory implements Parcelable { return 0; } + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public int size() { return bucketCount; } + /** @hide */ public long getBucketDuration() { return bucketDuration; } + /** @hide */ @UnsupportedAppUsage public long getStart() { if (bucketCount > 0) { @@ -239,6 +257,7 @@ public class NetworkStatsHistory implements Parcelable { } } + /** @hide */ @UnsupportedAppUsage public long getEnd() { if (bucketCount > 0) { @@ -250,6 +269,7 @@ public class NetworkStatsHistory implements Parcelable { /** * Return total bytes represented by this history. + * @hide */ public long getTotalBytes() { return totalBytes; @@ -258,6 +278,7 @@ public class NetworkStatsHistory implements Parcelable { /** * Return index of bucket that contains or is immediately before the * requested time. + * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public int getIndexBefore(long time) { @@ -273,6 +294,7 @@ public class NetworkStatsHistory implements Parcelable { /** * Return index of bucket that contains or is immediately after the * requested time. + * @hide */ public int getIndexAfter(long time) { int index = Arrays.binarySearch(bucketStart, 0, bucketCount, time); @@ -286,6 +308,7 @@ public class NetworkStatsHistory implements Parcelable { /** * Return specific stats entry. + * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public Entry getValues(int i, Entry recycle) { @@ -301,6 +324,7 @@ public class NetworkStatsHistory implements Parcelable { return entry; } + /** @hide */ public void setValues(int i, Entry entry) { // Unwind old values if (rxBytes != null) totalBytes -= rxBytes[i]; @@ -322,6 +346,7 @@ public class NetworkStatsHistory implements Parcelable { /** * Record that data traffic occurred in the given time range. Will * distribute across internal buckets, creating new buckets as needed. + * @hide */ @Deprecated public void recordData(long start, long end, long rxBytes, long txBytes) { @@ -332,6 +357,7 @@ public class NetworkStatsHistory implements Parcelable { /** * Record that data traffic occurred in the given time range. Will * distribute across internal buckets, creating new buckets as needed. + * @hide */ public void recordData(long start, long end, NetworkStats.Entry entry) { long rxBytes = entry.rxBytes; @@ -392,6 +418,7 @@ public class NetworkStatsHistory implements Parcelable { /** * Record an entire {@link NetworkStatsHistory} into this history. Usually * for combining together stats for external reporting. + * @hide */ @UnsupportedAppUsage public void recordEntireHistory(NetworkStatsHistory input) { @@ -402,6 +429,7 @@ public class NetworkStatsHistory implements Parcelable { * Record given {@link NetworkStatsHistory} into this history, copying only * buckets that atomically occur in the inclusive time range. Doesn't * interpolate across partial buckets. + * @hide */ public void recordHistory(NetworkStatsHistory input, long start, long end) { final NetworkStats.Entry entry = new NetworkStats.Entry( @@ -483,6 +511,7 @@ public class NetworkStatsHistory implements Parcelable { /** * Clear all data stored in this object. + * @hide */ public void clear() { bucketStart = EmptyArray.LONG; @@ -498,9 +527,10 @@ public class NetworkStatsHistory implements Parcelable { /** * Remove buckets older than requested cutoff. + * @hide */ - @Deprecated public void removeBucketsBefore(long cutoff) { + // TODO: Consider use getIndexBefore. int i; for (i = 0; i < bucketCount; i++) { final long curStart = bucketStart[i]; @@ -522,7 +552,9 @@ public class NetworkStatsHistory implements Parcelable { if (operations != null) operations = Arrays.copyOfRange(operations, i, length); bucketCount -= i; - // TODO: subtract removed values from totalBytes + totalBytes = 0; + if (rxBytes != null) totalBytes += CollectionUtils.total(rxBytes); + if (txBytes != null) totalBytes += CollectionUtils.total(txBytes); } } @@ -536,6 +568,7 @@ public class NetworkStatsHistory implements Parcelable { * @param start - start of the range, timestamp in milliseconds since the epoch. * @param end - end of the range, timestamp in milliseconds since the epoch. * @param recycle - entry instance for performance, could be null. + * @hide */ @UnsupportedAppUsage public Entry getValues(long start, long end, Entry recycle) { @@ -550,6 +583,7 @@ public class NetworkStatsHistory implements Parcelable { * @param end - end of the range, timestamp in milliseconds since the epoch. * @param now - current timestamp in milliseconds since the epoch (wall clock). * @param recycle - entry instance for performance, could be null. + * @hide */ @UnsupportedAppUsage public Entry getValues(long start, long end, long now, Entry recycle) { @@ -613,6 +647,7 @@ public class NetworkStatsHistory implements Parcelable { /** * @deprecated only for temporary testing + * @hide */ @Deprecated public void generateRandom(long start, long end, long bytes) { @@ -631,6 +666,7 @@ public class NetworkStatsHistory implements Parcelable { /** * @deprecated only for temporary testing + * @hide */ @Deprecated public void generateRandom(long start, long end, long rxBytes, long rxPackets, long txBytes, @@ -660,12 +696,14 @@ public class NetworkStatsHistory implements Parcelable { } } + /** @hide */ public static long randomLong(Random r, long start, long end) { return (long) (start + (r.nextFloat() * (end - start))); } /** * Quickly determine if this history intersects with given window. + * @hide */ public boolean intersects(long start, long end) { final long dataStart = getStart(); @@ -677,6 +715,7 @@ public class NetworkStatsHistory implements Parcelable { return false; } + /** @hide */ public void dump(IndentingPrintWriter pw, boolean fullHistory) { pw.print("NetworkStatsHistory: bucketDuration="); pw.println(bucketDuration / SECOND_IN_MILLIS); @@ -700,6 +739,7 @@ public class NetworkStatsHistory implements Parcelable { pw.decreaseIndent(); } + /** @hide */ public void dumpCheckin(PrintWriter pw) { pw.print("d,"); pw.print(bucketDuration / SECOND_IN_MILLIS); @@ -717,6 +757,7 @@ public class NetworkStatsHistory implements Parcelable { } } + /** @hide */ public void dumpDebug(ProtoOutputStream proto, long tag) { final long start = proto.start(tag); @@ -776,6 +817,7 @@ public class NetworkStatsHistory implements Parcelable { if (array != null) array[i] += value; } + /** @hide */ public int estimateResizeBuckets(long newBucketDuration) { return (int) (size() * getBucketDuration() / newBucketDuration); } @@ -783,6 +825,7 @@ public class NetworkStatsHistory implements Parcelable { /** * Utility methods for interacting with {@link DataInputStream} and * {@link DataOutputStream}, mostly dealing with writing partial arrays. + * @hide */ public static class DataStreamUtils { @Deprecated @@ -857,6 +900,7 @@ public class NetworkStatsHistory implements Parcelable { /** * Utility methods for interacting with {@link Parcel} structures, mostly * dealing with writing partial arrays. + * @hide */ public static class ParcelUtils { public static long[] readLongArray(Parcel in) { diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java index e9084b019668..a7e48d43631b 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java @@ -364,7 +364,7 @@ public final class NetworkTemplate implements Parcelable { private final int mMetered; private final int mRoaming; private final int mDefaultNetwork; - private final int mSubType; + private final int mRatType; /** * The subscriber Id match rule defines how the template should match networks with * specific subscriberId(s). See NetworkTemplate#SUBSCRIBER_ID_MATCH_RULE_* for more detail. @@ -413,18 +413,18 @@ public final class NetworkTemplate implements Parcelable { /** @hide */ // TODO: Remove it after updating all of the caller. public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds, - String wifiNetworkKey, int metered, int roaming, int defaultNetwork, int subType, + String wifiNetworkKey, int metered, int roaming, int defaultNetwork, int ratType, int oemManaged) { this(matchRule, subscriberId, matchSubscriberIds, wifiNetworkKey != null ? new String[] { wifiNetworkKey } : new String[0], - metered, roaming, defaultNetwork, subType, oemManaged, + metered, roaming, defaultNetwork, ratType, oemManaged, NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT); } /** @hide */ public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds, String[] matchWifiNetworkKeys, int metered, int roaming, - int defaultNetwork, int subType, int oemManaged, int subscriberIdMatchRule) { + int defaultNetwork, int ratType, int oemManaged, int subscriberIdMatchRule) { Objects.requireNonNull(matchWifiNetworkKeys); mMatchRule = matchRule; mSubscriberId = subscriberId; @@ -435,7 +435,7 @@ public final class NetworkTemplate implements Parcelable { mMetered = metered; mRoaming = roaming; mDefaultNetwork = defaultNetwork; - mSubType = subType; + mRatType = ratType; mOemManaged = oemManaged; mSubscriberIdMatchRule = subscriberIdMatchRule; checkValidSubscriberIdMatchRule(matchRule, subscriberIdMatchRule); @@ -453,7 +453,7 @@ public final class NetworkTemplate implements Parcelable { mMetered = in.readInt(); mRoaming = in.readInt(); mDefaultNetwork = in.readInt(); - mSubType = in.readInt(); + mRatType = in.readInt(); mOemManaged = in.readInt(); mSubscriberIdMatchRule = in.readInt(); } @@ -467,7 +467,7 @@ public final class NetworkTemplate implements Parcelable { dest.writeInt(mMetered); dest.writeInt(mRoaming); dest.writeInt(mDefaultNetwork); - dest.writeInt(mSubType); + dest.writeInt(mRatType); dest.writeInt(mOemManaged); dest.writeInt(mSubscriberIdMatchRule); } @@ -500,8 +500,8 @@ public final class NetworkTemplate implements Parcelable { builder.append(", defaultNetwork=").append(NetworkStats.defaultNetworkToString( mDefaultNetwork)); } - if (mSubType != NETWORK_TYPE_ALL) { - builder.append(", subType=").append(mSubType); + if (mRatType != NETWORK_TYPE_ALL) { + builder.append(", ratType=").append(mRatType); } if (mOemManaged != OEM_MANAGED_ALL) { builder.append(", oemManaged=").append(getOemManagedNames(mOemManaged)); @@ -514,7 +514,7 @@ public final class NetworkTemplate implements Parcelable { @Override public int hashCode() { return Objects.hash(mMatchRule, mSubscriberId, Arrays.hashCode(mMatchWifiNetworkKeys), - mMetered, mRoaming, mDefaultNetwork, mSubType, mOemManaged, mSubscriberIdMatchRule); + mMetered, mRoaming, mDefaultNetwork, mRatType, mOemManaged, mSubscriberIdMatchRule); } @Override @@ -526,7 +526,7 @@ public final class NetworkTemplate implements Parcelable { && mMetered == other.mMetered && mRoaming == other.mRoaming && mDefaultNetwork == other.mDefaultNetwork - && mSubType == other.mSubType + && mRatType == other.mRatType && mOemManaged == other.mOemManaged && mSubscriberIdMatchRule == other.mSubscriberIdMatchRule && Arrays.equals(mMatchWifiNetworkKeys, other.mMatchWifiNetworkKeys); @@ -635,7 +635,7 @@ public final class NetworkTemplate implements Parcelable { * Get the Radio Access Technology(RAT) type filter of the template. */ public int getRatType() { - return mSubType; + return mRatType; } /** @@ -708,8 +708,8 @@ public final class NetworkTemplate implements Parcelable { } private boolean matchesCollapsedRatType(NetworkIdentity ident) { - return mSubType == NETWORK_TYPE_ALL - || getCollapsedRatType(mSubType) == getCollapsedRatType(ident.mSubType); + return mRatType == NETWORK_TYPE_ALL + || getCollapsedRatType(mRatType) == getCollapsedRatType(ident.mRatType); } /** @@ -837,7 +837,7 @@ public final class NetworkTemplate implements Parcelable { switch (ident.mType) { case TYPE_WIFI: return matchesSubscriberId(ident.mSubscriberId) - && matchesWifiNetworkKey(ident.mNetworkId); + && matchesWifiNetworkKey(ident.mWifiNetworkKey); default: return false; } diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java index 0abc52309ad3..9b90f3b54542 100644 --- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java +++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java @@ -19,12 +19,16 @@ package com.android.server.net; import static android.Manifest.permission.NETWORK_STATS_PROVIDER; import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY; import static android.Manifest.permission.UPDATE_DEVICE_STATS; +import static android.app.usage.NetworkStatsManager.PREFIX_DEV; +import static android.app.usage.NetworkStatsManager.PREFIX_UID; +import static android.app.usage.NetworkStatsManager.PREFIX_UID_TAG; +import static android.app.usage.NetworkStatsManager.PREFIX_XT; import static android.content.Intent.ACTION_SHUTDOWN; import static android.content.Intent.ACTION_UID_REMOVED; import static android.content.Intent.ACTION_USER_REMOVED; import static android.content.Intent.EXTRA_UID; import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.net.NetworkIdentity.SUBTYPE_COMBINED; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.NetworkStats.DEFAULT_NETWORK_ALL; import static android.net.NetworkStats.IFACE_ALL; import static android.net.NetworkStats.IFACE_VT; @@ -47,23 +51,6 @@ import static android.net.TrafficStats.MB_IN_BYTES; import static android.net.TrafficStats.UID_TETHERING; import static android.net.TrafficStats.UNSUPPORTED; import static android.os.Trace.TRACE_TAG_NETWORK; -import static android.provider.Settings.Global.NETSTATS_AUGMENT_ENABLED; -import static android.provider.Settings.Global.NETSTATS_COMBINE_SUBTYPE_ENABLED; -import static android.provider.Settings.Global.NETSTATS_DEV_BUCKET_DURATION; -import static android.provider.Settings.Global.NETSTATS_DEV_DELETE_AGE; -import static android.provider.Settings.Global.NETSTATS_DEV_PERSIST_BYTES; -import static android.provider.Settings.Global.NETSTATS_DEV_ROTATE_AGE; -import static android.provider.Settings.Global.NETSTATS_GLOBAL_ALERT_BYTES; -import static android.provider.Settings.Global.NETSTATS_POLL_INTERVAL; -import static android.provider.Settings.Global.NETSTATS_SAMPLE_ENABLED; -import static android.provider.Settings.Global.NETSTATS_UID_BUCKET_DURATION; -import static android.provider.Settings.Global.NETSTATS_UID_DELETE_AGE; -import static android.provider.Settings.Global.NETSTATS_UID_PERSIST_BYTES; -import static android.provider.Settings.Global.NETSTATS_UID_ROTATE_AGE; -import static android.provider.Settings.Global.NETSTATS_UID_TAG_BUCKET_DURATION; -import static android.provider.Settings.Global.NETSTATS_UID_TAG_DELETE_AGE; -import static android.provider.Settings.Global.NETSTATS_UID_TAG_PERSIST_BYTES; -import static android.provider.Settings.Global.NETSTATS_UID_TAG_ROTATE_AGE; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import static android.text.format.DateUtils.DAY_IN_MILLIS; import static android.text.format.DateUtils.HOUR_IN_MILLIS; @@ -154,6 +141,7 @@ import com.android.net.module.util.BaseNetdUnsolicitedEventListener; import com.android.net.module.util.BestClock; import com.android.net.module.util.BinderUtils; import com.android.net.module.util.CollectionUtils; +import com.android.net.module.util.LocationPermissionChecker; import com.android.net.module.util.NetworkStatsUtils; import com.android.net.module.util.PermissionUtils; @@ -216,6 +204,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private static final int LOG_TAG_NETSTATS_MOBILE_SAMPLE = 51100; private static final int LOG_TAG_NETSTATS_WIFI_SAMPLE = 51101; + // TODO: Replace the hardcoded string and move it into ConnectivitySettingsManager. + private static final String NETSTATS_COMBINE_SUBTYPE_ENABLED = + "netstats_combine_subtype_enabled"; + private final Context mContext; private final NetworkStatsFactory mStatsFactory; private final AlarmManager mAlarmManager; @@ -242,11 +234,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private PendingIntent mPollIntent; - private static final String PREFIX_DEV = "dev"; - private static final String PREFIX_XT = "xt"; - private static final String PREFIX_UID = "uid"; - private static final String PREFIX_UID_TAG = "uid_tag"; - /** * Settings that can be changed externally. */ @@ -256,9 +243,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { boolean getSampleEnabled(); boolean getAugmentEnabled(); /** - * When enabled, all mobile data is reported under {@link NetworkIdentity#SUBTYPE_COMBINED}. - * When disabled, mobile data is broken down by a granular subtype representative of the - * actual subtype. {@see NetworkTemplate#getCollapsedRatType}. + * When enabled, all mobile data is reported under {@link NetworkTemplate#NETWORK_TYPE_ALL}. + * When disabled, mobile data is broken down by a granular ratType representative of the + * actual ratType. {@see NetworkTemplate#getCollapsedRatType}. * Enabling this decreases the level of detail but saves performance, disk space and * amount of data logged. */ @@ -305,6 +292,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { /** Set of any ifaces associated with mobile networks since boot. */ private volatile String[] mMobileIfaces = new String[0]; + /** Set of any ifaces associated with wifi networks since boot. */ + private volatile String[] mWifiIfaces = new String[0]; + /** Set of all ifaces currently used by traffic that does not explicitly specify a Network. */ @GuardedBy("mStatsLock") private Network[] mDefaultNetworks = new Network[0]; @@ -365,6 +355,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @NonNull private final NetworkStatsSubscriptionsMonitor mNetworkStatsSubscriptionsMonitor; + @NonNull + private final LocationPermissionChecker mLocationPermissionChecker; + private static @NonNull File getDefaultSystemDir() { return new File(Environment.getDataDirectory(), "system"); } @@ -427,7 +420,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { final NetworkStatsService service = new NetworkStatsService(context, INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)), alarmManager, wakeLock, getDefaultClock(), - new DefaultNetworkStatsSettings(context), new NetworkStatsFactory(netd), + new DefaultNetworkStatsSettings(), new NetworkStatsFactory(netd), new NetworkStatsObservers(), getDefaultSystemDir(), getDefaultBaseDir(), new Dependencies()); @@ -461,6 +454,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mContentResolver = mContext.getContentResolver(); mContentObserver = mDeps.makeContentObserver(mHandler, mSettings, mNetworkStatsSubscriptionsMonitor); + mLocationPermissionChecker = mDeps.makeLocationPermissionChecker(mContext); } /** @@ -508,6 +502,13 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } }; } + + /** + * @see LocationPermissionChecker + */ + public LocationPermissionChecker makeLocationPermissionChecker(final Context context) { + return new LocationPermissionChecker(context); + } } /** @@ -590,13 +591,13 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mSettings.getPollInterval(), pollIntent); mContentResolver.registerContentObserver(Settings.Global - .getUriFor(Settings.Global.NETSTATS_COMBINE_SUBTYPE_ENABLED), + .getUriFor(NETSTATS_COMBINE_SUBTYPE_ENABLED), false /* notifyForDescendants */, mContentObserver); // Post a runnable on handler thread to call onChange(). It's for getting current value of // NETSTATS_COMBINE_SUBTYPE_ENABLED to decide start or stop monitoring RAT type changes. mHandler.post(() -> mContentObserver.onChange(false, Settings.Global - .getUriFor(Settings.Global.NETSTATS_COMBINE_SUBTYPE_ENABLED))); + .getUriFor(NETSTATS_COMBINE_SUBTYPE_ENABLED))); registerGlobalAlert(); } @@ -773,6 +774,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public NetworkStats getDeviceSummaryForNetwork( NetworkTemplate template, long start, long end) { + enforceTemplatePermissions(template, callingPackage); return internalGetSummaryForNetwork(template, restrictedFlags, start, end, mAccessLevel, mCallingUid); } @@ -780,19 +782,33 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public NetworkStats getSummaryForNetwork( NetworkTemplate template, long start, long end) { + enforceTemplatePermissions(template, callingPackage); return internalGetSummaryForNetwork(template, restrictedFlags, start, end, mAccessLevel, mCallingUid); } + // TODO: Remove this after all callers are removed. @Override public NetworkStatsHistory getHistoryForNetwork(NetworkTemplate template, int fields) { + enforceTemplatePermissions(template, callingPackage); return internalGetHistoryForNetwork(template, restrictedFlags, fields, - mAccessLevel, mCallingUid); + mAccessLevel, mCallingUid, Long.MIN_VALUE, Long.MAX_VALUE); + } + + @Override + public NetworkStatsHistory getHistoryIntervalForNetwork(NetworkTemplate template, + int fields, long start, long end) { + enforceTemplatePermissions(template, callingPackage); + // TODO(b/200768422): Redact returned history if the template is location + // sensitive but the caller is not privileged. + return internalGetHistoryForNetwork(template, restrictedFlags, fields, + mAccessLevel, mCallingUid, start, end); } @Override public NetworkStats getSummaryForAllUid( NetworkTemplate template, long start, long end, boolean includeTags) { + enforceTemplatePermissions(template, callingPackage); try { final NetworkStats stats = getUidComplete() .getSummary(template, start, end, mAccessLevel, mCallingUid); @@ -810,6 +826,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public NetworkStats getTaggedSummaryForAllUid( NetworkTemplate template, long start, long end) { + enforceTemplatePermissions(template, callingPackage); try { final NetworkStats tagStats = getUidTagComplete() .getSummary(template, start, end, mAccessLevel, mCallingUid); @@ -822,6 +839,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public NetworkStatsHistory getHistoryForUid( NetworkTemplate template, int uid, int set, int tag, int fields) { + enforceTemplatePermissions(template, callingPackage); // NOTE: We don't augment UID-level statistics if (tag == TAG_NONE) { return getUidComplete().getHistory(template, null, uid, set, tag, fields, @@ -836,6 +854,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { public NetworkStatsHistory getHistoryIntervalForUid( NetworkTemplate template, int uid, int set, int tag, int fields, long start, long end) { + enforceTemplatePermissions(template, callingPackage); + // TODO(b/200768422): Redact returned history if the template is location + // sensitive but the caller is not privileged. // NOTE: We don't augment UID-level statistics if (tag == TAG_NONE) { return getUidComplete().getHistory(template, null, uid, set, tag, fields, @@ -857,6 +878,26 @@ public class NetworkStatsService extends INetworkStatsService.Stub { }; } + private void enforceTemplatePermissions(@NonNull NetworkTemplate template, + @NonNull String callingPackage) { + // For a template with wifi network keys, it is possible for a malicious + // client to track the user locations via querying data usage. Thus, enforce + // fine location permission check. + if (!template.getWifiNetworkKeys().isEmpty()) { + final boolean canAccessFineLocation = mLocationPermissionChecker + .checkCallersLocationPermission(callingPackage, + null /* featureId */, + Binder.getCallingUid(), + false /* coarseForTargetSdkLessThanQ */, + null /* message */); + if (!canAccessFineLocation) { + throw new SecurityException("Access fine location is required when querying" + + " with wifi network keys, make sure the app has the necessary" + + "permissions and the location toggle is on."); + } + } + } + private @NetworkStatsAccess.Level int checkAccessLevel(String callingPackage) { return NetworkStatsAccess.checkAccessLevel( mContext, Binder.getCallingPid(), Binder.getCallingUid(), callingPackage); @@ -893,7 +934,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // We've been using pure XT stats long enough that we no longer need to // splice DEV and XT together. final NetworkStatsHistory history = internalGetHistoryForNetwork(template, flags, FIELD_ALL, - accessLevel, callingUid); + accessLevel, callingUid, start, end); final long now = System.currentTimeMillis(); final NetworkStatsHistory.Entry entry = history.getValues(start, end, now, null); @@ -910,14 +951,14 @@ public class NetworkStatsService extends INetworkStatsService.Stub { * appropriate. */ private NetworkStatsHistory internalGetHistoryForNetwork(NetworkTemplate template, - int flags, int fields, @NetworkStatsAccess.Level int accessLevel, int callingUid) { + int flags, int fields, @NetworkStatsAccess.Level int accessLevel, int callingUid, + long start, long end) { // We've been using pure XT stats long enough that we no longer need to // splice DEV and XT together. final SubscriptionPlan augmentPlan = resolveSubscriptionPlan(template, flags); synchronized (mStatsLock) { return mXtStatsCached.getHistory(template, augmentPlan, - UID_ALL, SET_ALL, TAG_NONE, fields, Long.MIN_VALUE, Long.MAX_VALUE, - accessLevel, callingUid); + UID_ALL, SET_ALL, TAG_NONE, fields, start, end, accessLevel, callingUid); } } @@ -968,11 +1009,15 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } @Override - public NetworkStats getDetailedUidStats(String[] requiredIfaces) { + public NetworkStats getUidStatsForTransport(int transport) { enforceAnyPermissionOf(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); try { + final String[] relevantIfaces = + transport == TRANSPORT_WIFI ? mWifiIfaces : mMobileIfaces; + // TODO(b/215633405) : mMobileIfaces and mWifiIfaces already contain the stacked + // interfaces, so this is not useful, remove it. final String[] ifacesToQuery = - mStatsFactory.augmentWithStackedInterfaces(requiredIfaces); + mStatsFactory.augmentWithStackedInterfaces(relevantIfaces); return getNetworkStatsUidDetail(ifacesToQuery); } catch (RemoteException e) { Log.wtf(TAG, "Error compiling UID stats", e); @@ -1331,16 +1376,18 @@ public class NetworkStatsService extends INetworkStatsService.Stub { final boolean combineSubtypeEnabled = mSettings.getCombineSubtypeEnabled(); final ArraySet<String> mobileIfaces = new ArraySet<>(); + final ArraySet<String> wifiIfaces = new ArraySet<>(); for (NetworkStateSnapshot snapshot : snapshots) { final int displayTransport = getDisplayTransport(snapshot.getNetworkCapabilities().getTransportTypes()); final boolean isMobile = (NetworkCapabilities.TRANSPORT_CELLULAR == displayTransport); + final boolean isWifi = (NetworkCapabilities.TRANSPORT_WIFI == displayTransport); final boolean isDefault = CollectionUtils.contains( mDefaultNetworks, snapshot.getNetwork()); - final int subType = combineSubtypeEnabled ? SUBTYPE_COMBINED - : getSubTypeForStateSnapshot(snapshot); + final int ratType = combineSubtypeEnabled ? NetworkTemplate.NETWORK_TYPE_ALL + : getRatTypeForStateSnapshot(snapshot); final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, snapshot, - isDefault, subType); + isDefault, ratType); // Traffic occurring on the base interface is always counted for // both total usage and UID details. @@ -1355,12 +1402,12 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // VT is considered always metered in framework's layer. If VT is not metered // per carrier's policy, modem will report 0 usage for VT calls. if (snapshot.getNetworkCapabilities().hasCapability( - NetworkCapabilities.NET_CAPABILITY_IMS) && !ident.getMetered()) { + NetworkCapabilities.NET_CAPABILITY_IMS) && !ident.isMetered()) { // Copy the identify from IMS one but mark it as metered. NetworkIdentity vtIdent = new NetworkIdentity(ident.getType(), - ident.getSubType(), ident.getSubscriberId(), ident.getNetworkId(), - ident.getRoaming(), true /* metered */, + ident.getRatType(), ident.getSubscriberId(), ident.getWifiNetworkKey(), + ident.isRoaming(), true /* metered */, true /* onDefaultNetwork */, ident.getOemManaged()); final String ifaceVt = IFACE_VT + getSubIdForMobile(snapshot); findOrCreateNetworkIdentitySet(mActiveIfaces, ifaceVt).add(vtIdent); @@ -1370,6 +1417,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { if (isMobile) { mobileIfaces.add(baseIface); } + if (isWifi) { + wifiIfaces.add(baseIface); + } } // Traffic occurring on stacked interfaces is usually clatd. @@ -1411,6 +1461,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { if (isMobile) { mobileIfaces.add(iface); } + if (isWifi) { + wifiIfaces.add(iface); + } mStatsFactory.noteStackedIface(iface, baseIface); } @@ -1418,11 +1471,16 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } mMobileIfaces = mobileIfaces.toArray(new String[0]); + mWifiIfaces = wifiIfaces.toArray(new String[0]); // TODO (b/192758557): Remove debug log. if (CollectionUtils.contains(mMobileIfaces, null)) { throw new NullPointerException( "null element in mMobileIfaces: " + Arrays.toString(mMobileIfaces)); } + if (CollectionUtils.contains(mWifiIfaces, null)) { + throw new NullPointerException( + "null element in mWifiIfaces: " + Arrays.toString(mWifiIfaces)); + } } private static int getSubIdForMobile(@NonNull NetworkStateSnapshot state) { @@ -1440,11 +1498,11 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } /** - * For networks with {@code TRANSPORT_CELLULAR}, get subType that was obtained through + * For networks with {@code TRANSPORT_CELLULAR}, get ratType that was obtained through * {@link PhoneStateListener}. Otherwise, return 0 given that other networks with different * transport types do not actually fill this value. */ - private int getSubTypeForStateSnapshot(@NonNull NetworkStateSnapshot state) { + private int getRatTypeForStateSnapshot(@NonNull NetworkStateSnapshot state) { if (!state.getNetworkCapabilities().hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { return 0; } @@ -2191,24 +2249,11 @@ public class NetworkStatsService extends INetworkStatsService.Stub { * {@link android.provider.Settings.Global}. */ private static class DefaultNetworkStatsSettings implements NetworkStatsSettings { - private final ContentResolver mResolver; - - public DefaultNetworkStatsSettings(Context context) { - mResolver = Objects.requireNonNull(context.getContentResolver()); - // TODO: adjust these timings for production builds - } - - private long getGlobalLong(String name, long def) { - return Settings.Global.getLong(mResolver, name, def); - } - private boolean getGlobalBoolean(String name, boolean def) { - final int defInt = def ? 1 : 0; - return Settings.Global.getInt(mResolver, name, defInt) != 0; - } + DefaultNetworkStatsSettings() {} @Override public long getPollInterval() { - return getGlobalLong(NETSTATS_POLL_INTERVAL, 30 * MINUTE_IN_MILLIS); + return 30 * MINUTE_IN_MILLIS; } @Override public long getPollDelay() { @@ -2216,25 +2261,23 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } @Override public long getGlobalAlertBytes(long def) { - return getGlobalLong(NETSTATS_GLOBAL_ALERT_BYTES, def); + return def; } @Override public boolean getSampleEnabled() { - return getGlobalBoolean(NETSTATS_SAMPLE_ENABLED, true); + return true; } @Override public boolean getAugmentEnabled() { - return getGlobalBoolean(NETSTATS_AUGMENT_ENABLED, true); + return true; } @Override public boolean getCombineSubtypeEnabled() { - return getGlobalBoolean(NETSTATS_COMBINE_SUBTYPE_ENABLED, false); + return false; } @Override public Config getDevConfig() { - return new Config(getGlobalLong(NETSTATS_DEV_BUCKET_DURATION, HOUR_IN_MILLIS), - getGlobalLong(NETSTATS_DEV_ROTATE_AGE, 15 * DAY_IN_MILLIS), - getGlobalLong(NETSTATS_DEV_DELETE_AGE, 90 * DAY_IN_MILLIS)); + return new Config(HOUR_IN_MILLIS, 15 * DAY_IN_MILLIS, 90 * DAY_IN_MILLIS); } @Override public Config getXtConfig() { @@ -2242,31 +2285,27 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } @Override public Config getUidConfig() { - return new Config(getGlobalLong(NETSTATS_UID_BUCKET_DURATION, 2 * HOUR_IN_MILLIS), - getGlobalLong(NETSTATS_UID_ROTATE_AGE, 15 * DAY_IN_MILLIS), - getGlobalLong(NETSTATS_UID_DELETE_AGE, 90 * DAY_IN_MILLIS)); + return new Config(2 * HOUR_IN_MILLIS, 15 * DAY_IN_MILLIS, 90 * DAY_IN_MILLIS); } @Override public Config getUidTagConfig() { - return new Config(getGlobalLong(NETSTATS_UID_TAG_BUCKET_DURATION, 2 * HOUR_IN_MILLIS), - getGlobalLong(NETSTATS_UID_TAG_ROTATE_AGE, 5 * DAY_IN_MILLIS), - getGlobalLong(NETSTATS_UID_TAG_DELETE_AGE, 15 * DAY_IN_MILLIS)); + return new Config(2 * HOUR_IN_MILLIS, 5 * DAY_IN_MILLIS, 15 * DAY_IN_MILLIS); } @Override public long getDevPersistBytes(long def) { - return getGlobalLong(NETSTATS_DEV_PERSIST_BYTES, def); + return def; } @Override public long getXtPersistBytes(long def) { - return getDevPersistBytes(def); + return def; } @Override public long getUidPersistBytes(long def) { - return getGlobalLong(NETSTATS_UID_PERSIST_BYTES, def); + return def; } @Override public long getUidTagPersistBytes(long def) { - return getGlobalLong(NETSTATS_UID_TAG_PERSIST_BYTES, def); + return def; } } diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index a3eb0eccad9d..ce58ff6fc59d 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -236,7 +236,8 @@ public class ExternalStorageProvider extends FileSystemProvider { root.flags |= Root.FLAG_REMOVABLE_USB; } - if (volume.getType() != VolumeInfo.TYPE_EMULATED) { + if (volume.getType() != VolumeInfo.TYPE_EMULATED + && volume.getType() != VolumeInfo.TYPE_STUB) { root.flags |= Root.FLAG_SUPPORTS_EJECT; } diff --git a/packages/SettingsLib/res/values/config.xml b/packages/SettingsLib/res/values/config.xml index 45253bb7944a..b150e0169a96 100644 --- a/packages/SettingsLib/res/values/config.xml +++ b/packages/SettingsLib/res/values/config.xml @@ -28,4 +28,9 @@ <!-- Control whether status bar should distinguish HSPA data icon form UMTS data icon on devices --> <bool name="config_hspa_data_distinguishable">false</bool> + + <integer-array name="config_supportedDreamComplications"> + </integer-array> + <integer-array name="config_dreamComplicationsEnabledByDefault"> + </integer-array> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java index 5f2bef723cd9..64a0781c4643 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java @@ -31,9 +31,8 @@ public class InterestingConfigChanges { private int mLastDensity; public InterestingConfigChanges() { - this(ActivityInfo.CONFIG_LOCALE - | ActivityInfo.CONFIG_UI_MODE | ActivityInfo.CONFIG_SCREEN_LAYOUT - | ActivityInfo.CONFIG_ASSETS_PATHS); + this(ActivityInfo.CONFIG_LOCALE | ActivityInfo.CONFIG_LAYOUT_DIRECTION + | ActivityInfo.CONFIG_UI_MODE | ActivityInfo.CONFIG_ASSETS_PATHS); } public InterestingConfigChanges(int flags) { diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java index aed2ec10e924..3c444f2b95b3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java +++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java @@ -38,6 +38,8 @@ import android.util.AttributeSet; import android.util.Log; import android.util.Xml; +import com.android.settingslib.R; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -45,9 +47,12 @@ import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; public class DreamBackend { private static final String TAG = "DreamBackend"; @@ -78,19 +83,41 @@ public class DreamBackend { @Retention(RetentionPolicy.SOURCE) @IntDef({WHILE_CHARGING, WHILE_DOCKED, EITHER, NEVER}) - public @interface WhenToDream{} + public @interface WhenToDream {} public static final int WHILE_CHARGING = 0; public static final int WHILE_DOCKED = 1; public static final int EITHER = 2; public static final int NEVER = 3; + /** + * The type of dream complications which can be provided by a + * {@link com.android.systemui.dreams.ComplicationProvider}. + */ + @IntDef(prefix = {"COMPLICATION_TYPE_"}, value = { + COMPLICATION_TYPE_TIME, + COMPLICATION_TYPE_DATE, + COMPLICATION_TYPE_WEATHER, + COMPLICATION_TYPE_AIR_QUALITY, + COMPLICATION_TYPE_CAST_INFO + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ComplicationType {} + + public static final int COMPLICATION_TYPE_TIME = 1; + public static final int COMPLICATION_TYPE_DATE = 2; + public static final int COMPLICATION_TYPE_WEATHER = 3; + public static final int COMPLICATION_TYPE_AIR_QUALITY = 4; + public static final int COMPLICATION_TYPE_CAST_INFO = 5; + private final Context mContext; private final IDreamManager mDreamManager; private final DreamInfoComparator mComparator; private final boolean mDreamsEnabledByDefault; private final boolean mDreamsActivatedOnSleepByDefault; private final boolean mDreamsActivatedOnDockByDefault; + private final Set<Integer> mSupportedComplications; + private final Set<Integer> mDefaultEnabledComplications; private static DreamBackend sInstance; @@ -103,17 +130,31 @@ public class DreamBackend { public DreamBackend(Context context) { mContext = context.getApplicationContext(); + final Resources resources = mContext.getResources(); + mDreamManager = IDreamManager.Stub.asInterface( ServiceManager.getService(DreamService.DREAM_SERVICE)); mComparator = new DreamInfoComparator(getDefaultDream()); - mDreamsEnabledByDefault = mContext.getResources() - .getBoolean(com.android.internal.R.bool.config_dreamsEnabledByDefault); - mDreamsActivatedOnSleepByDefault = mContext.getResources() - .getBoolean(com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault); - mDreamsActivatedOnDockByDefault = mContext.getResources() - .getBoolean(com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault); - mDreamPreviewDefault = mContext.getResources().getDrawable( + mDreamsEnabledByDefault = resources.getBoolean( + com.android.internal.R.bool.config_dreamsEnabledByDefault); + mDreamsActivatedOnSleepByDefault = resources.getBoolean( + com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault); + mDreamsActivatedOnDockByDefault = resources.getBoolean( + com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault); + mDreamPreviewDefault = resources.getDrawable( com.android.internal.R.drawable.default_dream_preview); + + mSupportedComplications = + Arrays.stream(resources.getIntArray(R.array.config_supportedDreamComplications)) + .boxed() + .collect(Collectors.toSet()); + + mDefaultEnabledComplications = Arrays.stream( + resources.getIntArray(R.array.config_dreamComplicationsEnabledByDefault)) + .boxed() + // A complication can only be enabled by default if it is also supported. + .filter(mSupportedComplications::contains) + .collect(Collectors.toSet()); } public List<DreamInfo> getDreamInfos() { @@ -242,7 +283,57 @@ public class DreamBackend { default: break; } + } + + /** Gets all complications which have been enabled by the user. */ + public Set<Integer> getEnabledComplications() { + final String enabledComplications = Settings.Secure.getString( + mContext.getContentResolver(), + Settings.Secure.SCREENSAVER_ENABLED_COMPLICATIONS); + + if (enabledComplications == null) { + return mDefaultEnabledComplications; + } + + return parseFromString(enabledComplications); + } + + /** Gets all dream complications which are supported on this device. **/ + public Set<Integer> getSupportedComplications() { + return mSupportedComplications; + } + + /** + * Enables or disables a particular dream complication. + * + * @param complicationType The dream complication to be enabled/disabled. + * @param value If true, the complication is enabled. Otherwise it is disabled. + */ + public void setComplicationEnabled(@ComplicationType int complicationType, boolean value) { + if (!mSupportedComplications.contains(complicationType)) return; + + Set<Integer> enabledComplications = getEnabledComplications(); + if (value) { + enabledComplications.add(complicationType); + } else { + enabledComplications.remove(complicationType); + } + + Settings.Secure.putString(mContext.getContentResolver(), + Settings.Secure.SCREENSAVER_ENABLED_COMPLICATIONS, + convertToString(enabledComplications)); + } + + private static String convertToString(Set<Integer> set) { + return set.stream() + .map(String::valueOf) + .collect(Collectors.joining(",")); + } + private static Set<Integer> parseFromString(String string) { + return Arrays.stream(string.split(",")) + .map(Integer::parseInt) + .collect(Collectors.toSet()); } public boolean isEnabled() { diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java index 3e95b01824cc..5e9ac5a59091 100644 --- a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java +++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java @@ -16,23 +16,16 @@ package com.android.settingslib.net; -import static android.net.NetworkStatsHistory.FIELD_RX_BYTES; -import static android.net.NetworkStatsHistory.FIELD_TX_BYTES; - +import android.annotation.NonNull; import android.app.usage.NetworkStats; import android.app.usage.NetworkStatsManager; import android.content.Context; -import android.net.INetworkStatsService; -import android.net.INetworkStatsSession; import android.net.NetworkPolicy; import android.net.NetworkPolicyManager; -import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; -import android.net.TrafficStats; -import android.os.RemoteException; -import android.os.ServiceManager; import android.text.format.DateUtils; import android.util.Pair; +import android.util.Range; import androidx.annotation.VisibleForTesting; import androidx.loader.content.AsyncTaskLoader; @@ -52,8 +45,6 @@ public abstract class NetworkCycleDataLoader<D> extends AsyncTaskLoader<D> { protected final NetworkTemplate mNetworkTemplate; private final NetworkPolicy mPolicy; private final ArrayList<Long> mCycles; - @VisibleForTesting - final INetworkStatsService mNetworkStatsService; protected NetworkCycleDataLoader(Builder<?> builder) { super(builder.mContext); @@ -61,8 +52,6 @@ public abstract class NetworkCycleDataLoader<D> extends AsyncTaskLoader<D> { mCycles = builder.mCycles; mNetworkStatsManager = (NetworkStatsManager) builder.mContext.getSystemService(Context.NETWORK_STATS_SERVICE); - mNetworkStatsService = INetworkStatsService.Stub.asInterface( - ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); final NetworkPolicyEditor policyEditor = new NetworkPolicyEditor(NetworkPolicyManager.from(builder.mContext)); policyEditor.read(); @@ -112,23 +101,20 @@ public abstract class NetworkCycleDataLoader<D> extends AsyncTaskLoader<D> { @VisibleForTesting void loadFourWeeksData() { + if (mNetworkTemplate == null) return; + final NetworkStats stats = mNetworkStatsManager.queryDetailsForDevice( + mNetworkTemplate, Long.MIN_VALUE, Long.MAX_VALUE); try { - final INetworkStatsSession networkSession = mNetworkStatsService.openSession(); - final NetworkStatsHistory networkHistory = networkSession.getHistoryForNetwork( - mNetworkTemplate, FIELD_RX_BYTES | FIELD_TX_BYTES); - final long historyStart = networkHistory.getStart(); - final long historyEnd = networkHistory.getEnd(); - - long cycleEnd = historyEnd; - while (cycleEnd > historyStart) { + final Range<Long> historyTimeRange = getTimeRangeOf(stats); + + long cycleEnd = historyTimeRange.getUpper(); + while (cycleEnd > historyTimeRange.getLower()) { final long cycleStart = cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4); recordUsage(cycleStart, cycleEnd); cycleEnd = cycleStart; } - - TrafficStats.closeQuietly(networkSession); - } catch (RemoteException e) { - throw new RuntimeException(e); + } catch (IllegalArgumentException e) { + // Empty history, ignore. } } @@ -169,6 +155,32 @@ public abstract class NetworkCycleDataLoader<D> extends AsyncTaskLoader<D> { return bytes; } + @NonNull + @VisibleForTesting + Range getTimeRangeOf(@NonNull NetworkStats stats) { + long start = Long.MAX_VALUE; + long end = Long.MIN_VALUE; + while (hasNextBucket(stats)) { + final NetworkStats.Bucket bucket = getNextBucket(stats); + start = Math.min(start, bucket.getStartTimeStamp()); + end = Math.max(end, bucket.getEndTimeStamp()); + } + return new Range(start, end); + } + + @VisibleForTesting + boolean hasNextBucket(@NonNull NetworkStats stats) { + return stats.hasNextBucket(); + } + + @NonNull + @VisibleForTesting + NetworkStats.Bucket getNextBucket(@NonNull NetworkStats stats) { + NetworkStats.Bucket bucket = new NetworkStats.Bucket(); + stats.getNextBucket(bucket); + return bucket; + } + @VisibleForTesting(otherwise = VisibleForTesting.NONE) public ArrayList<Long> getCycles() { return mCycles; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java index 1edb7d18bc88..10ccd22eca83 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java @@ -64,7 +64,6 @@ import com.android.settingslib.testutils.shadow.ShadowUserManager; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -436,7 +435,6 @@ public class ApplicationsStateRoboTest { } @Test - @Ignore public void noAppRemoved_noWorkprofile_doResumeIfNeededLocked_shouldNotClearEntries() throws RemoteException { // scenario: only owner user @@ -630,7 +628,6 @@ public class ApplicationsStateRoboTest { } @Test - @Ignore public void noAppRemoved_workprofileExists_doResumeIfNeededLocked_shouldNotClearEntries() throws RemoteException { if (!MU_ENABLED) { @@ -711,11 +708,11 @@ public class ApplicationsStateRoboTest { throws RemoteException { if (ownerApps != null) { - when(mApplicationsState.mIpm.getInstalledApplications(anyInt(), eq(0))) + when(mApplicationsState.mIpm.getInstalledApplications(anyLong(), eq(0))) .thenReturn(new ParceledListSlice<>(ownerApps)); } if (profileApps != null) { - when(mApplicationsState.mIpm.getInstalledApplications(anyInt(), eq(PROFILE_USERID))) + when(mApplicationsState.mIpm.getInstalledApplications(anyLong(), eq(PROFILE_USERID))) .thenReturn(new ParceledListSlice<>(profileApps)); } final InterestingConfigChanges configChanges = mock(InterestingConfigChanges.class); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java new file mode 100644 index 000000000000..53d465305a69 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settingslib.dream; + + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.res.Resources; + +import com.android.settingslib.R; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowSettings; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowSettings.ShadowSecure.class}) +public final class DreamBackendTest { + private static final int[] SUPPORTED_DREAM_COMPLICATIONS = {1, 2, 3}; + private static final int[] DEFAULT_DREAM_COMPLICATIONS = {1, 3, 4}; + + @Mock + private Context mContext; + private DreamBackend mBackend; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mContext.getApplicationContext()).thenReturn(mContext); + + final Resources res = mock(Resources.class); + when(mContext.getResources()).thenReturn(res); + when(res.getIntArray(R.array.config_supportedDreamComplications)).thenReturn( + SUPPORTED_DREAM_COMPLICATIONS); + when(res.getIntArray(R.array.config_dreamComplicationsEnabledByDefault)).thenReturn( + DEFAULT_DREAM_COMPLICATIONS); + mBackend = new DreamBackend(mContext); + } + + @After + public void tearDown() { + ShadowSettings.ShadowSecure.reset(); + } + + @Test + public void testSupportedComplications() { + assertThat(mBackend.getSupportedComplications()).containsExactly(1, 2, 3); + } + + @Test + public void testGetEnabledDreamComplications_default() { + assertThat(mBackend.getEnabledComplications()).containsExactly(1, 3); + } + + @Test + public void testEnableComplication() { + mBackend.setComplicationEnabled(/* complicationType= */ 2, true); + assertThat(mBackend.getEnabledComplications()).containsExactly(1, 2, 3); + } + + @Test + public void testEnableComplication_notSupported() { + mBackend.setComplicationEnabled(/* complicationType= */ 5, true); + assertThat(mBackend.getEnabledComplications()).containsExactly(1, 3); + } + + @Test + public void testDisableComplication() { + mBackend.setComplicationEnabled(/* complicationType= */ 1, false); + assertThat(mBackend.getEnabledComplications()).containsExactly(3); + } +} + diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java index 74b91510cf3f..c79440e58e17 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java @@ -16,24 +16,24 @@ package com.android.settingslib.net; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.nullable; +import static android.app.usage.NetworkStats.Bucket.UID_ALL; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.annotation.NonNull; +import android.app.usage.NetworkStats; import android.app.usage.NetworkStatsManager; import android.content.Context; import android.net.ConnectivityManager; -import android.net.INetworkStatsService; -import android.net.INetworkStatsSession; import android.net.NetworkPolicy; import android.net.NetworkPolicyManager; -import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; -import android.os.RemoteException; import android.text.format.DateUtils; import android.util.Range; @@ -49,6 +49,8 @@ import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.Queue; +import java.util.concurrent.LinkedBlockingQueue; @RunWith(RobolectricTestRunner.class) public class NetworkCycleDataLoaderTest { @@ -63,8 +65,6 @@ public class NetworkCycleDataLoaderTest { private NetworkPolicy mPolicy; @Mock private Iterator<Range<ZonedDateTime>> mIterator; - @Mock - private INetworkStatsService mNetworkStatsService; private NetworkCycleDataTestLoader mLoader; @@ -132,20 +132,24 @@ public class NetworkCycleDataLoaderTest { verify(mLoader).recordUsage(nowInMs, nowInMs); } + private NetworkStats.Bucket makeMockBucket(int uid, long rxBytes, long txBytes, + long start, long end) { + NetworkStats.Bucket ret = mock(NetworkStats.Bucket.class); + when(ret.getUid()).thenReturn(uid); + when(ret.getRxBytes()).thenReturn(rxBytes); + when(ret.getTxBytes()).thenReturn(txBytes); + when(ret.getStartTimeStamp()).thenReturn(start); + when(ret.getEndTimeStamp()).thenReturn(end); + return ret; + } + @Test - public void loadFourWeeksData_shouldRecordUsageForLast4Weeks() throws RemoteException { + public void loadFourWeeksData_shouldRecordUsageForLast4Weeks() { mLoader = spy(new NetworkCycleDataTestLoader(mContext)); - ReflectionHelpers.setField(mLoader, "mNetworkStatsService", mNetworkStatsService); - final INetworkStatsSession networkSession = mock(INetworkStatsSession.class); - when(mNetworkStatsService.openSession()).thenReturn(networkSession); - final NetworkStatsHistory networkHistory = mock(NetworkStatsHistory.class); - when(networkSession.getHistoryForNetwork(nullable(NetworkTemplate.class), anyInt())) - .thenReturn(networkHistory); final long now = System.currentTimeMillis(); final long fourWeeksAgo = now - (DateUtils.WEEK_IN_MILLIS * 4); final long twoDaysAgo = now - (DateUtils.DAY_IN_MILLIS * 2); - when(networkHistory.getStart()).thenReturn(twoDaysAgo); - when(networkHistory.getEnd()).thenReturn(now); + mLoader.addBucket(makeMockBucket(UID_ALL, 123, 456, twoDaysAgo, now)); mLoader.loadFourWeeksData(); @@ -173,10 +177,31 @@ public class NetworkCycleDataLoaderTest { verify(mLoader).recordUsage(thirtyDaysAgo, twentyDaysAgo); } + @Test + public void getTimeRangeOf() { + mLoader = spy(new NetworkCycleDataTestLoader(mContext)); + // If empty, new Range(MAX_VALUE, MIN_VALUE) will be constructed. Hence, the function + // should throw. + assertThrows(IllegalArgumentException.class, + () -> mLoader.getTimeRangeOf(mock(NetworkStats.class))); + + mLoader.addBucket(makeMockBucket(UID_ALL, 123, 456, 0, 10)); + // Feed the function with unused NetworkStats. The actual data injection is + // done by addBucket. + assertEquals(new Range(0L, 10L), mLoader.getTimeRangeOf(mock(NetworkStats.class))); + + mLoader.addBucket(makeMockBucket(UID_ALL, 123, 456, 0, 10)); + mLoader.addBucket(makeMockBucket(UID_ALL, 123, 456, 30, 40)); + mLoader.addBucket(makeMockBucket(UID_ALL, 123, 456, 10, 25)); + assertEquals(new Range(0L, 40L), mLoader.getTimeRangeOf(mock(NetworkStats.class))); + } + public class NetworkCycleDataTestLoader extends NetworkCycleDataLoader<List<NetworkCycleData>> { + private final Queue<NetworkStats.Bucket> mMockedBuckets = new LinkedBlockingQueue<>(); private NetworkCycleDataTestLoader(Context context) { - super(NetworkCycleDataLoader.builder(mContext)); + super(NetworkCycleDataLoader.builder(mContext) + .setNetworkTemplate(mock(NetworkTemplate.class))); mContext = context; } @@ -188,5 +213,19 @@ public class NetworkCycleDataLoaderTest { List<NetworkCycleData> getCycleUsage() { return null; } + + public void addBucket(NetworkStats.Bucket bucket) { + mMockedBuckets.add(bucket); + } + + @Override + public boolean hasNextBucket(@NonNull NetworkStats unused) { + return !mMockedBuckets.isEmpty(); + } + + @Override + public NetworkStats.Bucket getNextBucket(@NonNull NetworkStats unused) { + return mMockedBuckets.remove(); + } } } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 231252502937..f20057d9f800 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -296,6 +296,7 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.NOTIFICATION_BUBBLES, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.LOCATION_SHOW_SYSTEM_OPS, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.DEVICE_STATE_ROTATION_LOCK, value -> { if (TextUtils.isEmpty(value)) { return true; diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index c16cae0412fe..46e24faed5b9 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -250,6 +250,7 @@ <uses-permission android:name="android.permission.MANAGE_APP_PREDICTIONS" /> <uses-permission android:name="android.permission.MANAGE_SEARCH_UI" /> <uses-permission android:name="android.permission.MANAGE_SMARTSPACE" /> + <uses-permission android:name="android.permission.MANAGE_WALLPAPER_EFFECTS_GENERATION" /> <uses-permission android:name="android.permission.MANAGE_UI_TRANSLATION" /> <uses-permission android:name="android.permission.NETWORK_SETTINGS" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> @@ -548,6 +549,10 @@ <!-- Permission required for CTS test - PeopleManagerTest --> <uses-permission android:name="android.permission.READ_PEOPLE_DATA" /> + <!-- Permissions required for CTS test - TrustTestCases --> + <uses-permission android:name="android.permission.PROVIDE_TRUST_AGENT" /> + <uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" /> + <!-- Permission required for CTS test - CtsGameManagerTestCases --> <uses-permission android:name="android.permission.MANAGE_GAME_MODE" /> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 65f22b805d4e..fc2756ecc8e5 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -716,22 +716,6 @@ <!-- Flag to enable privacy dot views, it shall be true for normal case --> <bool name="config_enablePrivacyDot">true</bool> - <!-- The positions widgets can be in defined as View.Gravity constants --> - <integer-array name="config_dreamComplicationPositions"> - </integer-array> - - <!-- Widget components to show as dream complications --> - <string-array name="config_dreamAppWidgetComplications" translatable="false"> - </string-array> - - <!-- Width percentage of dream complications --> - <item name="config_dreamComplicationWidthPercent" translatable="false" format="float" - type="dimen">0.33</item> - - <!-- Height percentage of dream complications --> - <item name="config_dreamComplicationHeightPercent" translatable="false" format="float" - type="dimen">0.25</item> - <!-- Flag to enable dream overlay service and its registration --> <bool name="config_dreamOverlayServiceEnabled">false</bool> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 08fb2c66e043..b22ad66ab67a 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -670,6 +670,10 @@ <string name="quick_settings_dark_mode_secondary_label_on_at">On at <xliff:g id="time" example="10 pm">%s</xliff:g></string> <!-- QuickSettings: Secondary text for when the Dark theme or some other tile will be on until some user-selected time. [CHAR LIMIT=20] --> <string name="quick_settings_dark_mode_secondary_label_until">Until <xliff:g id="time" example="7 am">%s</xliff:g></string> + <!-- QuickSettings: Secondary text for when the Dark theme will be enabled at bedtime. [CHAR LIMIT=40] --> + <string name="quick_settings_dark_mode_secondary_label_on_at_bedtime">On at bedtime</string> + <!-- QuickSettings: Secondary text for when the Dark theme or some other tile will be on until bedtime ends. [CHAR LIMIT=40] --> + <string name="quick_settings_dark_mode_secondary_label_until_bedtime_ends">Until bedtime ends</string> <!-- QuickSettings: NFC tile [CHAR LIMIT=NONE] --> <string name="quick_settings_nfc_label">NFC</string> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java index 1d2caf9ab545..6345d113faed 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java @@ -275,23 +275,27 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener, } public void dump(PrintWriter pw) { - pw.println("RegionSamplingHelper:"); - pw.println(" sampleView isAttached: " + mSampledView.isAttachedToWindow()); - pw.println(" sampleView isScValid: " + (mSampledView.isAttachedToWindow() + dump("", pw); + } + + public void dump(String prefix, PrintWriter pw) { + pw.println(prefix + "RegionSamplingHelper:"); + pw.println(prefix + "\tsampleView isAttached: " + mSampledView.isAttachedToWindow()); + pw.println(prefix + "\tsampleView isScValid: " + (mSampledView.isAttachedToWindow() ? mSampledView.getViewRootImpl().getSurfaceControl().isValid() : "notAttached")); - pw.println(" mSamplingEnabled: " + mSamplingEnabled); - pw.println(" mSamplingListenerRegistered: " + mSamplingListenerRegistered); - pw.println(" mSamplingRequestBounds: " + mSamplingRequestBounds); - pw.println(" mRegisteredSamplingBounds: " + mRegisteredSamplingBounds); - pw.println(" mLastMedianLuma: " + mLastMedianLuma); - pw.println(" mCurrentMedianLuma: " + mCurrentMedianLuma); - pw.println(" mWindowVisible: " + mWindowVisible); - pw.println(" mWindowHasBlurs: " + mWindowHasBlurs); - pw.println(" mWaitingOnDraw: " + mWaitingOnDraw); - pw.println(" mRegisteredStopLayer: " + mRegisteredStopLayer); - pw.println(" mWrappedStopLayer: " + mWrappedStopLayer); - pw.println(" mIsDestroyed: " + mIsDestroyed); + pw.println(prefix + "\tmSamplingEnabled: " + mSamplingEnabled); + pw.println(prefix + "\tmSamplingListenerRegistered: " + mSamplingListenerRegistered); + pw.println(prefix + "\tmSamplingRequestBounds: " + mSamplingRequestBounds); + pw.println(prefix + "\tmRegisteredSamplingBounds: " + mRegisteredSamplingBounds); + pw.println(prefix + "\tmLastMedianLuma: " + mLastMedianLuma); + pw.println(prefix + "\tmCurrentMedianLuma: " + mCurrentMedianLuma); + pw.println(prefix + "\tmWindowVisible: " + mWindowVisible); + pw.println(prefix + "\tmWindowHasBlurs: " + mWindowHasBlurs); + pw.println(prefix + "\tmWaitingOnDraw: " + mWaitingOnDraw); + pw.println(prefix + "\tmRegisteredStopLayer: " + mRegisteredStopLayer); + pw.println(prefix + "\tmWrappedStopLayer: " + mWrappedStopLayer); + pw.println(prefix + "\tmIsDestroyed: " + mIsDestroyed); } public interface SamplingCallback { diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java index d518cb5cdba1..bb7a0a719a74 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java @@ -52,13 +52,14 @@ import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLoggerImpl; import com.android.internal.view.RotationPolicy; -import com.android.systemui.shared.rotation.RotationButton.RotationButtonUpdatesCallback; import com.android.systemui.shared.recents.utilities.Utilities; import com.android.systemui.shared.recents.utilities.ViewRippler; +import com.android.systemui.shared.rotation.RotationButton.RotationButtonUpdatesCallback; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; +import java.io.PrintWriter; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Supplier; @@ -450,6 +451,30 @@ public class RotationButtonController { return mDarkIconColor; } + public void dumpLogs(String prefix, PrintWriter pw) { + pw.println(prefix + "RotationButtonController:"); + + pw.println(String.format( + "%s\tmIsRecentsAnimationRunning=%b", prefix, mIsRecentsAnimationRunning)); + pw.println(String.format("%s\tmHomeRotationEnabled=%b", prefix, mHomeRotationEnabled)); + pw.println(String.format( + "%s\tmLastRotationSuggestion=%d", prefix, mLastRotationSuggestion)); + pw.println(String.format( + "%s\tmPendingRotationSuggestion=%b", prefix, mPendingRotationSuggestion)); + pw.println(String.format( + "%s\tmHoveringRotationSuggestion=%b", prefix, mHoveringRotationSuggestion)); + pw.println(String.format("%s\tmListenersRegistered=%b", prefix, mListenersRegistered)); + pw.println(String.format( + "%s\tmIsNavigationBarShowing=%b", prefix, mIsNavigationBarShowing)); + pw.println(String.format("%s\tmBehavior=%d", prefix, mBehavior)); + pw.println(String.format( + "%s\tmSkipOverrideUserLockPrefsOnce=%b", prefix, mSkipOverrideUserLockPrefsOnce)); + pw.println(String.format( + "%s\tmLightIconColor=0x%s", prefix, Integer.toHexString(mLightIconColor))); + pw.println(String.format( + "%s\tmDarkIconColor=0x%s", prefix, Integer.toHexString(mDarkIconColor))); + } + public RotationButton getRotationButton() { return mRotationButton; } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java index 2ae32c71269a..f2f382dea595 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java @@ -25,6 +25,8 @@ import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.window.TransitionFilter.CONTAINER_ORDER_TOP; +import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_RECENTS; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; @@ -145,6 +147,11 @@ public class RemoteTransitionCompat implements Parcelable { && taskInfo.pictureInPictureParams.isAutoEnterEnabled()) { pipTask = taskInfo.token; } + } else if (change.getTaskInfo() != null + && change.getTaskInfo().topActivityType == ACTIVITY_TYPE_RECENTS) { + // This task is for recents, keep it on top. + t.setLayer(leashMap.get(change.getLeash()), + info.getChanges().size() * 3 - i); } } // Also make all the wallpapers opaque since we want the visible from the start @@ -310,53 +317,48 @@ public class RemoteTransitionCompat implements Parcelable { return; } if (mWrapped != null) mWrapped.finish(toHome, sendUserLeaveHint); - try { - if (!toHome && mPausingTasks != null && mOpeningLeashes == null) { - // The gesture went back to opening the app rather than continuing with - // recents, so end the transition by moving the app back to the top (and also - // re-showing it's task). - final WindowContainerTransaction wct = new WindowContainerTransaction(); - final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - for (int i = mPausingTasks.size() - 1; i >= 0; --i) { - // reverse order so that index 0 ends up on top - wct.reorder(mPausingTasks.get(i), true /* onTop */); - t.show(mInfo.getChange(mPausingTasks.get(i)).getLeash()); - } - mFinishCB.onTransitionFinished(wct, t); - } else { - if (mOpeningLeashes != null) { - // TODO: the launcher animation should handle this - final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - for (int i = 0; i < mOpeningLeashes.size(); ++i) { - t.show(mOpeningLeashes.get(i)); - t.setAlpha(mOpeningLeashes.get(i), 1.f); - } - t.apply(); - } - if (mPipTask != null && mPipTransaction != null) { - final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - t.show(mInfo.getChange(mPipTask).getLeash()); - PictureInPictureSurfaceTransaction.apply(mPipTransaction, - mInfo.getChange(mPipTask).getLeash(), t); - mPipTask = null; - mPipTransaction = null; - mFinishCB.onTransitionFinished(null /* wct */, t); - } else { - mFinishCB.onTransitionFinished(null /* wct */, null /* sct */); + final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + final WindowContainerTransaction wct; + + if (!toHome && mPausingTasks != null && mOpeningLeashes == null) { + // The gesture went back to opening the app rather than continuing with + // recents, so end the transition by moving the app back to the top (and also + // re-showing it's task). + wct = new WindowContainerTransaction(); + for (int i = mPausingTasks.size() - 1; i >= 0; --i) { + // reverse order so that index 0 ends up on top + wct.reorder(mPausingTasks.get(i), true /* onTop */); + t.show(mInfo.getChange(mPausingTasks.get(i)).getLeash()); + } + } else { + wct = null; + if (mOpeningLeashes != null) { + // TODO: the launcher animation should handle this + for (int i = 0; i < mOpeningLeashes.size(); ++i) { + t.show(mOpeningLeashes.get(i)); + t.setAlpha(mOpeningLeashes.get(i), 1.f); } - } - } catch (RemoteException e) { - Log.e("RemoteTransitionCompat", "Failed to call animation finish callback", e); + if (mPipTask != null && mPipTransaction != null) { + t.show(mInfo.getChange(mPipTask).getLeash()); + PictureInPictureSurfaceTransaction.apply(mPipTransaction, + mInfo.getChange(mPipTask).getLeash(), t); + mPipTask = null; + mPipTransaction = null; + } } // Release surface references now. This is apparently to free GPU // memory while doing quick operations (eg. during CTS). - SurfaceControl.Transaction t = new SurfaceControl.Transaction(); for (int i = 0; i < mLeashMap.size(); ++i) { if (mLeashMap.keyAt(i) == mLeashMap.valueAt(i)) continue; t.remove(mLeashMap.valueAt(i)); } - t.apply(); + try { + mFinishCB.onTransitionFinished(wct, t); + } catch (RemoteException e) { + Log.e("RemoteTransitionCompat", "Failed to call animation finish callback", e); + t.apply(); + } for (int i = 0; i < mInfo.getChanges().size(); ++i) { mInfo.getChanges().get(i).getLeash().release(); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java b/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java index 0340904cbd9d..b2658c9f9bdb 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java @@ -16,6 +16,8 @@ package com.android.keyguard; +import android.util.Log; + /** * Defines constants for the Keyguard. */ @@ -25,7 +27,7 @@ public class KeyguardConstants { * Turns on debugging information for the whole Keyguard. This is very verbose and should only * be used temporarily for debugging. */ - public static final boolean DEBUG = false; + public static final boolean DEBUG = Log.isLoggable("Keyguard", Log.DEBUG); public static final boolean DEBUG_SIM_STATES = true; public static final boolean DEBUG_BIOMETRIC_WAKELOCK = true; } diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index 276790483861..b3be87731fbc 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -122,7 +122,8 @@ public class SystemUIFactory { .setTaskSurfaceHelper(mWMComponent.getTaskSurfaceHelper()) .setRecentTasks(mWMComponent.getRecentTasks()) .setCompatUI(Optional.of(mWMComponent.getCompatUI())) - .setDragAndDrop(Optional.of(mWMComponent.getDragAndDrop())); + .setDragAndDrop(Optional.of(mWMComponent.getDragAndDrop())) + .setBackAnimation(mWMComponent.getBackAnimation()); } else { // TODO: Call on prepareSysUIComponentBuilder but not with real components. Other option // is separating this logic into newly creating SystemUITestsFactory. @@ -142,7 +143,8 @@ public class SystemUIFactory { .setTaskSurfaceHelper(Optional.ofNullable(null)) .setRecentTasks(Optional.ofNullable(null)) .setCompatUI(Optional.ofNullable(null)) - .setDragAndDrop(Optional.ofNullable(null)); + .setDragAndDrop(Optional.ofNullable(null)) + .setBackAnimation(Optional.ofNullable(null)); } mSysUIComponent = builder.build(); if (mInitializeComponents) { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java index 052ec86d8398..dbd215d9c713 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java @@ -22,8 +22,10 @@ import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_M import android.annotation.NonNull; import android.annotation.UiContext; +import android.content.ComponentCallbacks; import android.content.Context; import android.content.pm.ActivityInfo; +import android.content.res.Configuration; import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Rect; @@ -54,7 +56,8 @@ import java.util.Collections; * The button icon is movable by dragging and it would not overlap navigation bar window. * And the button UI would automatically be dismissed after displaying for a period of time. */ -class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureListener { +class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureListener, + ComponentCallbacks { @VisibleForTesting static final long FADING_ANIMATION_DURATION_MS = 300; @@ -75,6 +78,7 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL private int mMagnificationMode = ACCESSIBILITY_MAGNIFICATION_MODE_NONE; private final LayoutParams mParams; private final SwitchListener mSwitchListener; + private final Configuration mConfiguration; @VisibleForTesting final Rect mDraggableWindowBounds = new Rect(); private boolean mIsVisible = false; @@ -101,6 +105,7 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL MagnificationModeSwitch(Context context, @NonNull ImageView imageView, SfVsyncFrameCallbackProvider sfVsyncFrameProvider, SwitchListener switchListener) { mContext = context; + mConfiguration = new Configuration(context.getResources().getConfiguration()); mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class); mWindowManager = mContext.getSystemService(WindowManager.class); mSfVsyncFrameProvider = sfVsyncFrameProvider; @@ -270,6 +275,7 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL mIsFadeOutAnimating = false; mImageView.setAlpha(0f); mWindowManager.removeView(mImageView); + mContext.unregisterComponentCallbacks(this); mIsVisible = false; } @@ -291,6 +297,8 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL mImageView.setImageResource(getIconResId(mode)); } if (!mIsVisible) { + onConfigurationChanged(mContext.getResources().getConfiguration()); + mContext.registerComponentCallbacks(this); if (resetPosition) { mDraggableWindowBounds.set(getDraggableWindowBounds()); mParams.x = mDraggableWindowBounds.right; @@ -321,7 +329,21 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL } } + @Override + public void onConfigurationChanged(@NonNull Configuration newConfig) { + final int configDiff = newConfig.diff(mConfiguration); + mConfiguration.setTo(newConfig); + onConfigurationChanged(configDiff); + } + + @Override + public void onLowMemory() { + } + void onConfigurationChanged(int configDiff) { + if (configDiff == 0) { + return; + } if ((configDiff & (ActivityInfo.CONFIG_ORIENTATION | ActivityInfo.CONFIG_SCREEN_SIZE)) != 0) { final Rect previousDraggableBounds = new Rect(mDraggableWindowBounds); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java index 4784bc12099b..885a1777f30b 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java @@ -23,7 +23,6 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_M import android.annotation.MainThread; import android.annotation.Nullable; import android.content.Context; -import android.content.res.Configuration; import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.os.Handler; @@ -65,7 +64,6 @@ public class WindowMagnification extends CoreStartable implements WindowMagnifie private final OverviewProxyService mOverviewProxyService; private WindowMagnificationConnectionImpl mWindowMagnificationConnectionImpl; - private Configuration mLastConfiguration; private SysUiState mSysUiState; private static class ControllerSupplier extends @@ -107,7 +105,6 @@ public class WindowMagnification extends CoreStartable implements WindowMagnifie SysUiState sysUiState, OverviewProxyService overviewProxyService) { super(context); mHandler = mainHandler; - mLastConfiguration = new Configuration(context.getResources().getConfiguration()); mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class); mCommandQueue = commandQueue; mModeSwitchesController = modeSwitchesController; @@ -118,18 +115,6 @@ public class WindowMagnification extends CoreStartable implements WindowMagnifie } @Override - public void onConfigurationChanged(Configuration newConfig) { - final int configDiff = newConfig.diff(mLastConfiguration); - mLastConfiguration.setTo(newConfig); - mMagnificationControllerSupplier.forEach( - magnificationController -> magnificationController.onConfigurationChanged( - configDiff)); - if (mModeSwitchesController != null) { - mModeSwitchesController.onConfigurationChanged(configDiff); - } - } - - @Override public void start() { mCommandQueue.addCallback(this); mOverviewProxyService.addCallback(new OverviewProxyService.OverviewProxyListener() { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java index aa1a43397f65..0d20403b08f2 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java @@ -26,6 +26,7 @@ import android.animation.PropertyValuesHolder; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UiContext; +import android.content.ComponentCallbacks; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Configuration; @@ -76,7 +77,8 @@ import java.util.Locale; * Class to handle adding and removing a window magnification. */ class WindowMagnificationController implements View.OnTouchListener, SurfaceHolder.Callback, - MirrorWindowControl.MirrorWindowDelegate, MagnificationGestureDetector.OnGestureListener { + MirrorWindowControl.MirrorWindowDelegate, MagnificationGestureDetector.OnGestureListener, + ComponentCallbacks { private static final String TAG = "WindowMagnificationController"; @SuppressWarnings("isloggabletaglength") @@ -143,6 +145,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold private View mTopDrag; private View mRightDrag; private View mBottomDrag; + private final Configuration mConfiguration; @NonNull private final WindowMagnifierCallback mWindowMagnifierCallback; @@ -191,6 +194,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold mSfVsyncFrameProvider = sfVsyncFrameProvider; mWindowMagnifierCallback = callback; mSysUiState = sysUiState; + mConfiguration = new Configuration(context.getResources().getConfiguration()); final Display display = mContext.getDisplay(); mDisplayId = mContext.getDisplayId(); @@ -339,6 +343,18 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold } mMirrorViewBounds.setEmpty(); updateSystemUIStateIfNeeded(); + mContext.unregisterComponentCallbacks(this); + } + + @Override + public void onConfigurationChanged(@NonNull Configuration newConfig) { + final int configDiff = newConfig.diff(mConfiguration); + mConfiguration.setTo(newConfig); + onConfigurationChanged(configDiff); + } + + @Override + public void onLowMemory() { } /** @@ -351,6 +367,9 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold Log.d(TAG, "onConfigurationChanged = " + Configuration.configurationDiffToString( configDiff)); } + if (configDiff == 0) { + return; + } if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) { onRotate(); } @@ -390,7 +409,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold if (currentWindowBounds.equals(oldWindowBounds)) { if (DEBUG) { - Log.d(TAG, "updateMagnificationFrame -- window bounds is not changed"); + Log.d(TAG, "handleScreenSizeChanged -- window bounds is not changed"); } return false; } @@ -851,6 +870,10 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold deleteWindowMagnification(); return; } + if (!isWindowVisible()) { + onConfigurationChanged(mResources.getConfiguration()); + mContext.registerComponentCallbacks(this); + } mMagnificationFrameOffsetX = Float.isNaN(magnificationFrameOffsetRatioX) ? mMagnificationFrameOffsetX diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java index 8b7aa093600c..3ece3ccf38fa 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java @@ -224,7 +224,8 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud if (mUdfpsRequested && !getNotificationShadeVisible() && (!mIsBouncerVisible - || mInputBouncerHiddenAmount != KeyguardBouncer.EXPANSION_VISIBLE)) { + || mInputBouncerHiddenAmount != KeyguardBouncer.EXPANSION_VISIBLE) + && mKeyguardStateController.isShowing()) { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt index 25985181364d..8367e1128033 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -244,7 +244,8 @@ class ControlsControllerImpl @Inject constructor ( restoreFinishedReceiver, IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED), PERMISSION_SELF, - null + null, + Context.RECEIVER_NOT_EXPORTED ) listingController.addCallback(listingCallback) } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index b23569241f59..bda8e3c2ed63 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -36,6 +36,7 @@ import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider; import com.android.wm.shell.ShellCommandHandler; import com.android.wm.shell.TaskViewFactory; import com.android.wm.shell.apppairs.AppPairs; +import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.compatui.CompatUI; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; @@ -121,6 +122,9 @@ public interface SysUIComponent { @BindsInstance Builder setDragAndDrop(Optional<DragAndDrop> d); + @BindsInstance + Builder setBackAnimation(Optional<BackAnimation> b); + SysUIComponent build(); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java index 154f6fad5998..bbe9dbd57f53 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java @@ -26,7 +26,6 @@ import com.android.systemui.accessibility.WindowMagnification; import com.android.systemui.biometrics.AuthController; import com.android.systemui.clipboardoverlay.ClipboardListener; import com.android.systemui.dreams.DreamOverlayRegistrant; -import com.android.systemui.dreams.appwidgets.ComplicationPrimer; import com.android.systemui.globalactions.GlobalActionsComponent; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.dagger.KeyguardModule; @@ -212,11 +211,4 @@ public abstract class SystemUIBinder { @ClassKey(DreamOverlayRegistrant.class) public abstract CoreStartable bindDreamOverlayRegistrant( DreamOverlayRegistrant dreamOverlayRegistrant); - - /** Inject into AppWidgetOverlayPrimer. */ - @Binds - @IntoMap - @ClassKey(ComplicationPrimer.class) - public abstract CoreStartable bindAppWidgetOverlayPrimer( - ComplicationPrimer complicationPrimer); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java index b815d4e9884b..b9266923a157 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java @@ -24,6 +24,7 @@ import com.android.wm.shell.ShellCommandHandler; import com.android.wm.shell.ShellInit; import com.android.wm.shell.TaskViewFactory; import com.android.wm.shell.apppairs.AppPairs; +import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.compatui.CompatUI; import com.android.wm.shell.dagger.TvWMShellModule; @@ -123,4 +124,7 @@ public interface WMComponent { @WMSingleton DragAndDrop getDragAndDrop(); + + @WMSingleton + Optional<BackAnimation> getBackAnimation(); } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java index 36319380ad50..63d4d6becc27 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java @@ -163,16 +163,12 @@ public class DozeScreenState implements DozeMachine.Part { // Delay screen state transitions even longer while animations are running. boolean shouldDelayTransitionEnteringDoze = newState == DOZE_AOD - && mParameters.shouldControlScreenOff() && !turningOn; + && mParameters.shouldDelayDisplayDozeTransition() && !turningOn; // Delay screen state transition longer if UDFPS is actively authenticating a fp boolean shouldDelayTransitionForUDFPS = newState == DOZE_AOD && mUdfpsController != null && mUdfpsController.isFingerDown(); - if (shouldDelayTransitionEnteringDoze || shouldDelayTransitionForUDFPS) { - mWakeLock.setAcquired(true); - } - if (!messagePending) { if (DEBUG) { Log.d(TAG, "Display state changed to " + screenState + " delayed by " @@ -180,6 +176,18 @@ public class DozeScreenState implements DozeMachine.Part { } if (shouldDelayTransitionEnteringDoze) { + if (justInitialized) { + // If we are delaying transitioning to doze and the display was not + // turned on we set it to 'on' first to make sure that the animation + // is visible before eventually moving it to doze state. + // The display might be off at this point for example on foldable devices + // when we switch displays and go to doze at the same time. + applyScreenState(Display.STATE_ON); + + // Restore pending screen state as it gets cleared by 'applyScreenState' + mPendingScreenState = screenState; + } + mHandler.postDelayed(mApplyPendingScreenState, ENTER_DOZE_DELAY); } else if (shouldDelayTransitionForUDFPS) { mDozeLog.traceDisplayStateDelayedByUdfps(mPendingScreenState); @@ -190,6 +198,10 @@ public class DozeScreenState implements DozeMachine.Part { } else if (DEBUG) { Log.d(TAG, "Pending display state change to " + screenState); } + + if (shouldDelayTransitionEnteringDoze || shouldDelayTransitionForUDFPS) { + mWakeLock.setAcquired(true); + } } else if (turningOff) { mDozeHost.prepareForGentleSleep(() -> applyScreenState(screenState)); } else { diff --git a/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/AppWidgetProvider.java b/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/AppWidgetProvider.java deleted file mode 100644 index 687f7a296b1a..000000000000 --- a/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/AppWidgetProvider.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.dreams.appwidgets; - -import android.appwidget.AppWidgetHost; -import android.appwidget.AppWidgetHostView; -import android.appwidget.AppWidgetManager; -import android.appwidget.AppWidgetProviderInfo; -import android.content.ComponentName; -import android.content.Context; -import android.content.res.Resources; -import android.util.Log; - -import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Main; - -import java.util.List; - -import javax.inject.Inject; - -/** - * {@link AppWidgetProvider} is a singleton for accessing app widgets within SystemUI. This - * consolidates resources such as the {@link AppWidgetHost} across potentially multiple - * {@link ComplicationProvider} instances and other usages. - */ -@SysUISingleton -public class AppWidgetProvider { - private static final String TAG = "AppWidgetProvider"; - public static final int APP_WIDGET_HOST_ID = 1025; - - private final Context mContext; - private final AppWidgetManager mAppWidgetManager; - private final AppWidgetHost mAppWidgetHost; - private final Resources mResources; - - @Inject - public AppWidgetProvider(Context context, @Main Resources resources) { - mContext = context; - mResources = resources; - mAppWidgetManager = android.appwidget.AppWidgetManager.getInstance(context); - mAppWidgetHost = new AppWidgetHost(context, APP_WIDGET_HOST_ID); - mAppWidgetHost.startListening(); - } - - /** - * Returns an {@link AppWidgetHostView} associated with a given {@link ComponentName}. - * @param component The {@link ComponentName} of the target {@link AppWidgetHostView}. - * @return The {@link AppWidgetHostView} or {@code null} on error. - */ - public AppWidgetHostView getWidget(ComponentName component) { - final List<AppWidgetProviderInfo> appWidgetInfos = - mAppWidgetManager.getInstalledProviders(); - - for (AppWidgetProviderInfo widgetInfo : appWidgetInfos) { - if (widgetInfo.provider.equals(component)) { - final int widgetId = mAppWidgetHost.allocateAppWidgetId(); - - boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(widgetId, - widgetInfo.provider); - - if (!success) { - Log.e(TAG, "could not bind to app widget:" + component); - break; - } - - final AppWidgetHostView appWidgetView = - mAppWidgetHost.createView(mContext, widgetId, widgetInfo); - - if (appWidgetView != null) { - // Register a layout change listener to update the widget on any sizing changes. - appWidgetView.addOnLayoutChangeListener( - (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { - final float density = mResources.getDisplayMetrics().density; - final int height = Math.round((bottom - top) / density); - final int width = Math.round((right - left) / density); - appWidgetView.updateAppWidgetSize(null, width, height, - width, height); - }); - } - - return appWidgetView; - } - } - - return null; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/ComplicationPrimer.java b/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/ComplicationPrimer.java deleted file mode 100644 index 7d30fafda8a6..000000000000 --- a/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/ComplicationPrimer.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.dreams.appwidgets; - -import android.content.ComponentName; -import android.content.Context; -import android.content.res.Resources; -import android.view.Gravity; - -import androidx.constraintlayout.widget.ConstraintSet; - -import com.android.systemui.CoreStartable; -import com.android.systemui.R; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.dreams.ComplicationHostView; -import com.android.systemui.dreams.DreamOverlayStateController; -import com.android.systemui.dreams.appwidgets.dagger.AppWidgetComponent; - -import javax.inject.Inject; - -/** - * {@link ComplicationPrimer} reads the configured AppWidget Complications from resources on start - * and populates them into the {@link DreamOverlayStateController}. - */ -public class ComplicationPrimer extends CoreStartable { - private final Resources mResources; - private final DreamOverlayStateController mDreamOverlayStateController; - private final AppWidgetComponent.Factory mComponentFactory; - - @Inject - public ComplicationPrimer(Context context, @Main Resources resources, - DreamOverlayStateController overlayStateController, - AppWidgetComponent.Factory appWidgetOverlayFactory) { - super(context); - mResources = resources; - mDreamOverlayStateController = overlayStateController; - mComponentFactory = appWidgetOverlayFactory; - } - - @Override - public void start() { - } - - @Override - protected void onBootCompleted() { - super.onBootCompleted(); - loadDefaultWidgets(); - } - - /** - * Generates the {@link ComplicationHostView.LayoutParams} for a given gravity. Default - * dimension constraints are also included in the params. - * @param gravity The gravity for the layout as defined by {@link Gravity}. - * @param resources The resourcs from which default dimensions will be extracted from. - * @return {@link ComplicationHostView.LayoutParams} representing the provided gravity and - * default parameters. - */ - private static ComplicationHostView.LayoutParams getLayoutParams(int gravity, - Resources resources) { - final ComplicationHostView.LayoutParams params = new ComplicationHostView.LayoutParams( - ComplicationHostView.LayoutParams.MATCH_CONSTRAINT, - ComplicationHostView.LayoutParams.MATCH_CONSTRAINT); - - if ((gravity & Gravity.BOTTOM) == Gravity.BOTTOM) { - params.bottomToBottom = ConstraintSet.PARENT_ID; - } - - if ((gravity & Gravity.TOP) == Gravity.TOP) { - params.topToTop = ConstraintSet.PARENT_ID; - } - - if ((gravity & Gravity.END) == Gravity.END) { - params.endToEnd = ConstraintSet.PARENT_ID; - } - - if ((gravity & Gravity.START) == Gravity.START) { - params.startToStart = ConstraintSet.PARENT_ID; - } - - // For now, apply the same sizing constraints on every widget. - params.matchConstraintPercentHeight = - resources.getFloat(R.dimen.config_dreamComplicationHeightPercent); - params.matchConstraintPercentWidth = - resources.getFloat(R.dimen.config_dreamComplicationWidthPercent); - - return params; - } - - /** - * Helper method for loading widgets based on configuration. - */ - private void loadDefaultWidgets() { - final int[] positions = mResources.getIntArray(R.array.config_dreamComplicationPositions); - final String[] components = - mResources.getStringArray(R.array.config_dreamAppWidgetComplications); - - for (int i = 0; i < Math.min(positions.length, components.length); i++) { - final AppWidgetComponent component = mComponentFactory.build( - ComponentName.unflattenFromString(components[i]), - getLayoutParams(positions[i], mResources)); - - mDreamOverlayStateController.addComplication( - component.getAppWidgetComplicationProvider()); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/ComplicationProvider.java b/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/ComplicationProvider.java deleted file mode 100644 index 9188ee54d855..000000000000 --- a/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/ComplicationProvider.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.dreams.appwidgets; - -import android.appwidget.AppWidgetHostView; -import android.content.ComponentName; -import android.content.Context; -import android.util.Log; -import android.widget.RemoteViews; - -import com.android.systemui.dreams.ComplicationHost; -import com.android.systemui.dreams.ComplicationHostView; -import com.android.systemui.plugins.ActivityStarter; - -import javax.inject.Inject; - -/** - * {@link ComplicationProvider} is an implementation of - * {@link com.android.systemui.dreams.ComplicationProvider} for providing app widget-based - * complications. - */ -public class ComplicationProvider implements com.android.systemui.dreams.ComplicationProvider { - private static final String TAG = "AppWidgetCompProvider"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - - private final ActivityStarter mActivityStarter; - private final AppWidgetProvider mAppWidgetProvider; - private final ComponentName mComponentName; - private final ComplicationHostView.LayoutParams mLayoutParams; - - @Inject - public ComplicationProvider(ActivityStarter activityStarter, - ComponentName componentName, AppWidgetProvider widgetProvider, - ComplicationHostView.LayoutParams layoutParams) { - mActivityStarter = activityStarter; - mComponentName = componentName; - mAppWidgetProvider = widgetProvider; - mLayoutParams = layoutParams; - } - - @Override - public void onCreateComplication(Context context, - ComplicationHost.CreationCallback creationCallback, - ComplicationHost.InteractionCallback interactionCallback) { - final AppWidgetHostView widget = mAppWidgetProvider.getWidget(mComponentName); - - if (widget == null) { - Log.e(TAG, "could not create widget"); - return; - } - - widget.setInteractionHandler((view, pendingIntent, response) -> { - if (pendingIntent.isActivity()) { - if (DEBUG) { - Log.d(TAG, "launching pending intent from app widget:" + mComponentName); - } - interactionCallback.onExit(); - mActivityStarter.startPendingIntentDismissingKeyguard(pendingIntent, - null /*intentSentUiThreadCallback*/, view); - return true; - } else { - return RemoteViews.startPendingIntent(view, pendingIntent, - response.getLaunchOptions(view)); - } - }); - - creationCallback.onCreated(widget, mLayoutParams); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/dagger/AppWidgetComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/dagger/AppWidgetComponent.java deleted file mode 100644 index 7beed176eeca..000000000000 --- a/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/dagger/AppWidgetComponent.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.dreams.appwidgets.dagger; - -import android.content.ComponentName; - -import com.android.systemui.dreams.ComplicationHostView; -import com.android.systemui.dreams.appwidgets.ComplicationProvider; - -import dagger.BindsInstance; -import dagger.Subcomponent; - -/** */ -@Subcomponent -public interface AppWidgetComponent { - /** */ - @Subcomponent.Factory - interface Factory { - AppWidgetComponent build(@BindsInstance ComponentName component, - @BindsInstance ComplicationHostView.LayoutParams layoutParams); - } - - /** Builds a {@link ComplicationProvider}. */ - ComplicationProvider getAppWidgetComplicationProvider(); -} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java index 0d4688ec880c..072f50db64f6 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java @@ -16,15 +16,12 @@ package com.android.systemui.dreams.dagger; -import com.android.systemui.dreams.appwidgets.dagger.AppWidgetComponent; - import dagger.Module; /** * Dagger Module providing Communal-related functionality. */ @Module(subcomponents = { - AppWidgetComponent.class, DreamOverlayComponent.class}) public interface DreamModule { -} +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java index b24d08d3f8bb..3ae11ff28345 100644 --- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java +++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java @@ -54,7 +54,7 @@ public class FragmentHostManager { private final View mRootView; private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges( ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_LOCALE - | ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_ASSETS_PATHS); + | ActivityInfo.CONFIG_LAYOUT_DIRECTION | ActivityInfo.CONFIG_ASSETS_PATHS); private final FragmentService mManager; private final ExtensionFragmentManager mPlugins = new ExtensionFragmentManager(); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index d190dcb3ffb8..1bef32ad8caf 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -141,6 +141,7 @@ import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.pip.Pip; @@ -190,6 +191,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, private final Optional<Pip> mPipOptional; private final Optional<LegacySplitScreen> mSplitScreenOptional; private final Optional<Recents> mRecentsOptional; + private final Optional<BackAnimation> mBackAnimation; private final SystemActions mSystemActions; private final Handler mHandler; private final NavigationBarOverlayController mNavbarOverlayController; @@ -504,7 +506,8 @@ public class NavigationBar implements View.OnAttachStateChangeListener, AutoHideController mainAutoHideController, AutoHideController.Factory autoHideControllerFactory, Optional<TelecomManager> telecomManagerOptional, - InputMethodManager inputMethodManager) { + InputMethodManager inputMethodManager, + Optional<BackAnimation> backAnimation) { mContext = context; mWindowManager = windowManager; mAccessibilityManager = accessibilityManager; @@ -524,6 +527,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mPipOptional = pipOptional; mSplitScreenOptional = splitScreenOptional; mRecentsOptional = recentsOptional; + mBackAnimation = backAnimation; mSystemActions = systemActions; mHandler = mainHandler; mNavbarOverlayController = navbarOverlayController; @@ -629,6 +633,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mSplitScreenOptional.ifPresent(mNavigationBarView::registerDockedListener); mPipOptional.ifPresent(mNavigationBarView::addPipExclusionBoundsChangeListener); + mBackAnimation.ifPresent(mNavigationBarView::registerBackAnimation); prepareNavigationBarView(); checkNavBarModes(); @@ -1680,6 +1685,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, private final AutoHideController.Factory mAutoHideControllerFactory; private final Optional<TelecomManager> mTelecomManagerOptional; private final InputMethodManager mInputMethodManager; + private final Optional<BackAnimation> mBackAnimation; @Inject public Factory( @@ -1712,7 +1718,8 @@ public class NavigationBar implements View.OnAttachStateChangeListener, AutoHideController mainAutoHideController, AutoHideController.Factory autoHideControllerFactory, Optional<TelecomManager> telecomManagerOptional, - InputMethodManager inputMethodManager) { + InputMethodManager inputMethodManager, + Optional<BackAnimation> backAnimation) { mAssistManagerLazy = assistManagerLazy; mAccessibilityManager = accessibilityManager; mDeviceProvisionedController = deviceProvisionedController; @@ -1743,6 +1750,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mAutoHideControllerFactory = autoHideControllerFactory; mTelecomManagerOptional = telecomManagerOptional; mInputMethodManager = inputMethodManager; + mBackAnimation = backAnimation; } /** Construct a {@link NavigationBar} */ @@ -1759,7 +1767,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mNavbarOverlayController, mUiEventLogger, mNavBarHelper, mUserTracker, mMainLightBarController, mLightBarControllerFactory, mMainAutoHideController, mAutoHideControllerFactory, mTelecomManagerOptional, - mInputMethodManager); + mInputMethodManager, mBackAnimation); } } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java index a984974c6bba..98b49b1c4890 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java @@ -59,6 +59,7 @@ import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.pip.Pip; import java.io.FileDescriptor; @@ -109,7 +110,8 @@ public class NavigationBarController implements DumpManager dumpManager, AutoHideController autoHideController, LightBarController lightBarController, - Optional<Pip> pipOptional) { + Optional<Pip> pipOptional, + Optional<BackAnimation> backAnimation) { mContext = context; mHandler = mainHandler; mNavigationBarFactory = navigationBarFactory; @@ -121,7 +123,8 @@ public class NavigationBarController implements mTaskbarDelegate = taskbarDelegate; mTaskbarDelegate.setDependencies(commandQueue, overviewProxyService, navBarHelper, navigationModeController, sysUiFlagsContainer, - dumpManager, autoHideController, lightBarController, pipOptional); + dumpManager, autoHideController, lightBarController, pipOptional, + backAnimation.orElse(null)); mIsTablet = isTablet(mContext); dumpManager.registerDumpable(this); } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java index ac816ba9e8d5..2dd89f3c4dcd 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java @@ -91,6 +91,7 @@ import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.LightBarTransitionsController; import com.android.systemui.statusbar.phone.NotificationPanelViewController; import com.android.systemui.statusbar.phone.StatusBar; +import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.pip.Pip; @@ -1417,6 +1418,10 @@ public class NavigationBarView extends FrameLayout implements pip.removePipExclusionBoundsChangeListener(mPipListener); } + void registerBackAnimation(BackAnimation backAnimation) { + mEdgeBackGestureHandler.setBackAnimation(backAnimation); + } + private static void dumpButton(PrintWriter pw, String caption, ButtonDispatcher button) { pw.print(" " + caption + ": "); if (button == null) { diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index 002dd10f7356..441e79a97521 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -69,6 +69,7 @@ import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.BarTransitions; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.LightBarTransitionsController; +import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.pip.Pip; import java.io.FileDescriptor; @@ -150,6 +151,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, mAutoHideController.touchAutoHide(); } }; + private BackAnimation mBackAnimation; @Inject public TaskbarDelegate(Context context) { @@ -172,7 +174,8 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, SysUiState sysUiState, DumpManager dumpManager, AutoHideController autoHideController, LightBarController lightBarController, - Optional<Pip> pipOptional) { + Optional<Pip> pipOptional, + BackAnimation backAnimation) { // TODO: adding this in the ctor results in a dagger dependency cycle :( mCommandQueue = commandQueue; mOverviewProxyService = overviewProxyService; @@ -184,6 +187,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, mLightBarController = lightBarController; mLightBarTransitionsController = createLightBarTransitionsController(); mPipOptional = pipOptional; + mBackAnimation = backAnimation; } // Separated into a method to keep setDependencies() clean/readable. @@ -233,6 +237,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, mAutoHideController.setNavigationBar(mAutoHideUiElement); mLightBarController.setNavigationBar(mLightBarTransitionsController); mPipOptional.ifPresent(this::addPipExclusionBoundsChangeListener); + mEdgeBackGestureHandler.setBackAnimation(mBackAnimation); mInitialized = true; } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index ab48a28facd0..9e350ee60bff 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -79,6 +79,7 @@ import com.android.systemui.shared.tracing.ProtoTraceable; import com.android.systemui.tracing.ProtoTracer; import com.android.systemui.tracing.nano.EdgeBackGestureHandlerProto; import com.android.systemui.tracing.nano.SystemUiTraceProto; +import com.android.wm.shell.back.BackAnimation; import java.io.PrintWriter; import java.util.ArrayDeque; @@ -231,6 +232,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker private InputChannelCompat.InputEventReceiver mInputEventReceiver; private NavigationEdgeBackPlugin mEdgeBackPlugin; + private BackAnimation mBackAnimation; private int mLeftInset; private int mRightInset; private int mSysUiFlags; @@ -494,7 +496,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker Choreographer.getInstance(), this::onInputEvent); // Add a nav bar panel window - setEdgeBackPlugin(new NavigationBarEdgePanel(mContext)); + setEdgeBackPlugin(new NavigationBarEdgePanel(mContext, mBackAnimation)); mPluginManager.addPluginListener( this, NavigationEdgeBackPlugin.class, /*allowMultiple=*/ false); } @@ -509,7 +511,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker @Override public void onPluginDisconnected(NavigationEdgeBackPlugin plugin) { - setEdgeBackPlugin(new NavigationBarEdgePanel(mContext)); + setEdgeBackPlugin(new NavigationBarEdgePanel(mContext, mBackAnimation)); } private void setEdgeBackPlugin(NavigationEdgeBackPlugin edgeBackPlugin) { @@ -930,6 +932,10 @@ public class EdgeBackGestureHandler extends CurrentUserTracker proto.edgeBackGestureHandler.allowGesture = mAllowGesture; } + public void setBackAnimation(BackAnimation backAnimation) { + mBackAnimation = backAnimation; + } + /** * Injectable instance to create a new EdgeBackGestureHandler. * diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java index 8d1dfc842fba..c18209d9abca 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java @@ -57,6 +57,7 @@ import com.android.systemui.animation.Interpolators; import com.android.systemui.plugins.NavigationEdgeBackPlugin; import com.android.systemui.shared.navigationbar.RegionSamplingHelper; import com.android.systemui.statusbar.VibratorHelper; +import com.android.wm.shell.back.BackAnimation; import java.io.PrintWriter; import java.util.concurrent.Executor; @@ -277,11 +278,14 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl } }; private BackCallback mBackCallback; + private final BackAnimation mBackAnimation; - public NavigationBarEdgePanel(Context context) { + public NavigationBarEdgePanel(Context context, + BackAnimation backAnimation) { super(context); mWindowManager = context.getSystemService(WindowManager.class); + mBackAnimation = backAnimation; mVibratorHelper = Dependency.get(VibratorHelper.class); mDensity = context.getResources().getDisplayMetrics().density; @@ -459,6 +463,9 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl @Override public void onMotionEvent(MotionEvent event) { + if (mBackAnimation != null) { + mBackAnimation.onBackMotion(event); + } if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } @@ -866,6 +873,9 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl // Whenever the trigger back state changes the existing translation animation should be // cancelled mTranslationAnimation.cancel(); + if (mBackAnimation != null) { + mBackAnimation.setTriggerBack(triggerBack); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java index c8e2ca7e7ea8..e26c768a5e80 100644 --- a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java +++ b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java @@ -93,6 +93,7 @@ public class QRCodeScannerController implements private final DeviceConfigProxy mDeviceConfigProxy; private final ArrayList<Callback> mCallbacks = new ArrayList<>(); private final UserTracker mUserTracker; + private final boolean mConfigEnableLockScreenButton; private HashMap<Integer, ContentObserver> mQRCodeScannerPreferenceObserver = new HashMap<>(); private DeviceConfig.OnPropertiesChangedListener mOnDefaultQRCodeScannerChangedListener = null; @@ -118,6 +119,9 @@ public class QRCodeScannerController implements mSecureSettings = secureSettings; mDeviceConfigProxy = proxy; mUserTracker = userTracker; + + mConfigEnableLockScreenButton = mContext.getResources().getBoolean( + android.R.bool.config_enableQrCodeScannerOnLockScreen); } /** @@ -156,7 +160,7 @@ public class QRCodeScannerController implements * Returns true if lock screen entry point for QR Code Scanner is to be enabled. */ public boolean isEnabledForLockScreenButton() { - return mQRCodeScannerEnabled && mIntent != null; + return mQRCodeScannerEnabled && mIntent != null && mConfigEnableLockScreenButton; } /** @@ -235,6 +239,11 @@ public class QRCodeScannerController implements } private void updateQRCodeScannerPreferenceDetails(boolean updateSettings) { + if (!mConfigEnableLockScreenButton) { + // Settings only apply to lock screen entry point. + return; + } + boolean prevQRCodeScannerEnabled = mQRCodeScannerEnabled; mQRCodeScannerEnabled = mSecureSettings.getIntForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, 0, mUserTracker.getUserId()) != 0; @@ -251,8 +260,15 @@ public class QRCodeScannerController implements private void updateQRCodeScannerActivityDetails() { String qrCodeScannerActivity = mDeviceConfigProxy.getString( DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER, - mContext.getResources().getString(R.string.def_qr_code_component)); + SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER, ""); + + // "" means either the flags is not available or is set to "", and in both the cases we + // want to use R.string.def_qr_code_component + if (Objects.equals(qrCodeScannerActivity, "")) { + qrCodeScannerActivity = + mContext.getResources().getString(R.string.def_qr_code_component); + } + String prevQrCodeScannerActivity = mQRCodeScannerActivity; ComponentName componentName = null; Intent intent = new Intent(); @@ -281,8 +297,12 @@ public class QRCodeScannerController implements // Our intent should always be explicit and should have a component set if (intent.getComponent() == null) return false; - int flags = PackageManager.MATCH_DEFAULT_ONLY | PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; + int flags = PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.MATCH_UNINSTALLED_PACKAGES + | PackageManager.MATCH_DISABLED_COMPONENTS + | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS + | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS; return !mContext.getPackageManager().queryIntentActivities(intent, flags).isEmpty(); } @@ -296,6 +316,11 @@ public class QRCodeScannerController implements } private void unregisterQRCodePreferenceObserver() { + if (!mConfigEnableLockScreenButton) { + // Settings only apply to lock screen entry point. + return; + } + mQRCodeScannerPreferenceObserver.forEach((key, value) -> { mSecureSettings.unregisterContentObserver(value); }); @@ -357,6 +382,11 @@ public class QRCodeScannerController implements } private void registerQRCodePreferenceObserver() { + if (!mConfigEnableLockScreenButton) { + // Settings only apply to lock screen entry point. + return; + } + int userId = mUserTracker.getUserId(); if (mQRCodeScannerPreferenceObserver.getOrDefault(userId, null) != null) return; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java index 596d8f01248a..e2964eaf2a23 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java @@ -129,17 +129,27 @@ public class UiModeNightTile extends QSTileImpl<QSTile.BooleanState> implements ? R.string.quick_settings_dark_mode_secondary_label_until_sunrise : R.string.quick_settings_dark_mode_secondary_label_on_at_sunset); } else if (uiMode == UiModeManager.MODE_NIGHT_CUSTOM) { - final boolean use24HourFormat = android.text.format.DateFormat.is24HourFormat(mContext); - final LocalTime time; - if (nightMode) { - time = mUiModeManager.getCustomNightModeEnd(); + int nightModeCustomType = mUiModeManager.getNightModeCustomType(); + if (nightModeCustomType == UiModeManager.MODE_NIGHT_CUSTOM_TYPE_SCHEDULE) { + final boolean use24HourFormat = android.text.format.DateFormat.is24HourFormat( + mContext); + final LocalTime time; + if (nightMode) { + time = mUiModeManager.getCustomNightModeEnd(); + } else { + time = mUiModeManager.getCustomNightModeStart(); + } + state.secondaryLabel = mContext.getResources().getString(nightMode + ? R.string.quick_settings_dark_mode_secondary_label_until + : R.string.quick_settings_dark_mode_secondary_label_on_at, + use24HourFormat ? time.toString() : formatter.format(time)); + } else if (nightModeCustomType == UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME) { + state.secondaryLabel = mContext.getResources().getString(nightMode + ? R.string.quick_settings_dark_mode_secondary_label_until_bedtime_ends + : R.string.quick_settings_dark_mode_secondary_label_on_at_bedtime); } else { - time = mUiModeManager.getCustomNightModeStart(); + state.secondaryLabel = null; } - state.secondaryLabel = mContext.getResources().getString(nightMode - ? R.string.quick_settings_dark_mode_secondary_label_until - : R.string.quick_settings_dark_mode_secondary_label_on_at, - use24HourFormat ? time.toString() : formatter.format(time)); } else { state.secondaryLabel = null; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt index c8115e2c197a..9a932bae833e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt @@ -355,7 +355,8 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, } override fun onDraw(canvas: Canvas?) { - if (canvas == null || revealGradientWidth <= 0 || revealGradientHeight <= 0) { + if (canvas == null || revealGradientWidth <= 0 || revealGradientHeight <= 0 + || revealAmount == 0f) { if (revealAmount < 1f) { canvas?.drawColor(revealGradientEndColor) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/SectionHeaderVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/SectionHeaderVisibilityProvider.kt new file mode 100644 index 000000000000..03b978e7784c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/SectionHeaderVisibilityProvider.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification + +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +/** + * A class which keeps track of whether section headers should be shown in the notification shade. + * + * (In an ideal world, this would directly monitor the state of the keyguard and invalidate the + * pipeline to show/hide headers, but the KeyguardController already invalidates the pipeline when + * the keyguard's state changes. Instead of having both classes monitor for state changes and ending + * up with duplicate runs of the pipeline, we let the KeyguardController update the header + * visibility when it invalidates, and we just store that state here.) + */ +@SysUISingleton +class SectionHeaderVisibilityProvider @Inject constructor() { + var sectionHeadersVisible = true +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java index 3a39c39cfb20..f04b24ebfb42 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java @@ -101,7 +101,7 @@ public class AppOpsCoordinator implements Coordinator { }; /** - * Puts foreground service notifications into its own section. + * Puts colorized foreground service and call notifications into its own section. */ private final NotifSectioner mNotifSectioner = new NotifSectioner("ForegroundService", NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE) { @@ -109,12 +109,22 @@ public class AppOpsCoordinator implements Coordinator { public boolean isInSection(ListEntry entry) { NotificationEntry notificationEntry = entry.getRepresentativeEntry(); if (notificationEntry != null) { - Notification notification = notificationEntry.getSbn().getNotification(); - return notification.isForegroundService() - && notification.isColorized() - && entry.getRepresentativeEntry().getImportance() > IMPORTANCE_MIN; + return isColorizedForegroundService(notificationEntry) || isCall(notificationEntry); } return false; } + + private boolean isColorizedForegroundService(NotificationEntry entry) { + Notification notification = entry.getSbn().getNotification(); + return notification.isForegroundService() + && notification.isColorized() + && entry.getImportance() > IMPORTANCE_MIN; + } + + private boolean isCall(NotificationEntry entry) { + Notification notification = entry.getSbn().getNotification(); + return entry.getImportance() > IMPORTANCE_MIN + && notification.isStyle(Notification.CallStyle.class); + } }; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt index e59f4a62f9b7..ba88ad7844f1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt @@ -20,11 +20,13 @@ import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner import com.android.systemui.statusbar.notification.collection.render.NodeController import com.android.systemui.statusbar.notification.dagger.PeopleHeader import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.PeopleNotificationType import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE import javax.inject.Inject @@ -48,18 +50,36 @@ class ConversationCoordinator @Inject constructor( val sectioner = object : NotifSectioner("People", BUCKET_PEOPLE) { override fun isInSection(entry: ListEntry): Boolean = - isConversation(entry.representativeEntry!!) + isConversation(entry) override fun getHeaderNodeController() = // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and peopleHeaderController if (RankingCoordinator.SHOW_ALL_SECTIONS) peopleHeaderController else null } + val comparator = object : NotifComparator("People") { + override fun compare(entry1: ListEntry, entry2: ListEntry): Int { + assert(entry1.section === entry2.section) + if (entry1.section?.sectioner !== sectioner) { + return 0 + } + val type1 = getPeopleType(entry1) + val type2 = getPeopleType(entry2) + return type2.compareTo(type1) + } + } + override fun attach(pipeline: NotifPipeline) { pipeline.addPromoter(notificationPromoter) } - private fun isConversation(entry: NotificationEntry): Boolean = - peopleNotificationIdentifier.getPeopleNotificationType(entry) != TYPE_NON_PERSON + private fun isConversation(entry: ListEntry): Boolean = + getPeopleType(entry) != TYPE_NON_PERSON + + @PeopleNotificationType + private fun getPeopleType(entry: ListEntry): Int = + entry.representativeEntry?.let { + peopleNotificationIdentifier.getPeopleNotificationType(it) + } ?: TYPE_NON_PERSON companion object { private const val TAG = "ConversationCoordinator" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java deleted file mode 100644 index 74109120149e..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.notification.collection.coordinator; - -import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY; -import static com.android.systemui.statusbar.notification.interruption.HeadsUpController.alertAgain; - -import android.util.ArraySet; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.statusbar.NotificationRemoteInputManager; -import com.android.systemui.statusbar.notification.collection.ListEntry; -import com.android.systemui.statusbar.notification.collection.NotifPipeline; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope; -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner; -import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; -import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; -import com.android.systemui.statusbar.notification.collection.render.NodeController; -import com.android.systemui.statusbar.notification.dagger.IncomingHeader; -import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; -import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt; -import com.android.systemui.statusbar.policy.HeadsUpManager; -import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; -import com.android.systemui.util.concurrency.DelayableExecutor; - -import javax.inject.Inject; - -/** - * Coordinates heads up notification (HUN) interactions with the notification pipeline based on - * the HUN state reported by the {@link HeadsUpManager}. In this class we only consider one - * notification, in particular the {@link HeadsUpManager#getTopEntry()}, to be HeadsUpping at a - * time even though other notifications may be queued to heads up next. - * - * The current HUN, but not HUNs that are queued to heads up, will be: - * - Lifetime extended until it's no longer heads upping. - * - Promoted out of its group if it's a child of a group. - * - In the HeadsUpCoordinatorSection. Ordering is configured in {@link NotifCoordinators}. - * - Removed from HeadsUpManager if it's removed from the NotificationCollection. - * - * Note: The inflation callback in {@link PreparationCoordinator} handles showing HUNs. - */ -@CoordinatorScope -public class HeadsUpCoordinator implements Coordinator { - private static final String TAG = "HeadsUpCoordinator"; - - private final HeadsUpManager mHeadsUpManager; - private final HeadsUpViewBinder mHeadsUpViewBinder; - private final NotificationInterruptStateProvider mNotificationInterruptStateProvider; - private final NotificationRemoteInputManager mRemoteInputManager; - private final NodeController mIncomingHeaderController; - private final DelayableExecutor mExecutor; - - private NotifLifetimeExtender.OnEndLifetimeExtensionCallback mEndLifetimeExtension; - // notifs we've extended the lifetime for - private final ArraySet<NotificationEntry> mNotifsExtendingLifetime = new ArraySet<>(); - - @Inject - public HeadsUpCoordinator( - HeadsUpManager headsUpManager, - HeadsUpViewBinder headsUpViewBinder, - NotificationInterruptStateProvider notificationInterruptStateProvider, - NotificationRemoteInputManager remoteInputManager, - @IncomingHeader NodeController incomingHeaderController, - @Main DelayableExecutor executor) { - mHeadsUpManager = headsUpManager; - mHeadsUpViewBinder = headsUpViewBinder; - mNotificationInterruptStateProvider = notificationInterruptStateProvider; - mRemoteInputManager = remoteInputManager; - mIncomingHeaderController = incomingHeaderController; - mExecutor = executor; - } - - @Override - public void attach(NotifPipeline pipeline) { - mHeadsUpManager.addListener(mOnHeadsUpChangedListener); - pipeline.addCollectionListener(mNotifCollectionListener); - pipeline.addPromoter(mNotifPromoter); - pipeline.addNotificationLifetimeExtender(mLifetimeExtender); - } - - public NotifSectioner getSectioner() { - return mNotifSectioner; - } - - private void onHeadsUpViewBound(NotificationEntry entry) { - mHeadsUpManager.showNotification(entry); - } - - private final NotifCollectionListener mNotifCollectionListener = new NotifCollectionListener() { - /** - * Notification was just added and if it should heads up, bind the view and then show it. - */ - @Override - public void onEntryAdded(NotificationEntry entry) { - if (mNotificationInterruptStateProvider.shouldHeadsUp(entry)) { - mHeadsUpViewBinder.bindHeadsUpView( - entry, - HeadsUpCoordinator.this::onHeadsUpViewBound); - } - } - - /** - * Notification could've updated to be heads up or not heads up. Even if it did update to - * heads up, if the notification specified that it only wants to alert once, don't heads - * up again. - */ - @Override - public void onEntryUpdated(NotificationEntry entry) { - boolean hunAgain = alertAgain(entry, entry.getSbn().getNotification()); - // includes check for whether this notification should be filtered: - boolean shouldHeadsUp = mNotificationInterruptStateProvider.shouldHeadsUp(entry); - final boolean wasHeadsUp = mHeadsUpManager.isAlerting(entry.getKey()); - if (wasHeadsUp) { - if (shouldHeadsUp) { - mHeadsUpManager.updateNotification(entry.getKey(), hunAgain); - } else if (!mHeadsUpManager.isEntryAutoHeadsUpped(entry.getKey())) { - // We don't want this to be interrupting anymore, let's remove it - mHeadsUpManager.removeNotification( - entry.getKey(), false /* removeImmediately */); - } - } else if (shouldHeadsUp && hunAgain) { - // This notification was updated to be heads up, show it! - mHeadsUpViewBinder.bindHeadsUpView( - entry, - HeadsUpCoordinator.this::onHeadsUpViewBound); - } - } - - /** - * Stop alerting HUNs that are removed from the notification collection - */ - @Override - public void onEntryRemoved(NotificationEntry entry, int reason) { - final String entryKey = entry.getKey(); - if (mHeadsUpManager.isAlerting(entryKey)) { - boolean removeImmediatelyForRemoteInput = - mRemoteInputManager.isSpinning(entryKey) - && !FORCE_REMOTE_INPUT_HISTORY; - mHeadsUpManager.removeNotification(entry.getKey(), removeImmediatelyForRemoteInput); - } - } - - @Override - public void onEntryCleanUp(NotificationEntry entry) { - mHeadsUpViewBinder.abortBindCallback(entry); - } - }; - - private final NotifLifetimeExtender mLifetimeExtender = new NotifLifetimeExtender() { - @Override - public @NonNull String getName() { - return TAG; - } - - @Override - public void setCallback(@NonNull OnEndLifetimeExtensionCallback callback) { - mEndLifetimeExtension = callback; - } - - @Override - public boolean maybeExtendLifetime(@NonNull NotificationEntry entry, int reason) { - boolean extend = !mHeadsUpManager.canRemoveImmediately(entry.getKey()); - if (extend) { - if (isSticky(entry)) { - long removeAfterMillis = mHeadsUpManager.getEarliestRemovalTime(entry.getKey()); - mExecutor.executeDelayed(() -> { - if (mNotifsExtendingLifetime.contains(entry) - && mHeadsUpManager.canRemoveImmediately(entry.getKey())) { - mHeadsUpManager.removeNotification( - entry.getKey(), /* releaseImmediately */ true); - } - }, removeAfterMillis); - } else { - // remove as early as possible - mExecutor.execute( - () -> mHeadsUpManager.removeNotification( - entry.getKey(), /* releaseImmediately */ false)); - } - mNotifsExtendingLifetime.add(entry); - } - return extend; - } - - @Override - public void cancelLifetimeExtension(@NonNull NotificationEntry entry) { - mNotifsExtendingLifetime.remove(entry); - } - }; - - private final NotifPromoter mNotifPromoter = new NotifPromoter(TAG) { - @Override - public boolean shouldPromoteToTopLevel(NotificationEntry entry) { - return isCurrentlyShowingHun(entry); - } - }; - - private final NotifSectioner mNotifSectioner = new NotifSectioner("HeadsUp", - NotificationPriorityBucketKt.BUCKET_HEADS_UP) { - @Override - public boolean isInSection(ListEntry entry) { - return isCurrentlyShowingHun(entry); - } - - @Nullable - @Override - public NodeController getHeaderNodeController() { - // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and mIncomingHeaderController - if (RankingCoordinator.SHOW_ALL_SECTIONS) { - return mIncomingHeaderController; - } - return null; - } - }; - - private final OnHeadsUpChangedListener mOnHeadsUpChangedListener = - new OnHeadsUpChangedListener() { - @Override - public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) { - if (!isHeadsUp) { - mHeadsUpViewBinder.unbindHeadsUpView(entry); - endNotifLifetimeExtensionIfExtended(entry); - } - } - }; - - private boolean isSticky(NotificationEntry entry) { - return mHeadsUpManager.isSticky(entry.getKey()); - } - - private boolean isCurrentlyShowingHun(ListEntry entry) { - return mHeadsUpManager.isAlerting(entry.getKey()); - } - - private void endNotifLifetimeExtensionIfExtended(NotificationEntry entry) { - if (mNotifsExtendingLifetime.remove(entry)) { - mEndLifetimeExtension.onEndLifetimeExtension(mLifetimeExtender, entry); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt new file mode 100644 index 000000000000..b84b38233073 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.notification.collection.coordinator + +import android.util.ArraySet +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.statusbar.NotificationRemoteInputManager +import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback +import com.android.systemui.statusbar.notification.collection.render.NodeController +import com.android.systemui.statusbar.notification.dagger.IncomingHeader +import com.android.systemui.statusbar.notification.interruption.HeadsUpController +import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider +import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP +import com.android.systemui.statusbar.policy.HeadsUpManager +import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener +import com.android.systemui.util.concurrency.DelayableExecutor +import javax.inject.Inject + +/** + * Coordinates heads up notification (HUN) interactions with the notification pipeline based on + * the HUN state reported by the [HeadsUpManager]. In this class we only consider one + * notification, in particular the [HeadsUpManager.getTopEntry], to be HeadsUpping at a + * time even though other notifications may be queued to heads up next. + * + * The current HUN, but not HUNs that are queued to heads up, will be: + * - Lifetime extended until it's no longer heads upping. + * - Promoted out of its group if it's a child of a group. + * - In the HeadsUpCoordinatorSection. Ordering is configured in [NotifCoordinators]. + * - Removed from HeadsUpManager if it's removed from the NotificationCollection. + * + * Note: The inflation callback in [PreparationCoordinator] handles showing HUNs. + */ +@CoordinatorScope +class HeadsUpCoordinator @Inject constructor( + private val mHeadsUpManager: HeadsUpManager, + private val mHeadsUpViewBinder: HeadsUpViewBinder, + private val mNotificationInterruptStateProvider: NotificationInterruptStateProvider, + private val mRemoteInputManager: NotificationRemoteInputManager, + @IncomingHeader private val mIncomingHeaderController: NodeController, + @Main private val mExecutor: DelayableExecutor +) : Coordinator { + private var mEndLifetimeExtension: OnEndLifetimeExtensionCallback? = null + + // notifs we've extended the lifetime for + private val mNotifsExtendingLifetime = ArraySet<NotificationEntry>() + + override fun attach(pipeline: NotifPipeline) { + mHeadsUpManager.addListener(mOnHeadsUpChangedListener) + pipeline.addCollectionListener(mNotifCollectionListener) + pipeline.addPromoter(mNotifPromoter) + pipeline.addNotificationLifetimeExtender(mLifetimeExtender) + } + + private fun onHeadsUpViewBound(entry: NotificationEntry) { + mHeadsUpManager.showNotification(entry) + } + + private val mNotifCollectionListener = object : NotifCollectionListener { + /** + * Notification was just added and if it should heads up, bind the view and then show it. + */ + override fun onEntryAdded(entry: NotificationEntry) { + if (mNotificationInterruptStateProvider.shouldHeadsUp(entry)) { + mHeadsUpViewBinder.bindHeadsUpView(entry) { entry -> onHeadsUpViewBound(entry) } + } + } + + /** + * Notification could've updated to be heads up or not heads up. Even if it did update to + * heads up, if the notification specified that it only wants to alert once, don't heads + * up again. + */ + override fun onEntryUpdated(entry: NotificationEntry) { + val hunAgain = HeadsUpController.alertAgain(entry, entry.sbn.notification) + // includes check for whether this notification should be filtered: + val shouldHeadsUp = mNotificationInterruptStateProvider.shouldHeadsUp(entry) + val wasHeadsUp = mHeadsUpManager.isAlerting(entry.key) + if (wasHeadsUp) { + if (shouldHeadsUp) { + mHeadsUpManager.updateNotification(entry.key, hunAgain) + } else if (!mHeadsUpManager.isEntryAutoHeadsUpped(entry.key)) { + // We don't want this to be interrupting anymore, let's remove it + mHeadsUpManager.removeNotification( + entry.key, false /* removeImmediately */ + ) + } + } else if (shouldHeadsUp && hunAgain) { + // This notification was updated to be heads up, show it! + mHeadsUpViewBinder.bindHeadsUpView(entry) { entry -> onHeadsUpViewBound(entry) } + } + } + + /** + * Stop alerting HUNs that are removed from the notification collection + */ + override fun onEntryRemoved(entry: NotificationEntry, reason: Int) { + val entryKey = entry.key + if (mHeadsUpManager.isAlerting(entryKey)) { + val removeImmediatelyForRemoteInput = (mRemoteInputManager.isSpinning(entryKey) && + !NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY) + mHeadsUpManager.removeNotification(entry.key, removeImmediatelyForRemoteInput) + } + } + + override fun onEntryCleanUp(entry: NotificationEntry) { + mHeadsUpViewBinder.abortBindCallback(entry) + } + } + + private val mLifetimeExtender = object : NotifLifetimeExtender { + override fun getName() = TAG + + override fun setCallback(callback: OnEndLifetimeExtensionCallback) { + mEndLifetimeExtension = callback + } + + override fun maybeExtendLifetime(entry: NotificationEntry, reason: Int): Boolean { + if (mHeadsUpManager.canRemoveImmediately(entry.key)) { + return false + } + if (isSticky(entry)) { + val removeAfterMillis = mHeadsUpManager.getEarliestRemovalTime(entry.key) + mExecutor.executeDelayed({ + val canStillRemove = mHeadsUpManager.canRemoveImmediately(entry.key) + if (mNotifsExtendingLifetime.contains(entry) && canStillRemove) { + mHeadsUpManager.removeNotification(entry.key, /* releaseImmediately */ true) + } + }, removeAfterMillis) + } else { + mExecutor.execute { + mHeadsUpManager.removeNotification(entry.key, /* releaseImmediately */ false) + } + } + mNotifsExtendingLifetime.add(entry) + return true + } + + override fun cancelLifetimeExtension(entry: NotificationEntry) { + mNotifsExtendingLifetime.remove(entry) + } + } + + private val mNotifPromoter = object : NotifPromoter(TAG) { + override fun shouldPromoteToTopLevel(entry: NotificationEntry): Boolean = + isCurrentlyShowingHun(entry) + } + + val sectioner = object : NotifSectioner("HeadsUp", BUCKET_HEADS_UP) { + override fun isInSection(entry: ListEntry): Boolean = isCurrentlyShowingHun(entry) + + override fun getHeaderNodeController(): NodeController? = + // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and mIncomingHeaderController + if (RankingCoordinator.SHOW_ALL_SECTIONS) mIncomingHeaderController else null + } + + private val mOnHeadsUpChangedListener = object : OnHeadsUpChangedListener { + override fun onHeadsUpStateChanged(entry: NotificationEntry, isHeadsUp: Boolean) { + if (!isHeadsUp) { + mHeadsUpViewBinder.unbindHeadsUpView(entry) + endNotifLifetimeExtensionIfExtended(entry) + } + } + } + + private fun isSticky(entry: NotificationEntry) = mHeadsUpManager.isSticky(entry.key) + + private fun isCurrentlyShowingHun(entry: ListEntry) = mHeadsUpManager.isAlerting(entry.key) + + private fun endNotifLifetimeExtensionIfExtended(entry: NotificationEntry) { + if (mNotifsExtendingLifetime.remove(entry)) { + mEndLifetimeExtension?.onEndLifetimeExtension(mLifetimeExtender, entry) + } + } + + companion object { + private const val TAG = "HeadsUpCoordinator" + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java index 33005b34ff98..733be9c1ca2c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java @@ -36,6 +36,8 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.notification.SectionHeaderVisibilityProvider; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; @@ -48,7 +50,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import javax.inject.Inject; /** - * Filters low priority and privacy-sensitive notifications from the lockscreen. + * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section + * headers on the lockscreen. */ @CoordinatorScope public class KeyguardCoordinator implements Coordinator { @@ -62,6 +65,7 @@ public class KeyguardCoordinator implements Coordinator { private final StatusBarStateController mStatusBarStateController; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final HighPriorityProvider mHighPriorityProvider; + private final SectionHeaderVisibilityProvider mSectionHeaderVisibilityProvider; private boolean mHideSilentNotificationsOnLockscreen; @@ -74,7 +78,8 @@ public class KeyguardCoordinator implements Coordinator { BroadcastDispatcher broadcastDispatcher, StatusBarStateController statusBarStateController, KeyguardUpdateMonitor keyguardUpdateMonitor, - HighPriorityProvider highPriorityProvider) { + HighPriorityProvider highPriorityProvider, + SectionHeaderVisibilityProvider sectionHeaderVisibilityProvider) { mContext = context; mMainHandler = mainThreadHandler; mKeyguardStateController = keyguardStateController; @@ -83,6 +88,7 @@ public class KeyguardCoordinator implements Coordinator { mStatusBarStateController = statusBarStateController; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mHighPriorityProvider = highPriorityProvider; + mSectionHeaderVisibilityProvider = sectionHeaderVisibilityProvider; } @Override @@ -214,6 +220,8 @@ public class KeyguardCoordinator implements Coordinator { } private void invalidateListFromFilter(String reason) { + mSectionHeaderVisibilityProvider.setSectionHeadersVisible( + mStatusBarStateController.getState() != StatusBarState.KEYGUARD); mNotifFilter.invalidateList(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt index 757fb5a2fe9a..850cb4b88154 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt @@ -20,6 +20,7 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.statusbar.notification.NotifPipelineFlags import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner import java.io.FileDescriptor import java.io.PrintWriter @@ -63,6 +64,7 @@ class NotifCoordinatorsImpl @Inject constructor( private val mCoordinators: MutableList<Coordinator> = ArrayList() private val mOrderedSections: MutableList<NotifSectioner> = ArrayList() + private val mOrderedComparators: MutableList<NotifComparator> = ArrayList() /** * Creates all the coordinators. @@ -117,6 +119,9 @@ class NotifCoordinatorsImpl @Inject constructor( mOrderedSections.add(rankingCoordinator.alertingSectioner) // Alerting mOrderedSections.add(rankingCoordinator.silentSectioner) // Silent mOrderedSections.add(rankingCoordinator.minimizedSectioner) // Minimized + + // Manually add ordered comparators + mOrderedComparators.add(conversationCoordinator.comparator) } /** @@ -128,6 +133,7 @@ class NotifCoordinatorsImpl @Inject constructor( c.attach(pipeline) } pipeline.setSections(mOrderedSections) + pipeline.setComparators(mOrderedComparators) } override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java index 0d150edee128..f7bbd281ec51 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable; +import androidx.annotation.NonNull; + import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; @@ -39,5 +41,5 @@ public abstract class NotifComparator * @return a negative integer, zero, or a positive integer as the first argument is less than * equal to, or greater than the second (same as standard Comparator<> interface). */ - public abstract int compare(ListEntry o1, ListEntry o2); + public abstract int compare(@NonNull ListEntry o1, @NonNull ListEntry o2); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt index d16d76ad2f9a..ab777de21e34 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt @@ -77,7 +77,7 @@ class DebugModeFilterProvider @Inject constructor( if (needsInitialization) { val filter = IntentFilter().apply { addAction(ACTION_SET_NOTIF_DEBUG_MODE) } val permission = NOTIF_DEBUG_MODE_PERMISSION - context.registerReceiver(mReceiver, filter, permission, null) + context.registerReceiver(mReceiver, filter, permission, null, Context.RECEIVER_EXPORTED) Log.d(TAG, "Registered: $mReceiver") } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt index f13470ec2c94..607500edfd3a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.collection.render +import com.android.systemui.statusbar.notification.SectionHeaderVisibilityProvider import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.ListEntry @@ -35,6 +36,7 @@ import com.android.systemui.util.traceSection class NodeSpecBuilder( private val mediaContainerController: MediaContainerController, private val sectionsFeatureManager: NotificationSectionsFeatureManager, + private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider, private val viewBarn: NotifViewBarn ) { fun buildNodeSpec( @@ -51,6 +53,7 @@ class NodeSpecBuilder( var currentSection: NotifSection? = null val prevSections = mutableSetOf<NotifSection?>() + val showHeaders = sectionHeaderVisibilityProvider.sectionHeadersVisible for (entry in notifList) { val section = entry.section!! @@ -61,7 +64,7 @@ class NodeSpecBuilder( // If this notif begins a new section, first add the section's header view if (section != currentSection) { - if (section.headerController != currentSection?.headerController) { + if (section.headerController != currentSection?.headerController && showHeaders) { section.headerController?.let { headerController -> root.children.add(NodeSpecImpl(root, headerController)) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt index a1800ed12125..4de8e7a5641c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt @@ -40,6 +40,7 @@ class RootNodeController( override fun addChildAt(child: NodeController, index: Int) { listContainer.addContainerViewAt(child.view, index) + listContainer.onNotificationViewUpdateFinished() } override fun moveChildTo(child: NodeController, index: Int) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt index ad973927f21e..484707241b4d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection.render import android.content.Context import android.view.View import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager +import com.android.systemui.statusbar.notification.SectionHeaderVisibilityProvider import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotificationEntry @@ -38,13 +39,15 @@ class ShadeViewManager @AssistedInject constructor( @Assisted private val stackController: NotifStackController, mediaContainerController: MediaContainerController, featureManager: NotificationSectionsFeatureManager, + sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider, logger: ShadeViewDifferLogger, private val viewBarn: NotifViewBarn ) { // We pass a shim view here because the listContainer may not actually have a view associated // with it and the differ never actually cares about the root node's view. private val rootController = RootNodeController(listContainer, View(context)) - private val specBuilder = NodeSpecBuilder(mediaContainerController, featureManager, viewBarn) + private val specBuilder = NodeSpecBuilder(mediaContainerController, featureManager, + sectionHeaderVisibilityProvider, viewBarn) private val viewDiffer = ShadeViewDiffer(rootController, logger) /** Method for attaching this manager to the pipeline. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java index 1b42b58a55aa..d610b372702d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java @@ -305,6 +305,14 @@ public class DozeParameters implements } /** + * When this method returns true then moving display state to power save mode will be + * delayed for a few seconds. This might be useful to play animations without reducing FPS. + */ + public boolean shouldDelayDisplayDozeTransition() { + return mScreenOffAnimationController.shouldDelayDisplayDozeTransition(); + } + + /** * Whether we're capable of controlling the screen off animation if we want to. This isn't * possible if AOD isn't even enabled or if the flag is disabled. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 0bc633c74fc7..81e5b58aacea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -4596,7 +4596,14 @@ public class NotificationPanelViewController extends PanelViewController { // Can affect multi-user switcher visibility as it depends on screen size by default: // it is enabled only for devices with large screens (see config_keyguardUserSwitcher) - reInflateViews(); + boolean prevKeyguardUserSwitcherEnabled = mKeyguardUserSwitcherEnabled; + boolean prevKeyguardQsUserSwitchEnabled = mKeyguardQsUserSwitchEnabled; + updateUserSwitcherFlags(); + if (prevKeyguardUserSwitcherEnabled != mKeyguardUserSwitcherEnabled + || prevKeyguardQsUserSwitchEnabled != mKeyguardQsUserSwitchEnabled) { + reInflateViews(); + } + Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt index e806ca0d9005..091831f36022 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt @@ -180,6 +180,14 @@ class ScreenOffAnimationController @Inject constructor( animations.all { it.shouldAnimateDozingChange() } /** + * Returns true when moving display state to power save mode should be + * delayed for a few seconds. This might be useful to play animations in full quality, + * without reducing FPS. + */ + fun shouldDelayDisplayDozeTransition(): Boolean = + animations.any { it.shouldDelayDisplayDozeTransition() } + + /** * Return true to animate large <-> small clock transition */ fun shouldAnimateClockChange(): Boolean = @@ -207,6 +215,7 @@ interface ScreenOffAnimation { fun shouldHideScrimOnWakeUp(): Boolean = false fun overrideNotificationsDozeAmount(): Boolean = false fun shouldShowAodIconsWhenShade(): Boolean = false + fun shouldDelayDisplayDozeTransition(): Boolean = false fun shouldAnimateAodIcons(): Boolean = true fun shouldAnimateDozingChange(): Boolean = true fun shouldAnimateClockChange(): Boolean = true diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 4249d767a72c..5d83cc6259a0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -707,7 +707,10 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump mNotificationsAlpha = behindFraction * mDefaultScrimAlpha; } else { mBehindAlpha = behindFraction * mDefaultScrimAlpha; - mNotificationsAlpha = mBehindAlpha; + // Delay fade-in of notification scrim a bit further, to coincide with the + // view fade in. Otherwise the empty panel can be quite jarring. + mNotificationsAlpha = MathUtils.constrainedMap(0f, 1f, 0.3f, 0.75f, + mPanelExpansionFraction); } mInFrontAlpha = 0; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java index f8b0535b7ec7..72237b1ca6c6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java @@ -123,7 +123,8 @@ public class ShadeControllerImpl implements ShadeController { + " canPanelBeCollapsed(): " + getNotificationPanelViewController().canPanelBeCollapsed()); if (getNotificationShadeWindowView() != null - && getNotificationPanelViewController().canPanelBeCollapsed()) { + && getNotificationPanelViewController().canPanelBeCollapsed() + && (flags & CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL) == 0) { // release focus immediately to kick off focus change transition mNotificationShadeWindowController.setNotificationShadeFocusable(false); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index fdced64bf1c0..07ae33c42c21 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -268,6 +268,7 @@ public class StatusBar extends CoreStartable implements // Should match the values in PhoneWindowManager public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps"; + public static final String SYSTEM_DIALOG_REASON_DREAM = "dream"; static public final String SYSTEM_DIALOG_REASON_SCREENSHOT = "screenshot"; private static final String BANNER_ACTION_CANCEL = @@ -2635,8 +2636,17 @@ public class StatusBar extends CoreStartable implements if (mLockscreenUserManager.isCurrentProfile(getSendingUserId())) { int flags = CommandQueue.FLAG_EXCLUDE_NONE; String reason = intent.getStringExtra("reason"); - if (reason != null && reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) { - flags |= CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL; + if (reason != null) { + if (reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) { + flags |= CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL; + } + // Do not collapse notifications when starting dreaming if the notifications + // shade is used for the screen off animation. It might require expanded + // state for the scrims to be visible + if (reason.equals(SYSTEM_DIALOG_REASON_DREAM) + && mScreenOffAnimationController.shouldExpandNotifications()) { + flags |= CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL; + } } mShadeController.animateCollapsePanels(flags); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt index ea0dd72d673f..6746b3e8883a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -290,6 +290,9 @@ class UnlockedScreenOffAnimationController @Inject constructor( return true } + override fun shouldDelayDisplayDozeTransition(): Boolean = + dozeParameters.get().shouldControlUnlockedScreenOff() + fun addCallback(callback: Callback) { callbacks.add(callback) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java index 1030bfdb40fd..33f2140b150e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java @@ -127,10 +127,12 @@ public final class DeviceStateRotationLockSettingController int rotationLockSetting = mDeviceStateRotationLockSettingsManager.getRotationLockSetting(state); if (rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) { + // This should not happen. Device states that have an ignored setting, should also + // specify a fallback device state which is not ignored. // We won't handle this device state. The same rotation lock setting as before should // apply and any changes to the rotation lock setting will be written for the previous // valid device state. - Log.v(TAG, "Ignoring new device state: " + state); + Log.w(TAG, "Missing fallback. Ignoring new device state: " + state); return; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingsManager.java index a418c74848a5..bec5fc8e7b9f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingsManager.java @@ -52,12 +52,14 @@ public final class DeviceStateRotationLockSettingsManager { private final Handler mMainHandler = Handler.getMain(); private final Set<DeviceStateRotationLockSettingsListener> mListeners = new HashSet<>(); private SparseIntArray mDeviceStateRotationLockSettings; + private SparseIntArray mDeviceStateRotationLockFallbackSettings; private DeviceStateRotationLockSettingsManager(Context context) { mContentResolver = context.getContentResolver(); mDeviceStateRotationLockDefaults = context.getResources() .getStringArray(R.array.config_perDeviceStateRotationLockDefaults); + loadDefaults(); initializeInMemoryMap(); listenForSettingsChange(context); } @@ -114,6 +116,11 @@ public final class DeviceStateRotationLockSettingsManager { /** Updates the rotation lock setting for a specified device state. */ public void updateSetting(int deviceState, boolean rotationLocked) { + if (mDeviceStateRotationLockFallbackSettings.indexOfKey(deviceState) >= 0) { + // The setting for this device state is IGNORED, and has a fallback device state. + // The setting for that fallback device state should be the changed in this case. + deviceState = mDeviceStateRotationLockFallbackSettings.get(deviceState); + } mDeviceStateRotationLockSettings.put( deviceState, rotationLocked @@ -123,16 +130,37 @@ public final class DeviceStateRotationLockSettingsManager { } /** - * Returns the {@link DeviceStateRotationLockSetting} for the given device state. If no setting - * is specified for this device state, it will return {@link + * Returns the {@link Settings.Secure.DeviceStateRotationLockSetting} for the given device + * state. + * + * <p>If the setting for this device state is {@link DEVICE_STATE_ROTATION_LOCK_IGNORED}, it + * will return the setting for the fallback device state. + * + * <p>If no fallback is specified for this device state, it will return {@link * DEVICE_STATE_ROTATION_LOCK_IGNORED}. */ @Settings.Secure.DeviceStateRotationLockSetting public int getRotationLockSetting(int deviceState) { - return mDeviceStateRotationLockSettings.get( - deviceState, DEVICE_STATE_ROTATION_LOCK_IGNORED); + int rotationLockSetting = mDeviceStateRotationLockSettings.get( + deviceState, /* valueIfKeyNotFound= */ DEVICE_STATE_ROTATION_LOCK_IGNORED); + if (rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) { + rotationLockSetting = getFallbackRotationLockSetting(deviceState); + } + return rotationLockSetting; + } + + private int getFallbackRotationLockSetting(int deviceState) { + int indexOfFallbackState = mDeviceStateRotationLockFallbackSettings.indexOfKey(deviceState); + if (indexOfFallbackState < 0) { + Log.w(TAG, "Setting is ignored, but no fallback was specified."); + return DEVICE_STATE_ROTATION_LOCK_IGNORED; + } + int fallbackState = mDeviceStateRotationLockFallbackSettings.valueAt(indexOfFallbackState); + return mDeviceStateRotationLockSettings.get(fallbackState, + /* valueIfKeyNotFound= */ DEVICE_STATE_ROTATION_LOCK_IGNORED); } + /** Returns true if the rotation is locked for the current device state */ public boolean isRotationLocked(int deviceState) { return getRotationLockSetting(deviceState) == DEVICE_STATE_ROTATION_LOCK_LOCKED; @@ -223,21 +251,30 @@ public final class DeviceStateRotationLockSettingsManager { } private void loadDefaults() { - if (mDeviceStateRotationLockDefaults.length == 0) { - Log.w(TAG, "Empty default settings"); - mDeviceStateRotationLockSettings = new SparseIntArray(/* initialCapacity= */ 0); - return; - } - mDeviceStateRotationLockSettings = - new SparseIntArray(mDeviceStateRotationLockDefaults.length); - for (String serializedDefault : mDeviceStateRotationLockDefaults) { - String[] entry = serializedDefault.split(SEPARATOR_REGEX); + mDeviceStateRotationLockSettings = new SparseIntArray( + mDeviceStateRotationLockDefaults.length); + mDeviceStateRotationLockFallbackSettings = new SparseIntArray(1); + for (String entry : mDeviceStateRotationLockDefaults) { + String[] values = entry.split(SEPARATOR_REGEX); try { - int key = Integer.parseInt(entry[0]); - int value = Integer.parseInt(entry[1]); - mDeviceStateRotationLockSettings.put(key, value); + int deviceState = Integer.parseInt(values[0]); + int rotationLockSetting = Integer.parseInt(values[1]); + if (rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) { + if (values.length == 3) { + int fallbackDeviceState = Integer.parseInt(values[2]); + mDeviceStateRotationLockFallbackSettings.put(deviceState, + fallbackDeviceState); + } else { + Log.w(TAG, + "Rotation lock setting is IGNORED, but values have unexpected " + + "size of " + + values.length); + } + } + mDeviceStateRotationLockSettings.put(deviceState, rotationLockSetting); } catch (NumberFormatException e) { - Log.wtf(TAG, "Error deserializing default settings", e); + Log.wtf(TAG, "Error parsing settings entry. Entry was: " + entry, e); + return; } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java index 3831857c5c8d..59969c0447b3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java @@ -22,10 +22,14 @@ import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION; import static com.android.settingslib.Utils.updateLocationEnabled; +import android.app.AppOpsManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.PermissionChecker; +import android.content.pm.PackageManager; +import android.content.pm.UserInfo; import android.location.LocationManager; import android.os.Handler; import android.os.Looper; @@ -68,31 +72,37 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio private final BootCompleteCache mBootCompleteCache; private final UserTracker mUserTracker; private final H mHandler; - + private final Handler mBackgroundHandler; + private final PackageManager mPackageManager; private boolean mAreActiveLocationRequests; private boolean mShouldDisplayAllAccesses; + private boolean mShowSystemAccesses; @Inject public LocationControllerImpl(Context context, AppOpsController appOpsController, DeviceConfigProxy deviceConfigProxy, @Main Looper mainLooper, @Background Handler backgroundHandler, BroadcastDispatcher broadcastDispatcher, BootCompleteCache bootCompleteCache, - UserTracker userTracker) { + UserTracker userTracker, PackageManager packageManager) { mContext = context; mAppOpsController = appOpsController; mDeviceConfigProxy = deviceConfigProxy; mBootCompleteCache = bootCompleteCache; mHandler = new H(mainLooper); mUserTracker = userTracker; - mShouldDisplayAllAccesses = getDeviceConfigSetting(); + mBackgroundHandler = backgroundHandler; + mPackageManager = packageManager; + mShouldDisplayAllAccesses = getAllAccessesSetting(); + mShowSystemAccesses = getShowSystemSetting(); // Register to listen for changes in DeviceConfig settings. mDeviceConfigProxy.addOnPropertiesChangedListener( DeviceConfig.NAMESPACE_PRIVACY, backgroundHandler::post, properties -> { - mShouldDisplayAllAccesses = getDeviceConfigSetting(); + mShouldDisplayAllAccesses = getAllAccessesSetting(); + mShowSystemAccesses = getShowSystemSetting(); updateActiveLocationRequests(); }); @@ -176,11 +186,15 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio UserHandle.of(userId)); } - private boolean getDeviceConfigSetting() { + private boolean getAllAccessesSetting() { return mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED, false); } + private boolean getShowSystemSetting() { + return mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, + SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SHOW_SYSTEM, false); + } /** * Returns true if there currently exist active high power location requests. */ @@ -202,35 +216,74 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio * Returns true if there currently exist active location requests. */ @VisibleForTesting - protected boolean areActiveLocationRequests() { + protected void areActiveLocationRequests() { if (!mShouldDisplayAllAccesses) { - return false; + return; } - List<AppOpItem> appOpsItems = mAppOpsController.getActiveAppOps(); + boolean hadActiveLocationRequests = mAreActiveLocationRequests; + boolean shouldDisplay = false; + List<AppOpItem> appOpsItems = mAppOpsController.getActiveAppOps(); + final List<UserInfo> profiles = mUserTracker.getUserProfiles(); final int numItems = appOpsItems.size(); for (int i = 0; i < numItems; i++) { if (appOpsItems.get(i).getCode() == OP_FINE_LOCATION || appOpsItems.get(i).getCode() == OP_COARSE_LOCATION) { - return true; + if (mShowSystemAccesses) { + shouldDisplay = true; + } else { + shouldDisplay |= !isSystemApp(profiles, appOpsItems.get(i)); + } } } - return false; + mAreActiveLocationRequests = areActiveHighPowerLocationRequests() || shouldDisplay; + if (mAreActiveLocationRequests != hadActiveLocationRequests) { + mHandler.sendEmptyMessage(H.MSG_LOCATION_ACTIVE_CHANGED); + } + } + + private boolean isSystemApp(List<UserInfo> profiles, AppOpItem item) { + final String permission = AppOpsManager.opToPermission(item.getCode()); + UserHandle user = UserHandle.getUserHandleForUid(item.getUid()); + + // Don't show apps belonging to background users except managed users. + boolean foundUser = false; + final int numProfiles = profiles.size(); + for (int i = 0; i < numProfiles; i++) { + if (profiles.get(i).getUserHandle().equals(user)) { + foundUser = true; + } + } + if (!foundUser) { + return true; + } + + final int permissionFlags = mPackageManager.getPermissionFlags( + permission, item.getPackageName(), user); + if (PermissionChecker.checkPermissionForPreflight(mContext, permission, + PermissionChecker.PID_UNKNOWN, item.getUid(), item.getPackageName()) + == PermissionChecker.PERMISSION_GRANTED) { + return (permissionFlags + & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) + == 0; + } else { + return (permissionFlags + & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED) == 0; + } } // Reads the active location requests from either OP_MONITOR_HIGH_POWER_LOCATION, // OP_FINE_LOCATION, or OP_COARSE_LOCATION and updates the status view if necessary. private void updateActiveLocationRequests() { - boolean hadActiveLocationRequests = mAreActiveLocationRequests; if (mShouldDisplayAllAccesses) { - mAreActiveLocationRequests = - areActiveHighPowerLocationRequests() || areActiveLocationRequests(); + mBackgroundHandler.post(this::areActiveLocationRequests); } else { + boolean hadActiveLocationRequests = mAreActiveLocationRequests; mAreActiveLocationRequests = areActiveHighPowerLocationRequests(); - } - if (mAreActiveLocationRequests != hadActiveLocationRequests) { - mHandler.sendEmptyMessage(H.MSG_LOCATION_ACTIVE_CHANGED); + if (mAreActiveLocationRequests != hadActiveLocationRequests) { + mHandler.sendEmptyMessage(H.MSG_LOCATION_ACTIVE_CHANGED); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java index 46fa20d094a0..48949f92413d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -51,6 +51,7 @@ import android.view.ViewAnimationUtils; import android.view.ViewGroup; import android.view.WindowInsets; import android.view.WindowInsetsAnimation; +import android.view.WindowInsetsController; import android.view.accessibility.AccessibilityEvent; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.EditorInfo; @@ -665,8 +666,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene } // Hide soft-keyboard when the input view became invisible // (i.e. The notification shade collapsed by pressing the home key) - if (visibility != VISIBLE && !mEditText.isVisibleToUser() - && !mController.isRemoteInputActive()) { + if (visibility != VISIBLE && !mController.isRemoteInputActive()) { mEditText.hideIme(); } } @@ -779,8 +779,9 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene } private void hideIme() { - if (mInputMethodManager != null) { - mInputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); + final WindowInsetsController insetsController = getWindowInsetsController(); + if (insetsController != null) { + insetsController.hide(WindowInsets.Type.ime()); } } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt index 4f037a0f1ace..aaf35afe936d 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt @@ -136,6 +136,8 @@ constructor( override fun shouldAnimateClockChange(): Boolean = !isAnimationPlaying() + override fun shouldDelayDisplayDozeTransition(): Boolean = shouldPlayAnimation() + /** Called when AOD status is changed */ override fun onAlwaysOnChanged(alwaysOn: Boolean) { alwaysOnEnabled = alwaysOn diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java index 6ddfbb2f430f..bc89da7d504c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java @@ -111,6 +111,7 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + mContext = Mockito.spy(getContext()); final WindowManager wm = mContext.getSystemService(WindowManager.class); mSwitchListener = new SwitchListenerStub(); mWindowManager = spy(new TestableWindowManager(wm)); @@ -139,16 +140,18 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { public void tearDown() { mFadeOutAnimation = null; mMotionEventHelper.recycleEvents(); + mMagnificationModeSwitch.removeButton(); } @Test - public void removeButton_buttonIsShowing_removeView() { + public void removeButton_buttonIsShowing_removeViewAndUnregisterComponentCallbacks() { mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); mMagnificationModeSwitch.removeButton(); verify(mWindowManager).removeView(mSpyImageView); verify(mViewPropertyAnimator).cancel(); + verify(mContext).unregisterComponentCallbacks(mMagnificationModeSwitch); } @Test @@ -464,6 +467,13 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { } @Test + public void showButton_registerComponentCallbacks() { + mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); + + verify(mContext).registerComponentCallbacks(mMagnificationModeSwitch); + } + + @Test public void onLocaleChanged_buttonIsShowing_updateA11yWindowTitle() { final String newA11yWindowTitle = "new a11y window title"; mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java index 216f63fce885..a56218b08224 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java @@ -91,7 +91,6 @@ public class ModeSwitchesControllerTest extends SysuiTestCase { verify(mModeSwitch).onConfigurationChanged(ActivityInfo.CONFIG_DENSITY); } - @Test public void testOnSwitchClick_showWindowModeButton_invokeListener() { mModeSwitchesController.showButton(Display.DEFAULT_DISPLAY, diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java index 5ad651728c66..dcb7307250fe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java @@ -16,6 +16,8 @@ package com.android.systemui.accessibility; +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.view.Choreographer.FrameCallback; import static android.view.WindowInsets.Type.systemGestures; import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; @@ -45,6 +47,7 @@ import static org.mockito.Mockito.when; import android.app.Instrumentation; import android.content.Context; import android.content.pm.ActivityInfo; +import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Insets; import android.graphics.PointF; @@ -223,13 +226,9 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { final int screenSize = mContext.getResources().getDimensionPixelSize( R.dimen.magnification_max_frame_size) * 10; mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize)); - //We need to initialize new one because the window size is determined when initialization. - final WindowMagnificationController controller = new WindowMagnificationController(mContext, - mHandler, mWindowMagnificationAnimationController, mSfVsyncFrameProvider, - mMirrorWindowControl, mTransaction, mWindowMagnifierCallback, mSysUiState); mInstrumentation.runOnMainSync(() -> { - controller.enableWindowMagnificationInternal(Float.NaN, Float.NaN, + mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN); }); @@ -242,17 +241,17 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { } @Test - public void deleteWindowMagnification_destroyControl() { - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - }); + public void deleteWindowMagnification_destroyControlAndUnregisterComponentCallback() { + mInstrumentation.runOnMainSync( + () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, + Float.NaN, + Float.NaN)); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.deleteWindowMagnification(); - }); + mInstrumentation.runOnMainSync( + () -> mWindowMagnificationController.deleteWindowMagnification()); verify(mMirrorWindowControl).destroyControl(); + verify(mContext).unregisterComponentCallbacks(mWindowMagnificationController); } @Test @@ -322,11 +321,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { @Test public void onOrientationChanged_enabled_updateDisplayRotationAndCenterStayAtSamePosition() { - final Display display = Mockito.spy(mContext.getDisplay()); - final int currentRotation = display.getRotation(); - final int newRotation = (currentRotation + 1) % 4; - when(display.getRotation()).thenReturn(newRotation); - when(mContext.getDisplay()).thenReturn(display); + final int newRotation = simulateRotateTheDevice(); final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds()); final float center = Math.min(windowBounds.exactCenterX(), windowBounds.exactCenterY()); final float displayWidth = windowBounds.width(); @@ -535,6 +530,30 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { } @Test + public void enableWindowMagnification_rotationIsChanged_updateRotationValue() { + final Configuration config = mContext.getResources().getConfiguration(); + config.orientation = config.orientation == ORIENTATION_LANDSCAPE ? ORIENTATION_PORTRAIT + : ORIENTATION_LANDSCAPE; + final int newRotation = simulateRotateTheDevice(); + + mInstrumentation.runOnMainSync( + () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, + Float.NaN, Float.NaN)); + + assertEquals(newRotation, mWindowMagnificationController.mRotation); + } + + @Test + public void enableWindowMagnification_registerComponentCallback() { + mInstrumentation.runOnMainSync( + () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, + Float.NaN, + Float.NaN)); + + verify(mContext).registerComponentCallbacks(mWindowMagnificationController); + } + + @Test public void onLocaleChanged_enabled_updateA11yWindowTitle() { final String newA11yWindowTitle = "new a11y window title"; mInstrumentation.runOnMainSync(() -> { @@ -610,4 +629,14 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { .build(); mWindowManager.setWindowInsets(testInsets); } + + @Surface.Rotation + private int simulateRotateTheDevice() { + final Display display = Mockito.spy(mContext.getDisplay()); + final int currentRotation = display.getRotation(); + final int newRotation = (currentRotation + 1) % 4; + when(display.getRotation()).thenReturn(newRotation); + when(mContext.getDisplay()).thenReturn(display); + return newRotation; + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java index 343658d31272..d3f30c508b8b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java @@ -30,7 +30,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; -import android.content.res.Configuration; import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.os.RemoteException; @@ -159,15 +158,6 @@ public class WindowMagnificationTest extends SysuiTestCase { } @Test - public void onConfigurationChanged_updateModeSwitches() { - final Configuration config = new Configuration(); - config.densityDpi = Configuration.DENSITY_DPI_ANY; - mWindowMagnification.onConfigurationChanged(config); - - verify(mModeSwitchesController).onConfigurationChanged(anyInt()); - } - - @Test public void overviewProxyIsConnected_noController_resetFlag() { mOverviewProxyListener.onConnectionChanged(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java index 3e19cc436dca..cdffaecadd77 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java @@ -192,7 +192,7 @@ public class DozeScreenStateTest extends SysuiTestCase { public void test_holdsWakeLockWhenGoingToLowPowerDelayed() { // Transition to low power mode will be delayed to let // animations play at 60 fps. - when(mDozeParameters.shouldControlScreenOff()).thenReturn(true); + when(mDozeParameters.shouldDelayDisplayDozeTransition()).thenReturn(true); mHandlerFake.setMode(QUEUEING); mScreen.transitionTo(UNINITIALIZED, INITIALIZED); @@ -209,7 +209,7 @@ public class DozeScreenStateTest extends SysuiTestCase { public void test_releasesWakeLock_abortingLowPowerDelayed() { // Transition to low power mode will be delayed to let // animations play at 60 fps. - when(mDozeParameters.shouldControlScreenOff()).thenReturn(true); + when(mDozeParameters.shouldDelayDisplayDozeTransition()).thenReturn(true); mHandlerFake.setMode(QUEUEING); mScreen.transitionTo(UNINITIALIZED, INITIALIZED); diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/appwidgets/ComplicationPrimerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/appwidgets/ComplicationPrimerTest.java deleted file mode 100644 index adf110bb2494..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/appwidgets/ComplicationPrimerTest.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.dreams.appwidgets; - -import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.ComponentName; -import android.content.res.Resources; -import android.testing.AndroidTestingRunner; -import android.view.Gravity; - -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.SmallTest; - -import com.android.systemui.R; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.SysuiTestableContext; -import com.android.systemui.dreams.DreamOverlayStateController; -import com.android.systemui.dreams.appwidgets.dagger.AppWidgetComponent; -import com.android.systemui.utils.leaks.LeakCheckedTest; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class ComplicationPrimerTest extends SysuiTestCase { - @Rule - public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck(); - - @Rule - public SysuiTestableContext mContext = new SysuiTestableContext( - InstrumentationRegistry.getContext(), mLeakCheck); - - @Mock - Resources mResources; - - @Mock - AppWidgetComponent mAppWidgetComplicationComponent1; - @Mock - AppWidgetComponent mAppWidgetComplicationComponent2; - - @Mock - ComplicationProvider mComplicationProvider1; - - @Mock - ComplicationProvider mComplicationProvider2; - - final ComponentName mAppComplicationComponent1 = - ComponentName.unflattenFromString("com.foo.bar/.Baz"); - final ComponentName mAppComplicationComponent2 = - ComponentName.unflattenFromString("com.foo.bar/.Baz2"); - - final int mAppComplicationGravity1 = Gravity.BOTTOM | Gravity.START; - final int mAppComplicationGravity2 = Gravity.BOTTOM | Gravity.END; - - final String[] mComponents = new String[]{mAppComplicationComponent1.flattenToString(), - mAppComplicationComponent2.flattenToString() }; - final int[] mPositions = new int[]{mAppComplicationGravity1, mAppComplicationGravity2}; - - @Mock - DreamOverlayStateController mDreamOverlayStateController; - - @Mock - AppWidgetComponent.Factory mAppWidgetComplicationProviderFactory; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - when(mAppWidgetComplicationProviderFactory.build(eq(mAppComplicationComponent1), any())) - .thenReturn(mAppWidgetComplicationComponent1); - when(mAppWidgetComplicationComponent1.getAppWidgetComplicationProvider()) - .thenReturn(mComplicationProvider1); - when(mAppWidgetComplicationProviderFactory.build(eq(mAppComplicationComponent2), any())) - .thenReturn(mAppWidgetComplicationComponent2); - when(mAppWidgetComplicationComponent2.getAppWidgetComplicationProvider()) - .thenReturn(mComplicationProvider2); - when(mResources.getIntArray(R.array.config_dreamComplicationPositions)) - .thenReturn(mPositions); - when(mResources.getStringArray(R.array.config_dreamAppWidgetComplications)) - .thenReturn(mComponents); - } - - @Test - public void testLoading() { - final ComplicationPrimer primer = new ComplicationPrimer(mContext, - mResources, - mDreamOverlayStateController, - mAppWidgetComplicationProviderFactory); - - // Inform primer to begin. - primer.onBootCompleted(); - - // Verify the first component is added to the state controller with the proper position. - { - final ArgumentCaptor<ConstraintLayout.LayoutParams> layoutParamsArgumentCaptor = - ArgumentCaptor.forClass(ConstraintLayout.LayoutParams.class); - verify(mAppWidgetComplicationProviderFactory, times(1)) - .build(eq(mAppComplicationComponent1), - layoutParamsArgumentCaptor.capture()); - - assertEquals(layoutParamsArgumentCaptor.getValue().startToStart, - ConstraintLayout.LayoutParams.PARENT_ID); - assertEquals(layoutParamsArgumentCaptor.getValue().bottomToBottom, - ConstraintLayout.LayoutParams.PARENT_ID); - - verify(mDreamOverlayStateController, times(1)) - .addComplication(eq(mComplicationProvider1)); - } - - // Verify the second component is added to the state controller with the proper position. - { - final ArgumentCaptor<ConstraintLayout.LayoutParams> layoutParamsArgumentCaptor = - ArgumentCaptor.forClass(ConstraintLayout.LayoutParams.class); - verify(mAppWidgetComplicationProviderFactory, times(1)) - .build(eq(mAppComplicationComponent2), - layoutParamsArgumentCaptor.capture()); - - assertEquals(layoutParamsArgumentCaptor.getValue().endToEnd, - ConstraintLayout.LayoutParams.PARENT_ID); - assertEquals(layoutParamsArgumentCaptor.getValue().bottomToBottom, - ConstraintLayout.LayoutParams.PARENT_ID); - verify(mDreamOverlayStateController, times(1)) - .addComplication(eq(mComplicationProvider1)); - } - } - - @Test - public void testNoComponents() { - when(mResources.getStringArray(R.array.config_dreamAppWidgetComplications)) - .thenReturn(new String[]{}); - - final ComplicationPrimer primer = new ComplicationPrimer(mContext, - mResources, - mDreamOverlayStateController, - mAppWidgetComplicationProviderFactory); - - // Inform primer to begin. - primer.onBootCompleted(); - - - // Make sure there is no request to add a widget if no components are specified by the - // product. - verify(mAppWidgetComplicationProviderFactory, never()).build(any(), any()); - verify(mDreamOverlayStateController, never()).addComplication(any()); - } - - @Test - public void testNoPositions() { - when(mResources.getIntArray(R.array.config_dreamComplicationPositions)) - .thenReturn(new int[]{}); - - final ComplicationPrimer primer = new ComplicationPrimer(mContext, - mResources, - mDreamOverlayStateController, - mAppWidgetComplicationProviderFactory); - - primer.onBootCompleted(); - - // Make sure there is no request to add a widget if no positions are specified by the - // product. - verify(mAppWidgetComplicationProviderFactory, never()).build(any(), any()); - verify(mDreamOverlayStateController, never()).addComplication(any()); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/appwidgets/ComplicationProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/appwidgets/ComplicationProviderTest.java deleted file mode 100644 index f538112edbda..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/appwidgets/ComplicationProviderTest.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.dreams.appwidgets; - -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.isNull; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.app.PendingIntent; -import android.appwidget.AppWidgetHostView; -import android.content.ComponentName; -import android.testing.AndroidTestingRunner; -import android.widget.RemoteViews; - -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.SysuiTestableContext; -import com.android.systemui.dreams.ComplicationHost; -import com.android.systemui.dreams.ComplicationHostView; -import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.utils.leaks.LeakCheckedTest; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class ComplicationProviderTest extends SysuiTestCase { - @Mock - ActivityStarter mActivityStarter; - - @Mock - ComponentName mComponentName; - - @Mock - AppWidgetProvider mAppWidgetProvider; - - @Mock - AppWidgetHostView mAppWidgetHostView; - - @Mock - ComplicationHost.CreationCallback mCreationCallback; - - @Mock - ComplicationHost.InteractionCallback mInteractionCallback; - - @Mock - PendingIntent mPendingIntent; - - @Mock - RemoteViews.RemoteResponse mRemoteResponse; - - ComplicationProvider mComplicationProvider; - - RemoteViews.InteractionHandler mInteractionHandler; - - @Rule - public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck(); - - @Rule - public SysuiTestableContext mContext = new SysuiTestableContext( - InstrumentationRegistry.getContext(), mLeakCheck); - - ComplicationHostView.LayoutParams mLayoutParams = new ComplicationHostView.LayoutParams( - ComplicationHostView.LayoutParams.MATCH_PARENT, - ComplicationHostView.LayoutParams.MATCH_PARENT); - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - when(mPendingIntent.isActivity()).thenReturn(true); - when(mAppWidgetProvider.getWidget(mComponentName)).thenReturn(mAppWidgetHostView); - - mComplicationProvider = new ComplicationProvider( - mActivityStarter, - mComponentName, - mAppWidgetProvider, - mLayoutParams - ); - - final ArgumentCaptor<RemoteViews.InteractionHandler> creationCallbackCapture = - ArgumentCaptor.forClass(RemoteViews.InteractionHandler.class); - - mComplicationProvider.onCreateComplication(mContext, mCreationCallback, - mInteractionCallback); - verify(mAppWidgetHostView, times(1)) - .setInteractionHandler(creationCallbackCapture.capture()); - mInteractionHandler = creationCallbackCapture.getValue(); - } - - @Test - public void testWidgetBringup() { - // Make sure widget was requested. - verify(mAppWidgetProvider, times(1)).getWidget(eq(mComponentName)); - - // Make sure widget was returned to callback. - verify(mCreationCallback, times(1)).onCreated(eq(mAppWidgetHostView), - eq(mLayoutParams)); - } - - @Test - public void testWidgetInteraction() { - // Trigger interaction. - mInteractionHandler.onInteraction(mAppWidgetHostView, mPendingIntent, - mRemoteResponse); - - // Ensure activity is started. - verify(mActivityStarter, times(1)) - .startPendingIntentDismissingKeyguard(eq(mPendingIntent), isNull(), - eq(mAppWidgetHostView)); - // Verify exit is requested. - verify(mInteractionCallback, times(1)).onExit(); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java index 3e8e8748a679..73d2b0bf1a0f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java @@ -47,6 +47,7 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.pip.Pip; import org.junit.After; @@ -92,7 +93,8 @@ public class NavigationBarControllerTest extends SysuiTestCase { mock(DumpManager.class), mock(AutoHideController.class), mock(LightBarController.class), - Optional.of(mock(Pip.class)))); + Optional.of(mock(Pip.class)), + Optional.of(mock(BackAnimation.class)))); initializeNavigationBars(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java index 5003013358be..9ca898b9dea9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java @@ -95,6 +95,7 @@ import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.utils.leaks.LeakCheckedTest; +import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.pip.Pip; @@ -105,7 +106,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.mockito.Spy; import java.util.Optional; @@ -383,7 +383,8 @@ public class NavigationBarTest extends SysuiTestCase { mAutoHideController, mAutoHideControllerFactory, Optional.of(mTelecomManager), - mInputMethodManager); + mInputMethodManager, + Optional.of(mock(BackAnimation.class))); return spy(factory.create(context)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java index 03a0da7d91fb..4a6bbbcf1d6b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java @@ -24,6 +24,7 @@ import static com.android.systemui.qrcodescanner.controller.QRCodeScannerControl import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -73,7 +74,7 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { private DeviceConfigProxyFake mProxyFake; private void setUpLocal(String deviceConfigActivity, String defaultActivity, - boolean validateActivity, boolean enableSetting) { + boolean validateActivity, boolean enableSetting, boolean enableOnLockScreen) { MockitoAnnotations.initMocks(this); int enableSettingInt = enableSetting ? 1 : 0; @@ -91,6 +92,8 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)).thenReturn(true); mContext.getOrCreateTestableResources().addOverride(R.string.def_qr_code_component, defaultActivity); + mContext.getOrCreateTestableResources().addOverride( + android.R.bool.config_enableQrCodeScannerOnLockScreen, enableOnLockScreen); mProxyFake = new DeviceConfigProxyFake(); mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, @@ -126,7 +129,8 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { @Test public void qrCodeScannerInit_withoutDefaultValue() { setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */ - "", /* validateActivity */ true, /* enableSetting */true); + "", /* validateActivity */ true, /* enableSetting */ true, + /* enableOnLockScreen */ true); verifyActivityDetails(null); assertThat(mController.isEnabledForLockScreenButton()).isFalse(); assertThat(mController.isEnabledForQuickSettings()).isFalse(); @@ -135,7 +139,8 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { @Test public void qrCodeScannerInit_withIncorrectDefaultValue() { setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */ - "abc/.def", /* validateActivity */ false, /* enableSetting */ true); + "abc/.def", /* validateActivity */ false, /* enableSetting */ true, + /* enableOnLockScreen */ true); verifyActivityDetails(null); assertThat(mController.isEnabledForLockScreenButton()).isFalse(); } @@ -143,7 +148,8 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { @Test public void qrCodeScannerInit_withCorrectDefaultValue() { setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */ - "abc/.def", /* validateActivity */ true, /* enableSetting */true); + "abc/.def", /* validateActivity */ true, /* enableSetting */true, + /* enableOnLockScreen */ true); verifyActivityDetails("abc/.def"); assertThat(mController.isEnabledForLockScreenButton()).isTrue(); assertThat(mController.isEnabledForQuickSettings()).isTrue(); @@ -152,7 +158,8 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { @Test public void qrCodeScannerInit_withCorrectDeviceConfig() { setUpLocal(/* deviceConfigActivity */ "abc/.def", /* defaultActivity */ - "", /* validateActivity */ true, /* enableSetting */true); + "", /* validateActivity */ true, /* enableSetting */true, + /* enableOnLockScreen */ true); verifyActivityDetails("abc/.def"); assertThat(mController.isEnabledForLockScreenButton()).isTrue(); assertThat(mController.isEnabledForQuickSettings()).isTrue(); @@ -161,7 +168,8 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { @Test public void qrCodeScannerInit_withCorrectDeviceConfig_withCorrectDefaultValue() { setUpLocal(/* deviceConfigActivity */ "abc/.def", /* defaultActivity */ - "xyz/.qrs", /* validateActivity */true, /* enableSetting */ true); + "xyz/.qrs", /* validateActivity */true, /* enableSetting */ true, + /* enableOnLockScreen */ true); verifyActivityDetails("abc/.def"); assertThat(mController.isEnabledForLockScreenButton()).isTrue(); assertThat(mController.isEnabledForQuickSettings()).isTrue(); @@ -170,7 +178,8 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { @Test public void qrCodeScannerInit_withCorrectDeviceConfig_fullActivity() { setUpLocal(/* deviceConfigActivity */ "abc/abc.def", /* defaultActivity */ - "", /* validateActivity */ true, /* enableSetting */ true); + "", /* validateActivity */ true, /* enableSetting */ true, + /* enableOnLockScreen */ true); verifyActivityDetails("abc/abc.def"); assertThat(mController.isEnabledForLockScreenButton()).isTrue(); assertThat(mController.isEnabledForQuickSettings()).isTrue(); @@ -179,7 +188,8 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { @Test public void qrCodeScannerInit_withIncorrectDeviceConfig() { setUpLocal(/* deviceConfigActivity */ "def/.efg", /* defaultActivity */ - "", /* validateActivity */ false, /* enableSetting */ true); + "", /* validateActivity */ false, /* enableSetting */ true, + /* enableOnLockScreen */ true); verifyActivityDetails(null); assertThat(mController.isEnabledForLockScreenButton()).isFalse(); assertThat(mController.isEnabledForQuickSettings()).isFalse(); @@ -188,7 +198,8 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { @Test public void verifyDeviceConfigChange_withDefaultActivity() { setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */ - "abc/.def", /* validateActivity */ true, /* enableSetting */true); + "abc/.def", /* validateActivity */ true, /* enableSetting */true, + /* enableOnLockScreen */ true); verifyActivityDetails("abc/.def"); assertThat(mController.isEnabledForLockScreenButton()).isTrue(); assertThat(mController.isEnabledForQuickSettings()).isTrue(); @@ -214,7 +225,8 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { @Test public void verifyDeviceConfigChange_withoutDefaultActivity() { setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */ - "", /* validateActivity */ true, /* enableSetting */ true); + "", /* validateActivity */ true, /* enableSetting */ true, + /* enableOnLockScreen */ true); verifyActivityDetails(null); assertThat(mController.isEnabledForLockScreenButton()).isFalse(); assertThat(mController.isEnabledForQuickSettings()).isFalse(); @@ -239,7 +251,8 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { @Test public void verifyDeviceConfigChangeToSameValue() { setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */ - "", /* validateActivity */ true, /* enableSetting */true); + "", /* validateActivity */ true, /* enableSetting */true, + /* enableOnLockScreen */ true); mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER, @@ -261,7 +274,8 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { @Test public void verifyPreferenceChange() { setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */ - "abc/.def", /* validateActivity */ true, /* enableSetting */true); + "abc/.def", /* validateActivity */ true, /* enableSetting */true, + /* enableOnLockScreen */ true); mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "0", UserHandle.USER_CURRENT); mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "0", @@ -278,7 +292,8 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { @Test public void verifyPreferenceChangeToSameValue() { setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */ - "abc/.def", /* validateActivity */ true, /* enableSetting */true); + "abc/.def", /* validateActivity */ true, /* enableSetting */true, + /* enableOnLockScreen */ true); verifyActivityDetails("abc/.def"); assertThat(mController.isEnabledForLockScreenButton()).isTrue(); assertThat(mController.isEnabledForQuickSettings()).isTrue(); @@ -301,7 +316,8 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { @Test public void verifyUnregisterRegisterChangeObservers() { setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */ - "abc/.def", /* validateActivity */ true, /* enableSetting */true); + "abc/.def", /* validateActivity */ true, /* enableSetting */true, + /* enableOnLockScreen */ true); verifyActivityDetails("abc/.def"); assertThat(mController.isEnabledForLockScreenButton()).isTrue(); assertThat(mController.isEnabledForQuickSettings()).isTrue(); @@ -312,7 +328,7 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { assertThat(mController.isEnabledForLockScreenButton()).isFalse(); assertThat(mController.isEnabledForQuickSettings()).isFalse(); - // Unregister once again and make sure, it affect affect the next register event + // Unregister once again and make sure it affects the next register event mController.unregisterQRCodeScannerChangeObservers(DEFAULT_QR_CODE_SCANNER_CHANGE, QR_CODE_SCANNER_PREFERENCE_CHANGE); mController.registerQRCodeScannerChangeObservers(DEFAULT_QR_CODE_SCANNER_CHANGE, @@ -321,4 +337,15 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { assertThat(mController.isEnabledForLockScreenButton()).isTrue(); assertThat(mController.isEnabledForQuickSettings()).isTrue(); } + + @Test + public void verifyDisableLockscreenButton() { + setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */ + "abc/.def", /* validateActivity */ true, /* enableSetting */true, + /* enableOnLockScreen */ false); + assertThat(mController.getIntent()).isNotNull(); + assertThat(mController.isEnabledForLockScreenButton()).isFalse(); + assertThat(mController.isEnabledForQuickSettings()).isTrue(); + assertThat(getSettingsQRCodeDefaultComponent()).isNull(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java index b832577c16ac..25dd23a955e6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java @@ -1968,7 +1968,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { } @Override - public int compare(ListEntry o1, ListEntry o2) { + public int compare(@NonNull ListEntry o1, @NonNull ListEntry o2) { boolean contains1 = mPreferredPackages.contains( o1.getRepresentativeEntry().getSbn().getPackageName()); boolean contains2 = mPreferredPackages.contains( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java index f2e7081e096b..bc32759a9938 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java @@ -28,6 +28,10 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.Notification; +import android.app.PendingIntent; +import android.app.Person; +import android.content.Intent; import android.graphics.Color; import android.os.UserHandle; import android.service.notification.StatusBarNotification; @@ -151,4 +155,35 @@ public class AppOpsCoordinatorTest extends SysuiTestCase { // THEN the entry is NOT in the fgs section assertFalse(mFgsSection.isInSection(mEntryBuilder.build())); } + + @Test + public void testIncludeCallInSection_importanceDefault() { + // GIVEN the notification represents a call with > min importance + mEntryBuilder + .setImportance(IMPORTANCE_DEFAULT) + .modifyNotification(mContext) + .setStyle(makeCallStyle()); + + // THEN the entry is in the fgs section + assertTrue(mFgsSection.isInSection(mEntryBuilder.build())); + } + + @Test + public void testDiscludeCallInSection_importanceMin() { + // GIVEN the notification represents a call with min importance + mEntryBuilder + .setImportance(IMPORTANCE_MIN) + .modifyNotification(mContext) + .setStyle(makeCallStyle()); + + // THEN the entry is NOT in the fgs section + assertFalse(mFgsSection.isInSection(mEntryBuilder.build())); + } + + private Notification.CallStyle makeCallStyle() { + final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, + new Intent("action"), PendingIntent.FLAG_IMMUTABLE); + final Person person = new Person.Builder().setName("person").build(); + return Notification.CallStyle.forIncomingCall(person, pendingIntent, pendingIntent); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt index a46b44002812..8deac94214bd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt @@ -24,18 +24,24 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner import com.android.systemui.statusbar.notification.collection.render.NodeController import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON +import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.withArgCaptor +import com.google.common.truth.Truth.assertThat import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever @@ -47,12 +53,15 @@ class ConversationCoordinatorTest : SysuiTestCase() { // captured listeners and pluggables: private lateinit var promoter: NotifPromoter private lateinit var peopleSectioner: NotifSectioner + private lateinit var peopleComparator: NotifComparator @Mock private lateinit var pipeline: NotifPipeline @Mock private lateinit var peopleNotificationIdentifier: PeopleNotificationIdentifier @Mock private lateinit var channel: NotificationChannel @Mock private lateinit var headerController: NodeController private lateinit var entry: NotificationEntry + private lateinit var entryA: NotificationEntry + private lateinit var entryB: NotificationEntry private lateinit var coordinator: ConversationCoordinator @@ -70,8 +79,15 @@ class ConversationCoordinatorTest : SysuiTestCase() { } peopleSectioner = coordinator.sectioner + peopleComparator = coordinator.comparator entry = NotificationEntryBuilder().setChannel(channel).build() + + val section = NotifSection(peopleSectioner, 0) + entryA = NotificationEntryBuilder().setChannel(channel) + .setSection(section).setTag("A").build() + entryB = NotificationEntryBuilder().setChannel(channel) + .setSection(section).setTag("B").build() } @Test @@ -90,4 +106,36 @@ class ConversationCoordinatorTest : SysuiTestCase() { assertTrue(peopleSectioner.isInSection(entry)) assertFalse(peopleSectioner.isInSection(NotificationEntryBuilder().build())) } + + @Test + fun testComparatorIgnoresFromOtherSection() { + val e1 = NotificationEntryBuilder().setId(1).setChannel(channel).build() + val e2 = NotificationEntryBuilder().setId(2).setChannel(channel).build() + + // wrong section -- never classify + assertThat(peopleComparator.compare(e1, e2)).isEqualTo(0) + verify(peopleNotificationIdentifier, never()).getPeopleNotificationType(any()) + } + + @Test + fun testComparatorPutsImportantPeopleFirst() { + whenever(peopleNotificationIdentifier.getPeopleNotificationType(entryA)) + .thenReturn(TYPE_IMPORTANT_PERSON) + whenever(peopleNotificationIdentifier.getPeopleNotificationType(entryB)) + .thenReturn(TYPE_PERSON) + + // only put people notifications in this section + assertThat(peopleComparator.compare(entryA, entryB)).isEqualTo(-1) + } + + @Test + fun testComparatorEquatesPeopleWithSameType() { + whenever(peopleNotificationIdentifier.getPeopleNotificationType(entryA)) + .thenReturn(TYPE_PERSON) + whenever(peopleNotificationIdentifier.getPeopleNotificationType(entryB)) + .thenReturn(TYPE_PERSON) + + // only put people notifications in this section + assertThat(peopleComparator.compare(entryA, entryB)).isEqualTo(0) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java deleted file mode 100644 index 8ee892c4be58..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java +++ /dev/null @@ -1,274 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.notification.collection.coordinator; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.NotificationRemoteInputManager; -import com.android.systemui.statusbar.notification.collection.NotifPipeline; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner; -import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; -import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; -import com.android.systemui.statusbar.notification.collection.render.NodeController; -import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; -import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback; -import com.android.systemui.statusbar.policy.HeadsUpManager; -import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; -import com.android.systemui.util.concurrency.FakeExecutor; -import com.android.systemui.util.time.FakeSystemClock; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.ArrayList; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper -public class HeadsUpCoordinatorTest extends SysuiTestCase { - - private HeadsUpCoordinator mCoordinator; - - // captured listeners and pluggables: - private NotifCollectionListener mCollectionListener; - private NotifPromoter mNotifPromoter; - private NotifLifetimeExtender mNotifLifetimeExtender; - private OnHeadsUpChangedListener mOnHeadsUpChangedListener; - private NotifSectioner mNotifSectioner; - - @Mock private NotifPipeline mNotifPipeline; - @Mock private HeadsUpManager mHeadsUpManager; - @Mock private HeadsUpViewBinder mHeadsUpViewBinder; - @Mock private NotificationInterruptStateProvider mNotificationInterruptStateProvider; - @Mock private NotificationRemoteInputManager mRemoteInputManager; - @Mock private NotifLifetimeExtender.OnEndLifetimeExtensionCallback mEndLifetimeExtension; - @Mock private NodeController mHeaderController; - - private NotificationEntry mEntry; - private final FakeSystemClock mClock = new FakeSystemClock(); - private final FakeExecutor mExecutor = new FakeExecutor(mClock); - private final ArrayList<NotificationEntry> mHuns = new ArrayList(); - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - mCoordinator = new HeadsUpCoordinator( - mHeadsUpManager, - mHeadsUpViewBinder, - mNotificationInterruptStateProvider, - mRemoteInputManager, - mHeaderController, - mExecutor); - - mCoordinator.attach(mNotifPipeline); - - // capture arguments: - ArgumentCaptor<NotifCollectionListener> notifCollectionCaptor = - ArgumentCaptor.forClass(NotifCollectionListener.class); - ArgumentCaptor<NotifPromoter> notifPromoterCaptor = - ArgumentCaptor.forClass(NotifPromoter.class); - ArgumentCaptor<NotifLifetimeExtender> notifLifetimeExtenderCaptor = - ArgumentCaptor.forClass(NotifLifetimeExtender.class); - ArgumentCaptor<OnHeadsUpChangedListener> headsUpChangedListenerCaptor = - ArgumentCaptor.forClass(OnHeadsUpChangedListener.class); - - verify(mNotifPipeline).addCollectionListener(notifCollectionCaptor.capture()); - verify(mNotifPipeline).addPromoter(notifPromoterCaptor.capture()); - verify(mNotifPipeline).addNotificationLifetimeExtender( - notifLifetimeExtenderCaptor.capture()); - verify(mHeadsUpManager).addListener(headsUpChangedListenerCaptor.capture()); - - given(mHeadsUpManager.getAllEntries()).willAnswer(i -> mHuns.stream()); - given(mHeadsUpManager.isAlerting(anyString())).willAnswer(i -> { - String key = i.getArgument(0); - for (NotificationEntry entry : mHuns) { - if (entry.getKey().equals(key)) return true; - } - return false; - }); - when(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L); - - mCollectionListener = notifCollectionCaptor.getValue(); - mNotifPromoter = notifPromoterCaptor.getValue(); - mNotifLifetimeExtender = notifLifetimeExtenderCaptor.getValue(); - mOnHeadsUpChangedListener = headsUpChangedListenerCaptor.getValue(); - - mNotifSectioner = mCoordinator.getSectioner(); - mNotifLifetimeExtender.setCallback(mEndLifetimeExtension); - mEntry = new NotificationEntryBuilder().build(); - } - - @Test - public void testCancelStickyNotification() { - when(mHeadsUpManager.isSticky(anyString())).thenReturn(true); - addHUN(mEntry); - when(mHeadsUpManager.canRemoveImmediately(anyString())).thenReturn(false, true); - when(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 0L); - assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0)); - mClock.advanceTime(1000L); - mExecutor.runAllReady(); - verify(mHeadsUpManager, times(0)) - .removeNotification(anyString(), eq(false)); - verify(mHeadsUpManager, times(1)) - .removeNotification(anyString(), eq(true)); - } - - @Test - public void testCancelUpdatedStickyNotification() { - when(mHeadsUpManager.isSticky(anyString())).thenReturn(true); - addHUN(mEntry); - when(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 500L); - assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0)); - mClock.advanceTime(1000L); - mExecutor.runAllReady(); - verify(mHeadsUpManager, times(0)) - .removeNotification(anyString(), eq(false)); - verify(mHeadsUpManager, times(0)) - .removeNotification(anyString(), eq(true)); - } - - @Test - public void testCancelNotification() { - when(mHeadsUpManager.isSticky(anyString())).thenReturn(false); - addHUN(mEntry); - when(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 500L); - assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0)); - mClock.advanceTime(1000L); - mExecutor.runAllReady(); - verify(mHeadsUpManager, times(1)) - .removeNotification(anyString(), eq(false)); - verify(mHeadsUpManager, times(0)) - .removeNotification(anyString(), eq(true)); - } - - @Test - public void testPromotesCurrentHUN() { - // GIVEN the current HUN is set to mEntry - addHUN(mEntry); - - // THEN only promote the current HUN, mEntry - assertTrue(mNotifPromoter.shouldPromoteToTopLevel(mEntry)); - assertFalse(mNotifPromoter.shouldPromoteToTopLevel(new NotificationEntryBuilder() - .setPkg("test-package2") - .build())); - } - - @Test - public void testIncludeInSectionCurrentHUN() { - // GIVEN the current HUN is set to mEntry - addHUN(mEntry); - - // THEN only section the current HUN, mEntry - assertTrue(mNotifSectioner.isInSection(mEntry)); - assertFalse(mNotifSectioner.isInSection(new NotificationEntryBuilder() - .setPkg("test-package") - .build())); - } - - @Test - public void testLifetimeExtendsCurrentHUN() { - // GIVEN there is a HUN, mEntry - addHUN(mEntry); - - given(mHeadsUpManager.canRemoveImmediately(anyString())).willAnswer(i -> { - String key = i.getArgument(0); - for (NotificationEntry entry : mHuns) { - if (entry.getKey().equals(key)) return false; - } - return true; - }); - // THEN only the current HUN, mEntry, should be lifetimeExtended - assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, /* cancellationReason */ 0)); - assertFalse(mNotifLifetimeExtender.maybeExtendLifetime( - new NotificationEntryBuilder() - .setPkg("test-package") - .build(), /* cancellationReason */ 0)); - } - - @Test - public void testShowHUNOnInflationFinished() { - // WHEN a notification should HUN and its inflation is finished - when(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true); - - ArgumentCaptor<BindCallback> bindCallbackCaptor = - ArgumentCaptor.forClass(BindCallback.class); - mCollectionListener.onEntryAdded(mEntry); - verify(mHeadsUpViewBinder).bindHeadsUpView(eq(mEntry), bindCallbackCaptor.capture()); - - bindCallbackCaptor.getValue().onBindFinished(mEntry); - - // THEN we tell the HeadsUpManager to show the notification - verify(mHeadsUpManager).showNotification(mEntry); - } - - @Test - public void testNoHUNOnInflationFinished() { - // WHEN a notification shouldn't HUN and its inflation is finished - when(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(false); - ArgumentCaptor<BindCallback> bindCallbackCaptor = - ArgumentCaptor.forClass(BindCallback.class); - mCollectionListener.onEntryAdded(mEntry); - - // THEN we never bind the heads up view or tell HeadsUpManager to show the notification - verify(mHeadsUpViewBinder, never()).bindHeadsUpView( - eq(mEntry), bindCallbackCaptor.capture()); - verify(mHeadsUpManager, never()).showNotification(mEntry); - } - - @Test - public void testOnEntryRemovedRemovesHeadsUpNotification() { - // GIVEN the current HUN is mEntry - addHUN(mEntry); - - // WHEN mEntry is removed from the notification collection - mCollectionListener.onEntryRemoved(mEntry, /* cancellation reason */ 0); - when(mRemoteInputManager.isSpinning(any())).thenReturn(false); - - // THEN heads up manager should remove the entry - verify(mHeadsUpManager).removeNotification(mEntry.getKey(), false); - } - - private void addHUN(NotificationEntry entry) { - mHuns.add(entry); - when(mHeadsUpManager.getTopEntry()).thenReturn(entry); - mOnHeadsUpChangedListener.onHeadsUpStateChanged(entry, entry != null); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt new file mode 100644 index 000000000000..c67a2331b023 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.notification.collection.coordinator + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.NotificationRemoteInputManager +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback +import com.android.systemui.statusbar.notification.collection.render.NodeController +import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider +import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback +import com.android.systemui.statusbar.policy.HeadsUpManager +import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.withArgCaptor +import com.android.systemui.util.time.FakeSystemClock +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyString +import org.mockito.BDDMockito.given +import org.mockito.Mockito.never +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import java.util.ArrayList +import org.mockito.Mockito.`when` as whenever + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class HeadsUpCoordinatorTest : SysuiTestCase() { + private lateinit var mCoordinator: HeadsUpCoordinator + + // captured listeners and pluggables: + private lateinit var mCollectionListener: NotifCollectionListener + private lateinit var mNotifPromoter: NotifPromoter + private lateinit var mNotifLifetimeExtender: NotifLifetimeExtender + private lateinit var mOnHeadsUpChangedListener: OnHeadsUpChangedListener + private lateinit var mNotifSectioner: NotifSectioner + + private val mNotifPipeline: NotifPipeline = mock() + private val mHeadsUpManager: HeadsUpManager = mock() + private val mHeadsUpViewBinder: HeadsUpViewBinder = mock() + private val mNotificationInterruptStateProvider: NotificationInterruptStateProvider = mock() + private val mRemoteInputManager: NotificationRemoteInputManager = mock() + private val mEndLifetimeExtension: OnEndLifetimeExtensionCallback = mock() + private val mHeaderController: NodeController = mock() + + private lateinit var mEntry: NotificationEntry + private val mExecutor = FakeExecutor(FakeSystemClock()) + private val mHuns: ArrayList<NotificationEntry> = ArrayList() + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + mCoordinator = HeadsUpCoordinator( + mHeadsUpManager, + mHeadsUpViewBinder, + mNotificationInterruptStateProvider, + mRemoteInputManager, + mHeaderController, + mExecutor) + mCoordinator.attach(mNotifPipeline) + + // capture arguments: + mCollectionListener = withArgCaptor { + verify(mNotifPipeline).addCollectionListener(capture()) + } + mNotifPromoter = withArgCaptor { + verify(mNotifPipeline).addPromoter(capture()) + } + mNotifLifetimeExtender = withArgCaptor { + verify(mNotifPipeline).addNotificationLifetimeExtender(capture()) + } + mOnHeadsUpChangedListener = withArgCaptor { + verify(mHeadsUpManager).addListener(capture()) + } + given(mHeadsUpManager.allEntries).willAnswer { mHuns.stream() } + given(mHeadsUpManager.isAlerting(anyString())).willAnswer { invocation -> + val key = invocation.getArgument<String>(0) + mHuns.any { entry -> entry.key == key } + } + given(mHeadsUpManager.canRemoveImmediately(anyString())).willAnswer { invocation -> + val key = invocation.getArgument<String>(0) + !mHuns.any { entry -> entry.key == key } + } + whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L) + mNotifSectioner = mCoordinator.sectioner + mNotifLifetimeExtender.setCallback(mEndLifetimeExtension) + mEntry = NotificationEntryBuilder().build() + } + + @Test + fun testCancelStickyNotification() { + whenever(mHeadsUpManager.isSticky(anyString())).thenReturn(true) + addHUN(mEntry) + whenever(mHeadsUpManager.canRemoveImmediately(anyString())).thenReturn(false, true) + whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 0L) + assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0)) + mExecutor.advanceClockToLast() + mExecutor.runAllReady() + verify(mHeadsUpManager, times(0)).removeNotification(anyString(), eq(false)) + verify(mHeadsUpManager, times(1)).removeNotification(anyString(), eq(true)) + } + + @Test + fun testCancelUpdatedStickyNotification() { + whenever(mHeadsUpManager.isSticky(anyString())).thenReturn(true) + addHUN(mEntry) + whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 500L) + assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0)) + mExecutor.advanceClockToLast() + mExecutor.runAllReady() + verify(mHeadsUpManager, times(0)).removeNotification(anyString(), eq(false)) + verify(mHeadsUpManager, times(0)).removeNotification(anyString(), eq(true)) + } + + @Test + fun testCancelNotification() { + whenever(mHeadsUpManager.isSticky(anyString())).thenReturn(false) + addHUN(mEntry) + whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 500L) + assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0)) + mExecutor.advanceClockToLast() + mExecutor.runAllReady() + verify(mHeadsUpManager, times(1)).removeNotification(anyString(), eq(false)) + verify(mHeadsUpManager, times(0)).removeNotification(anyString(), eq(true)) + } + + @Test + fun testPromotesCurrentHUN() { + // GIVEN the current HUN is set to mEntry + addHUN(mEntry) + + // THEN only promote the current HUN, mEntry + assertTrue(mNotifPromoter.shouldPromoteToTopLevel(mEntry)) + assertFalse(mNotifPromoter.shouldPromoteToTopLevel(NotificationEntryBuilder() + .setPkg("test-package2") + .build())) + } + + @Test + fun testIncludeInSectionCurrentHUN() { + // GIVEN the current HUN is set to mEntry + addHUN(mEntry) + + // THEN only section the current HUN, mEntry + assertTrue(mNotifSectioner.isInSection(mEntry)) + assertFalse(mNotifSectioner.isInSection(NotificationEntryBuilder() + .setPkg("test-package") + .build())) + } + + @Test + fun testLifetimeExtendsCurrentHUN() { + // GIVEN there is a HUN, mEntry + addHUN(mEntry) + + // THEN only the current HUN, mEntry, should be lifetimeExtended + assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, /* cancellationReason */ 0)) + assertFalse(mNotifLifetimeExtender.maybeExtendLifetime( + NotificationEntryBuilder() + .setPkg("test-package") + .build(), /* cancellationReason */ 0)) + } + + @Test + fun testShowHUNOnInflationFinished() { + // WHEN a notification should HUN and its inflation is finished + whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true) + + mCollectionListener.onEntryAdded(mEntry) + withArgCaptor<BindCallback> { + verify(mHeadsUpViewBinder).bindHeadsUpView(eq(mEntry), capture()) + }.onBindFinished(mEntry) + + // THEN we tell the HeadsUpManager to show the notification + verify(mHeadsUpManager).showNotification(mEntry) + } + + @Test + fun testNoHUNOnInflationFinished() { + // WHEN a notification shouldn't HUN and its inflation is finished + whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(false) + mCollectionListener.onEntryAdded(mEntry) + + // THEN we never bind the heads up view or tell HeadsUpManager to show the notification + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mEntry), any()) + verify(mHeadsUpManager, never()).showNotification(mEntry) + } + + @Test + fun testOnEntryRemovedRemovesHeadsUpNotification() { + // GIVEN the current HUN is mEntry + addHUN(mEntry) + + // WHEN mEntry is removed from the notification collection + mCollectionListener.onEntryRemoved(mEntry, /* cancellation reason */ 0) + whenever(mRemoteInputManager.isSpinning(any())).thenReturn(false) + + // THEN heads up manager should remove the entry + verify(mHeadsUpManager).removeNotification(mEntry.key, false) + } + + private fun addHUN(entry: NotificationEntry) { + mHuns.add(entry) + whenever(mHeadsUpManager.topEntry).thenReturn(entry) + mOnHeadsUpChangedListener.onHeadsUpStateChanged(entry, true) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java index 917c049fd578..d0947497f0ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java @@ -41,6 +41,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.RankingBuilder; +import com.android.systemui.statusbar.notification.SectionHeaderVisibilityProvider; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder; import com.android.systemui.statusbar.notification.collection.NotifPipeline; @@ -70,6 +71,7 @@ public class KeyguardCoordinatorTest extends SysuiTestCase { @Mock private StatusBarStateController mStatusBarStateController; @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock private HighPriorityProvider mHighPriorityProvider; + @Mock private SectionHeaderVisibilityProvider mSectionHeaderVisibilityProvider; @Mock private NotifPipeline mNotifPipeline; private NotificationEntry mEntry; @@ -81,7 +83,7 @@ public class KeyguardCoordinatorTest extends SysuiTestCase { KeyguardCoordinator keyguardCoordinator = new KeyguardCoordinator( mContext, mMainHandler, mKeyguardStateController, mLockscreenUserManager, mBroadcastDispatcher, mStatusBarStateController, - mKeyguardUpdateMonitor, mHighPriorityProvider); + mKeyguardUpdateMonitor, mHighPriorityProvider, mSectionHeaderVisibilityProvider); mEntry = new NotificationEntryBuilder() .setUser(new UserHandle(NOTIF_USER_ID)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt index f77381000ae2..4e309d49c6d2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection.render import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager +import com.android.systemui.statusbar.notification.SectionHeaderVisibilityProvider import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder import com.android.systemui.statusbar.notification.collection.ListEntry @@ -43,6 +44,7 @@ class NodeSpecBuilderTest : SysuiTestCase() { private val mediaContainerController: MediaContainerController = mock() private val sectionsFeatureManager: NotificationSectionsFeatureManager = mock() + private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider = mock() private val viewBarn: NotifViewBarn = mock() private var rootController: NodeController = buildFakeController("rootController") @@ -72,11 +74,13 @@ class NodeSpecBuilderTest : SysuiTestCase() { fakeViewBarn.getViewByEntry(it.getArgument(0)) } - specBuilder = NodeSpecBuilder(mediaContainerController, sectionsFeatureManager, viewBarn) + specBuilder = NodeSpecBuilder(mediaContainerController, sectionsFeatureManager, + sectionHeaderVisibilityProvider, viewBarn) } @Test fun testMultipleSectionsWithSameController() { + whenever(sectionHeaderVisibilityProvider.sectionHeadersVisible).thenReturn(true) checkOutput( listOf( notif(0, section0), @@ -95,6 +99,7 @@ class NodeSpecBuilderTest : SysuiTestCase() { @Test(expected = RuntimeException::class) fun testMultipleSectionsWithSameControllerNonConsecutive() { + whenever(sectionHeaderVisibilityProvider.sectionHeadersVisible).thenReturn(true) checkOutput( listOf( notif(0, section0), @@ -108,6 +113,7 @@ class NodeSpecBuilderTest : SysuiTestCase() { @Test fun testSimpleMapping() { + whenever(sectionHeaderVisibilityProvider.sectionHeadersVisible).thenReturn(true) checkOutput( // GIVEN a simple flat list of notifications all in the same headerless section listOf( @@ -129,6 +135,7 @@ class NodeSpecBuilderTest : SysuiTestCase() { @Test fun testSimpleMappingWithMedia() { + whenever(sectionHeaderVisibilityProvider.sectionHeadersVisible).thenReturn(true) // WHEN media controls are enabled whenever(sectionsFeatureManager.isMediaControlsEnabled()).thenReturn(true) @@ -154,6 +161,8 @@ class NodeSpecBuilderTest : SysuiTestCase() { @Test fun testHeaderInjection() { + // WHEN section headers are supposed to be visible + whenever(sectionHeaderVisibilityProvider.sectionHeadersVisible).thenReturn(true) checkOutput( // GIVEN a flat list of notifications, spread across three sections listOf( @@ -177,7 +186,31 @@ class NodeSpecBuilderTest : SysuiTestCase() { } @Test + fun testHeaderSuppression() { + // WHEN section headers are supposed to be hidden + whenever(sectionHeaderVisibilityProvider.sectionHeadersVisible).thenReturn(false) + checkOutput( + // GIVEN a flat list of notifications, spread across three sections + listOf( + notif(0, section0), + notif(1, section0), + notif(2, section1), + notif(3, section2) + ), + + // THEN each section has its header injected + tree( + notifNode(0), + notifNode(1), + notifNode(2), + notifNode(3) + ) + ) + } + + @Test fun testGroups() { + whenever(sectionHeaderVisibilityProvider.sectionHeadersVisible).thenReturn(true) checkOutput( // GIVEN a mixed list of top-level notifications and groups listOf( @@ -218,6 +251,7 @@ class NodeSpecBuilderTest : SysuiTestCase() { @Test fun testSecondSectionWithNoHeader() { + whenever(sectionHeaderVisibilityProvider.sectionHeadersVisible).thenReturn(true) checkOutput( // GIVEN a middle section with no associated header view listOf( @@ -247,6 +281,7 @@ class NodeSpecBuilderTest : SysuiTestCase() { @Test(expected = RuntimeException::class) fun testRepeatedSectionsThrow() { + whenever(sectionHeaderVisibilityProvider.sectionHeadersVisible).thenReturn(true) checkOutput( // GIVEN a malformed list where sections are not contiguous listOf( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index f21fca23df5b..10f4435d3f97 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -647,8 +647,15 @@ public class ScrimControllerTest extends SysuiTestCase { mScrimController.setRawPanelExpansionFraction(0.3f); assertScrimAlpha(Map.of( mScrimInFront, TRANSPARENT, - mNotificationsScrim, SEMI_TRANSPARENT, + mNotificationsScrim, TRANSPARENT, mScrimBehind, SEMI_TRANSPARENT)); + + // Then, notification scrim should fade in + mScrimController.setRawPanelExpansionFraction(0.7f); + assertScrimAlpha(Map.of( + mScrimInFront, TRANSPARENT, + mNotificationsScrim, SEMI_TRANSPARENT, + mScrimBehind, OPAQUE)); } @@ -1132,6 +1139,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void testScrimsVisible_whenShadeVisible() { + mScrimController.setClipsQsScrim(true); mScrimController.transitionTo(ScrimState.UNLOCKED); mScrimController.setRawPanelExpansionFraction(0.3f); // notifications scrim alpha change require calling setQsPosition diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java index a8522c787029..6364d2f23299 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java @@ -52,13 +52,14 @@ import org.mockito.MockitoAnnotations; @SmallTest public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase { - private static final String[] DEFAULT_SETTINGS = new String[] {"0:0", "1:2"}; + private static final String[] DEFAULT_SETTINGS = new String[]{"0:1", "2:0:1", "1:2"}; private final FakeSystemClock mFakeSystemClock = new FakeSystemClock(); private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock); - @Mock DeviceStateManager mDeviceStateManager; - RotationPolicyWrapper mFakeRotationPolicy = new FakeRotationPolicy(); - DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController; + @Mock + private DeviceStateManager mDeviceStateManager; + private final RotationPolicyWrapper mFakeRotationPolicy = new FakeRotationPolicy(); + private DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController; private DeviceStateManager.DeviceStateCallback mDeviceStateCallback; private DeviceStateRotationLockSettingsManager mSettingsManager; private TestableContentResolver mContentResolver; @@ -93,7 +94,7 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase mContentResolver, Settings.Secure.DEVICE_STATE_ROTATION_LOCK, UserHandle.USER_CURRENT)) - .isEqualTo("0:0:1:2"); + .isEqualTo("0:1:1:2:2:0"); } @Test @@ -125,6 +126,31 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase } @Test + public void whenDeviceStateSwitched_settingIsIgnored_loadsDefaultFallbackSetting() { + initializeSettingsWith(); + mFakeRotationPolicy.setRotationLock(true); + + // State 2 -> Ignored -> Fall back to state 1 which is unlocked + mDeviceStateCallback.onStateChanged(2); + + assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); + } + + @Test + public void whenDeviceStateSwitched_ignoredSetting_fallbackValueChanges_usesFallbackValue() { + initializeSettingsWith( + 0, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, + 1, DEVICE_STATE_ROTATION_LOCK_LOCKED, + 2, DEVICE_STATE_ROTATION_LOCK_IGNORED); + mFakeRotationPolicy.setRotationLock(false); + + // State 2 -> Ignored -> Fall back to state 1 which is locked + mDeviceStateCallback.onStateChanged(2); + + assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue(); + } + + @Test public void whenUserChangesSetting_saveSettingForCurrentState() { initializeSettingsWith( 0, DEVICE_STATE_ROTATION_LOCK_LOCKED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); @@ -159,15 +185,15 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase } @Test - public void whenDeviceStateSwitchedToIgnoredState_newSettingsSaveForPreviousState() { + public void whenDeviceStateSwitchedToIgnoredState_noFallback_newSettingsSaveForPreviousState() { initializeSettingsWith( - 0, DEVICE_STATE_ROTATION_LOCK_IGNORED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); + 8, DEVICE_STATE_ROTATION_LOCK_IGNORED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); mFakeRotationPolicy.setRotationLock(true); mDeviceStateCallback.onStateChanged(1); assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); - mDeviceStateCallback.onStateChanged(0); + mDeviceStateCallback.onStateChanged(8); assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); mDeviceStateRotationLockSettingController.onRotationLockStateChanged( @@ -178,7 +204,7 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase mContentResolver, Settings.Secure.DEVICE_STATE_ROTATION_LOCK, UserHandle.USER_CURRENT)) - .isEqualTo("0:0:1:1"); + .isEqualTo("1:1:8:0"); } @Test @@ -198,12 +224,78 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue(); } + @Test + public void onRotationLockStateChanged_newSettingIsPersisted() { + initializeSettingsWith( + 0, DEVICE_STATE_ROTATION_LOCK_LOCKED, + 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); + mDeviceStateCallback.onStateChanged(0); + + mDeviceStateRotationLockSettingController.onRotationLockStateChanged( + /* rotationLocked= */ false, + /* affordanceVisible= */ true + ); + + assertThat( + Settings.Secure.getStringForUser( + mContentResolver, + Settings.Secure.DEVICE_STATE_ROTATION_LOCK, + UserHandle.USER_CURRENT)) + .isEqualTo("0:2:1:2"); + } + + @Test + public void onRotationLockStateChanged_deviceStateIsIgnored_newSettingIsPersistedToFallback() { + initializeSettingsWith( + 0, DEVICE_STATE_ROTATION_LOCK_LOCKED, + 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, + 2, DEVICE_STATE_ROTATION_LOCK_IGNORED); + mDeviceStateCallback.onStateChanged(2); + + mDeviceStateRotationLockSettingController.onRotationLockStateChanged( + /* rotationLocked= */ true, + /* affordanceVisible= */ true + ); + + assertThat( + Settings.Secure.getStringForUser( + mContentResolver, + Settings.Secure.DEVICE_STATE_ROTATION_LOCK, + UserHandle.USER_CURRENT)) + .isEqualTo("0:1:1:1:2:0"); + } + + @Test + public void onRotationLockStateChange_stateIgnored_noFallback_settingIsPersistedToPrevious() { + initializeSettingsWith( + 0, DEVICE_STATE_ROTATION_LOCK_LOCKED, + 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, + 8, DEVICE_STATE_ROTATION_LOCK_IGNORED); + mDeviceStateCallback.onStateChanged(1); + mDeviceStateCallback.onStateChanged(8); + + mDeviceStateRotationLockSettingController.onRotationLockStateChanged( + /* rotationLocked= */ true, + /* affordanceVisible= */ true + ); + + assertThat( + Settings.Secure.getStringForUser( + mContentResolver, + Settings.Secure.DEVICE_STATE_ROTATION_LOCK, + UserHandle.USER_CURRENT)) + .isEqualTo("0:1:1:1:8:0"); + } + private void initializeSettingsWith(int... values) { if (values.length % 2 != 0) { throw new IllegalArgumentException("Expecting key-value pairs"); } StringBuilder sb = new StringBuilder(); - for (int i = 0; i < values.length; sb.append(":")) { + for (int i = 0; i < values.length; ) { + if (i > 0) { + sb.append(":"); + } sb.append(values[i++]).append(":").append(values[i++]); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java index 087f2e6006cf..2126dda7b310 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java @@ -22,6 +22,7 @@ import static org.mockito.Mockito.when; import android.app.AppOpsManager; import android.content.Intent; +import android.content.pm.UserInfo; import android.location.LocationManager; import android.os.Handler; import android.os.UserHandle; @@ -68,6 +69,8 @@ public class LocationControllerImplTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); when(mUserTracker.getUserId()).thenReturn(UserHandle.USER_SYSTEM); when(mUserTracker.getUserHandle()).thenReturn(UserHandle.SYSTEM); + when(mUserTracker.getUserProfiles()) + .thenReturn(ImmutableList.of(new UserInfo(0, "name", 0))); mDeviceConfigProxy = new DeviceConfigProxyFake(); mTestableLooper = TestableLooper.get(this); @@ -78,7 +81,8 @@ public class LocationControllerImplTest extends SysuiTestCase { new Handler(mTestableLooper.getLooper()), mock(BroadcastDispatcher.class), mock(BootCompleteCache.class), - mUserTracker); + mUserTracker, + mContext.getPackageManager()); mTestableLooper.processAllMessages(); } @@ -161,17 +165,38 @@ public class LocationControllerImplTest extends SysuiTestCase { @Test public void testCallbackNotified_additionalOps() { LocationChangeCallback callback = mock(LocationChangeCallback.class); - mLocationController.addCallback(callback); + mDeviceConfigProxy.setProperty( + DeviceConfig.NAMESPACE_PRIVACY, + SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED, + "true", + true); + mTestableLooper.processAllMessages(); + + when(mAppOpsController.getActiveAppOps()) + .thenReturn(ImmutableList.of( + new AppOpItem(AppOpsManager.OP_FINE_LOCATION, 0, + "com.google.android.googlequicksearchbox", + System.currentTimeMillis()))); + mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0, + "com.google.android.googlequicksearchbox", true); mTestableLooper.processAllMessages(); - mLocationController.onReceive(mContext, new Intent(LocationManager.MODE_CHANGED_ACTION)); + verify(callback, times(1)).onLocationActiveChanged(true); + when(mAppOpsController.getActiveAppOps()).thenReturn(ImmutableList.of()); + mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0, + "com.google.android.googlequicksearchbox", false); mTestableLooper.processAllMessages(); - verify(callback, times(2)).onLocationSettingsChanged(anyBoolean()); + verify(callback, times(1)).onLocationActiveChanged(false); + } + @Test + public void testCallbackNotified_additionalOps_shouldShowSystem() { + LocationChangeCallback callback = mock(LocationChangeCallback.class); + mLocationController.addCallback(callback); mDeviceConfigProxy.setProperty( DeviceConfig.NAMESPACE_PRIVACY, SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED, @@ -181,10 +206,40 @@ public class LocationControllerImplTest extends SysuiTestCase { when(mAppOpsController.getActiveAppOps()) .thenReturn(ImmutableList.of( - new AppOpItem(AppOpsManager.OP_FINE_LOCATION, 0, "", + new AppOpItem(AppOpsManager.OP_FINE_LOCATION, 0, + "com.google.android.gms", System.currentTimeMillis()))); mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0, - "", true); + "com.google.android.gms", true); + + mTestableLooper.processAllMessages(); + + verify(callback, times(0)).onLocationActiveChanged(true); + } + + + @Test + public void testCallbackNotified_additionalOps_shouldNotShowSystem() { + LocationChangeCallback callback = mock(LocationChangeCallback.class); + mLocationController.addCallback(callback); + mDeviceConfigProxy.setProperty( + DeviceConfig.NAMESPACE_PRIVACY, + SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED, + "true", + true); + mDeviceConfigProxy.setProperty( + DeviceConfig.NAMESPACE_PRIVACY, + SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SHOW_SYSTEM, + "true", + true); + mTestableLooper.processAllMessages(); + + when(mAppOpsController.getActiveAppOps()) + .thenReturn(ImmutableList.of( + new AppOpItem(AppOpsManager.OP_FINE_LOCATION, 0, "com.google.android.gms", + System.currentTimeMillis()))); + mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0, + "com.google.android.gms", true); mTestableLooper.processAllMessages(); @@ -192,7 +247,7 @@ public class LocationControllerImplTest extends SysuiTestCase { when(mAppOpsController.getActiveAppOps()).thenReturn(ImmutableList.of()); mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0, - "", false); + "com.google.android.gms", false); mTestableLooper.processAllMessages(); verify(callback, times(1)).onLocationActiveChanged(false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt index f600b12993b9..4e2736c74007 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt @@ -68,7 +68,7 @@ class UnfoldLatencyTrackerTest : SysuiTestCase() { context.mainExecutor, context, screenLifecycle - ) + ).apply { init() } deviceStates = FoldableTestUtils.findDeviceStates(context) verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture()) diff --git a/services/companion/java/com/android/server/companion/AssociationStore.java b/services/companion/java/com/android/server/companion/AssociationStore.java index 58fc8f7fe5b6..01905bb2297f 100644 --- a/services/companion/java/com/android/server/companion/AssociationStore.java +++ b/services/companion/java/com/android/server/companion/AssociationStore.java @@ -49,7 +49,25 @@ public interface AssociationStore { /** Listener for any changes to {@link AssociationInfo}-s. */ interface OnChangeListener { default void onAssociationChanged( - @ChangeType int changeType, AssociationInfo association) {} + @ChangeType int changeType, AssociationInfo association) { + switch (changeType) { + case CHANGE_TYPE_ADDED: + onAssociationAdded(association); + break; + + case CHANGE_TYPE_REMOVED: + onAssociationRemoved(association); + break; + + case CHANGE_TYPE_UPDATED_ADDRESS_CHANGED: + onAssociationUpdated(association, true); + break; + + case CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED: + onAssociationUpdated(association, false); + break; + } + } default void onAssociationAdded(AssociationInfo association) {} diff --git a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java index dbcdd0f877a1..cda554e9d0cf 100644 --- a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java +++ b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java @@ -125,8 +125,7 @@ class AssociationStoreImpl implements AssociationStore { // Update the MacAddress-to-List<Association> map if needed. final MacAddress updatedAddress = updated.getDeviceMacAddress(); final MacAddress currentAddress = current.getDeviceMacAddress(); - macAddressChanged = Objects.equals( - current.getDeviceMacAddress(), updated.getDeviceMacAddress()); + macAddressChanged = Objects.equals(currentAddress, updatedAddress); if (macAddressChanged) { if (currentAddress != null) { mAddressMap.get(currentAddress).remove(id); @@ -213,11 +212,9 @@ class AssociationStoreImpl implements AssociationStore { final Set<Integer> ids = mAddressMap.get(address); if (ids == null) return Collections.emptyList(); - final List<AssociationInfo> associations = new ArrayList<>(); - for (AssociationInfo association : mIdMap.values()) { - if (address.equals(association.getDeviceMacAddress())) { - associations.add(association); - } + final List<AssociationInfo> associations = new ArrayList<>(ids.size()); + for (Integer id : ids) { + associations.add(mIdMap.get(id)); } return Collections.unmodifiableList(associations); @@ -263,24 +260,6 @@ class AssociationStoreImpl implements AssociationStore { synchronized (mListeners) { for (OnChangeListener listener : mListeners) { listener.onAssociationChanged(changeType, association); - - switch (changeType) { - case CHANGE_TYPE_ADDED: - listener.onAssociationAdded(association); - break; - - case CHANGE_TYPE_REMOVED: - listener.onAssociationRemoved(association); - break; - - case CHANGE_TYPE_UPDATED_ADDRESS_CHANGED: - listener.onAssociationUpdated(association, true); - break; - - case CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED: - listener.onAssociationUpdated(association, false); - break; - } } } } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 1e50c80f0e71..b2b55765f178 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -77,11 +77,13 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; +import android.content.pm.UserInfo; import android.net.MacAddress; import android.net.NetworkPolicyManager; import android.os.Binder; import android.os.Environment; import android.os.Handler; +import android.os.Message; import android.os.Parcel; import android.os.PowerWhitelistManager; import android.os.RemoteCallbackList; @@ -185,6 +187,7 @@ public class CompanionDeviceManagerService extends SystemService private final CompanionDeviceManagerServiceInternal mLocalService = new LocalService(this); final Handler mMainHandler = Handler.getMain(); + private final PersistUserStateHandler mUserPersistenceHandler = new PersistUserStateHandler(); private CompanionDevicePresenceController mCompanionDevicePresenceController; /** @@ -366,9 +369,8 @@ public class CompanionDeviceManagerService extends SystemService final List<AssociationInfo> updatedAssociations = mAssociationStore.getAssociationsForUser(userId); - final Map<String, Set<Integer>> usedIdsForUser = getPreviouslyUsedIdsForUser(userId); - BackgroundThread.getHandler().post(() -> - mPersistentStore.persistStateForUser(userId, updatedAssociations, usedIdsForUser)); + + mUserPersistenceHandler.postPersistUserState(userId); // Notify listeners if ADDED, REMOVED or UPDATED_ADDRESS_CHANGED. // Do NOT notify when UPDATED_ADDRESS_UNCHANGED, which means a minor tweak in association's @@ -381,6 +383,13 @@ public class CompanionDeviceManagerService extends SystemService restartBleScan(); } + private void persistStateForUser(@UserIdInt int userId) { + final List<AssociationInfo> updatedAssociations = + mAssociationStore.getAssociationsForUser(userId); + final Map<String, Set<Integer>> usedIdsForUser = getPreviouslyUsedIdsForUser(userId); + mPersistentStore.persistStateForUser(userId, updatedAssociations, usedIdsForUser); + } + private void notifyListeners( @UserIdInt int userId, @NonNull List<AssociationInfo> associations) { mListeners.broadcast((listener, callbackUserId) -> { @@ -1349,4 +1358,47 @@ public class CompanionDeviceManagerService extends SystemService mService.associationCleanUp(profile); } } + + /** + * This method must only be called from {@link CompanionDeviceShellCommand} for testing + * purposes only! + */ + void persistState() { + mUserPersistenceHandler.clearMessages(); + for (UserInfo user : mUserManager.getAliveUsers()) { + persistStateForUser(user.id); + } + } + + /** + * This class is dedicated to handling requests to persist user state. + */ + @SuppressLint("HandlerLeak") + private class PersistUserStateHandler extends Handler { + PersistUserStateHandler() { + super(BackgroundThread.get().getLooper()); + } + + /** + * Persists user state unless there is already an outstanding request for the given user. + */ + synchronized void postPersistUserState(@UserIdInt int userId) { + if (!hasMessages(userId)) { + sendMessage(obtainMessage(userId)); + } + } + + /** + * Clears *ALL* outstanding persist requests for *ALL* users. + */ + synchronized void clearMessages() { + removeCallbacksAndMessages(null); + } + + @Override + public void handleMessage(@NonNull Message msg) { + final int userId = msg.what; + persistStateForUser(userId); + } + } } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java index 5f46d5c4c4bf..9b2bd82fcfed 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java @@ -83,6 +83,7 @@ class CompanionDeviceShellCommand extends android.os.ShellCommand { } break; case "clear-association-memory-cache": { + mService.persistState(); mService.loadAssociationsFromDisk(); } break; diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java index 734e5c3f6f8b..0fd29675d469 100644 --- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java +++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java @@ -21,6 +21,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_SECURE; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; @@ -33,6 +34,7 @@ import android.util.Slog; import android.window.DisplayWindowPolicyController; import java.util.List; +import java.util.Set; /** @@ -49,13 +51,23 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { @ChangeId @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) public static final long ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE = 201712607L; - @NonNull private final ArraySet<UserHandle> mAllowedUsers; + @NonNull + private final ArraySet<UserHandle> mAllowedUsers; + @Nullable + private final ArraySet<ComponentName> mAllowedActivities; + @Nullable + private final ArraySet<ComponentName> mBlockedActivities; - @NonNull final ArraySet<Integer> mRunningUids = new ArraySet<>(); + @NonNull + final ArraySet<Integer> mRunningUids = new ArraySet<>(); GenericWindowPolicyController(int windowFlags, int systemWindowFlags, - @NonNull ArraySet<UserHandle> allowedUsers) { + @NonNull ArraySet<UserHandle> allowedUsers, + @Nullable Set<ComponentName> allowedActivities, + @Nullable Set<ComponentName> blockedActivities) { mAllowedUsers = allowedUsers; + mAllowedActivities = allowedActivities == null ? null : new ArraySet<>(allowedActivities); + mBlockedActivities = blockedActivities == null ? null : new ArraySet<>(blockedActivities); setInterestedWindowFlags(windowFlags, systemWindowFlags); } @@ -108,6 +120,18 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { Slog.d(TAG, "Virtual device activity not allowed from user " + activityUser); return false; } + if (mBlockedActivities != null + && mBlockedActivities.contains(activityInfo.getComponentName())) { + Slog.d(TAG, + "Virtual device blocking launch of " + activityInfo.getComponentName()); + return false; + } + if (mAllowedActivities != null + && !mAllowedActivities.contains(activityInfo.getComponentName())) { + Slog.d(TAG, + activityInfo.getComponentName() + " is not in the allowed list."); + return false; + } if (!CompatChanges.isChangeEnabled(ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE, activityInfo.packageName, activityUser)) { // TODO(b/201712607): Add checks for the apps that use SurfaceView#setSecure. diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 0c0ee521af0f..59c9d8c625b5 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -53,8 +53,7 @@ import com.android.internal.annotations.VisibleForTesting; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; +import java.util.Set; final class VirtualDeviceImpl extends IVirtualDevice.Stub @@ -69,7 +68,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub private final int mOwnerUid; private final InputController mInputController; @VisibleForTesting - final List<Integer> mVirtualDisplayIds = new ArrayList<>(); + final Set<Integer> mVirtualDisplayIds = new ArraySet<>(); private final OnDeviceCloseListener mListener; private final IBinder mAppToken; private final VirtualDeviceParams mParams; @@ -328,6 +327,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { fout.println(" VirtualDevice: "); fout.println(" mAssociationId: " + mAssociationInfo.getId()); + fout.println(" mParams: " + mParams); fout.println(" mVirtualDisplayIds: "); synchronized (mVirtualDeviceLock) { for (int id : mVirtualDisplayIds) { @@ -345,7 +345,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub mVirtualDisplayIds.add(displayId); final GenericWindowPolicyController dwpc = new GenericWindowPolicyController(FLAG_SECURE, - SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, getAllowedUserHandles()); + SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, getAllowedUserHandles(), + mParams.getAllowedActivities(), + mParams.getBlockedActivities()); mWindowPolicyControllers.put(displayId, dwpc); return dwpc; } diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 60cae4d67f5a..1666d15b2387 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -70,6 +70,7 @@ public abstract class PackageManagerInternal implements PackageSettingsSnapshotP PACKAGE_SYSTEM, PACKAGE_SETUP_WIZARD, PACKAGE_INSTALLER, + PACKAGE_UNINSTALLER, PACKAGE_VERIFIER, PACKAGE_BROWSER, PACKAGE_SYSTEM_TEXT_CLASSIFIER, @@ -89,20 +90,21 @@ public abstract class PackageManagerInternal implements PackageSettingsSnapshotP public static final int PACKAGE_SYSTEM = 0; public static final int PACKAGE_SETUP_WIZARD = 1; public static final int PACKAGE_INSTALLER = 2; - public static final int PACKAGE_VERIFIER = 3; - public static final int PACKAGE_BROWSER = 4; - public static final int PACKAGE_SYSTEM_TEXT_CLASSIFIER = 5; - public static final int PACKAGE_PERMISSION_CONTROLLER = 6; - public static final int PACKAGE_WELLBEING = 7; - public static final int PACKAGE_DOCUMENTER = 8; - public static final int PACKAGE_CONFIGURATOR = 9; - public static final int PACKAGE_INCIDENT_REPORT_APPROVER = 10; - public static final int PACKAGE_APP_PREDICTOR = 11; - public static final int PACKAGE_OVERLAY_CONFIG_SIGNATURE = 12; - public static final int PACKAGE_WIFI = 13; - public static final int PACKAGE_COMPANION = 14; - public static final int PACKAGE_RETAIL_DEMO = 15; - public static final int PACKAGE_RECENTS = 16; + public static final int PACKAGE_UNINSTALLER = 3; + public static final int PACKAGE_VERIFIER = 4; + public static final int PACKAGE_BROWSER = 5; + public static final int PACKAGE_SYSTEM_TEXT_CLASSIFIER = 6; + public static final int PACKAGE_PERMISSION_CONTROLLER = 7; + public static final int PACKAGE_WELLBEING = 8; + public static final int PACKAGE_DOCUMENTER = 9; + public static final int PACKAGE_CONFIGURATOR = 10; + public static final int PACKAGE_INCIDENT_REPORT_APPROVER = 11; + public static final int PACKAGE_APP_PREDICTOR = 12; + public static final int PACKAGE_OVERLAY_CONFIG_SIGNATURE = 13; + public static final int PACKAGE_WIFI = 14; + public static final int PACKAGE_COMPANION = 15; + public static final int PACKAGE_RETAIL_DEMO = 16; + public static final int PACKAGE_RECENTS = 17; // Integer value of the last known package ID. Increases as new ID is added to KnownPackage. // Please note the numbers should be continuous. public static final int LAST_KNOWN_PACKAGE = PACKAGE_RECENTS; @@ -1141,6 +1143,8 @@ public abstract class PackageManagerInternal implements PackageSettingsSnapshotP return "Setup Wizard"; case PACKAGE_INSTALLER: return "Installer"; + case PACKAGE_UNINSTALLER: + return "Uninstaller"; case PACKAGE_VERIFIER: return "Verifier"; case PACKAGE_BROWSER: diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index 844ac86e8eb5..5d48d7821cad 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -853,7 +853,9 @@ public final class BatteryService extends SystemService { pw.println("Battery service (battery) commands:"); pw.println(" help"); pw.println(" Print this help text."); - pw.println(" set [-f] [ac|usb|wireless|status|level|temp|present|invalid] <value>"); + pw.println(" get [-f] [ac|usb|wireless|status|level|temp|present|counter|invalid]"); + pw.println( + " set [-f] [ac|usb|wireless|status|level|temp|present|counter|invalid] <value>"); pw.println(" Force a battery property value, freezing battery state."); pw.println(" -f: force a battery change broadcast be sent, prints new sequence."); pw.println(" unplug [-f]"); @@ -863,7 +865,7 @@ public final class BatteryService extends SystemService { pw.println(" Unfreeze battery state, returning to current hardware values."); pw.println(" -f: force a battery change broadcast be sent, prints new sequence."); if (Build.IS_DEBUGGABLE) { - pw.println(" disable_charge"); + pw.println(" suspend_input"); pw.println(" Suspend charging even if plugged in. "); } } @@ -893,6 +895,46 @@ public final class BatteryService extends SystemService { android.Manifest.permission.DEVICE_POWER, null); unplugBattery(/* forceUpdate= */ (opts & OPTION_FORCE_UPDATE) != 0, pw); } break; + case "get": { + final String key = shell.getNextArg(); + if (key == null) { + pw.println("No property specified"); + return -1; + + } + switch (key) { + case "present": + pw.println(mHealthInfo.batteryPresent); + break; + case "ac": + pw.println(mHealthInfo.chargerAcOnline); + break; + case "usb": + pw.println(mHealthInfo.chargerUsbOnline); + break; + case "wireless": + pw.println(mHealthInfo.chargerWirelessOnline); + break; + case "status": + pw.println(mHealthInfo.batteryStatus); + break; + case "level": + pw.println(mHealthInfo.batteryLevel); + break; + case "counter": + pw.println(mHealthInfo.batteryChargeCounterUah); + break; + case "temp": + pw.println(mHealthInfo.batteryTemperatureTenthsCelsius); + break; + case "invalid": + pw.println(mInvalidCharger); + break; + default: + pw.println("Unknown get option: " + key); + break; + } + } break; case "set": { int opts = parseOptions(shell); getContext().enforceCallingOrSelfPermission( diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index e040319b8aee..888710857706 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -4138,7 +4138,7 @@ public final class ActiveServices { // for a previous process to come up. To deal with this, we store // in the service any current isolated process it is running in or // waiting to have come up. - app = r.isolatedProc; + app = r.isolationHostProc; if (WebViewZygote.isMultiprocessEnabled() && r.serviceInfo.packageName.equals(WebViewZygote.getPackageName())) { hostingRecord = HostingRecord.byWebviewZygote(r.instanceName); @@ -4165,7 +4165,7 @@ public final class ActiveServices { return msg; } if (isolated) { - r.isolatedProc = app; + r.isolationHostProc = app; } } @@ -4976,7 +4976,7 @@ public final class ActiveServices { try { for (int i=0; i<mPendingServices.size(); i++) { sr = mPendingServices.get(i); - if (proc != sr.isolatedProc && (proc.uid != sr.appInfo.uid + if (proc != sr.isolationHostProc && (proc.uid != sr.appInfo.uid || !processName.equals(sr.processName))) { continue; } @@ -5016,7 +5016,7 @@ public final class ActiveServices { boolean didImmediateRestart = false; for (int i=0; i<mRestartingServices.size(); i++) { sr = mRestartingServices.get(i); - if (proc != sr.isolatedProc && (proc.uid != sr.appInfo.uid + if (proc != sr.isolationHostProc && (proc.uid != sr.appInfo.uid || !processName.equals(sr.processName))) { continue; } @@ -5048,9 +5048,9 @@ public final class ActiveServices { ServiceRecord sr = mPendingServices.get(i); if ((proc.uid == sr.appInfo.uid && proc.processName.equals(sr.processName)) - || sr.isolatedProc == proc) { + || sr.isolationHostProc == proc) { Slog.w(TAG, "Forcing bringing down service: " + sr); - sr.isolatedProc = null; + sr.isolationHostProc = null; mPendingServices.remove(i); size = mPendingServices.size(); i--; @@ -5083,7 +5083,7 @@ public final class ActiveServices { stopServiceAndUpdateAllowlistManagerLocked(service); } service.setProcess(null, null, 0, null); - service.isolatedProc = null; + service.isolationHostProc = null; if (mTmpCollectionResults == null) { mTmpCollectionResults = new ArrayList<>(); } @@ -5321,7 +5321,7 @@ public final class ActiveServices { sr.app.mServices.updateBoundClientUids(); } sr.setProcess(null, null, 0, null); - sr.isolatedProc = null; + sr.isolationHostProc = null; sr.executeNesting = 0; synchronized (mAm.mProcessStats.mLock) { sr.forceClearTracker(); diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 9ffafe256033..921208cbfa3d 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -2233,6 +2233,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub pw.println(" --read-daily: read-load last written daily stats."); pw.println(" --settings: dump the settings key/values related to batterystats"); pw.println(" --cpu: dump cpu stats for debugging purpose"); + pw.println(" --power-profile: dump the power profile constants"); pw.println(" <package.name>: optional name of package to filter output by."); pw.println(" -h: print this help text."); pw.println("Battery stats (batterystats) commands:"); @@ -2270,6 +2271,12 @@ public final class BatteryStatsService extends IBatteryStats.Stub } } + private void dumpPowerProfile(PrintWriter pw) { + synchronized (mStats) { + mStats.dumpPowerProfileLocked(pw); + } + } + private int doEnableOrDisable(PrintWriter pw, int i, String[] args, boolean enable) { i++; if (i >= args.length) { @@ -2412,6 +2419,9 @@ public final class BatteryStatsService extends IBatteryStats.Stub } else if ("--measured-energy".equals(arg)) { dumpMeasuredEnergyStats(pw); return; + } else if ("--power-profile".equals(arg)) { + dumpPowerProfile(pw); + return; } else if ("-a".equals(arg)) { flags |= BatteryStats.DUMP_VERBOSE; } else if (arg.length() > 0 && arg.charAt(0) == '-'){ diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java index 625dd636b24e..3c5b872ec717 100644 --- a/services/core/java/com/android/server/am/ContentProviderHelper.java +++ b/services/core/java/com/android/server/am/ContentProviderHelper.java @@ -15,6 +15,7 @@ */ package com.android.server.am; +import static android.content.ContentProvider.isAuthorityRedirectedForCloneProfile; import static android.os.Process.PROC_CHAR; import static android.os.Process.PROC_OUT_LONG; import static android.os.Process.PROC_PARENS; @@ -46,6 +47,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.PathPermission; import android.content.pm.ProviderInfo; +import android.content.pm.UserInfo; import android.database.ContentObserver; import android.net.Uri; import android.os.Binder; @@ -72,6 +74,7 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import com.android.server.LocalServices; import com.android.server.RescueParty; +import com.android.server.pm.UserManagerInternal; import com.android.server.pm.parsing.pkg.AndroidPackage; import java.io.FileDescriptor; @@ -177,12 +180,22 @@ public class ContentProviderHelper { cpr = mProviderMap.getProviderByName(name, UserHandle.USER_SYSTEM); if (cpr != null) { cpi = cpr.info; + if (mService.isSingleton( cpi.processName, cpi.applicationInfo, cpi.name, cpi.flags) && mService.isValidSingletonCall( r == null ? callingUid : r.uid, cpi.applicationInfo.uid)) { userId = UserHandle.USER_SYSTEM; checkCrossUser = false; + } else if (isAuthorityRedirectedForCloneProfile(name)) { + UserManagerInternal umInternal = LocalServices.getService( + UserManagerInternal.class); + UserInfo userInfo = umInternal.getUserInfo(userId); + + if (userInfo != null && userInfo.isCloneProfile()) { + userId = umInternal.getProfileParentId(userId); + checkCrossUser = false; + } } else { cpr = null; cpi = null; @@ -1026,6 +1039,7 @@ public class ContentProviderHelper { * at the given authority and user. */ String checkContentProviderAccess(String authority, int userId) { + boolean checkUser = true; if (userId == UserHandle.USER_ALL) { mService.mContext.enforceCallingOrSelfPermission( android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, TAG); @@ -1041,6 +1055,17 @@ public class ContentProviderHelper { | PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId); + if (cpi == null && isAuthorityRedirectedForCloneProfile(authority)) { + // This might be a provider that's running only in the system user that's + // also serving clone profiles + cpi = AppGlobals.getPackageManager().resolveContentProvider(authority, + ActivityManagerService.STOCK_PM_FLAGS + | PackageManager.GET_URI_PERMISSION_PATTERNS + | PackageManager.MATCH_DISABLED_COMPONENTS + | PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, + UserHandle.USER_SYSTEM); + } } catch (RemoteException ignored) { } if (cpi == null) { @@ -1048,6 +1073,16 @@ public class ContentProviderHelper { + "; expected to find a valid ContentProvider for this authority"; } + if (isAuthorityRedirectedForCloneProfile(authority)) { + UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class); + UserInfo userInfo = umInternal.getUserInfo(userId); + + if (userInfo != null && userInfo.isCloneProfile()) { + userId = umInternal.getProfileParentId(userId); + checkUser = false; + } + } + final int callingPid = Binder.getCallingPid(); ProcessRecord r; final String appName; @@ -1060,7 +1095,7 @@ public class ContentProviderHelper { } return checkContentProviderPermission(cpi, callingPid, Binder.getCallingUid(), - userId, true, appName); + userId, checkUser, appName); } int checkContentProviderUriPermission(Uri uri, int userId, int callingUid, int modeFlags) { diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index b1234962efc2..bdfd02e9c25a 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -598,7 +598,7 @@ public class OomAdjuster { for (int i = psr.numberOfConnections() - 1; i >= 0; i--) { ConnectionRecord cr = psr.getConnectionAt(i); ProcessRecord service = (cr.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0 - ? cr.binding.service.isolatedProc : cr.binding.service.app; + ? cr.binding.service.isolationHostProc : cr.binding.service.app; if (service == null || service == pr) { continue; } diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index c830554c398b..be187e21db47 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -513,7 +513,7 @@ class ProcessRecord implements WindowProcessListener { } } processInfo = procInfo; - isolated = _info.uid != _uid; + isolated = Process.isIsolated(_uid); appZygote = (UserHandle.getAppId(_uid) >= Process.FIRST_APP_ZYGOTE_ISOLATED_UID && UserHandle.getAppId(_uid) <= Process.LAST_APP_ZYGOTE_ISOLATED_UID); uid = _uid; diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 9b32e61763f3..24e7ba4a32b9 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -102,7 +102,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN // IBinder -> ConnectionRecord of all bound clients ProcessRecord app; // where this service is running or null. - ProcessRecord isolatedProc; // keep track of isolated process, if requested + ProcessRecord isolationHostProc; // process which we've started for this service (used for + // isolated and supplemental processes) ServiceState tracker; // tracking service execution, may be null ServiceState restartTracker; // tracking service restart boolean allowlistManager; // any bindings to this service have BIND_ALLOW_WHITELIST_MANAGEMENT? @@ -352,8 +353,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN if (app != null) { app.dumpDebug(proto, ServiceRecordProto.APP); } - if (isolatedProc != null) { - isolatedProc.dumpDebug(proto, ServiceRecordProto.ISOLATED_PROC); + if (isolationHostProc != null) { + isolationHostProc.dumpDebug(proto, ServiceRecordProto.ISOLATED_PROC); } proto.write(ServiceRecordProto.WHITELIST_MANAGER, allowlistManager); proto.write(ServiceRecordProto.DELAYED, delayed); @@ -455,8 +456,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN pw.print(prefix); pw.print("dataDir="); pw.println(appInfo.dataDir); } pw.print(prefix); pw.print("app="); pw.println(app); - if (isolatedProc != null) { - pw.print(prefix); pw.print("isolatedProc="); pw.println(isolatedProc); + if (isolationHostProc != null) { + pw.print(prefix); pw.print("isolationHostProc="); pw.println(isolationHostProc); } if (allowlistManager) { pw.print(prefix); pw.print("allowlistManager="); pw.println(allowlistManager); diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java index d5ac03ab7c0d..48e66b6c6aeb 100644 --- a/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java +++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java @@ -27,6 +27,8 @@ import android.service.games.IGameSessionService; import com.android.internal.infra.ServiceConnector; import com.android.internal.os.BackgroundThread; +import com.android.server.LocalServices; +import com.android.server.wm.WindowManagerInternal; final class GameServiceProviderInstanceFactoryImpl implements GameServiceProviderInstanceFactory { private final Context mContext; @@ -44,6 +46,7 @@ final class GameServiceProviderInstanceFactoryImpl implements GameServiceProvide BackgroundThread.getExecutor(), new GameClassifierImpl(mContext.getPackageManager()), ActivityTaskManager.getService(), + LocalServices.getService(WindowManagerInternal.class), new GameServiceConnector(mContext, gameServiceProviderConfiguration), new GameSessionServiceConnector(mContext, gameServiceProviderConfiguration)); } diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java index cc060e94a52a..31eb8c1c9429 100644 --- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java +++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java @@ -17,24 +17,31 @@ package com.android.server.app; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityManager.RunningTaskInfo; import android.app.IActivityTaskManager; import android.app.TaskStackListener; import android.content.ComponentName; -import android.os.IBinder; +import android.graphics.Rect; import android.os.RemoteException; import android.os.UserHandle; import android.service.games.CreateGameSessionRequest; +import android.service.games.CreateGameSessionResult; +import android.service.games.GameSessionViewHostConfiguration; import android.service.games.GameStartedEvent; import android.service.games.IGameService; import android.service.games.IGameServiceController; import android.service.games.IGameSession; import android.service.games.IGameSessionService; import android.util.Slog; +import android.view.SurfaceControlViewHost.SurfacePackage; import com.android.internal.annotations.GuardedBy; import com.android.internal.infra.AndroidFuture; import com.android.internal.infra.ServiceConnector; +import com.android.server.wm.WindowManagerInternal; +import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; @@ -62,6 +69,12 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan GameServiceProviderInstanceImpl.this.onTaskRemoved(taskId); }); } + + // TODO(b/204503192): Limit the lifespan of the game session in the Game Service provider + // to only when the associated task is running. Right now it is possible for a task to + // move into the background and for all associated processes to die and for the Game Session + // provider's GameSessionService to continue to be running. Ideally we could unbind the + // service when this happens. }; private final IGameServiceController mGameServiceController = @@ -79,6 +92,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan private final Executor mBackgroundExecutor; private final GameClassifier mGameClassifier; private final IActivityTaskManager mActivityTaskManager; + private final WindowManagerInternal mWindowManagerInternal; private final ServiceConnector<IGameService> mGameServiceConnector; private final ServiceConnector<IGameSessionService> mGameSessionServiceConnector; @@ -89,16 +103,18 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan private volatile boolean mIsRunning; GameServiceProviderInstanceImpl( - UserHandle userHandle, + @NonNull UserHandle userHandle, @NonNull Executor backgroundExecutor, @NonNull GameClassifier gameClassifier, @NonNull IActivityTaskManager activityTaskManager, + @NonNull WindowManagerInternal windowManagerInternal, @NonNull ServiceConnector<IGameService> gameServiceConnector, @NonNull ServiceConnector<IGameSessionService> gameSessionServiceConnector) { mUserHandle = userHandle; mBackgroundExecutor = backgroundExecutor; mGameClassifier = gameClassifier; mActivityTaskManager = activityTaskManager; + mWindowManagerInternal = windowManagerInternal; mGameServiceConnector = gameServiceConnector; mGameSessionServiceConnector = gameSessionServiceConnector; } @@ -151,16 +167,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } for (GameSessionRecord gameSessionRecord : mGameSessions.values()) { - IGameSession gameSession = gameSessionRecord.getGameSession(); - if (gameSession == null) { - continue; - } - - try { - gameSession.destroy(); - } catch (RemoteException ex) { - Slog.w(TAG, "Failed to destroy session: " + gameSessionRecord, ex); - } + destroyGameSessionFromRecord(gameSessionRecord); } mGameSessions.clear(); @@ -186,30 +193,30 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } @GuardedBy("mLock") - private void gameTaskStartedLocked(int sessionId, @NonNull ComponentName componentName) { + private void gameTaskStartedLocked(int taskId, @NonNull ComponentName componentName) { if (DEBUG) { - Slog.i(TAG, "gameStartedLocked() id: " + sessionId + " component: " + componentName); + Slog.i(TAG, "gameStartedLocked() id: " + taskId + " component: " + componentName); } if (!mIsRunning) { return; } - GameSessionRecord existingGameSessionRecord = mGameSessions.get(sessionId); + GameSessionRecord existingGameSessionRecord = mGameSessions.get(taskId); if (existingGameSessionRecord != null) { - Slog.w(TAG, "Existing game session found for task (id: " + sessionId + Slog.w(TAG, "Existing game session found for task (id: " + taskId + ") creation. Ignoring."); return; } GameSessionRecord gameSessionRecord = GameSessionRecord.awaitingGameSessionRequest( - sessionId, componentName); - mGameSessions.put(sessionId, gameSessionRecord); + taskId, componentName); + mGameSessions.put(taskId, gameSessionRecord); AndroidFuture<Void> unusedPostGameStartedFuture = mGameServiceConnector.post( gameService -> { gameService.gameStarted( - new GameStartedEvent(sessionId, componentName.getPackageName())); + new GameStartedEvent(taskId, componentName.getPackageName())); }); } @@ -220,7 +227,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan return; } - destroyGameSessionIfNecessaryLocked(taskId); + removeAndDestroyGameSessionIfNecessaryLocked(taskId); } } @@ -231,107 +238,151 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } @GuardedBy("mLock") - private void createGameSessionLocked(int sessionId) { + private void createGameSessionLocked(int taskId) { if (DEBUG) { - Slog.i(TAG, "createGameSessionLocked() id: " + sessionId); + Slog.i(TAG, "createGameSessionLocked() id: " + taskId); } if (!mIsRunning) { return; } - GameSessionRecord existingGameSessionRecord = mGameSessions.get(sessionId); + GameSessionRecord existingGameSessionRecord = mGameSessions.get(taskId); if (existingGameSessionRecord == null) { - Slog.w(TAG, "No existing game session record found for task (id: " + sessionId + Slog.w(TAG, "No existing game session record found for task (id: " + taskId + ") creation. Ignoring."); return; } if (!existingGameSessionRecord.isAwaitingGameSessionRequest()) { - Slog.w(TAG, "Existing game session for task (id: " + sessionId + Slog.w(TAG, "Existing game session for task (id: " + taskId + ") is not awaiting game session request. Ignoring."); return; } - mGameSessions.put(sessionId, existingGameSessionRecord.withGameSessionRequested()); - - ComponentName componentName = existingGameSessionRecord.getComponentName(); - - // TODO(b/207035150): Allow the game service provider to determine if a game session - // should be created. For now we will assume all games should have a session. - AndroidFuture<IBinder> gameSessionFuture = new AndroidFuture<IBinder>() - .orTimeout(CREATE_GAME_SESSION_TIMEOUT_MS, TimeUnit.MILLISECONDS) - .whenCompleteAsync((gameSessionIBinder, exception) -> { - IGameSession gameSession = IGameSession.Stub.asInterface(gameSessionIBinder); - if (exception != null || gameSession == null) { - Slog.w(TAG, "Failed to create GameSession: " + existingGameSessionRecord, - exception); - synchronized (mLock) { - destroyGameSessionIfNecessaryLocked(sessionId); - } - return; - } - - synchronized (mLock) { - attachGameSessionLocked(sessionId, gameSession); - } - }, mBackgroundExecutor); + + GameSessionViewHostConfiguration gameSessionViewHostConfiguration = + createViewHostConfigurationForTask(taskId); + if (gameSessionViewHostConfiguration == null) { + Slog.w(TAG, "Failed to create view host configuration for task (id" + taskId + + ") creation. Ignoring."); + return; + } + + if (DEBUG) { + Slog.d(TAG, "Determined initial view host configuration for task (id: " + taskId + "): " + + gameSessionViewHostConfiguration); + } + + mGameSessions.put(taskId, existingGameSessionRecord.withGameSessionRequested()); + + AndroidFuture<CreateGameSessionResult> createGameSessionResultFuture = + new AndroidFuture<CreateGameSessionResult>() + .orTimeout(CREATE_GAME_SESSION_TIMEOUT_MS, TimeUnit.MILLISECONDS) + .whenCompleteAsync((createGameSessionResult, exception) -> { + if (exception != null || createGameSessionResult == null) { + Slog.w(TAG, "Failed to create GameSession: " + + existingGameSessionRecord, + exception); + synchronized (mLock) { + removeAndDestroyGameSessionIfNecessaryLocked(taskId); + } + return; + } + + synchronized (mLock) { + attachGameSessionLocked(taskId, createGameSessionResult); + } + }, mBackgroundExecutor); AndroidFuture<Void> unusedPostCreateGameSessionFuture = mGameSessionServiceConnector.post(gameService -> { CreateGameSessionRequest createGameSessionRequest = - new CreateGameSessionRequest(sessionId, componentName.getPackageName()); - gameService.create(createGameSessionRequest, gameSessionFuture); + new CreateGameSessionRequest( + taskId, + existingGameSessionRecord.getComponentName().getPackageName()); + gameService.create( + createGameSessionRequest, + gameSessionViewHostConfiguration, + createGameSessionResultFuture); }); } @GuardedBy("mLock") - private void attachGameSessionLocked(int sessionId, @NonNull IGameSession gameSession) { + private void attachGameSessionLocked( + int taskId, + @NonNull CreateGameSessionResult createGameSessionResult) { if (DEBUG) { - Slog.i(TAG, "attachGameSession() id: " + sessionId); + Slog.d(TAG, "attachGameSession() id: " + taskId); } - GameSessionRecord gameSessionRecord = mGameSessions.get(sessionId); - boolean isValidAttachRequest = true; + GameSessionRecord gameSessionRecord = mGameSessions.get(taskId); + if (gameSessionRecord == null) { - Slog.w(TAG, "No associated game session record. Destroying id: " + sessionId); - isValidAttachRequest = false; + Slog.w(TAG, "No associated game session record. Destroying id: " + taskId); + destroyGameSessionDuringAttach(taskId, createGameSessionResult); + return; } - if (gameSessionRecord != null && !gameSessionRecord.isGameSessionRequested()) { - Slog.w(TAG, - "Game session not requested for existing game session record. Destroying id: " - + sessionId); - isValidAttachRequest = false; + + if (!gameSessionRecord.isGameSessionRequested()) { + destroyGameSessionDuringAttach(taskId, createGameSessionResult); + return; } - if (!isValidAttachRequest) { - try { - gameSession.destroy(); - } catch (RemoteException ex) { - Slog.w(TAG, "Failed to destroy session: " + gameSessionRecord, ex); - } + try { + mWindowManagerInternal.addTaskOverlay( + taskId, + createGameSessionResult.getSurfacePackage()); + } catch (IllegalArgumentException ex) { + Slog.w(TAG, "Failed to add task overlay. Destroying id: " + taskId); + destroyGameSessionDuringAttach(taskId, createGameSessionResult); return; } - mGameSessions.put(sessionId, gameSessionRecord.withGameSession(gameSession)); + mGameSessions.put(taskId, + gameSessionRecord.withGameSession( + createGameSessionResult.getGameSession(), + createGameSessionResult.getSurfacePackage())); + } + + private void destroyGameSessionDuringAttach( + int taskId, + CreateGameSessionResult createGameSessionResult) { + try { + createGameSessionResult.getGameSession().destroy(); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to destroy session: " + taskId); + } } @GuardedBy("mLock") - private void destroyGameSessionIfNecessaryLocked(int sessionId) { - // TODO(b/204503192): Limit the lifespan of the game session in the Game Service provider - // to only when the associated task is running. Right now it is possible for a task to - // move into the background and for all associated processes to die and for the Game Session - // provider's GameSessionService to continue to be running. Ideally we could unbind the - // service when this happens. + private void removeAndDestroyGameSessionIfNecessaryLocked(int taskId) { if (DEBUG) { - Slog.i(TAG, "destroyGameSession() id: " + sessionId); + Slog.d(TAG, "destroyGameSession() id: " + taskId); } - GameSessionRecord gameSessionRecord = mGameSessions.remove(sessionId); + GameSessionRecord gameSessionRecord = mGameSessions.remove(taskId); if (gameSessionRecord == null) { if (DEBUG) { - Slog.w(TAG, "No game session found for id: " + sessionId); + Slog.w(TAG, "No game session found for id: " + taskId); } return; } + destroyGameSessionFromRecord(gameSessionRecord); + } + + private void destroyGameSessionFromRecord(@NonNull GameSessionRecord gameSessionRecord) { + SurfacePackage surfacePackage = gameSessionRecord.getSurfacePackage(); + if (surfacePackage != null) { + try { + mWindowManagerInternal.removeTaskOverlay( + gameSessionRecord.getTaskId(), + surfacePackage); + } catch (IllegalArgumentException ex) { + Slog.i(TAG, + "Failed to remove task overlay. This is expected if the task is already " + + "destroyed: " + + gameSessionRecord); + } + } IGameSession gameSession = gameSessionRecord.getGameSession(); if (gameSession != null) { @@ -344,7 +395,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan if (mGameSessions.isEmpty()) { if (DEBUG) { - Slog.i(TAG, "No active game sessions. Disconnecting GameSessionService"); + Slog.d(TAG, "No active game sessions. Disconnecting GameSessionService"); } if (mGameSessionServiceConnector != null) { @@ -352,4 +403,41 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } } } + + + @Nullable + private GameSessionViewHostConfiguration createViewHostConfigurationForTask(int taskId) { + RunningTaskInfo runningTaskInfo = getRunningTaskInfoForTask(taskId); + if (runningTaskInfo == null) { + return null; + } + + Rect bounds = runningTaskInfo.configuration.windowConfiguration.getBounds(); + return new GameSessionViewHostConfiguration( + runningTaskInfo.displayId, + bounds.width(), + bounds.height()); + } + + @Nullable + private RunningTaskInfo getRunningTaskInfoForTask(int taskId) { + List<RunningTaskInfo> runningTaskInfos; + try { + runningTaskInfos = mActivityTaskManager.getTasks( + /* maxNum= */ Integer.MAX_VALUE, + /* filterOnlyVisibleRecents= */ true, + /* keepIntentExtra= */ false); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to fetch running tasks"); + return null; + } + + for (RunningTaskInfo taskInfo : runningTaskInfos) { + if (taskInfo.taskId == taskId) { + return taskInfo; + } + } + + return null; + } } diff --git a/services/core/java/com/android/server/app/GameSessionRecord.java b/services/core/java/com/android/server/app/GameSessionRecord.java index e9daceb0cd39..a241812f7868 100644 --- a/services/core/java/com/android/server/app/GameSessionRecord.java +++ b/services/core/java/com/android/server/app/GameSessionRecord.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.service.games.IGameSession; +import android.view.SurfaceControlViewHost.SurfacePackage; import java.util.Objects; @@ -37,26 +38,34 @@ final class GameSessionRecord { } private final int mTaskId; + private final State mState; private final ComponentName mRootComponentName; @Nullable private final IGameSession mIGameSession; - private final State mState; + @Nullable + private final SurfacePackage mSurfacePackage; static GameSessionRecord awaitingGameSessionRequest(int taskId, ComponentName rootComponentName) { - return new GameSessionRecord(taskId, rootComponentName, /* gameSession= */ null, - State.NO_GAME_SESSION_REQUESTED); + return new GameSessionRecord( + taskId, + State.NO_GAME_SESSION_REQUESTED, + rootComponentName, + /* gameSession= */ null, + /* surfacePackage= */ null); } private GameSessionRecord( int taskId, + @NonNull State state, @NonNull ComponentName rootComponentName, @Nullable IGameSession gameSession, - @NonNull State state) { + @Nullable SurfacePackage surfacePackage) { this.mTaskId = taskId; + this.mState = state; this.mRootComponentName = rootComponentName; this.mIGameSession = gameSession; - this.mState = state; + this.mSurfacePackage = surfacePackage; } public boolean isAwaitingGameSessionRequest() { @@ -65,8 +74,12 @@ final class GameSessionRecord { @NonNull public GameSessionRecord withGameSessionRequested() { - return new GameSessionRecord(mTaskId, mRootComponentName, /* gameSession=*/ null, - State.GAME_SESSION_REQUESTED); + return new GameSessionRecord( + mTaskId, + State.GAME_SESSION_REQUESTED, + mRootComponentName, + /* gameSession=*/ null, + /* surfacePackage=*/ null); } public boolean isGameSessionRequested() { @@ -74,15 +87,20 @@ final class GameSessionRecord { } @NonNull - public GameSessionRecord withGameSession(@NonNull IGameSession gameSession) { + public GameSessionRecord withGameSession( + @NonNull IGameSession gameSession, + @NonNull SurfacePackage surfacePackage) { Objects.requireNonNull(gameSession); - return new GameSessionRecord(mTaskId, mRootComponentName, gameSession, - State.GAME_SESSION_ATTACHED); + return new GameSessionRecord(mTaskId, + State.GAME_SESSION_ATTACHED, + mRootComponentName, + gameSession, + surfacePackage); } - @Nullable - public IGameSession getGameSession() { - return mIGameSession; + @NonNull + public int getTaskId() { + return mTaskId; } @NonNull @@ -90,17 +108,29 @@ final class GameSessionRecord { return mRootComponentName; } + @Nullable + public IGameSession getGameSession() { + return mIGameSession; + } + + @Nullable + public SurfacePackage getSurfacePackage() { + return mSurfacePackage; + } + @Override public String toString() { return "GameSessionRecord{" + "mTaskId=" + mTaskId + + ", mState=" + + mState + ", mRootComponentName=" + mRootComponentName + ", mIGameSession=" + mIGameSession - + ", mState=" - + mState + + ", mSurfacePackage=" + + mSurfacePackage + '}'; } @@ -115,12 +145,16 @@ final class GameSessionRecord { } GameSessionRecord that = (GameSessionRecord) o; - return mTaskId == that.mTaskId && mRootComponentName.equals(that.mRootComponentName) - && Objects.equals(mIGameSession, that.mIGameSession) && mState == that.mState; + return mTaskId == that.mTaskId + && mState == that.mState + && mRootComponentName.equals(that.mRootComponentName) + && Objects.equals(mIGameSession, that.mIGameSession) + && Objects.equals(mSurfacePackage, that.mSurfacePackage); } @Override public int hashCode() { - return Objects.hash(mTaskId, mRootComponentName, mIGameSession, mState); + return Objects.hash( + mTaskId, mState, mRootComponentName, mIGameSession, mState, mSurfacePackage); } } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 34f915e41cd7..c6d382923169 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -50,8 +50,6 @@ import android.os.UserHandle; import android.provider.Settings; import android.util.Log; import android.util.MathUtils; -import android.util.MutableFloat; -import android.util.MutableInt; import android.util.Slog; import android.util.TimeUtils; import android.view.Display; @@ -1382,7 +1380,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // Animate the screen brightness when the screen is on or dozing. // Skip the animation when the screen is off or suspended or transition to/from VR. - boolean brightnessAdjusted = false; if (!mPendingScreenOff) { if (mSkipScreenOnBrightnessRamp) { if (state == Display.STATE_ON) { @@ -1475,19 +1472,15 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // slider event so notify as if the system changed the brightness. userInitiatedChange = false; } - notifyBrightnessTrackerChanged(brightnessState, userInitiatedChange, + notifyBrightnessChanged(brightnessState, userInitiatedChange, hadUserBrightnessPoint); } // We save the brightness info *after* the brightness setting has been changed and // adjustments made so that the brightness info reflects the latest value. - brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting(), animateValue); + saveBrightnessInfo(getScreenBrightnessSetting(), animateValue); } else { - brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting()); - } - - if (brightnessAdjusted) { - postBrightnessChangeRunnable(); + saveBrightnessInfo(getScreenBrightnessSetting()); } // Log any changes to what is currently driving the brightness setting. @@ -1603,50 +1596,31 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call public BrightnessInfo getBrightnessInfo() { synchronized (mCachedBrightnessInfo) { return new BrightnessInfo( - mCachedBrightnessInfo.brightness.value, - mCachedBrightnessInfo.adjustedBrightness.value, - mCachedBrightnessInfo.brightnessMin.value, - mCachedBrightnessInfo.brightnessMax.value, - mCachedBrightnessInfo.hbmMode.value, - mCachedBrightnessInfo.hbmTransitionPoint.value); + mCachedBrightnessInfo.brightness, + mCachedBrightnessInfo.adjustedBrightness, + mCachedBrightnessInfo.brightnessMin, + mCachedBrightnessInfo.brightnessMax, + mCachedBrightnessInfo.hbmMode, + mCachedBrightnessInfo.highBrightnessTransitionPoint); } } - private boolean saveBrightnessInfo(float brightness) { - return saveBrightnessInfo(brightness, brightness); + private void saveBrightnessInfo(float brightness) { + saveBrightnessInfo(brightness, brightness); } - private boolean saveBrightnessInfo(float brightness, float adjustedBrightness) { + private void saveBrightnessInfo(float brightness, float adjustedBrightness) { synchronized (mCachedBrightnessInfo) { - boolean changed = false; - - changed |= - mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightness, - brightness); - changed |= - mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.adjustedBrightness, - adjustedBrightness); - changed |= - mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMin, - mHbmController.getCurrentBrightnessMin()); - changed |= - mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMax, - mHbmController.getCurrentBrightnessMax()); - changed |= - mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.hbmMode, - mHbmController.getHighBrightnessMode()); - changed |= - mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.hbmTransitionPoint, - mHbmController.getTransitionPoint()); - - return changed; + mCachedBrightnessInfo.brightness = brightness; + mCachedBrightnessInfo.adjustedBrightness = adjustedBrightness; + mCachedBrightnessInfo.brightnessMin = mHbmController.getCurrentBrightnessMin(); + mCachedBrightnessInfo.brightnessMax = mHbmController.getCurrentBrightnessMax(); + mCachedBrightnessInfo.hbmMode = mHbmController.getHighBrightnessMode(); + mCachedBrightnessInfo.highBrightnessTransitionPoint = + mHbmController.getTransitionPoint(); } } - void postBrightnessChangeRunnable() { - mHandler.post(mOnBrightnessChangeRunnable); - } - private HighBrightnessModeController createHbmControllerLocked() { final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig(); @@ -1661,7 +1635,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call displayUniqueId, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, hbmData, () -> { sendUpdatePowerStateLocked(); - postBrightnessChangeRunnable(); + mHandler.post(mOnBrightnessChangeRunnable); // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern. if (mAutomaticBrightnessController != null) { mAutomaticBrightnessController.update(); @@ -2163,7 +2137,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private void setCurrentScreenBrightness(float brightnessValue) { if (brightnessValue != mCurrentScreenBrightnessSetting) { mCurrentScreenBrightnessSetting = brightnessValue; - postBrightnessChangeRunnable(); + mHandler.post(mOnBrightnessChangeRunnable); } } @@ -2215,7 +2189,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call return true; } - private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated, + private void notifyBrightnessChanged(float brightness, boolean userInitiated, boolean hadUserDataPoint) { final float brightnessInNits = convertToNits(brightness); if (mPowerRequest.useAutoBrightness && brightnessInNits >= 0.0f @@ -2325,17 +2299,16 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call pw.println(" mColorFadeFadesConfig=" + mColorFadeFadesConfig); pw.println(" mColorFadeEnabled=" + mColorFadeEnabled); synchronized (mCachedBrightnessInfo) { - pw.println(" mCachedBrightnessInfo.brightness=" + - mCachedBrightnessInfo.brightness.value); + pw.println(" mCachedBrightnessInfo.brightness=" + mCachedBrightnessInfo.brightness); pw.println(" mCachedBrightnessInfo.adjustedBrightness=" + - mCachedBrightnessInfo.adjustedBrightness.value); + mCachedBrightnessInfo.adjustedBrightness); pw.println(" mCachedBrightnessInfo.brightnessMin=" + - mCachedBrightnessInfo.brightnessMin.value); + mCachedBrightnessInfo.brightnessMin); pw.println(" mCachedBrightnessInfo.brightnessMax=" + - mCachedBrightnessInfo.brightnessMax.value); - pw.println(" mCachedBrightnessInfo.hbmMode=" + mCachedBrightnessInfo.hbmMode.value); - pw.println(" mCachedBrightnessInfo.hbmTransitionPoint=" + - mCachedBrightnessInfo.hbmTransitionPoint.value); + mCachedBrightnessInfo.brightnessMax); + pw.println(" mCachedBrightnessInfo.hbmMode=" + mCachedBrightnessInfo.hbmMode); + pw.println(" mCachedBrightnessInfo.highBrightnessTransitionPoint=" + + mCachedBrightnessInfo.highBrightnessTransitionPoint); } pw.println(" mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig); pw.println(" mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig); @@ -2493,10 +2466,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private void reportStats(float brightness) { float hbmTransitionPoint = PowerManager.BRIGHTNESS_MAX; synchronized(mCachedBrightnessInfo) { - if (mCachedBrightnessInfo.hbmTransitionPoint == null) { - return; - } - hbmTransitionPoint = mCachedBrightnessInfo.hbmTransitionPoint.value; + hbmTransitionPoint = mCachedBrightnessInfo.highBrightnessTransitionPoint; } final boolean aboveTransition = brightness > hbmTransitionPoint; @@ -2793,31 +2763,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } static class CachedBrightnessInfo { - public MutableFloat brightness = new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT); - public MutableFloat adjustedBrightness = - new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT); - public MutableFloat brightnessMin = - new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT); - public MutableFloat brightnessMax = - new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT); - public MutableInt hbmMode = new MutableInt(BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF); - public MutableFloat hbmTransitionPoint = - new MutableFloat(HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID); - - public boolean checkAndSetFloat(MutableFloat mf, float f) { - if (mf.value != f) { - mf.value = f; - return true; - } - return false; - } - - public boolean checkAndSetInt(MutableInt mi, int i) { - if (mi.value != i) { - mi.value = i; - return true; - } - return false; - } + public float brightness; + public float adjustedBrightness; + public float brightnessMin; + public float brightnessMax; + public int hbmMode; + public float highBrightnessTransitionPoint; } } diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index 7719dfec928b..93c73be5021e 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -397,12 +397,16 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { // We already told the displays to turn off, now we need to wake the device as // we transition to this new state. We do it here so that the waking happens // between the transition from one layout to another. - mPowerManager.wakeUp(SystemClock.uptimeMillis(), - PowerManager.WAKE_REASON_UNFOLD_DEVICE, "server.display:unfold"); + mHandler.post(() -> { + mPowerManager.wakeUp(SystemClock.uptimeMillis(), + PowerManager.WAKE_REASON_UNFOLD_DEVICE, "server.display:unfold"); + }); } else if (sleepDevice) { // Send the device to sleep when required. - mPowerManager.goToSleep(SystemClock.uptimeMillis(), - PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD, 0); + mHandler.post(() -> { + mPowerManager.goToSleep(SystemClock.uptimeMillis(), + PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD, 0); + }); } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index ec49b4768b74..a897208f11b0 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -97,7 +97,6 @@ import android.os.Bundle; import android.os.Debug; import android.os.Handler; import android.os.IBinder; -import android.os.IInterface; import android.os.LocaleList; import android.os.Message; import android.os.Parcel; @@ -220,11 +219,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } private static final int MSG_SHOW_IM_SUBTYPE_PICKER = 1; - private static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 2; private static final int MSG_SHOW_IM_CONFIG = 3; - private static final int MSG_UNBIND_INPUT = 1000; - private static final int MSG_BIND_INPUT = 1010; private static final int MSG_SHOW_SOFT_INPUT = 1020; private static final int MSG_HIDE_SOFT_INPUT = 1030; private static final int MSG_HIDE_CURRENT_INPUT_METHOD = 1035; @@ -1782,8 +1778,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub com.android.internal.R.bool.show_ongoing_ime_switcher); if (mShowOngoingImeSwitcherForPhones) { mWindowManagerInternal.setOnHardKeyboardStatusChangeListener(available -> { - mHandler.obtainMessage(MSG_HARD_KEYBOARD_SWITCH_CHANGED, available ? 1 : 0) - .sendToTarget(); + mHandler.obtainMessage(MSG_HARD_KEYBOARD_SWITCH_CHANGED, + available ? 1 : 0, 0 /* unused */).sendToTarget(); }); } @@ -2203,8 +2199,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mBoundToMethod = false; IInputMethod curMethod = getCurMethodLocked(); if (curMethod != null) { - executeOrSendMessage(curMethod, mCaller.obtainMessageO( - MSG_UNBIND_INPUT, curMethod)); + try { + curMethod.unbindInput(); + } catch (RemoteException e) { + // There is nothing interesting about the method dying. + } } } mCurClient = null; @@ -2216,8 +2215,24 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - private void executeOrSendMessage(IInterface target, Message msg) { + // TODO(b/215609403): This method will be removed soon! + private void executeOrSendMessage(IInputMethod target, Message msg) { + if (target.asBinder() instanceof Binder) { + throw new UnsupportedOperationException( + "InputMethodService is not supported to run in the system_server"); + } + handleMessage(msg); + msg.recycle(); + } + + private void executeOrSendMessage(IInputMethodClient target, Message msg) { if (target.asBinder() instanceof Binder) { + // This is supposed to be emulating the one-way semantics when the IME client is + // system_server itself, which has not been explicitly prohibited so far while we have + // never ever officially supported such a use case... + // We probably should create a simple wrapper of IInputMethodClient as the first step + // to get rid of executeOrSendMessage() then should prohibit system_server to be the + // IME client for long term. mCaller.sendMessage(msg); } else { handleMessage(msg); @@ -2234,8 +2249,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mBoundToMethod = false; IInputMethod curMethod = getCurMethodLocked(); if (curMethod != null) { - executeOrSendMessage(curMethod, mCaller.obtainMessageO( - MSG_UNBIND_INPUT, curMethod)); + try { + curMethod.unbindInput(); + } catch (RemoteException e) { + // There is nothing interesting about the method dying. + } } } @@ -2285,8 +2303,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub InputBindResult attachNewInputLocked(@StartInputReason int startInputReason, boolean initial) { if (!mBoundToMethod) { IInputMethod curMethod = getCurMethodLocked(); - executeOrSendMessage(curMethod, mCaller.obtainMessageOO( - MSG_BIND_INPUT, curMethod, mCurClient.binding)); + try { + curMethod.bindInput(mCurClient.binding); + } catch (RemoteException e) { + } mBoundToMethod = true; } @@ -3679,8 +3699,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (!calledFromValidUserLocked()) { return; } - executeOrSendMessage(getCurMethodLocked(), mCaller.obtainMessageO( - MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId)); + showInputMethodAndSubtypeEnabler(inputMethodId); } } @@ -4203,31 +4222,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mMenuController.showInputMethodMenu(showAuxSubtypes, displayId); return true; - case MSG_SHOW_IM_SUBTYPE_ENABLER: - showInputMethodAndSubtypeEnabler((String)msg.obj); - return true; - case MSG_SHOW_IM_CONFIG: showConfigureInputMethods(); return true; // --------------------------------------------------------- - case MSG_UNBIND_INPUT: - try { - ((IInputMethod)msg.obj).unbindInput(); - } catch (RemoteException e) { - // There is nothing interesting about the method dying. - } - return true; - case MSG_BIND_INPUT: - args = (SomeArgs)msg.obj; - try { - ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2); - } catch (RemoteException e) { - } - args.recycle(); - return true; case MSG_SHOW_SOFT_INPUT: args = (SomeArgs) msg.obj; try { diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java index ffc1aed4c672..91de9e559e13 100644 --- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java +++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java @@ -344,10 +344,19 @@ class BluetoothRouteProvider { } private void addActiveRoute(BluetoothRouteInfo btRoute) { + if (btRoute == null) { + if (DEBUG) { + Log.d(TAG, " btRoute is null"); + } + return; + } if (DEBUG) { Log.d(TAG, "Adding active route: " + btRoute.route); } - if (btRoute == null || mActiveRoutes.contains(btRoute)) { + if (mActiveRoutes.contains(btRoute)) { + if (DEBUG) { + Log.d(TAG, " btRoute is already added."); + } return; } setRouteConnectionState(btRoute, STATE_CONNECTED); diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 5e333daed7d9..05f000c607d8 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -101,6 +101,8 @@ public class PreferencesHelper implements RankingConfig { @VisibleForTesting static final int NOTIFICATION_CHANNEL_COUNT_LIMIT = 50000; + @VisibleForTesting + static final int NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT = 50000; private static final int NOTIFICATION_PREFERENCES_PULL_LIMIT = 1000; private static final int NOTIFICATION_CHANNEL_PULL_LIMIT = 2000; @@ -254,6 +256,7 @@ public class PreferencesHelper implements RankingConfig { } } boolean skipWarningLogged = false; + boolean skipGroupWarningLogged = false; boolean hasSAWPermission = false; if (upgradeForBubbles && uid != UNKNOWN_UID) { hasSAWPermission = mAppOps.noteOpNoThrow( @@ -303,6 +306,14 @@ public class PreferencesHelper implements RankingConfig { String tagName = parser.getName(); // Channel groups if (TAG_GROUP.equals(tagName)) { + if (r.groups.size() >= NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT) { + if (!skipGroupWarningLogged) { + Slog.w(TAG, "Skipping further groups for " + r.pkg + + "; app has too many"); + skipGroupWarningLogged = true; + } + continue; + } String id = parser.getAttributeValue(null, ATT_ID); CharSequence groupName = parser.getAttributeValue(null, ATT_NAME); @@ -867,6 +878,9 @@ public class PreferencesHelper implements RankingConfig { } if (fromTargetApp) { group.setBlocked(false); + if (r.groups.size() >= NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT) { + throw new IllegalStateException("Limit exceed; cannot create more groups"); + } } final NotificationChannelGroup oldGroup = r.groups.get(group.getId()); if (oldGroup != null) { diff --git a/services/core/java/com/android/server/om/IdmapDaemon.java b/services/core/java/com/android/server/om/IdmapDaemon.java index c9e564a0ce9d..8e944b7a965d 100644 --- a/services/core/java/com/android/server/om/IdmapDaemon.java +++ b/services/core/java/com/android/server/om/IdmapDaemon.java @@ -37,13 +37,14 @@ import com.android.server.FgThread; import java.io.File; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; /** - * To prevent idmap2d from continuously running, the idmap daemon will terminate after 10 - * seconds without a transaction. + * To prevent idmap2d from continuously running, the idmap daemon will terminate after 10 seconds + * without a transaction. **/ class IdmapDaemon { // The amount of time in milliseconds to wait after a transaction to the idmap service is made @@ -67,11 +68,14 @@ class IdmapDaemon { * to the service is open. **/ private class Connection implements AutoCloseable { + @Nullable + private final IIdmap2 mIdmap2; private boolean mOpened = true; - private Connection() { + private Connection(IIdmap2 idmap2) { synchronized (mIdmapToken) { mOpenedCount.incrementAndGet(); + mIdmap2 = idmap2; } } @@ -102,6 +106,11 @@ class IdmapDaemon { }, mIdmapToken, SERVICE_TIMEOUT_MS); } } + + @Nullable + public IIdmap2 getIdmap2() { + return mIdmap2; + } } static IdmapDaemon getInstance() { @@ -115,14 +124,29 @@ class IdmapDaemon { @Nullable String overlayName, int policies, boolean enforce, int userId) throws TimeoutException, RemoteException { try (Connection c = connect()) { - return mService.createIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName), + final IIdmap2 idmap2 = c.getIdmap2(); + if (idmap2 == null) { + Slog.w(TAG, "idmap2d service is not ready for createIdmap(\"" + targetPath + + "\", \"" + overlayPath + "\", \"" + overlayName + "\", " + policies + ", " + + enforce + ", " + userId + ")"); + return null; + } + + return idmap2.createIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName), policies, enforce, userId); } } boolean removeIdmap(String overlayPath, int userId) throws TimeoutException, RemoteException { try (Connection c = connect()) { - return mService.removeIdmap(overlayPath, userId); + final IIdmap2 idmap2 = c.getIdmap2(); + if (idmap2 == null) { + Slog.w(TAG, "idmap2d service is not ready for removeIdmap(\"" + overlayPath + + "\", " + userId + ")"); + return false; + } + + return idmap2.removeIdmap(overlayPath, userId); } } @@ -130,14 +154,29 @@ class IdmapDaemon { @Nullable String overlayName, int policies, boolean enforce, int userId) throws Exception { try (Connection c = connect()) { - return mService.verifyIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName), + final IIdmap2 idmap2 = c.getIdmap2(); + if (idmap2 == null) { + Slog.w(TAG, "idmap2d service is not ready for verifyIdmap(\"" + targetPath + + "\", \"" + overlayPath + "\", \"" + overlayName + "\", " + policies + ", " + + enforce + ", " + userId + ")"); + return false; + } + + return idmap2.verifyIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName), policies, enforce, userId); } } boolean idmapExists(String overlayPath, int userId) { try (Connection c = connect()) { - return new File(mService.getIdmapPath(overlayPath, userId)).isFile(); + final IIdmap2 idmap2 = c.getIdmap2(); + if (idmap2 == null) { + Slog.w(TAG, "idmap2d service is not ready for idmapExists(\"" + overlayPath + + "\", " + userId + ")"); + return false; + } + + return new File(idmap2.getIdmapPath(overlayPath, userId)).isFile(); } catch (Exception e) { Slog.wtf(TAG, "failed to check if idmap exists for " + overlayPath, e); return false; @@ -146,7 +185,13 @@ class IdmapDaemon { FabricatedOverlayInfo createFabricatedOverlay(@NonNull FabricatedOverlayInternal overlay) { try (Connection c = connect()) { - return mService.createFabricatedOverlay(overlay); + final IIdmap2 idmap2 = c.getIdmap2(); + if (idmap2 == null) { + Slog.w(TAG, "idmap2d service is not ready for createFabricatedOverlay()"); + return null; + } + + return idmap2.createFabricatedOverlay(overlay); } catch (Exception e) { Slog.wtf(TAG, "failed to fabricate overlay " + overlay, e); return null; @@ -155,7 +200,14 @@ class IdmapDaemon { boolean deleteFabricatedOverlay(@NonNull String path) { try (Connection c = connect()) { - return mService.deleteFabricatedOverlay(path); + final IIdmap2 idmap2 = c.getIdmap2(); + if (idmap2 == null) { + Slog.w(TAG, "idmap2d service is not ready for deleteFabricatedOverlay(\"" + path + + "\")"); + return false; + } + + return idmap2.deleteFabricatedOverlay(path); } catch (Exception e) { Slog.wtf(TAG, "failed to delete fabricated overlay '" + path + "'", e); return false; @@ -164,10 +216,18 @@ class IdmapDaemon { synchronized List<FabricatedOverlayInfo> getFabricatedOverlayInfos() { final ArrayList<FabricatedOverlayInfo> allInfos = new ArrayList<>(); - try (Connection c = connect()) { - mService.acquireFabricatedOverlayIterator(); + Connection c = null; + try { + c = connect(); + final IIdmap2 service = c.getIdmap2(); + if (service == null) { + Slog.w(TAG, "idmap2d service is not ready for getFabricatedOverlayInfos()"); + return Collections.emptyList(); + } + + service.acquireFabricatedOverlayIterator(); List<FabricatedOverlayInfo> infos; - while (!(infos = mService.nextFabricatedOverlayInfos()).isEmpty()) { + while (!(infos = service.nextFabricatedOverlayInfos()).isEmpty()) { allInfos.addAll(infos); } return allInfos; @@ -175,17 +235,26 @@ class IdmapDaemon { Slog.wtf(TAG, "failed to get all fabricated overlays", e); } finally { try { - mService.releaseFabricatedOverlayIterator(); + if (c.getIdmap2() != null) { + c.getIdmap2().releaseFabricatedOverlayIterator(); + } } catch (RemoteException e) { // ignore } + c.close(); } return allInfos; } String dumpIdmap(@NonNull String overlayPath) { try (Connection c = connect()) { - String dump = mService.dumpIdmap(overlayPath); + final IIdmap2 service = c.getIdmap2(); + if (service == null) { + final String dumpText = "idmap2d service is not ready for dumpIdmap()"; + Slog.w(TAG, dumpText); + return dumpText; + } + String dump = service.dumpIdmap(overlayPath); return TextUtils.nullIfEmpty(dump); } catch (Exception e) { Slog.wtf(TAG, "failed to dump idmap", e); @@ -193,8 +262,16 @@ class IdmapDaemon { } } + @Nullable private IBinder getIdmapService() throws TimeoutException, RemoteException { - SystemService.start(IDMAP_DAEMON); + try { + SystemService.start(IDMAP_DAEMON); + } catch (RuntimeException e) { + if (e.getMessage().contains("failed to set system property")) { + Slog.w(TAG, "Failed to enable idmap2 daemon", e); + return null; + } + } final long endMillis = SystemClock.elapsedRealtime() + SERVICE_CONNECT_TIMEOUT_MS; while (SystemClock.elapsedRealtime() <= endMillis) { @@ -226,17 +303,23 @@ class IdmapDaemon { } } + @NonNull private Connection connect() throws TimeoutException, RemoteException { synchronized (mIdmapToken) { FgThread.getHandler().removeCallbacksAndMessages(mIdmapToken); if (mService != null) { // Not enough time has passed to stop the idmap service. Reuse the existing // interface. - return new Connection(); + return new Connection(mService); + } + + IBinder binder = getIdmapService(); + if (binder == null) { + return new Connection(null); } - mService = IIdmap2.Stub.asInterface(getIdmapService()); - return new Connection(); + mService = IIdmap2.Stub.asInterface(binder); + return new Connection(mService); } } } diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index 6f10a6bbb9ca..2e9ad50f23f6 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -45,6 +45,7 @@ import android.os.Trace; import android.sysprop.ApexProperties; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.PrintWriterPrinter; import android.util.Singleton; import android.util.Slog; import android.util.SparseArray; @@ -1164,6 +1165,10 @@ public abstract class ApexManager { ipw.println("Path: " + pi.applicationInfo.sourceDir); ipw.println("IsActive: " + isActive(pi)); ipw.println("IsFactory: " + isFactory(pi)); + ipw.println("ApplicationInfo: "); + ipw.increaseIndent(); + pi.applicationInfo.dump(new PrintWriterPrinter(ipw), ""); + ipw.decreaseIndent(); ipw.decreaseIndent(); } ipw.decreaseIndent(); diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java index a66af3cf7603..bdb48bdbf6d9 100644 --- a/services/core/java/com/android/server/pm/AppDataHelper.java +++ b/services/core/java/com/android/server/pm/AppDataHelper.java @@ -476,13 +476,9 @@ final class AppDataHelper { } else if (!ps.getInstalled(userId)) { throw new PackageManagerException( "Package " + packageName + " not installed for user " + userId); - } else if (ps.getPkg() == null) { - throw new PackageManagerException("Package " + packageName + " is not parsed yet"); - } else { - if (!shouldHaveAppStorage(ps.getPkg())) { - throw new PackageManagerException( - "Package " + packageName + " shouldn't have storage"); - } + } else if (ps.getPkg() != null && !shouldHaveAppStorage(ps.getPkg())) { + throw new PackageManagerException( + "Package " + packageName + " shouldn't have storage"); } } } diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 2aa0e0174d0d..69c475a05a93 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.DELETE_PACKAGES; import static android.Manifest.permission.INSTALL_PACKAGES; import static android.Manifest.permission.REQUEST_DELETE_PACKAGES; import static android.Manifest.permission.SET_HARMFUL_APP_WARNINGS; +import static android.content.ContentProvider.isAuthorityRedirectedForCloneProfile; import static android.content.Intent.ACTION_MAIN; import static android.content.Intent.CATEGORY_DEFAULT; import static android.content.Intent.CATEGORY_HOME; @@ -92,14 +93,6 @@ import android.content.pm.SigningDetails; import android.content.pm.SigningInfo; import android.content.pm.UserInfo; import android.content.pm.VersionedPackage; -import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedInstrumentation; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedMainComponent; -import com.android.server.pm.pkg.component.ParsedProvider; -import com.android.server.pm.pkg.component.ParsedService; -import com.android.server.pm.pkg.PackageUserStateUtils; import android.os.Binder; import android.os.Build; import android.os.IBinder; @@ -142,6 +135,14 @@ import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageStateUtils; import com.android.server.pm.pkg.PackageUserState; import com.android.server.pm.pkg.PackageUserStateInternal; +import com.android.server.pm.pkg.PackageUserStateUtils; +import com.android.server.pm.pkg.component.ParsedActivity; +import com.android.server.pm.pkg.component.ParsedInstrumentation; +import com.android.server.pm.pkg.component.ParsedIntentInfo; +import com.android.server.pm.pkg.component.ParsedMainComponent; +import com.android.server.pm.pkg.component.ParsedProvider; +import com.android.server.pm.pkg.component.ParsedService; +import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils; import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; import com.android.server.pm.verify.domain.DomainVerificationUtils; import com.android.server.uri.UriGrantsManagerInternal; @@ -350,7 +351,6 @@ public class ComputerEngine implements Computer { private final CompilerStats mCompilerStats; private final BackgroundDexOptService mBackgroundDexOptService; private final PackageManagerInternal.ExternalSourcesPolicy mExternalSourcesPolicy; - private final ProtectedPackages mProtectedPackages; // PackageManagerService attributes that are primitives are referenced through the // pms object directly. Primitives are the only attributes so referenced. @@ -402,7 +402,6 @@ public class ComputerEngine implements Computer { mCompilerStats = args.service.mCompilerStats; mBackgroundDexOptService = args.service.mBackgroundDexOptService; mExternalSourcesPolicy = args.service.mExternalSourcesPolicy; - mProtectedPackages = args.service.mProtectedPackages; // Used to reference PMS attributes that are primitives and which are not // updated under control of the PMS lock. @@ -4619,8 +4618,24 @@ public class ComputerEngine implements Computer { } } if (!checkedGrants) { - enforceCrossUserPermission(callingUid, userId, false, false, "resolveContentProvider"); + boolean enforceCrossUser = true; + + if (isAuthorityRedirectedForCloneProfile(name)) { + final UserManagerInternal umInternal = mInjector.getUserManagerInternal(); + + UserInfo userInfo = umInternal.getUserInfo(UserHandle.getUserId(callingUid)); + if (userInfo != null && userInfo.isCloneProfile() + && userInfo.profileGroupId == userId) { + enforceCrossUser = false; + } + } + + if (enforceCrossUser) { + enforceCrossUserPermission(callingUid, userId, false, false, + "resolveContentProvider"); + } } + if (providerInfo == null) { return null; } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 13f91e0dca62..81a2c0046adf 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -234,8 +234,6 @@ import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageStateUtils; import com.android.server.pm.pkg.PackageUserState; -import com.android.server.pm.pkg.PackageUserStateInternal; -import com.android.server.pm.pkg.SuspendParams; import com.android.server.pm.pkg.component.ParsedInstrumentation; import com.android.server.pm.pkg.component.ParsedMainComponent; import com.android.server.pm.pkg.mutate.PackageStateMutator; @@ -291,7 +289,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.function.Function; -import java.util.function.Predicate; /** * Keep track of all those APKs everywhere. @@ -970,6 +967,7 @@ public class PackageManagerService extends IPackageManager.Stub private final PreferredActivityHelper mPreferredActivityHelper; private final ResolveIntentHelper mResolveIntentHelper; private final DexOptHelper mDexOptHelper; + private final SuspendPackageHelper mSuspendPackageHelper; /** * Invalidate the package info cache, which includes updating the cached computer. @@ -1705,6 +1703,7 @@ public class PackageManagerService extends IPackageManager.Stub mPreferredActivityHelper = testParams.preferredActivityHelper; mResolveIntentHelper = testParams.resolveIntentHelper; mDexOptHelper = testParams.dexOptHelper; + mSuspendPackageHelper = testParams.suspendPackageHelper; mSharedLibraries.setDeletePackageHelper(mDeletePackageHelper); invalidatePackageInfoCache(); @@ -1851,6 +1850,8 @@ public class PackageManagerService extends IPackageManager.Stub mPreferredActivityHelper = new PreferredActivityHelper(this); mResolveIntentHelper = new ResolveIntentHelper(this, mPreferredActivityHelper); mDexOptHelper = new DexOptHelper(this); + mSuspendPackageHelper = new SuspendPackageHelper(this, mInjector, mBroadcastHelper, + mProtectedPackages); synchronized (mLock) { // Create the computer as soon as the state objects have been installed. The @@ -2682,7 +2683,7 @@ public class PackageManagerService extends IPackageManager.Stub return mComputer.getPackageUid(packageName, flags, userId); } - private int getPackageUidInternal(String packageName, + int getPackageUidInternal(String packageName, @PackageManager.PackageInfoFlagsBits long flags, int userId, int callingUid) { return mComputer.getPackageUidInternal(packageName, flags, userId, callingUid); } @@ -4294,51 +4295,6 @@ public class PackageManagerService extends IPackageManager.Stub info.sendPackageRemovedBroadcasts(true /*killApp*/, false /*removedBySystem*/); } - @VisibleForTesting(visibility = Visibility.PRIVATE) - void sendPackagesSuspendedForUser(String intent, String[] pkgList, int[] uidList, int userId) { - final List<List<String>> pkgsToSend = new ArrayList(pkgList.length); - final List<IntArray> uidsToSend = new ArrayList(pkgList.length); - final List<SparseArray<int[]>> allowListsToSend = new ArrayList(pkgList.length); - final int[] userIds = new int[] {userId}; - // Get allow lists for the pkg in the pkgList. Merge into the existed pkgs and uids if - // allow lists are the same. - for (int i = 0; i < pkgList.length; i++) { - final String pkgName = pkgList[i]; - final int uid = uidList[i]; - SparseArray<int[]> allowList = mAppsFilter.getVisibilityAllowList( - getPackageStateInternal(pkgName, Process.SYSTEM_UID), - userIds, getPackageStates()); - if (allowList == null) { - allowList = new SparseArray<>(0); - } - boolean merged = false; - for (int j = 0; j < allowListsToSend.size(); j++) { - if (Arrays.equals(allowListsToSend.get(j).get(userId), allowList.get(userId))) { - pkgsToSend.get(j).add(pkgName); - uidsToSend.get(j).add(uid); - merged = true; - break; - } - } - if (!merged) { - pkgsToSend.add(new ArrayList<>(Arrays.asList(pkgName))); - uidsToSend.add(IntArray.wrap(new int[] {uid})); - allowListsToSend.add(allowList); - } - } - - for (int i = 0; i < pkgsToSend.size(); i++) { - final Bundle extras = new Bundle(3); - extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, - pkgsToSend.get(i).toArray(new String[pkgsToSend.get(i).size()])); - extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidsToSend.get(i).toArray()); - final SparseArray<int[]> allowList = allowListsToSend.get(i).size() == 0 - ? null : allowListsToSend.get(i); - sendPackageBroadcast(intent, null, extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null, - null, userIds, null, allowList, null); - } - } - /** * Returns true if application is not found or there was an error. Otherwise it returns * the hidden state of the package for the given user. @@ -4381,7 +4337,8 @@ public class PackageManagerService extends IPackageManager.Stub + userId); } Objects.requireNonNull(packageNames, "packageNames cannot be null"); - if (restrictionFlags != 0 && !isSuspendAllowedForUser(userId)) { + if (restrictionFlags != 0 + && !mSuspendPackageHelper.isSuspendAllowedForUser(userId, callingUid)) { Slog.w(TAG, "Cannot restrict packages due to restrictions on user " + userId); return packageNames; } @@ -4389,8 +4346,9 @@ public class PackageManagerService extends IPackageManager.Stub final List<String> changedPackagesList = new ArrayList<>(packageNames.length); final IntArray changedUids = new IntArray(packageNames.length); final List<String> unactionedPackages = new ArrayList<>(packageNames.length); - final boolean[] canRestrict = (restrictionFlags != 0) ? canSuspendPackageForUserInternal( - packageNames, userId) : null; + final boolean[] canRestrict = (restrictionFlags != 0) + ? mSuspendPackageHelper.canSuspendPackageForUser(packageNames, userId, callingUid) + : null; for (int i = 0; i < packageNames.length; i++) { final String packageName = packageNames[i]; @@ -4469,84 +4427,8 @@ public class PackageManagerService extends IPackageManager.Stub final int callingUid = Binder.getCallingUid(); enforceCanSetPackagesSuspendedAsUser(callingPackage, callingUid, userId, "setPackagesSuspendedAsUser"); - - if (ArrayUtils.isEmpty(packageNames)) { - return packageNames; - } - if (suspended && !isSuspendAllowedForUser(userId)) { - Slog.w(TAG, "Cannot suspend due to restrictions on user " + userId); - return packageNames; - } - - final List<String> changedPackagesList = new ArrayList<>(packageNames.length); - final IntArray changedUids = new IntArray(packageNames.length); - final List<String> modifiedPackagesList = new ArrayList<>(packageNames.length); - final IntArray modifiedUids = new IntArray(packageNames.length); - final List<String> unactionedPackages = new ArrayList<>(packageNames.length); - final boolean[] canSuspend = suspended ? canSuspendPackageForUserInternal(packageNames, - userId) : null; - - for (int i = 0; i < packageNames.length; i++) { - final String packageName = packageNames[i]; - if (callingPackage.equals(packageName)) { - Slog.w(TAG, "Calling package: " + callingPackage + " trying to " - + (suspended ? "" : "un") + "suspend itself. Ignoring"); - unactionedPackages.add(packageName); - continue; - } - final PackageSetting pkgSetting; - synchronized (mLock) { - pkgSetting = mSettings.getPackageLPr(packageName); - if (pkgSetting == null - || shouldFilterApplication(pkgSetting, callingUid, userId)) { - Slog.w(TAG, "Could not find package setting for package: " + packageName - + ". Skipping suspending/un-suspending."); - unactionedPackages.add(packageName); - continue; - } - } - if (canSuspend != null && !canSuspend[i]) { - unactionedPackages.add(packageName); - continue; - } - final boolean packageUnsuspended; - final boolean packageModified; - synchronized (mLock) { - if (suspended) { - packageModified = pkgSetting.addOrUpdateSuspension(callingPackage, - dialogInfo, appExtras, launcherExtras, userId); - } else { - packageModified = pkgSetting.removeSuspension(callingPackage, userId); - } - packageUnsuspended = !suspended && !pkgSetting.getSuspended(userId); - } - if (suspended || packageUnsuspended) { - changedPackagesList.add(packageName); - changedUids.add(UserHandle.getUid(userId, pkgSetting.getAppId())); - } - if (packageModified) { - modifiedPackagesList.add(packageName); - modifiedUids.add(UserHandle.getUid(userId, pkgSetting.getAppId())); - } - } - - if (!changedPackagesList.isEmpty()) { - final String[] changedPackages = changedPackagesList.toArray(new String[0]); - sendPackagesSuspendedForUser( - suspended ? Intent.ACTION_PACKAGES_SUSPENDED - : Intent.ACTION_PACKAGES_UNSUSPENDED, - changedPackages, changedUids.toArray(), userId); - sendMyPackageSuspendedOrUnsuspended(changedPackages, suspended, userId); - synchronized (mLock) { - scheduleWritePackageRestrictionsLocked(userId); - } - } - // Send the suspension changed broadcast to ensure suspension state is not stale. - if (!modifiedPackagesList.isEmpty()) { - sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED, - modifiedPackagesList.toArray(new String[0]), modifiedUids.toArray(), userId); - } - return unactionedPackages.toArray(new String[0]); + return mSuspendPackageHelper.setPackagesSuspended(packageNames, suspended, appExtras, + launcherExtras, dialogInfo, callingPackage, userId, callingUid); } @Override @@ -4556,56 +4438,8 @@ public class PackageManagerService extends IPackageManager.Stub throw new SecurityException("Calling package " + packageName + " does not belong to calling uid " + callingUid); } - return getSuspendedPackageAppExtrasInternal(packageName, userId); - } - - private Bundle getSuspendedPackageAppExtrasInternal(String packageName, int userId) { - final PackageStateInternal ps = getPackageStateInternal(packageName); - if (ps == null) { - return null; - } - final PackageUserStateInternal pus = ps.getUserStateOrDefault(userId); - final Bundle allExtras = new Bundle(); - if (pus.isSuspended()) { - for (int i = 0; i < pus.getSuspendParams().size(); i++) { - final SuspendParams params = pus.getSuspendParams().valueAt(i); - if (params != null && params.getAppExtras() != null) { - allExtras.putAll(params.getAppExtras()); - } - } - } - return (allExtras.size() > 0) ? allExtras : null; - } - - private void sendMyPackageSuspendedOrUnsuspended(String[] affectedPackages, boolean suspended, - int userId) { - final String action = suspended - ? Intent.ACTION_MY_PACKAGE_SUSPENDED - : Intent.ACTION_MY_PACKAGE_UNSUSPENDED; - mHandler.post(() -> { - final IActivityManager am = ActivityManager.getService(); - if (am == null) { - Slog.wtf(TAG, "IActivityManager null. Cannot send MY_PACKAGE_ " - + (suspended ? "" : "UN") + "SUSPENDED broadcasts"); - return; - } - final int[] targetUserIds = new int[] {userId}; - for (String packageName : affectedPackages) { - final Bundle appExtras = suspended - ? getSuspendedPackageAppExtrasInternal(packageName, userId) - : null; - final Bundle intentExtras; - if (appExtras != null) { - intentExtras = new Bundle(1); - intentExtras.putBundle(Intent.EXTRA_SUSPENDED_PACKAGE_EXTRAS, appExtras); - } else { - intentExtras = null; - } - mHandler.post(() -> mBroadcastHelper.doSendBroadcast(action, null, intentExtras, - Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, packageName, null, - targetUserIds, false, null, null)); - } - }); + return mSuspendPackageHelper.getSuspendedPackageAppExtras( + packageName, userId, callingUid); } @Override @@ -4618,50 +4452,14 @@ public class PackageManagerService extends IPackageManager.Stub synchronized (mLock) { allPackages = mPackages.keySet().toArray(new String[mPackages.size()]); } - removeSuspensionsBySuspendingPackage(allPackages, suspendingPackage::equals, userId); + mSuspendPackageHelper.removeSuspensionsBySuspendingPackage( + allPackages, suspendingPackage::equals, userId); } private boolean isSuspendingAnyPackages(String suspendingPackage, int userId) { return mComputer.isSuspendingAnyPackages(suspendingPackage, userId); } - /** - * Removes any suspensions on given packages that were added by packages that pass the given - * predicate. - * - * <p> Caller must flush package restrictions if it cares about immediate data consistency. - * - * @param packagesToChange The packages on which the suspension are to be removed. - * @param suspendingPackagePredicate A predicate identifying the suspending packages whose - * suspensions will be removed. - * @param userId The user for which the changes are taking place. - */ - private void removeSuspensionsBySuspendingPackage(String[] packagesToChange, - Predicate<String> suspendingPackagePredicate, int userId) { - final List<String> unsuspendedPackages = new ArrayList<>(); - final IntArray unsuspendedUids = new IntArray(); - synchronized (mLock) { - for (String packageName : packagesToChange) { - final PackageSetting ps = mSettings.getPackageLPr(packageName); - if (ps != null && ps.getUserStateOrDefault(userId).isSuspended()) { - ps.removeSuspension(suspendingPackagePredicate, userId); - if (!ps.getUserStateOrDefault(userId).isSuspended()) { - unsuspendedPackages.add(ps.getPackageName()); - unsuspendedUids.add(UserHandle.getUid(userId, ps.getAppId())); - } - } - } - scheduleWritePackageRestrictionsLocked(userId); - } - if (!unsuspendedPackages.isEmpty()) { - final String[] packageArray = unsuspendedPackages.toArray( - new String[unsuspendedPackages.size()]); - sendMyPackageSuspendedOrUnsuspended(packageArray, false, userId); - sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_UNSUSPENDED, - packageArray, unsuspendedUids.toArray(), userId); - } - } - void removeAllDistractingPackageRestrictions(int userId) { final String[] allPackages = mComputer.getAllAvailablePackageNames(); removeDistractingPackageRestrictions(allPackages, userId); @@ -4698,24 +4496,6 @@ public class PackageManagerService extends IPackageManager.Stub } } - private boolean isCallerDeviceOrProfileOwner(int userId) { - final int callingUid = Binder.getCallingUid(); - if (callingUid == Process.SYSTEM_UID) { - return true; - } - final String ownerPackage = mProtectedPackages.getDeviceOwnerOrProfileOwnerPackage(userId); - if (ownerPackage != null) { - return callingUid == getPackageUidInternal(ownerPackage, 0, userId, callingUid); - } - return false; - } - - private boolean isSuspendAllowedForUser(int userId) { - return isCallerDeviceOrProfileOwner(userId) - || (!mUserManager.hasUserRestriction(UserManager.DISALLOW_APPS_CONTROL, userId) - && !mUserManager.hasUserRestriction(UserManager.DISALLOW_UNINSTALL_APPS, userId)); - } - @Override public String[] getUnsuspendablePackagesForUser(String[] packageNames, int userId) { Objects.requireNonNull(packageNames, "packageNames cannot be null"); @@ -4726,125 +4506,8 @@ public class PackageManagerService extends IPackageManager.Stub throw new SecurityException("Calling uid " + callingUid + " cannot query getUnsuspendablePackagesForUser for user " + userId); } - if (!isSuspendAllowedForUser(userId)) { - Slog.w(TAG, "Cannot suspend due to restrictions on user " + userId); - return packageNames; - } - final ArraySet<String> unactionablePackages = new ArraySet<>(); - final boolean[] canSuspend = canSuspendPackageForUserInternal(packageNames, userId); - for (int i = 0; i < packageNames.length; i++) { - if (!canSuspend[i]) { - unactionablePackages.add(packageNames[i]); - continue; - } - synchronized (mLock) { - final PackageSetting ps = mSettings.getPackageLPr(packageNames[i]); - if (ps == null || shouldFilterApplication(ps, callingUid, userId)) { - Slog.w(TAG, "Could not find package setting for package: " + packageNames[i]); - unactionablePackages.add(packageNames[i]); - } - } - } - return unactionablePackages.toArray(new String[unactionablePackages.size()]); - } - - /** - * Returns an array of booleans, such that the ith boolean denotes whether the ith package can - * be suspended or not. - * - * @param packageNames The package names to check suspendability for. - * @param userId The user to check in - * @return An array containing results of the checks - */ - @NonNull - private boolean[] canSuspendPackageForUserInternal(@NonNull String[] packageNames, int userId) { - final boolean[] canSuspend = new boolean[packageNames.length]; - final boolean isCallerOwner = isCallerDeviceOrProfileOwner(userId); - final long callingId = Binder.clearCallingIdentity(); - try { - final String activeLauncherPackageName = getActiveLauncherPackageName(userId); - final String dialerPackageName = mDefaultAppProvider.getDefaultDialer(userId); - for (int i = 0; i < packageNames.length; i++) { - canSuspend[i] = false; - final String packageName = packageNames[i]; - - if (isPackageDeviceAdmin(packageName, userId)) { - Slog.w(TAG, "Cannot suspend package \"" + packageName - + "\": has an active device admin"); - continue; - } - if (packageName.equals(activeLauncherPackageName)) { - Slog.w(TAG, "Cannot suspend package \"" + packageName - + "\": contains the active launcher"); - continue; - } - if (packageName.equals(mRequiredInstallerPackage)) { - Slog.w(TAG, "Cannot suspend package \"" + packageName - + "\": required for package installation"); - continue; - } - if (packageName.equals(mRequiredUninstallerPackage)) { - Slog.w(TAG, "Cannot suspend package \"" + packageName - + "\": required for package uninstallation"); - continue; - } - if (packageName.equals(mRequiredVerifierPackage)) { - Slog.w(TAG, "Cannot suspend package \"" + packageName - + "\": required for package verification"); - continue; - } - if (packageName.equals(dialerPackageName)) { - Slog.w(TAG, "Cannot suspend package \"" + packageName - + "\": is the default dialer"); - continue; - } - if (packageName.equals(mRequiredPermissionControllerPackage)) { - Slog.w(TAG, "Cannot suspend package \"" + packageName - + "\": required for permissions management"); - continue; - } - synchronized (mLock) { - if (mProtectedPackages.isPackageStateProtected(userId, packageName)) { - Slog.w(TAG, "Cannot suspend package \"" + packageName - + "\": protected package"); - continue; - } - if (!isCallerOwner && mSettings.getBlockUninstallLPr(userId, packageName)) { - Slog.w(TAG, "Cannot suspend package \"" + packageName - + "\": blocked by admin"); - continue; - } - - AndroidPackage pkg = mPackages.get(packageName); - if (pkg != null) { - // Cannot suspend SDK libs as they are controlled by SDK manager. - if (pkg.isSdkLibrary()) { - Slog.w(TAG, "Cannot suspend package: " + packageName - + " providing SDK library: " - + pkg.getSdkLibName()); - continue; - } - // Cannot suspend static shared libs as they are considered - // a part of the using app (emulating static linking). Also - // static libs are installed always on internal storage. - if (pkg.isStaticSharedLibrary()) { - Slog.w(TAG, "Cannot suspend package: " + packageName - + " providing static shared library: " - + pkg.getStaticSharedLibName()); - continue; - } - } - } - if (PLATFORM_PACKAGE_NAME.equals(packageName)) { - Slog.w(TAG, "Cannot suspend the platform package: " + packageName); - continue; - } - canSuspend[i] = true; - } - } finally { - Binder.restoreCallingIdentity(callingId); - } - return canSuspend; + return mSuspendPackageHelper.getUnsuspendablePackagesForUser( + packageNames, userId, callingUid); } @Override @@ -7420,41 +7083,27 @@ public class PackageManagerService extends IPackageManager.Stub @Override public Bundle getSuspendedPackageLauncherExtras(String packageName, int userId) { - final PackageStateInternal packageState = getPackageStateInternal(packageName); - if (packageState == null) { - return null; - } - Bundle allExtras = new Bundle(); - PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId); - if (userState.isSuspended()) { - for (int i = 0; i < userState.getSuspendParams().size(); i++) { - final SuspendParams params = userState.getSuspendParams().valueAt(i); - if (params != null && params.getLauncherExtras() != null) { - allExtras.putAll(params.getLauncherExtras()); - } - } - } - return (allExtras.size() > 0) ? allExtras : null; + return mSuspendPackageHelper.getSuspendedPackageLauncherExtras( + packageName, userId, Binder.getCallingUid()); } @Override public boolean isPackageSuspended(String packageName, int userId) { - final PackageStateInternal packageState = getPackageStateInternal(packageName); - return packageState != null && packageState.getUserStateOrDefault(userId) - .isSuspended(); + return mSuspendPackageHelper.isPackageSuspended( + packageName, userId, Binder.getCallingUid()); } @Override public void removeAllNonSystemPackageSuspensions(int userId) { final String[] allPackages = mComputer.getAllAvailablePackageNames(); - PackageManagerService.this.removeSuspensionsBySuspendingPackage(allPackages, + mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(allPackages, (suspendingPackage) -> !PLATFORM_PACKAGE_NAME.equals(suspendingPackage), userId); } @Override public void removeNonSystemPackageSuspensions(String packageName, int userId) { - PackageManagerService.this.removeSuspensionsBySuspendingPackage( + mSuspendPackageHelper.removeSuspensionsBySuspendingPackage( new String[]{packageName}, (suspendingPackage) -> !PLATFORM_PACKAGE_NAME.equals(suspendingPackage), userId); @@ -7480,46 +7129,15 @@ public class PackageManagerService extends IPackageManager.Stub @Override public String getSuspendingPackage(String suspendedPackage, int userId) { - final PackageStateInternal packageState = getPackageStateInternal(suspendedPackage); - if (packageState == null) { - return null; - } - - final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId); - if (!userState.isSuspended()) { - return null; - } - - String suspendingPackage = null; - for (int i = 0; i < userState.getSuspendParams().size(); i++) { - suspendingPackage = userState.getSuspendParams().keyAt(i); - if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) { - return suspendingPackage; - } - } - return suspendingPackage; + return mSuspendPackageHelper.getSuspendingPackage( + suspendedPackage, userId, Binder.getCallingUid()); } @Override public SuspendDialogInfo getSuspendedDialogInfo(String suspendedPackage, String suspendingPackage, int userId) { - final PackageStateInternal packageState = getPackageStateInternal(suspendedPackage); - if (packageState == null) { - return null; - } - - final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId); - if (!userState.isSuspended()) { - return null; - } - - final ArrayMap<String, SuspendParams> suspendParamsMap = userState.getSuspendParams(); - if (suspendParamsMap == null) { - return null; - } - - final SuspendParams suspendParams = suspendParamsMap.get(suspendingPackage); - return (suspendParams != null) ? suspendParams.getDialogInfo() : null; + return mSuspendPackageHelper.getSuspendedDialogInfo( + suspendedPackage, suspendingPackage, userId, Binder.getCallingUid()); } @Override @@ -9083,6 +8701,8 @@ public class PackageManagerService extends IPackageManager.Stub return new String[] { mDefaultAppProvider.getDefaultBrowser(userId) }; case PackageManagerInternal.PACKAGE_INSTALLER: return mComputer.filterOnlySystemPackages(mRequiredInstallerPackage); + case PackageManagerInternal.PACKAGE_UNINSTALLER: + return mComputer.filterOnlySystemPackages(mRequiredUninstallerPackage); case PackageManagerInternal.PACKAGE_SETUP_WIZARD: return mComputer.filterOnlySystemPackages(mSetupWizardPackage); case PackageManagerInternal.PACKAGE_SYSTEM: diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java index a1acc388146e..0d6555c52623 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java @@ -111,4 +111,5 @@ public final class PackageManagerServiceTestParams { public PreferredActivityHelper preferredActivityHelper; public ResolveIntentHelper resolveIntentHelper; public DexOptHelper dexOptHelper; + public SuspendPackageHelper suspendPackageHelper; } diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index d8f0cc340884..e03cf0a10537 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -51,7 +51,6 @@ import android.content.pm.Signature; import android.content.pm.SigningDetails; import android.content.pm.parsing.ApkLiteParseUtils; import android.content.pm.parsing.PackageLite; -import com.android.server.pm.pkg.component.ParsedMainComponent; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.os.Binder; @@ -92,6 +91,7 @@ import com.android.server.compat.PlatformCompat; import com.android.server.pm.dex.PackageDexUsage; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.pkg.component.ParsedMainComponent; import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; import dalvik.system.VMRuntime; @@ -560,8 +560,8 @@ public class PackageManagerServiceUtils { if (!match) { throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE, - "Package " + packageName + - " signatures do not match previously installed version; ignoring!"); + "Existing package " + packageName + + " signatures do not match newer version; ignoring!"); } } // Check for shared user signatures diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index bf7ef1b24776..15e64dffe892 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -146,8 +146,10 @@ class ShortcutPackage extends ShortcutPackageItem { private static final String ATTR_PERSON_IS_IMPORTANT = "is-important"; private static final String NAME_CATEGORIES = "categories"; + private static final String NAME_CAPABILITY = "capability"; private static final String TAG_STRING_ARRAY_XMLUTILS = "string-array"; + private static final String TAG_MAP_XMLUTILS = "map"; private static final String ATTR_NAME_XMLUTILS = "name"; private static final String KEY_DYNAMIC = "dynamic"; @@ -1829,6 +1831,12 @@ class ShortcutPackage extends ShortcutPackageItem { } ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras()); + + final Map<String, Map<String, List<String>>> capabilityBindings = + si.getCapabilityBindings(); + if (capabilityBindings != null && !capabilityBindings.isEmpty()) { + XmlUtils.writeMapXml(si.getCapabilityBindings(), NAME_CAPABILITY, out); + } } out.endTag(null, TAG_SHORTCUT); @@ -1961,6 +1969,7 @@ class ShortcutPackage extends ShortcutPackageItem { int backupVersionCode; ArraySet<String> categories = null; ArrayList<Person> persons = new ArrayList<>(); + Map<String, Map<String, List<String>>> capabilityBindings = null; id = ShortcutService.parseStringAttribute(parser, ATTR_ID); activityComponent = ShortcutService.parseComponentNameAttribute(parser, @@ -2029,6 +2038,13 @@ class ShortcutPackage extends ShortcutPackageItem { } } continue; + case TAG_MAP_XMLUTILS: + if (NAME_CAPABILITY.equals(ShortcutService.parseStringAttribute(parser, + ATTR_NAME_XMLUTILS))) { + capabilityBindings = (Map<String, Map<String, List<String>>>) + XmlUtils.readValueXml(parser, new String[1]); + } + continue; } throw ShortcutService.throwForInvalidTag(depth, tag); } @@ -2064,7 +2080,7 @@ class ShortcutPackage extends ShortcutPackageItem { rank, extras, lastChangedTimestamp, flags, iconResId, iconResName, bitmapPath, iconUri, disabledReason, persons.toArray(new Person[persons.size()]), locusId, - splashScreenThemeResName); + splashScreenThemeResName, capabilityBindings); } private static Intent parseIntent(TypedXmlPullParser parser) diff --git a/services/core/java/com/android/server/pm/ShortcutParser.java b/services/core/java/com/android/server/pm/ShortcutParser.java index b86c50b23687..63f1f2d518f7 100644 --- a/services/core/java/com/android/server/pm/ShortcutParser.java +++ b/services/core/java/com/android/server/pm/ShortcutParser.java @@ -459,7 +459,8 @@ public class ShortcutParser { disabledReason, null /* persons */, null /* locusId */, - splashScreenThemeResName); + splashScreenThemeResName, + null /* capabilityBindings */); } private static String parseCategory(ShortcutService service, AttributeSet attrs) { diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java new file mode 100644 index 000000000000..f466ca72f681 --- /dev/null +++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java @@ -0,0 +1,611 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import static android.content.pm.PackageManagerInternal.PACKAGE_INSTALLER; +import static android.content.pm.PackageManagerInternal.PACKAGE_PERMISSION_CONTROLLER; +import static android.content.pm.PackageManagerInternal.PACKAGE_UNINSTALLER; +import static android.content.pm.PackageManagerInternal.PACKAGE_VERIFIER; +import static android.os.Process.SYSTEM_UID; + +import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; +import static com.android.server.pm.PackageManagerService.TAG; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.IActivityManager; +import android.content.Intent; +import android.content.pm.PackageManagerInternal.KnownPackage; +import android.content.pm.SuspendDialogInfo; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.PersistableBundle; +import android.os.UserHandle; +import android.os.UserManager; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.IntArray; +import android.util.Slog; +import android.util.SparseArray; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; +import com.android.server.pm.parsing.pkg.AndroidPackage; +import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.pkg.PackageUserStateInternal; +import com.android.server.pm.pkg.SuspendParams; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; + +public final class SuspendPackageHelper { + // TODO(b/198166813): remove PMS dependency + private final PackageManagerService mPm; + private final PackageManagerServiceInjector mInjector; + + private final BroadcastHelper mBroadcastHelper; + private final ProtectedPackages mProtectedPackages; + + /** + * Constructor for {@link PackageManagerService}. + */ + SuspendPackageHelper(PackageManagerService pm, PackageManagerServiceInjector injector, + BroadcastHelper broadcastHelper, ProtectedPackages protectedPackages) { + mPm = pm; + mInjector = injector; + mBroadcastHelper = broadcastHelper; + mProtectedPackages = protectedPackages; + } + + /** + * Updates the package to the suspended or unsuspended state. + * + * @param packageNames The names of the packages to set the suspended status. + * @param suspended {@code true} to suspend packages, or {@code false} to unsuspend packages. + * @param appExtras An optional {@link PersistableBundle} that the suspending app can provide + * which will be shared with the apps being suspended. Ignored if + * {@code suspended} is false. + * @param launcherExtras An optional {@link PersistableBundle} that the suspending app can + * provide which will be shared with the launcher. Ignored if + * {@code suspended} is false. + * @param dialogInfo An optional {@link SuspendDialogInfo} object describing the dialog that + * should be shown to the user when they try to launch a suspended app. + * Ignored if {@code suspended} is false. + * @param callingPackage The caller's package name. + * @param userId The user where packages reside. + * @param callingUid The caller's uid. + * @return The names of failed packages. + */ + @Nullable + String[] setPackagesSuspended(@Nullable String[] packageNames, boolean suspended, + @Nullable PersistableBundle appExtras, @Nullable PersistableBundle launcherExtras, + @Nullable SuspendDialogInfo dialogInfo, @NonNull String callingPackage, + int userId, int callingUid) { + if (ArrayUtils.isEmpty(packageNames)) { + return packageNames; + } + if (suspended && !isSuspendAllowedForUser(userId, callingUid)) { + Slog.w(TAG, "Cannot suspend due to restrictions on user " + userId); + return packageNames; + } + + final List<String> changedPackagesList = new ArrayList<>(packageNames.length); + final IntArray changedUids = new IntArray(packageNames.length); + final List<String> modifiedPackagesList = new ArrayList<>(packageNames.length); + final IntArray modifiedUids = new IntArray(packageNames.length); + final List<String> unactionedPackages = new ArrayList<>(packageNames.length); + final boolean[] canSuspend = + suspended ? canSuspendPackageForUser(packageNames, userId, callingUid) : null; + + for (int i = 0; i < packageNames.length; i++) { + final String packageName = packageNames[i]; + if (callingPackage.equals(packageName)) { + Slog.w(TAG, "Calling package: " + callingPackage + " trying to " + + (suspended ? "" : "un") + "suspend itself. Ignoring"); + unactionedPackages.add(packageName); + continue; + } + final PackageSetting pkgSetting; + synchronized (mPm.mLock) { + pkgSetting = mPm.mSettings.getPackageLPr(packageName); + if (pkgSetting == null + || mPm.shouldFilterApplication(pkgSetting, callingUid, userId)) { + Slog.w(TAG, "Could not find package setting for package: " + packageName + + ". Skipping suspending/un-suspending."); + unactionedPackages.add(packageName); + continue; + } + } + if (canSuspend != null && !canSuspend[i]) { + unactionedPackages.add(packageName); + continue; + } + final boolean packageUnsuspended; + final boolean packageModified; + synchronized (mPm.mLock) { + if (suspended) { + packageModified = pkgSetting.addOrUpdateSuspension(callingPackage, + dialogInfo, appExtras, launcherExtras, userId); + } else { + packageModified = pkgSetting.removeSuspension(callingPackage, userId); + } + packageUnsuspended = !suspended && !pkgSetting.getSuspended(userId); + } + if (suspended || packageUnsuspended) { + changedPackagesList.add(packageName); + changedUids.add(UserHandle.getUid(userId, pkgSetting.getAppId())); + } + if (packageModified) { + modifiedPackagesList.add(packageName); + modifiedUids.add(UserHandle.getUid(userId, pkgSetting.getAppId())); + } + } + + if (!changedPackagesList.isEmpty()) { + final String[] changedPackages = changedPackagesList.toArray(new String[0]); + sendPackagesSuspendedForUser( + suspended ? Intent.ACTION_PACKAGES_SUSPENDED + : Intent.ACTION_PACKAGES_UNSUSPENDED, + changedPackages, changedUids.toArray(), userId); + sendMyPackageSuspendedOrUnsuspended(changedPackages, suspended, userId); + synchronized (mPm.mLock) { + mPm.scheduleWritePackageRestrictionsLocked(userId); + } + } + // Send the suspension changed broadcast to ensure suspension state is not stale. + if (!modifiedPackagesList.isEmpty()) { + sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED, + modifiedPackagesList.toArray(new String[0]), modifiedUids.toArray(), userId); + } + return unactionedPackages.toArray(new String[0]); + } + + /** + * Returns the names in the {@code packageNames} which can not be suspended by the caller. + * + * @param packageNames The names of packages to check. + * @param userId The user where packages reside. + * @param callingUid The caller's uid. + * @return The names of packages which are Unsuspendable. + */ + @NonNull + String[] getUnsuspendablePackagesForUser(@NonNull String[] packageNames, int userId, + int callingUid) { + if (!isSuspendAllowedForUser(userId, callingUid)) { + Slog.w(TAG, "Cannot suspend due to restrictions on user " + userId); + return packageNames; + } + final ArraySet<String> unactionablePackages = new ArraySet<>(); + final boolean[] canSuspend = canSuspendPackageForUser(packageNames, userId, callingUid); + for (int i = 0; i < packageNames.length; i++) { + if (!canSuspend[i]) { + unactionablePackages.add(packageNames[i]); + continue; + } + synchronized (mPm.mLock) { + final PackageSetting ps = mPm.mSettings.getPackageLPr(packageNames[i]); + if (ps == null || mPm.shouldFilterApplication(ps, callingUid, userId)) { + Slog.w(TAG, "Could not find package setting for package: " + packageNames[i]); + unactionablePackages.add(packageNames[i]); + } + } + } + return unactionablePackages.toArray(new String[unactionablePackages.size()]); + } + + /** + * Returns the app extras of the given suspended package. + * + * @param packageName The suspended package name. + * @param userId The user where the package resides. + * @param callingUid The caller's uid. + * @return The app extras of the suspended package. + */ + @Nullable + Bundle getSuspendedPackageAppExtras(@NonNull String packageName, int userId, int callingUid) { + final PackageStateInternal ps = mPm.getPackageStateInternal(packageName, callingUid); + if (ps == null) { + return null; + } + final PackageUserStateInternal pus = ps.getUserStateOrDefault(userId); + final Bundle allExtras = new Bundle(); + if (pus.isSuspended()) { + for (int i = 0; i < pus.getSuspendParams().size(); i++) { + final SuspendParams params = pus.getSuspendParams().valueAt(i); + if (params != null && params.getAppExtras() != null) { + allExtras.putAll(params.getAppExtras()); + } + } + } + return (allExtras.size() > 0) ? allExtras : null; + } + + /** + * Removes any suspensions on given packages that were added by packages that pass the given + * predicate. + * + * <p> Caller must flush package restrictions if it cares about immediate data consistency. + * + * @param packagesToChange The packages on which the suspension are to be removed. + * @param suspendingPackagePredicate A predicate identifying the suspending packages whose + * suspensions will be removed. + * @param userId The user for which the changes are taking place. + */ + void removeSuspensionsBySuspendingPackage(@NonNull String[] packagesToChange, + @NonNull Predicate<String> suspendingPackagePredicate, int userId) { + final List<String> unsuspendedPackages = new ArrayList<>(); + final IntArray unsuspendedUids = new IntArray(); + synchronized (mPm.mLock) { + for (String packageName : packagesToChange) { + final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName); + if (ps != null && ps.getUserStateOrDefault(userId).isSuspended()) { + ps.removeSuspension(suspendingPackagePredicate, userId); + if (!ps.getUserStateOrDefault(userId).isSuspended()) { + unsuspendedPackages.add(ps.getPackageName()); + unsuspendedUids.add(UserHandle.getUid(userId, ps.getAppId())); + } + } + } + mPm.scheduleWritePackageRestrictionsLocked(userId); + } + if (!unsuspendedPackages.isEmpty()) { + final String[] packageArray = unsuspendedPackages.toArray( + new String[unsuspendedPackages.size()]); + sendMyPackageSuspendedOrUnsuspended(packageArray, false, userId); + sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_UNSUSPENDED, + packageArray, unsuspendedUids.toArray(), userId); + } + } + + /** + * Returns the launcher extras for the given suspended package. + * + * @param packageName The name of the suspended package. + * @param userId The user where the package resides. + * @param callingUid The caller's uid. + * @return The launcher extras. + */ + @Nullable + Bundle getSuspendedPackageLauncherExtras(@NonNull String packageName, int userId, + int callingUid) { + final PackageStateInternal packageState = mPm.getPackageStateInternal( + packageName, callingUid); + if (packageState == null) { + return null; + } + Bundle allExtras = new Bundle(); + PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId); + if (userState.isSuspended()) { + for (int i = 0; i < userState.getSuspendParams().size(); i++) { + final SuspendParams params = userState.getSuspendParams().valueAt(i); + if (params != null && params.getLauncherExtras() != null) { + allExtras.putAll(params.getLauncherExtras()); + } + } + } + return (allExtras.size() > 0) ? allExtras : null; + } + + /** + * Return {@code true}, if the given package is suspended. + * + * @param packageName The name of package to check. + * @param userId The user where the package resides. + * @param callingUid The caller's uid. + * @return {@code true}, if the given package is suspended. + */ + boolean isPackageSuspended(@NonNull String packageName, int userId, int callingUid) { + final PackageStateInternal packageState = mPm.getPackageStateInternal( + packageName, callingUid); + return packageState != null && packageState.getUserStateOrDefault(userId) + .isSuspended(); + } + + /** + * Given a suspended package, returns the name of package which invokes suspending to it. + * + * @param suspendedPackage The suspended package to check. + * @param userId The user where the package resides. + * @param callingUid The caller's uid. + * @return The name of suspending package. + */ + @Nullable + String getSuspendingPackage(@NonNull String suspendedPackage, int userId, int callingUid) { + final PackageStateInternal packageState = mPm.getPackageStateInternal( + suspendedPackage, callingUid); + if (packageState == null) { + return null; + } + + final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId); + if (!userState.isSuspended()) { + return null; + } + + String suspendingPackage = null; + for (int i = 0; i < userState.getSuspendParams().size(); i++) { + suspendingPackage = userState.getSuspendParams().keyAt(i); + if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) { + return suspendingPackage; + } + } + return suspendingPackage; + } + + /** + * Returns the dialog info of the given suspended package. + * + * @param suspendedPackage The name of the suspended package. + * @param suspendingPackage The name of the suspending package. + * @param userId The user where the package resides. + * @param callingUid The caller's uid. + * @return The dialog info. + */ + @Nullable + SuspendDialogInfo getSuspendedDialogInfo(@NonNull String suspendedPackage, + @NonNull String suspendingPackage, int userId, int callingUid) { + final PackageStateInternal packageState = mPm.getPackageStateInternal( + suspendedPackage, callingUid); + if (packageState == null) { + return null; + } + + final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId); + if (!userState.isSuspended()) { + return null; + } + + final ArrayMap<String, SuspendParams> suspendParamsMap = userState.getSuspendParams(); + if (suspendParamsMap == null) { + return null; + } + + final SuspendParams suspendParams = suspendParamsMap.get(suspendingPackage); + return (suspendParams != null) ? suspendParams.getDialogInfo() : null; + } + + /** + * Return {@code true} if the user is allowed to suspend packages by the caller. + * + * @param userId The user id to check. + * @param callingUid The caller's uid. + * @return {@code true} if the user is allowed to suspend packages by the caller. + */ + boolean isSuspendAllowedForUser(int userId, int callingUid) { + final UserManagerService userManager = mInjector.getUserManagerService(); + return isCallerDeviceOrProfileOwner(userId, callingUid) + || (!userManager.hasUserRestriction(UserManager.DISALLOW_APPS_CONTROL, userId) + && !userManager.hasUserRestriction(UserManager.DISALLOW_UNINSTALL_APPS, userId)); + } + + /** + * Returns an array of booleans, such that the ith boolean denotes whether the ith package can + * be suspended or not. + * + * @param packageNames The package names to check suspendability for. + * @param userId The user to check in + * @param callingUid The caller's uid. + * @return An array containing results of the checks + */ + @NonNull + boolean[] canSuspendPackageForUser(@NonNull String[] packageNames, int userId, int callingUid) { + final boolean[] canSuspend = new boolean[packageNames.length]; + final boolean isCallerOwner = isCallerDeviceOrProfileOwner(userId, callingUid); + final long token = Binder.clearCallingIdentity(); + try { + final DefaultAppProvider defaultAppProvider = mInjector.getDefaultAppProvider(); + final String activeLauncherPackageName = defaultAppProvider.getDefaultHome(userId); + final String dialerPackageName = defaultAppProvider.getDefaultDialer(userId); + final String requiredInstallerPackage = getKnownPackageName(PACKAGE_INSTALLER, userId); + final String requiredUninstallerPackage = + getKnownPackageName(PACKAGE_UNINSTALLER, userId); + final String requiredVerifierPackage = getKnownPackageName(PACKAGE_VERIFIER, userId); + final String requiredPermissionControllerPackage = + getKnownPackageName(PACKAGE_PERMISSION_CONTROLLER, userId); + for (int i = 0; i < packageNames.length; i++) { + canSuspend[i] = false; + final String packageName = packageNames[i]; + + if (mPm.isPackageDeviceAdmin(packageName, userId)) { + Slog.w(TAG, "Cannot suspend package \"" + packageName + + "\": has an active device admin"); + continue; + } + if (packageName.equals(activeLauncherPackageName)) { + Slog.w(TAG, "Cannot suspend package \"" + packageName + + "\": contains the active launcher"); + continue; + } + if (packageName.equals(requiredInstallerPackage)) { + Slog.w(TAG, "Cannot suspend package \"" + packageName + + "\": required for package installation"); + continue; + } + if (packageName.equals(requiredUninstallerPackage)) { + Slog.w(TAG, "Cannot suspend package \"" + packageName + + "\": required for package uninstallation"); + continue; + } + if (packageName.equals(requiredVerifierPackage)) { + Slog.w(TAG, "Cannot suspend package \"" + packageName + + "\": required for package verification"); + continue; + } + if (packageName.equals(dialerPackageName)) { + Slog.w(TAG, "Cannot suspend package \"" + packageName + + "\": is the default dialer"); + continue; + } + if (packageName.equals(requiredPermissionControllerPackage)) { + Slog.w(TAG, "Cannot suspend package \"" + packageName + + "\": required for permissions management"); + continue; + } + synchronized (mPm.mLock) { + if (mProtectedPackages.isPackageStateProtected(userId, packageName)) { + Slog.w(TAG, "Cannot suspend package \"" + packageName + + "\": protected package"); + continue; + } + if (!isCallerOwner && mPm.mSettings.getBlockUninstallLPr(userId, packageName)) { + Slog.w(TAG, "Cannot suspend package \"" + packageName + + "\": blocked by admin"); + continue; + } + + AndroidPackage pkg = mPm.mPackages.get(packageName); + if (pkg != null) { + // Cannot suspend SDK libs as they are controlled by SDK manager. + if (pkg.isSdkLibrary()) { + Slog.w(TAG, "Cannot suspend package: " + packageName + + " providing SDK library: " + + pkg.getSdkLibName()); + continue; + } + // Cannot suspend static shared libs as they are considered + // a part of the using app (emulating static linking). Also + // static libs are installed always on internal storage. + if (pkg.isStaticSharedLibrary()) { + Slog.w(TAG, "Cannot suspend package: " + packageName + + " providing static shared library: " + + pkg.getStaticSharedLibName()); + continue; + } + } + } + if (PLATFORM_PACKAGE_NAME.equals(packageName)) { + Slog.w(TAG, "Cannot suspend the platform package: " + packageName); + continue; + } + canSuspend[i] = true; + } + } finally { + Binder.restoreCallingIdentity(token); + } + return canSuspend; + } + + /** + * Send broadcast intents for packages suspension changes. + * + * @param intent The action name of the suspension intent. + * @param pkgList The names of packages which have suspension changes. + * @param uidList The uids of packages which have suspension changes. + * @param userId The user where packages reside. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + void sendPackagesSuspendedForUser(@NonNull String intent, @NonNull String[] pkgList, + @NonNull int[] uidList, int userId) { + final List<List<String>> pkgsToSend = new ArrayList(pkgList.length); + final List<IntArray> uidsToSend = new ArrayList(pkgList.length); + final List<SparseArray<int[]>> allowListsToSend = new ArrayList(pkgList.length); + final int[] userIds = new int[] {userId}; + // Get allow lists for the pkg in the pkgList. Merge into the existed pkgs and uids if + // allow lists are the same. + for (int i = 0; i < pkgList.length; i++) { + final String pkgName = pkgList[i]; + final int uid = uidList[i]; + SparseArray<int[]> allowList = mInjector.getAppsFilter().getVisibilityAllowList( + mPm.getPackageStateInternal(pkgName, SYSTEM_UID), + userIds, mPm.getPackageStates()); + if (allowList == null) { + allowList = new SparseArray<>(0); + } + boolean merged = false; + for (int j = 0; j < allowListsToSend.size(); j++) { + if (Arrays.equals(allowListsToSend.get(j).get(userId), allowList.get(userId))) { + pkgsToSend.get(j).add(pkgName); + uidsToSend.get(j).add(uid); + merged = true; + break; + } + } + if (!merged) { + pkgsToSend.add(new ArrayList<>(Arrays.asList(pkgName))); + uidsToSend.add(IntArray.wrap(new int[] {uid})); + allowListsToSend.add(allowList); + } + } + + final Handler handler = mInjector.getHandler(); + for (int i = 0; i < pkgsToSend.size(); i++) { + final Bundle extras = new Bundle(3); + extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, + pkgsToSend.get(i).toArray(new String[pkgsToSend.get(i).size()])); + extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidsToSend.get(i).toArray()); + final SparseArray<int[]> allowList = allowListsToSend.get(i).size() == 0 + ? null : allowListsToSend.get(i); + handler.post(() -> mBroadcastHelper.sendPackageBroadcast(intent, null /* pkg */, + extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null /* targetPkg */, + null /* finishedReceiver */, userIds, null /* instantUserIds */, + allowList, null /* bOptions */)); + } + } + + private String getKnownPackageName(@KnownPackage int knownPackage, int userId) { + final String[] knownPackages = mPm.getKnownPackageNamesInternal(knownPackage, userId); + return knownPackages.length > 0 ? knownPackages[0] : null; + } + + private boolean isCallerDeviceOrProfileOwner(int userId, int callingUid) { + if (callingUid == SYSTEM_UID) { + return true; + } + final String ownerPackage = mProtectedPackages.getDeviceOwnerOrProfileOwnerPackage(userId); + if (ownerPackage != null) { + return callingUid == mPm.getPackageUidInternal( + ownerPackage, 0, userId, callingUid); + } + return false; + } + + private void sendMyPackageSuspendedOrUnsuspended(String[] affectedPackages, boolean suspended, + int userId) { + final Handler handler = mInjector.getHandler(); + final String action = suspended + ? Intent.ACTION_MY_PACKAGE_SUSPENDED + : Intent.ACTION_MY_PACKAGE_UNSUSPENDED; + handler.post(() -> { + final IActivityManager am = ActivityManager.getService(); + if (am == null) { + Slog.wtf(TAG, "IActivityManager null. Cannot send MY_PACKAGE_ " + + (suspended ? "" : "UN") + "SUSPENDED broadcasts"); + return; + } + final int[] targetUserIds = new int[] {userId}; + for (String packageName : affectedPackages) { + final Bundle appExtras = suspended + ? getSuspendedPackageAppExtras(packageName, userId, SYSTEM_UID) + : null; + final Bundle intentExtras; + if (appExtras != null) { + intentExtras = new Bundle(1); + intentExtras.putBundle(Intent.EXTRA_SUSPENDED_PACKAGE_EXTRAS, appExtras); + } else { + intentExtras = null; + } + handler.post(() -> mBroadcastHelper.doSendBroadcast(action, null, intentExtras, + Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, packageName, null, + targetUserIds, false, null, null)); + } + }); + } +} diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 1cfcdf51f5b8..8e16835f8b62 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -558,9 +558,9 @@ public class PermissionManagerService extends IPermissionManager.Stub { } @Override - public void selfRevokePermissions(@NonNull String packageName, + public void revokeOwnPermissionsOnKill(@NonNull String packageName, @NonNull List<String> permissions) { - mPermissionManagerServiceImpl.selfRevokePermissions(packageName, permissions); + mPermissionManagerServiceImpl.revokeOwnPermissionsOnKill(packageName, permissions); } @Override diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index 981fd8e9e789..c46503829080 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -1592,7 +1592,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } @Override - public void selfRevokePermissions(String packageName, List<String> permissions) { + public void revokeOwnPermissionsOnKill(String packageName, List<String> permissions) { final int callingUid = Binder.getCallingUid(); int callingUserId = UserHandle.getUserId(callingUid); int targetPackageUid = mPackageManagerInt.getPackageUid(packageName, 0, callingUserId); @@ -1607,7 +1607,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt + permName + " because it does not hold that permission"); } } - mPermissionControllerManager.selfRevokePermissions(packageName, permissions); + mPermissionControllerManager.revokeOwnPermissionsOnKill(packageName, permissions); } private boolean mayManageRolePermission(int uid) { diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java index c582f9efa7a0..b558e3dee623 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java @@ -344,7 +344,7 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte * @param packageName The name of the package for which the permissions will be revoked. * @param permissions List of permissions to be revoked. */ - void selfRevokePermissions(String packageName, List<String> permissions); + void revokeOwnPermissionsOnKill(String packageName, List<String> permissions); /** * Get whether you should show UI with rationale for requesting a permission. You should do this diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index 16176f026578..b7ca4defda5f 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -121,7 +121,6 @@ import android.os.IThermalService; import android.os.OutcomeReceiver; import android.os.ParcelFileDescriptor; import android.os.Parcelable; -import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceSpecificException; @@ -231,7 +230,6 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Arrays; -import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -2332,11 +2330,7 @@ public class StatsPullAtomService extends SystemService { List<ProcessMemoryState> managedProcessList = LocalServices.getService(ActivityManagerInternal.class) .getMemoryStateForProcesses(); - managedProcessList.sort(Comparator.comparingInt(x -> x.oomScore)); for (ProcessMemoryState process : managedProcessList) { - if (process.uid == Process.SYSTEM_UID) { - continue; - } KernelAllocationStats.ProcessDmabuf proc = KernelAllocationStats.getDmabufAllocations(process.pid); if (proc == null || (proc.retainedBuffersCount <= 0 && proc.mappedBuffersCount <= 0)) { @@ -2353,6 +2347,32 @@ public class StatsPullAtomService extends SystemService { proc.mappedSizeKb, proc.mappedBuffersCount)); } + SparseArray<String> processCmdlines = getProcessCmdlines(); + managedProcessList.forEach(managedProcess -> processCmdlines.delete(managedProcess.pid)); + int size = processCmdlines.size(); + for (int i = 0; i < size; ++i) { + int pid = processCmdlines.keyAt(i); + int uid = getUidForPid(pid); + // ignore root processes (unlikely to be interesting) + if (uid <= 0) { + continue; + } + KernelAllocationStats.ProcessDmabuf proc = + KernelAllocationStats.getDmabufAllocations(pid); + if (proc == null || (proc.retainedBuffersCount <= 0 && proc.mappedBuffersCount <= 0)) { + continue; + } + pulledData.add( + FrameworkStatsLog.buildStatsEvent( + atomTag, + uid, + processCmdlines.valueAt(i), + -1001 /*Placeholder for native processes, OOM_SCORE_ADJ_MIN - 1.*/, + proc.retainedSizeKb, + proc.retainedBuffersCount, + proc.mappedSizeKb, + proc.mappedBuffersCount)); + } return StatsManager.PULL_SUCCESS; } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 17f5566a9864..0edd06acd7e9 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -22,6 +22,7 @@ import static android.app.StatusBarManager.NAV_BAR_MODE_OVERRIDE_KIDS; import static android.app.StatusBarManager.NAV_BAR_MODE_OVERRIDE_NONE; import static android.app.StatusBarManager.NavBarModeOverride; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY; import android.Manifest; import android.annotation.NonNull; @@ -38,6 +39,7 @@ import android.compat.annotation.EnabledSince; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.om.IOverlayManager; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; @@ -60,6 +62,7 @@ import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; +import android.os.ServiceManager; import android.os.ShellCallback; import android.os.UserHandle; import android.provider.Settings; @@ -79,6 +82,7 @@ import android.view.WindowInsetsController.Behavior; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.internal.os.TransferPipe; import com.android.internal.statusbar.IAddTileResultCallback; @@ -154,6 +158,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D private final ArrayMap<String, Long> mCurrentRequestAddTilePackages = new ArrayMap<>(); private static final long REQUEST_TIME_OUT = TimeUnit.MINUTES.toNanos(5); + private IOverlayManager mOverlayManager; + private class DeathRecipient implements IBinder.DeathRecipient { public void binderDied() { mBar.asBinder().unlinkToDeath(this,0); @@ -256,6 +262,18 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D mTileRequestTracker = new TileRequestTracker(mContext); } + private IOverlayManager getOverlayManager() { + // No need to synchronize; worst-case scenario it will be fetched twice. + if (mOverlayManager == null) { + mOverlayManager = IOverlayManager.Stub.asInterface( + ServiceManager.getService(Context.OVERLAY_SERVICE)); + if (mOverlayManager == null) { + Slog.w("StatusBarManager", "warning: no OVERLAY_SERVICE"); + } + } + return mOverlayManager; + } + @Override public void onDisplayAdded(int displayId) {} @@ -1296,6 +1314,11 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D }); } + @VisibleForTesting + void registerOverlayManager(IOverlayManager overlayManager) { + mOverlayManager = overlayManager; + } + /** * @param clearNotificationEffects whether to consider notifications as "shown" and stop * LED, vibration, and ringing @@ -1869,6 +1892,14 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D try { Settings.Secure.putIntForUser(mContext.getContentResolver(), Settings.Secure.NAV_BAR_KIDS_MODE, navBarModeOverride, userId); + + IOverlayManager overlayManager = getOverlayManager(); + if (overlayManager != null && navBarModeOverride == NAV_BAR_MODE_OVERRIDE_KIDS + && isPackageSupported(NAV_BAR_MODE_3BUTTON_OVERLAY)) { + overlayManager.setEnabledExclusiveInCategory(NAV_BAR_MODE_3BUTTON_OVERLAY, userId); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); } finally { Binder.restoreCallingIdentity(userIdentity); } @@ -1896,6 +1927,21 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D return navBarKidsMode; } + private boolean isPackageSupported(String packageName) { + if (packageName == null) { + return false; + } + try { + return mContext.getPackageManager().getPackageInfo(packageName, + PackageManager.PackageInfoFlags.of(0)) != null; + } catch (PackageManager.NameNotFoundException ignored) { + if (SPEW) { + Slog.d(TAG, "Package not found: " + packageName); + } + } + return false; + } + /** @hide */ public void passThroughShellCommand(String[] args, FileDescriptor fd) { enforceStatusBarOrShell(); diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java index 79231f709738..593250cb9293 100644 --- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java +++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java @@ -461,6 +461,19 @@ public class TrustAgentWrapper { } /** + * @see android.service.trust.TrustAgentService#onUserRequestedUnlock() + */ + public void onUserRequestedUnlock() { + try { + if (mTrustAgentService != null) { + mTrustAgentService.onUserRequestedUnlock(); + } + } catch (RemoteException e) { + onError(e); + } + } + + /** * @see android.service.trust.TrustAgentService#onUnlockLockout(int) */ public void onUnlockLockout(int timeoutMs) { diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index fc87253825f4..150eebb8276e 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -123,6 +123,7 @@ public class TrustManagerService extends SystemService { private static final int MSG_DISPATCH_UNLOCK_LOCKOUT = 13; private static final int MSG_REFRESH_DEVICE_LOCKED_FOR_USER = 14; private static final int MSG_SCHEDULE_TRUST_TIMEOUT = 15; + public static final int MSG_USER_REQUESTED_UNLOCK = 16; private static final String REFRESH_DEVICE_LOCKED_EXCEPT_USER = "except"; @@ -981,6 +982,15 @@ public class TrustManagerService extends SystemService { } } + private void dispatchUserRequestedUnlock(int userId) { + for (int i = 0; i < mActiveAgents.size(); i++) { + AgentInfo info = mActiveAgents.valueAt(i); + if (info.userId == userId) { + info.agent.onUserRequestedUnlock(); + } + } + } + private void dispatchUnlockLockout(int timeoutMs, int userId) { for (int i = 0; i < mActiveAgents.size(); i++) { AgentInfo info = mActiveAgents.valueAt(i); @@ -1110,6 +1120,12 @@ public class TrustManagerService extends SystemService { } @Override + public void reportUserRequestedUnlock(int userId) throws RemoteException { + enforceReportPermission(); + mHandler.obtainMessage(MSG_USER_REQUESTED_UNLOCK, userId).sendToTarget(); + } + + @Override public void reportUnlockLockout(int timeoutMs, int userId) throws RemoteException { enforceReportPermission(); mHandler.obtainMessage(MSG_DISPATCH_UNLOCK_LOCKOUT, timeoutMs, userId) @@ -1389,6 +1405,9 @@ public class TrustManagerService extends SystemService { case MSG_DISPATCH_UNLOCK_ATTEMPT: dispatchUnlockAttempt(msg.arg1 != 0, msg.arg2); break; + case MSG_USER_REQUESTED_UNLOCK: + dispatchUserRequestedUnlock(msg.arg1); + break; case MSG_DISPATCH_UNLOCK_LOCKOUT: dispatchUnlockLockout(msg.arg1, msg.arg2); break; diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java index c54d490b5162..6c5d9520151b 100644 --- a/services/core/java/com/android/server/vibrator/VibrationSettings.java +++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java @@ -97,6 +97,15 @@ final class VibrationSettings { USAGE_ALARM, USAGE_COMMUNICATION_REQUEST)); + /** + * Usage allowed for vibrations when {@link Settings.System#VIBRATE_ON} is disabled. + * + * <p>The only allowed usage is accessibility, which is applied when the user enables talkback. + * Other usages that must ignore this setting should use + * {@link VibrationAttributes#FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF}. + */ + private static final int VIBRATE_ON_DISABLED_USAGE_ALLOWED = USAGE_ACCESSIBILITY; + /** Listener for changes on vibration settings. */ interface OnVibratorSettingsChanged { /** Callback triggered when any of the vibrator settings change. */ @@ -127,6 +136,8 @@ final class VibrationSettings { private SparseIntArray mCurrentVibrationIntensities = new SparseIntArray(); @GuardedBy("mLock") private boolean mBatterySaverMode; + @GuardedBy("mLock") + private boolean mVibrateOn; VibrationSettings(Context context, Handler handler) { this(context, handler, new VibrationConfig(context.getResources())); @@ -199,6 +210,7 @@ final class VibrationSettings { // Listen to all settings that might affect the result of Vibrator.getVibrationIntensity. registerSettingsObserver(Settings.System.getUriFor(Settings.System.VIBRATE_INPUT_DEVICES)); + registerSettingsObserver(Settings.System.getUriFor(Settings.System.VIBRATE_ON)); registerSettingsObserver(Settings.System.getUriFor(Settings.System.VIBRATE_WHEN_RINGING)); registerSettingsObserver(Settings.System.getUriFor(Settings.System.APPLY_RAMPING_RINGER)); registerSettingsObserver(Settings.System.getUriFor( @@ -314,11 +326,14 @@ final class VibrationSettings { return Vibration.Status.IGNORED_FOR_POWER; } - int intensity = getCurrentIntensity(usage); - if ((intensity == Vibrator.VIBRATION_INTENSITY_OFF) - && !attrs.isFlagSet( - VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)) { - return Vibration.Status.IGNORED_FOR_SETTINGS; + if (!attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)) { + if (!mVibrateOn && (VIBRATE_ON_DISABLED_USAGE_ALLOWED != usage)) { + return Vibration.Status.IGNORED_FOR_SETTINGS; + } + + if (getCurrentIntensity(usage) == Vibrator.VIBRATION_INTENSITY_OFF) { + return Vibration.Status.IGNORED_FOR_SETTINGS; + } } if (!shouldVibrateForRingerModeLocked(usage)) { @@ -357,6 +372,7 @@ final class VibrationSettings { void updateSettings() { synchronized (mLock) { mVibrateInputDevices = loadSystemSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0) > 0; + mVibrateOn = loadSystemSetting(Settings.System.VIBRATE_ON, 1) > 0; int alarmIntensity = toIntensity( loadSystemSetting(Settings.System.ALARM_VIBRATION_INTENSITY, -1), @@ -437,8 +453,9 @@ final class VibrationSettings { + "mVibratorConfig=" + mVibrationConfig + ", mVibrateInputDevices=" + mVibrateInputDevices + ", mBatterySaverMode=" + mBatterySaverMode - + ", mProcStatesCache=" + mUidObserver.mProcStatesCache + + ", mVibrateOn=" + mVibrateOn + ", mVibrationIntensities=" + vibrationIntensitiesString + + ", mProcStatesCache=" + mUidObserver.mProcStatesCache + '}'; } } @@ -446,6 +463,8 @@ final class VibrationSettings { /** Write current settings into given {@link ProtoOutputStream}. */ public void dumpProto(ProtoOutputStream proto) { synchronized (mLock) { + proto.write(VibratorManagerServiceDumpProto.VIBRATE_ON, mVibrateOn); + proto.write(VibratorManagerServiceDumpProto.LOW_POWER_MODE, mBatterySaverMode); proto.write(VibratorManagerServiceDumpProto.ALARM_INTENSITY, getCurrentIntensity(USAGE_ALARM)); proto.write(VibratorManagerServiceDumpProto.ALARM_DEFAULT_INTENSITY, diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java index 16813485b988..316bf2017585 100644 --- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java +++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java @@ -22,7 +22,6 @@ import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; -import android.compat.annotation.Disabled; import android.os.IBinder; import android.os.InputConstants; import android.os.Looper; @@ -47,7 +46,6 @@ class ActivityRecordInputSink { * Feature flag for making Activities consume all touches within their task bounds. */ @ChangeId - @Disabled static final long ENABLE_TOUCH_OPAQUE_ACTIVITIES = 194480991L; private static final String TAG = "ActivityRecordInputSink"; @@ -116,8 +114,7 @@ class ActivityRecordInputSink { private InputWindowHandle createInputWindowHandle() { InputWindowHandle inputWindowHandle = new InputWindowHandle(null, mActivityRecord.getDisplayId()); - inputWindowHandle.replaceTouchableRegionWithCrop( - mActivityRecord.getParentSurfaceControl()); + inputWindowHandle.replaceTouchableRegionWithCrop = true; inputWindowHandle.name = mName; inputWindowHandle.ownerUid = Process.myUid(); inputWindowHandle.ownerPid = Process.myPid(); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index ddd624d115c3..ed9dcef864c6 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -221,10 +221,10 @@ import android.util.SparseArray; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; import android.view.IRecentsAnimationRunner; -import android.view.IRemoteAnimationRunner; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationDefinition; import android.view.WindowManager; +import android.window.BackNavigationInfo; import android.window.IWindowOrganizerController; import android.window.SplashScreenView.SplashScreenViewParcelable; import android.window.TaskSnapshot; @@ -458,7 +458,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { private final ClientLifecycleManager mLifecycleManager; @Nullable - private final BackGestureController mBackGestureController; + private final BackNavigationController mBackNavigationController; private TaskChangeNotificationController mTaskChangeNotificationController; /** The controller for all operations related to locktask. */ @@ -836,8 +836,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mSystemThread = ActivityThread.currentActivityThread(); mUiContext = mSystemThread.getSystemUiContext(); mLifecycleManager = new ClientLifecycleManager(); - mBackGestureController = BackGestureController.isEnabled() ? new BackGestureController() - : null; mVisibleActivityProcessTracker = new VisibleActivityProcessTracker(this); mInternal = new LocalService(); GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version", GL_ES_VERSION_UNDEFINED); @@ -845,6 +843,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mTaskOrganizerController = mWindowOrganizerController.mTaskOrganizerController; mTaskFragmentOrganizerController = mWindowOrganizerController.mTaskFragmentOrganizerController; + mBackNavigationController = BackNavigationController.isEnabled() + ? new BackNavigationController() : null; } public void onSystemReady() { @@ -1022,6 +1022,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mLockTaskController.setWindowManager(wm); mTaskSupervisor.setWindowManager(wm); mRootWindowContainer.setWindowManager(wm); + if (mBackNavigationController != null) { + mBackNavigationController.setTaskSnapshotController(wm.mTaskSnapshotController); + } } } @@ -1768,11 +1771,13 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override - public void startBackPreview(IRemoteAnimationRunner runner) { - if (mBackGestureController == null) { - return; + public BackNavigationInfo startBackNavigation() { + mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS, + "startBackNavigation()"); + if (mBackNavigationController == null) { + return null; } - mBackGestureController.startBackPreview(); + return mBackNavigationController.startBackNavigation(getTopDisplayFocusedRootTask()); } /** diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java new file mode 100644 index 000000000000..a8779fa79675 --- /dev/null +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.WindowConfiguration; +import android.content.ComponentName; +import android.hardware.HardwareBuffer; +import android.os.RemoteCallback; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.util.Slog; +import android.view.SurfaceControl; +import android.window.BackNavigationInfo; +import android.window.TaskSnapshot; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.protolog.common.ProtoLog; + +/** + * Controller to handle actions related to the back gesture on the server side. + */ +class BackNavigationController { + + private static final String TAG = "BackNavigationController"; + private static final String BACK_PREDICTABILITY_PROP = "persist.debug.back_predictability"; + + @Nullable + private TaskSnapshotController mTaskSnapshotController; + + /** + * Returns true if the back predictability feature is enabled + */ + static boolean isEnabled() { + return SystemProperties.getInt(BACK_PREDICTABILITY_PROP, 0) > 0; + } + + /** + * Set up the necessary leashes and build a {@link BackNavigationInfo} instance for an upcoming + * back gesture animation. + * + * @param task the currently focused {@link Task}. + * @return a {@link BackNavigationInfo} instance containing the required leashes and metadata + * for the animation. + */ + @Nullable + BackNavigationInfo startBackNavigation(@NonNull Task task) { + return startBackNavigation(task, null); + } + + /** + * @param tx, a transaction to be used for the attaching the animation leash. + * This is used in tests. If null, the object will be initialized with a new {@link + * android.view.SurfaceControl.Transaction} + * @see #startBackNavigation(Task) + */ + @VisibleForTesting + @Nullable + BackNavigationInfo startBackNavigation(@NonNull Task task, + @Nullable SurfaceControl.Transaction tx) { + + if (tx == null) { + tx = new SurfaceControl.Transaction(); + } + + int backType = BackNavigationInfo.TYPE_UNDEFINED; + Task prevTask = task; + ActivityRecord prev; + WindowContainer<?> removedWindowContainer; + ActivityRecord activityRecord; + SurfaceControl animationLeashParent; + WindowConfiguration taskWindowConfiguration; + SurfaceControl animLeash; + HardwareBuffer screenshotBuffer = null; + int prevTaskId; + int prevUserId; + + synchronized (task.mWmService.mGlobalLock) { + activityRecord = task.topRunningActivity(); + removedWindowContainer = activityRecord; + taskWindowConfiguration = task.getTaskInfo().configuration.windowConfiguration; + + ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "startBackNavigation task=%s, topRunningActivity=%s", + task, activityRecord); + + // IME is visible, back gesture will dismiss it, nothing to preview. + if (task.getDisplayContent().getImeContainer().isVisible()) { + return null; + } + + // Current Activity is home, there is no previous activity to display + if (activityRecord.isActivityTypeHome()) { + return null; + } + + prev = task.getActivity( + (r) -> !r.finishing && r.getTask() == task && !r.isTopRunningActivity()); + + if (prev != null) { + backType = BackNavigationInfo.TYPE_CROSS_ACTIVITY; + } else if (task.returnsToHomeRootTask()) { + prevTask = null; + removedWindowContainer = task; + backType = BackNavigationInfo.TYPE_RETURN_TO_HOME; + } else if (activityRecord.isRootOfTask()) { + // TODO(208789724): Create single source of truth for this, maybe in + // RootWindowContainer + // TODO: Also check Task.shouldUpRecreateTaskLocked() for prev logic + prevTask = task.mRootWindowContainer.getTaskBelow(task); + removedWindowContainer = task; + if (prevTask.isActivityTypeHome()) { + backType = BackNavigationInfo.TYPE_RETURN_TO_HOME; + } else { + prev = prevTask.getTopNonFinishingActivity(); + backType = BackNavigationInfo.TYPE_CROSS_TASK; + } + } + + prevTaskId = prevTask != null ? prevTask.mTaskId : 0; + prevUserId = prevTask != null ? prevTask.mUserId : 0; + + ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Previous Activity is %s", + prev != null ? prev.mActivityComponent : null); + + //TODO(207481538) Remove once the infrastructure to support per-activity screenshot is + // implemented. For now we simply have the mBackScreenshots hash map that dumbly + // saves the screenshots. + if (needsScreenshot(backType) && prev != null && prev.mActivityComponent != null) { + screenshotBuffer = getActivitySnapshot(task, prev.mActivityComponent); + } + + // Prepare a leash to animate the current top window + animLeash = removedWindowContainer.makeAnimationLeash() + .setName("BackPreview Leash") + .setHidden(false) + .setBLASTLayer() + .build(); + removedWindowContainer.reparentSurfaceControl(tx, animLeash); + + animationLeashParent = removedWindowContainer.getAnimationLeashParent(); + } + + SurfaceControl.Builder builder = new SurfaceControl.Builder() + .setName("BackPreview Screenshot") + .setParent(animationLeashParent) + .setHidden(false) + .setBLASTLayer(); + SurfaceControl screenshotSurface = builder.build(); + + // Find a screenshot of the previous activity + + if (needsScreenshot(backType) && prevTask != null) { + if (screenshotBuffer == null) { + screenshotBuffer = getTaskSnapshot(prevTaskId, prevUserId); + } + } + tx.apply(); + + WindowContainer<?> finalRemovedWindowContainer = removedWindowContainer; + try { + activityRecord.token.linkToDeath( + () -> resetSurfaces(finalRemovedWindowContainer), 0); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to link to death", e); + resetSurfaces(removedWindowContainer); + return null; + } + + return new BackNavigationInfo(backType, + animLeash, + screenshotSurface, + screenshotBuffer, + taskWindowConfiguration, + new RemoteCallback(result -> resetSurfaces(finalRemovedWindowContainer + ))); + } + + + private HardwareBuffer getActivitySnapshot(@NonNull Task task, + ComponentName activityComponent) { + // Check if we have a screenshot of the previous activity, indexed by its + // component name. + SurfaceControl.ScreenshotHardwareBuffer backBuffer = task.mBackScreenshots + .get(activityComponent.flattenToString()); + return backBuffer != null ? backBuffer.getHardwareBuffer() : null; + + } + + private HardwareBuffer getTaskSnapshot(int taskId, int userId) { + if (mTaskSnapshotController == null) { + return null; + } + TaskSnapshot snapshot = mTaskSnapshotController.getSnapshot(taskId, + userId, true /* restoreFromDisk */, false /* isLowResolution */); + return snapshot != null ? snapshot.getHardwareBuffer() : null; + } + + private boolean needsScreenshot(int backType) { + switch (backType) { + case BackNavigationInfo.TYPE_RETURN_TO_HOME: + case BackNavigationInfo.TYPE_DIALOG_CLOSE: + return false; + } + return true; + } + + private void resetSurfaces(@NonNull WindowContainer<?> windowContainer) { + synchronized (windowContainer.mWmService.mGlobalLock) { + ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Back: Reset surfaces"); + SurfaceControl.Transaction tx = windowContainer.getSyncTransaction(); + SurfaceControl surfaceControl = windowContainer.getSurfaceControl(); + if (surfaceControl != null) { + tx.reparent(surfaceControl, + windowContainer.getParent().getSurfaceControl()); + tx.apply(); + } + } + } + + void setTaskSnapshotController(@Nullable TaskSnapshotController taskSnapshotController) { + mTaskSnapshotController = taskSnapshotController; + } +} diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index b681a96d2c0f..c8781ae62384 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -39,6 +39,7 @@ import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND; import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OPEN; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES; import static com.android.server.wm.ActivityRecord.State.PAUSED; import static com.android.server.wm.ActivityRecord.State.PAUSING; @@ -100,6 +101,7 @@ import com.android.internal.util.function.pooled.PooledPredicate; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Objects; import java.util.function.Consumer; @@ -254,6 +256,10 @@ class TaskFragment extends WindowContainer<WindowContainer> { private final Rect mTmpStableBounds = new Rect(); private final Rect mTmpNonDecorBounds = new Rect(); + //TODO(b/207481538) Remove once the infrastructure to support per-activity screenshot is + // implemented + HashMap<String, SurfaceControl.ScreenshotHardwareBuffer> mBackScreenshots = new HashMap<>(); + private final EnsureActivitiesVisibleHelper mEnsureActivitiesVisibleHelper = new EnsureActivitiesVisibleHelper(this); private final EnsureVisibleActivitiesConfigHelper mEnsureVisibleActivitiesConfigHelper = @@ -1683,6 +1689,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { @Override void addChild(WindowContainer child, int index) { + ActivityRecord r = topRunningActivity(); mClearedTaskForReuse = false; boolean isAddingActivity = child.asActivityRecord() != null; @@ -1697,6 +1704,18 @@ class TaskFragment extends WindowContainer<WindowContainer> { super.addChild(child, index); if (isAddingActivity && task != null) { + + // TODO(b/207481538): temporary per-activity screenshoting + if (r != null && BackNavigationController.isEnabled()) { + ProtoLog.v(WM_DEBUG_BACK_PREVIEW, "Screenshotting Activity %s", + r.mActivityComponent.flattenToString()); + Rect outBounds = r.getBounds(); + SurfaceControl.ScreenshotHardwareBuffer backBuffer = SurfaceControl.captureLayers( + r.mSurfaceControl, + new Rect(0, 0, outBounds.width(), outBounds.height()), + 1f); + mBackScreenshots.put(r.mActivityComponent.flattenToString(), backBuffer); + } child.asActivityRecord().inHistory = true; task.onDescendantActivityAdded(taskHadActivity, activityType, child.asActivityRecord()); } @@ -2290,6 +2309,14 @@ class TaskFragment extends WindowContainer<WindowContainer> { void removeChild(WindowContainer child, boolean removeSelfIfPossible) { super.removeChild(child); + if (BackNavigationController.isEnabled()) { + //TODO(b/207481538) Remove once the infrastructure to support per-activity screenshot is + // implemented + ActivityRecord r = child.asActivityRecord(); + if (r != null) { + mBackScreenshots.remove(r.mActivityComponent.flattenToString()); + } + } if (removeSelfIfPossible && (!mCreatedByOrganizer || mIsRemovalRequested) && !hasChild()) { removeImmediately("removeLastChild " + child); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index ebe9f9327032..9b87b9d6104b 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -19,6 +19,7 @@ import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.admin.DevicePolicyDrawableResource; import android.app.admin.DevicePolicySafetyChecker; +import android.app.admin.DevicePolicyStringResource; import android.app.admin.FullyManagedDeviceProvisioningParams; import android.app.admin.IDevicePolicyManager; import android.app.admin.ManagedProfileProvisioningParams; @@ -177,4 +178,15 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { int drawableId, int drawableStyle, int drawableSource) { return null; } + + @Override + public void setStrings(@NonNull List<DevicePolicyStringResource> strings){} + + @Override + public void resetStrings(String[] stringIds){} + + @Override + public ParcelableResource getString(String stringId) { + return null; + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java index 534229402888..9a982357afca 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java @@ -20,6 +20,7 @@ import static android.app.admin.DevicePolicyResources.Drawable.Source.UPDATABLE_ import static android.app.admin.DevicePolicyResources.Drawable.Style; import static android.app.admin.DevicePolicyResources.Drawable.Style.UPDATABLE_DRAWABLE_STYLES; import static android.app.admin.DevicePolicyResources.Drawable.UPDATABLE_DRAWABLE_IDS; +import static android.app.admin.DevicePolicyResources.Strings.UPDATABLE_STRING_IDS; import static java.util.Objects.requireNonNull; @@ -27,6 +28,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.admin.DevicePolicyDrawableResource; import android.app.admin.DevicePolicyResources; +import android.app.admin.DevicePolicyStringResource; import android.app.admin.ParcelableResource; import android.os.Environment; import android.util.AtomicFile; @@ -64,14 +66,26 @@ class DeviceManagementResourcesProvider { private static final String ATTR_DRAWABLE_STYLE = "drawable-style"; private static final String ATTR_DRAWABLE_SOURCE = "drawable-source"; private static final String ATTR_DRAWABLE_ID = "drawable-id"; + private static final String TAG_STRING_ENTRY = "string-entry"; + private static final String ATTR_SOURCE_ID = "source-id"; - + /** + * Map of <drawable_id, <style_id, resource_value>> + */ private final Map<Integer, Map<Integer, ParcelableResource>> mUpdatedDrawablesForStyle = new HashMap<>(); + /** + * Map of <drawable_id, <source_id, resource_value>> + */ private final Map<Integer, Map<Integer, ParcelableResource>> mUpdatedDrawablesForSource = new HashMap<>(); + /** + * Map of <string_id, resource_value> + */ + private final Map<String, ParcelableResource> mUpdatedStrings = new HashMap<>(); + private final Object mLock = new Object(); private final Injector mInjector; @@ -114,12 +128,10 @@ class DeviceManagementResourcesProvider { private boolean updateDrawable( int drawableId, int drawableStyle, ParcelableResource updatableResource) { if (!UPDATABLE_DRAWABLE_IDS.contains(drawableId)) { - throw new IllegalArgumentException( - "Can't update drawable resource, invalid drawable " + "id " + drawableId); + Log.w(TAG, "Updating a resource for an unknown drawable id " + drawableId); } if (!UPDATABLE_DRAWABLE_STYLES.contains(drawableStyle)) { - throw new IllegalArgumentException( - "Can't update drawable resource, invalid style id " + drawableStyle); + Log.w(TAG, "Updating a resource for an unknown style id " + drawableStyle); } synchronized (mLock) { if (!mUpdatedDrawablesForStyle.containsKey(drawableId)) { @@ -139,12 +151,10 @@ class DeviceManagementResourcesProvider { private boolean updateDrawableForSource( int drawableId, int drawableSource, ParcelableResource updatableResource) { if (!UPDATABLE_DRAWABLE_IDS.contains(drawableId)) { - throw new IllegalArgumentException("Can't update drawable resource, invalid drawable " - + "id " + drawableId); + Log.w(TAG, "Updating a resource for an unknown drawable id " + drawableId); } if (!UPDATABLE_DRAWABLE_SOURCES.contains(drawableSource)) { - throw new IllegalArgumentException("Can't update drawable resource, invalid source id " - + drawableSource); + Log.w(TAG, "Updating a resource for an unknown source id " + drawableSource); } synchronized (mLock) { if (!mUpdatedDrawablesForSource.containsKey(drawableId)) { @@ -183,19 +193,15 @@ class DeviceManagementResourcesProvider { ParcelableResource getDrawable( int drawableId, int drawableStyle, int drawableSource) { if (!UPDATABLE_DRAWABLE_IDS.contains(drawableId)) { - Log.e(TAG, "Can't get updated drawable resource, invalid drawable id " - + drawableId); - return null; + Log.w(TAG, "Getting an updated resource for an unknown drawable id " + drawableId); } if (!UPDATABLE_DRAWABLE_STYLES.contains(drawableStyle)) { - Log.e(TAG, "Can't get updated drawable resource, invalid style id " + Log.w(TAG, "Getting an updated resource for an unknown drawable style " + drawableStyle); - return null; } if (!UPDATABLE_DRAWABLE_SOURCES.contains(drawableSource)) { - Log.e(TAG, "Can't get updated drawable resource, invalid source id " + Log.w(TAG, "Getting an updated resource for an unknown drawable Source " + drawableSource); - return null; } if (mUpdatedDrawablesForSource.containsKey(drawableId) && mUpdatedDrawablesForSource.get(drawableId).containsKey(drawableSource)) { @@ -216,6 +222,73 @@ class DeviceManagementResourcesProvider { return null; } + /** + * Returns {@code false} if no resources were updated. + */ + boolean updateStrings(@NonNull List<DevicePolicyStringResource> strings) { + boolean updated = false; + for (int i = 0; i < strings.size(); i++) { + String stringId = strings.get(i).getStringId(); + ParcelableResource resource = strings.get(i).getResource(); + + Objects.requireNonNull(resource, "ParcelableResource must be provided."); + updated |= updateString(stringId, resource); + } + if (!updated) { + return false; + } + synchronized (mLock) { + write(); + return true; + } + } + + private boolean updateString(String stringId, ParcelableResource updatableResource) { + if (!UPDATABLE_STRING_IDS.contains(stringId)) { + Log.w(TAG, "Updating a resource for an unknown string id " + stringId); + } + synchronized (mLock) { + ParcelableResource current = mUpdatedStrings.get(stringId); + if (updatableResource.equals(current)) { + return false; + } + mUpdatedStrings.put(stringId, updatableResource); + return true; + } + } + + /** + * Returns {@code false} if no resources were removed. + */ + boolean removeStrings(@NonNull String[] stringIds) { + synchronized (mLock) { + boolean removed = false; + for (int i = 0; i < stringIds.length; i++) { + String stringId = stringIds[i]; + removed |= mUpdatedStrings.remove(stringId) != null; + } + if (!removed) { + return false; + } + write(); + return true; + } + } + + @Nullable + ParcelableResource getString(String stringId) { + if (!UPDATABLE_STRING_IDS.contains(stringId)) { + Log.w(TAG, "Getting an updated resource for an unknown string id " + stringId); + } + + if (mUpdatedStrings.containsKey(stringId)) { + return mUpdatedStrings.get(stringId); + } + + Log.d(TAG, "No updated string found for string id " + stringId); + return null; + } + private void write() { Log.d(TAG, "Writing updated resources to file."); new ResourcesReaderWriter().writeToFileLocked(); @@ -362,6 +435,18 @@ class DeviceManagementResourcesProvider { out.endTag(/* namespace= */ null, TAG_DRAWABLE_SOURCE_ENTRY); } } + if (mUpdatedStrings != null && !mUpdatedStrings.isEmpty()) { + for (Map.Entry<String, ParcelableResource> entry + : mUpdatedStrings.entrySet()) { + out.startTag(/* namespace= */ null, TAG_STRING_ENTRY); + out.attribute( + /* namespace= */ null, + ATTR_SOURCE_ID, + entry.getKey()); + entry.getValue().writeToXmlFile(out); + out.endTag(/* namespace= */ null, TAG_STRING_ENTRY); + } + } } private boolean readInner( @@ -401,6 +486,12 @@ class DeviceManagementResourcesProvider { ParcelableResource.createFromXml(parser)); } break; + case TAG_STRING_ENTRY: + String sourceId = parser.getAttributeValue( + /* namespace= */ null, ATTR_SOURCE_ID); + mUpdatedStrings.put( + sourceId, ParcelableResource.createFromXml(parser)); + break; default: Log.e(TAG, "Unexpected tag: " + tag); return false; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 0f15db182981..7c0d549b2fe0 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -59,6 +59,7 @@ import static android.app.admin.DevicePolicyManager.DELEGATION_SECURITY_LOGGING; import static android.app.admin.DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER; import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_ID; import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE_DRAWABLE; +import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE_STRING; import static android.app.admin.DevicePolicyManager.ID_TYPE_BASE_INFO; import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI; import static android.app.admin.DevicePolicyManager.ID_TYPE_INDIVIDUAL_ATTESTATION; @@ -175,6 +176,7 @@ import android.app.admin.DevicePolicyManager.PersonalAppsSuspensionReason; import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.DevicePolicyManagerLiteInternal; import android.app.admin.DevicePolicySafetyChecker; +import android.app.admin.DevicePolicyStringResource; import android.app.admin.DeviceStateCache; import android.app.admin.FactoryResetProtectionPolicy; import android.app.admin.FullyManagedDeviceProvisioningParams; @@ -18026,4 +18028,50 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mContext.sendBroadcastAsUser(intent, user); } } + + @Override + public void setStrings(@NonNull List<DevicePolicyStringResource> strings) { + Preconditions.checkCallAuthorization(hasCallingOrSelfPermission( + android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES)); + + Objects.requireNonNull(strings, "strings must be provided."); + + mInjector.binderWithCleanCallingIdentity(() -> { + if (mDeviceManagementResourcesProvider.updateStrings(strings)) + sendStringsUpdatedBroadcast( + strings.stream().map(s -> s.getStringId()).toArray(String[]::new)); + }); + } + + @Override + public void resetStrings(String[] stringIds) { + Preconditions.checkCallAuthorization(hasCallingOrSelfPermission( + android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES)); + + mInjector.binderWithCleanCallingIdentity(() -> { + if (mDeviceManagementResourcesProvider.removeStrings(stringIds)) { + sendStringsUpdatedBroadcast(stringIds); + } + }); + } + + @Override + public ParcelableResource getString(String stringId) { + return mInjector.binderWithCleanCallingIdentity(() -> + mDeviceManagementResourcesProvider.getString(stringId)); + } + + private void sendStringsUpdatedBroadcast(String[] stringIds) { + final Intent intent = new Intent(ACTION_DEVICE_POLICY_RESOURCE_UPDATED); + intent.putExtra(EXTRA_RESOURCE_ID, stringIds); + intent.putExtra(EXTRA_RESOURCE_TYPE_STRING, /* value= */ true); + intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); + intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + + List<UserInfo> users = mUserManager.getAliveUsers(); + for (int i = 0; i < users.size(); i++) { + UserHandle user = users.get(i).getUserHandle(); + mContext.sendBroadcastAsUser(intent, user); + } + } } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 599e90b7e4a5..a7b7d1aafb71 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -261,6 +261,8 @@ public final class SystemServer implements Dumpable { "/apex/com.android.os.statsd/javalib/service-statsd.jar"; private static final String CONNECTIVITY_SERVICE_APEX_PATH = "/apex/com.android.tethering/javalib/service-connectivity.jar"; + private static final String NEARBY_SERVICE_APEX_PATH = + "/apex/com.android.nearby/javalib/service-nearby.jar"; private static final String STATS_COMPANION_LIFECYCLE_CLASS = "com.android.server.stats.StatsCompanion$Lifecycle"; private static final String STATS_PULL_ATOM_SERVICE_CLASS = @@ -271,6 +273,8 @@ public final class SystemServer implements Dumpable { "com.android.server.usb.UsbService$Lifecycle"; private static final String MIDI_SERVICE_CLASS = "com.android.server.midi.MidiService$Lifecycle"; + private static final String NEARBY_SERVICE_CLASS = + "com.android.server.nearby.NearbyService"; private static final String WIFI_APEX_SERVICE_JAR_PATH = "/apex/com.android.wifi/javalib/service-wifi.jar"; private static final String WIFI_SERVICE_CLASS = @@ -403,8 +407,6 @@ public final class SystemServer implements Dumpable { private static final String SAFETY_CENTER_SERVICE_CLASS = "com.android.safetycenter.SafetyCenterService"; - private static final String SUPPLEMENTALPROCESS_APEX_PATH = - "/apex/com.android.supplementalprocess/javalib/service-supplementalprocess.jar"; private static final String SUPPLEMENTALPROCESS_SERVICE_CLASS = "com.android.server.supplementalprocess.SupplementalProcessManagerService$Lifecycle"; @@ -1976,6 +1978,16 @@ public final class SystemServer implements Dumpable { } t.traceEnd(); + // Start Nearby Service. + t.traceBegin("StartNearbyService"); + try { + mSystemServiceManager.startServiceFromJar(NEARBY_SERVICE_CLASS, + NEARBY_SERVICE_APEX_PATH); + } catch (Throwable e) { + reportWtf("starting NearbyService", e); + } + t.traceEnd(); + t.traceBegin("StartConnectivityService"); // This has to be called after NetworkManagementService, NetworkStatsService // and NetworkPolicyManager because ConnectivityService needs to take these @@ -2558,8 +2570,7 @@ public final class SystemServer implements Dumpable { // Supplemental Process t.traceBegin("StartSupplementalProcessManagerService"); - mSystemServiceManager.startServiceFromJar(SUPPLEMENTALPROCESS_SERVICE_CLASS, - SUPPLEMENTALPROCESS_APEX_PATH); + mSystemServiceManager.startService(SUPPLEMENTALPROCESS_SERVICE_CLASS); t.traceEnd(); if (safeMode) { diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java index 715fe6e5f85e..6e724792b6e9 100644 --- a/services/midi/java/com/android/server/midi/MidiService.java +++ b/services/midi/java/com/android/server/midi/MidiService.java @@ -18,9 +18,11 @@ package com.android.server.midi; import android.annotation.NonNull; import android.bluetooth.BluetoothDevice; +import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; @@ -33,6 +35,7 @@ import android.media.midi.IMidiDeviceListener; import android.media.midi.IMidiDeviceOpenCallback; import android.media.midi.IMidiDeviceServer; import android.media.midi.IMidiManager; +import android.media.midi.MidiDevice; import android.media.midi.MidiDeviceInfo; import android.media.midi.MidiDeviceService; import android.media.midi.MidiDeviceStatus; @@ -55,6 +58,7 @@ import com.android.server.SystemService.TargetUser; import org.xmlpull.v1.XmlPullParser; import java.io.FileDescriptor; +import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; @@ -96,9 +100,12 @@ public class MidiService extends IMidiManager.Stub { = new HashMap<MidiDeviceInfo, Device>(); // list of all Bluetooth devices, keyed by BluetoothDevice - private final HashMap<BluetoothDevice, Device> mBluetoothDevices + private final HashMap<BluetoothDevice, Device> mBluetoothDevices = new HashMap<BluetoothDevice, Device>(); + private final HashMap<BluetoothDevice, MidiDevice> mBleMidiDeviceMap = + new HashMap<BluetoothDevice, MidiDevice>(); + // list of all devices, keyed by IMidiDeviceServer private final HashMap<IBinder, Device> mDevicesByServer = new HashMap<IBinder, Device>(); @@ -569,10 +576,45 @@ public class MidiService extends IMidiManager.Stub { } } + private final BroadcastReceiver mBleMidiReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action == null) { + Log.w(TAG, "MidiService, action is null"); + return; + } + + switch (action) { + case BluetoothDevice.ACTION_ACL_CONNECTED: { + Log.d(TAG, "ACTION_ACL_CONNECTED"); + BluetoothDevice btDevice = + intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + openBluetoothDevice(btDevice); + } + break; + + case BluetoothDevice.ACTION_ACL_DISCONNECTED: { + Log.d(TAG, "ACTION_ACL_DISCONNECTED"); + BluetoothDevice btDevice = + intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + closeBluetoothDevice(btDevice); + } + break; + } + } + }; + public MidiService(Context context) { mContext = context; mPackageManager = context.getPackageManager(); + // Setup broadcast receivers + IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); + filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); + context.registerReceiver(mBleMidiReceiver, filter); + mBluetoothServiceUid = -1; } @@ -701,9 +743,43 @@ public class MidiService extends IMidiManager.Stub { } } + private void openBluetoothDevice(BluetoothDevice bluetoothDevice) { + Log.d(TAG, "openBluetoothDevice() device: " + bluetoothDevice); + + MidiManager midiManager = mContext.getSystemService(MidiManager.class); + midiManager.openBluetoothDevice(bluetoothDevice, + new MidiManager.OnDeviceOpenedListener() { + @Override + public void onDeviceOpened(MidiDevice device) { + synchronized (mBleMidiDeviceMap) { + mBleMidiDeviceMap.put(bluetoothDevice, device); + } + } + }, null); + } + + private void closeBluetoothDevice(BluetoothDevice bluetoothDevice) { + Log.d(TAG, "closeBluetoothDevice() device: " + bluetoothDevice); + + MidiDevice midiDevice; + synchronized (mBleMidiDeviceMap) { + midiDevice = mBleMidiDeviceMap.remove(bluetoothDevice); + } + + if (midiDevice != null) { + try { + midiDevice.close(); + } catch (IOException ex) { + Log.e(TAG, "Exception closing BLE-MIDI device" + ex); + } + } + } + @Override public void openBluetoothDevice(IBinder token, BluetoothDevice bluetoothDevice, IMidiDeviceOpenCallback callback) { + Log.d(TAG, "openBluetoothDevice()"); + Client client = getClient(token); if (client == null) return; diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java index 0d513bb2d68c..167090693a10 100644 --- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java @@ -18,29 +18,38 @@ package com.android.server.app; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.server.app.GameServiceProviderInstanceImplTest.FakeGameService.GameServiceState; +import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; import android.annotation.Nullable; +import android.app.ActivityManager.RunningTaskInfo; import android.app.IActivityTaskManager; import android.app.ITaskStackListener; import android.content.ComponentName; import android.content.pm.PackageManager; -import android.os.IBinder; +import android.graphics.Rect; import android.os.RemoteException; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.service.games.CreateGameSessionRequest; +import android.service.games.CreateGameSessionResult; +import android.service.games.GameSessionViewHostConfiguration; import android.service.games.GameStartedEvent; import android.service.games.IGameService; import android.service.games.IGameServiceController; import android.service.games.IGameSession; import android.service.games.IGameSessionService; +import android.view.SurfaceControlViewHost.SurfacePackage; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -48,20 +57,20 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.infra.AndroidFuture; import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.FunctionalUtils.ThrowingConsumer; +import com.android.internal.util.Preconditions; +import com.android.server.wm.WindowManagerInternal; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.InOrder; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoSession; import org.mockito.quality.Strictness; import java.util.ArrayList; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; +import java.util.HashMap; /** @@ -72,6 +81,9 @@ import java.util.function.Supplier; @Presubmit public final class GameServiceProviderInstanceImplTest { + private static final GameSessionViewHostConfiguration + DEFAULT_GAME_SESSION_VIEW_HOST_CONFIGURATION = + new GameSessionViewHostConfiguration(1, 500, 800); private static final int USER_ID = 10; private static final String APP_A_PACKAGE = "com.package.app.a"; private static final ComponentName APP_A_MAIN_ACTIVITY = @@ -86,14 +98,14 @@ public final class GameServiceProviderInstanceImplTest { @Mock private IActivityTaskManager mMockActivityTaskManager; @Mock - private IGameService mMockGameService; - @Mock - private IGameSessionService mMockGameSessionService; + private WindowManagerInternal mMockWindowManagerInternal; private FakeGameClassifier mFakeGameClassifier; + private FakeGameService mFakeGameService; private FakeServiceConnector<IGameService> mFakeGameServiceConnector; + private FakeGameSessionService mFakeGameSessionService; private FakeServiceConnector<IGameSessionService> mFakeGameSessionServiceConnector; private ArrayList<ITaskStackListener> mTaskStackListeners; - private InOrder mInOrder; + private ArrayList<RunningTaskInfo> mRunningTaskInfos; @Before public void setUp() throws PackageManager.NameNotFoundException, RemoteException { @@ -102,13 +114,13 @@ public final class GameServiceProviderInstanceImplTest { .strictness(Strictness.LENIENT) .startMocking(); - mInOrder = inOrder(mMockGameService, mMockGameSessionService); - mFakeGameClassifier = new FakeGameClassifier(); mFakeGameClassifier.recordGamePackage(GAME_A_PACKAGE); - mFakeGameServiceConnector = new FakeServiceConnector<>(mMockGameService); - mFakeGameSessionServiceConnector = new FakeServiceConnector<>(mMockGameSessionService); + mFakeGameService = new FakeGameService(); + mFakeGameServiceConnector = new FakeServiceConnector<>(mFakeGameService); + mFakeGameSessionService = new FakeGameSessionService(); + mFakeGameSessionServiceConnector = new FakeServiceConnector<>(mFakeGameSessionService); mTaskStackListeners = new ArrayList<>(); doAnswer(invocation -> { @@ -116,6 +128,10 @@ public final class GameServiceProviderInstanceImplTest { return null; }).when(mMockActivityTaskManager).registerTaskStackListener(any()); + mRunningTaskInfos = new ArrayList<>(); + when(mMockActivityTaskManager.getTasks(anyInt(), anyBoolean(), anyBoolean())).thenReturn( + mRunningTaskInfos); + doAnswer(invocation -> { mTaskStackListeners.remove(invocation.getArgument(0)); return null; @@ -126,6 +142,7 @@ public final class GameServiceProviderInstanceImplTest { ConcurrentUtils.DIRECT_EXECUTOR, mFakeGameClassifier, mMockActivityTaskManager, + mMockWindowManagerInternal, mFakeGameServiceConnector, mFakeGameSessionServiceConnector); } @@ -139,8 +156,7 @@ public final class GameServiceProviderInstanceImplTest { public void start_startsGameSession() throws Exception { mGameServiceProviderInstance.start(); - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verifyNoMoreInteractions(); + assertThat(mFakeGameService.getState()).isEqualTo(GameServiceState.CONNECTED); assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); @@ -149,9 +165,9 @@ public final class GameServiceProviderInstanceImplTest { @Test public void start_multipleTimes_startsGameSessionOnce() throws Exception { mGameServiceProviderInstance.start(); + mGameServiceProviderInstance.start(); - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verifyNoMoreInteractions(); + assertThat(mFakeGameService.getState()).isEqualTo(GameServiceState.CONNECTED); assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); @@ -161,9 +177,10 @@ public final class GameServiceProviderInstanceImplTest { public void stop_neverStarted_doesNothing() throws Exception { mGameServiceProviderInstance.stop(); + + assertThat(mFakeGameService.getState()).isEqualTo(GameServiceState.DISCONNECTED); assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(0); assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); - mInOrder.verifyNoMoreInteractions(); } @Test @@ -171,9 +188,8 @@ public final class GameServiceProviderInstanceImplTest { mGameServiceProviderInstance.start(); mGameServiceProviderInstance.stop(); - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verify(mMockGameService).disconnected(); - mInOrder.verifyNoMoreInteractions(); + assertThat(mFakeGameService.getState()).isEqualTo(GameServiceState.DISCONNECTED); + assertThat(mFakeGameService.getConnectedCount()).isEqualTo(1); assertThat(mFakeGameServiceConnector.getIsConnected()).isFalse(); assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); @@ -187,11 +203,8 @@ public final class GameServiceProviderInstanceImplTest { mGameServiceProviderInstance.start(); mGameServiceProviderInstance.stop(); - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verify(mMockGameService).disconnected(); - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verify(mMockGameService).disconnected(); - mInOrder.verifyNoMoreInteractions(); + assertThat(mFakeGameService.getState()).isEqualTo(GameServiceState.DISCONNECTED); + assertThat(mFakeGameService.getConnectedCount()).isEqualTo(2); assertThat(mFakeGameServiceConnector.getIsConnected()).isFalse(); assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(2); assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); @@ -203,9 +216,8 @@ public final class GameServiceProviderInstanceImplTest { mGameServiceProviderInstance.stop(); mGameServiceProviderInstance.stop(); - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verify(mMockGameService).disconnected(); - mInOrder.verifyNoMoreInteractions(); + assertThat(mFakeGameService.getState()).isEqualTo(GameServiceState.DISCONNECTED); + assertThat(mFakeGameService.getConnectedCount()).isEqualTo(1); assertThat(mFakeGameServiceConnector.getIsConnected()).isFalse(); assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); @@ -215,7 +227,6 @@ public final class GameServiceProviderInstanceImplTest { public void gameTaskStarted_neverStarted_doesNothing() throws Exception { dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); - mInOrder.verifyNoMoreInteractions(); assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(0); assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); } @@ -224,35 +235,25 @@ public final class GameServiceProviderInstanceImplTest { public void gameTaskRemoved_neverStarted_doesNothing() throws Exception { dispatchTaskRemoved(10); - mInOrder.verifyNoMoreInteractions(); assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(0); assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); } @Test - public void gameTaskStarted_afterStopped_doesNothing() throws Exception { + public void gameTaskStarted_afterStopped_doesNotSendGameStartedEvent() throws Exception { mGameServiceProviderInstance.start(); mGameServiceProviderInstance.stop(); dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verify(mMockGameService).disconnected(); - mInOrder.verifyNoMoreInteractions(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isFalse(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); + assertThat(mFakeGameService.getGameStartedEvents()).isEmpty(); } @Test - public void appTaskStarted_doesNothing() throws Exception { + public void appTaskStarted_doesNotSendGameStartedEvent() throws Exception { mGameServiceProviderInstance.start(); dispatchTaskCreated(10, APP_A_MAIN_ACTIVITY); - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verifyNoMoreInteractions(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); + assertThat(mFakeGameService.getGameStartedEvents()).isEmpty(); } @Test @@ -260,26 +261,17 @@ public final class GameServiceProviderInstanceImplTest { mGameServiceProviderInstance.start(); dispatchTaskCreated(10, null); - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verifyNoMoreInteractions(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); + assertThat(mFakeGameService.getGameStartedEvents()).isEmpty(); } @Test - public void gameSessionRequested_withoutTaskDispatch_ignoredAndDoesNotCrash() throws Exception { + public void gameSessionRequested_withoutTaskDispatch_doesNotCrashAndDoesNotCreateGameSession() + throws Exception { mGameServiceProviderInstance.start(); - ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass( - IGameServiceController.class); - verify(mMockGameService).connected(controllerArgumentCaptor.capture()); - controllerArgumentCaptor.getValue().createGameSession(10); - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verifyNoMoreInteractions(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); + mFakeGameService.requestCreateGameSession(10); + + assertThat(mFakeGameSessionService.getCapturedCreateInvocations()).isEmpty(); } @Test @@ -287,427 +279,323 @@ public final class GameServiceProviderInstanceImplTest { mGameServiceProviderInstance.start(); dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(10, GAME_A_PACKAGE))); - mInOrder.verifyNoMoreInteractions(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); + GameStartedEvent expectedGameStartedEvent = new GameStartedEvent(10, GAME_A_PACKAGE); + assertThat(mFakeGameService.getGameStartedEvents()) + .containsExactly(expectedGameStartedEvent).inOrder(); } @Test - public void gameTaskStartedAndSessionRequested_createsGameSession() throws Exception { - CreateGameSessionRequest createGameSessionRequest = - new CreateGameSessionRequest(10, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession10Future = - captureCreateGameSessionFuture(createGameSessionRequest); + public void gameTaskStarted_requestToCreateGameSessionIncludesTaskConfiguration() + throws Exception { + mGameServiceProviderInstance.start(); + startTask(10, GAME_A_MAIN_ACTIVITY); + + mFakeGameService.requestCreateGameSession(10); + + FakeGameSessionService.CapturedCreateInvocation capturedCreateInvocation = + getOnlyElement(mFakeGameSessionService.getCapturedCreateInvocations()); + assertThat(capturedCreateInvocation.mGameSessionViewHostConfiguration) + .isEqualTo(DEFAULT_GAME_SESSION_VIEW_HOST_CONFIGURATION); + } + @Test + public void gameTaskStarted_failsToDetermineTaskOverlayConfiguration_gameSessionNotCreated() + throws Exception { mGameServiceProviderInstance.start(); - ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass( - IGameServiceController.class); - verify(mMockGameService).connected(controllerArgumentCaptor.capture()); - dispatchTaskCreatedAndTriggerSessionRequest(10, GAME_A_MAIN_ACTIVITY, - controllerArgumentCaptor.getValue()); - IGameSessionStub gameSession10 = new IGameSessionStub(); - gameSession10Future.get().complete(gameSession10); - - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(10, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest), any()); - mInOrder.verifyNoMoreInteractions(); + dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); + + mFakeGameService.requestCreateGameSession(10); + + assertThat(mFakeGameSessionService.getCapturedCreateInvocations()).isEmpty(); + } + + @Test + public void gameTaskStartedAndSessionRequested_createsGameSession() throws Exception { + mGameServiceProviderInstance.start(); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + assertThat(gameSession10.mIsDestroyed).isFalse(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); - assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1); } @Test public void gameTaskStartedAndSessionRequested_secondSessionRequest_ignoredAndDoesNotCrash() throws Exception { - CreateGameSessionRequest createGameSessionRequest = - new CreateGameSessionRequest(10, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession10Future = - captureCreateGameSessionFuture(createGameSessionRequest); + mGameServiceProviderInstance.start(); + startTask(10, GAME_A_MAIN_ACTIVITY); + + mFakeGameService.requestCreateGameSession(10); + mFakeGameService.requestCreateGameSession(10); + + CreateGameSessionRequest expectedCreateGameSessionRequest = new CreateGameSessionRequest(10, + GAME_A_PACKAGE); + assertThat(getOnlyElement( + mFakeGameSessionService.getCapturedCreateInvocations()).mCreateGameSessionRequest) + .isEqualTo(expectedCreateGameSessionRequest); + } + @Test + public void gameSessionSuccessfullyCreated_createsTaskOverlay() throws Exception { mGameServiceProviderInstance.start(); - ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass( - IGameServiceController.class); - verify(mMockGameService).connected(controllerArgumentCaptor.capture()); - dispatchTaskCreatedAndTriggerSessionRequest(10, GAME_A_MAIN_ACTIVITY, - controllerArgumentCaptor.getValue()); - IGameSessionStub gameSession10 = new IGameSessionStub(); - gameSession10Future.get().complete(gameSession10); - - controllerArgumentCaptor.getValue().createGameSession(10); - - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(10, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest), any()); - mInOrder.verifyNoMoreInteractions(); - assertThat(gameSession10.mIsDestroyed).isFalse(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); - assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + + verify(mMockWindowManagerInternal).addTaskOverlay(eq(10), eq(mockSurfacePackage10)); + verifyNoMoreInteractions(mMockWindowManagerInternal); } @Test public void gameTaskRemoved_whileAwaitingGameSessionAttached_destroysGameSession() throws Exception { - CreateGameSessionRequest createGameSessionRequest = - new CreateGameSessionRequest(10, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession10Future = - captureCreateGameSessionFuture(createGameSessionRequest); - mGameServiceProviderInstance.start(); - ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass( - IGameServiceController.class); - verify(mMockGameService).connected(controllerArgumentCaptor.capture()); - dispatchTaskCreatedAndTriggerSessionRequest(10, GAME_A_MAIN_ACTIVITY, - controllerArgumentCaptor.getValue()); + + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + dispatchTaskRemoved(10); - IGameSessionStub gameSession10 = new IGameSessionStub(); - gameSession10Future.get().complete(gameSession10); - - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(10, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest), any()); - mInOrder.verifyNoMoreInteractions(); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + assertThat(gameSession10.mIsDestroyed).isTrue(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); - assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isFalse(); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1); } @Test - public void gameTaskRemoved_destroysGameSession() throws Exception { - CreateGameSessionRequest createGameSessionRequest = - new CreateGameSessionRequest(10, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession10Future = - captureCreateGameSessionFuture(createGameSessionRequest); - + public void gameTaskRemoved_whileGameSessionAttached_destroysGameSession() throws Exception { mGameServiceProviderInstance.start(); - ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass( - IGameServiceController.class); - verify(mMockGameService).connected(controllerArgumentCaptor.capture()); - dispatchTaskCreatedAndTriggerSessionRequest(10, GAME_A_MAIN_ACTIVITY, - controllerArgumentCaptor.getValue()); - IGameSessionStub gameSession10 = new IGameSessionStub(); - gameSession10Future.get().complete(gameSession10); + + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + dispatchTaskRemoved(10); - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(10, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest), any()); - mInOrder.verifyNoMoreInteractions(); assertThat(gameSession10.mIsDestroyed).isTrue(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); - assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isFalse(); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1); + } + + @Test + public void gameTaskRemoved_removesTaskOverlay() throws Exception { + mGameServiceProviderInstance.start(); + + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + + stopTask(10); + + verify(mMockWindowManagerInternal).addTaskOverlay(eq(10), eq(mockSurfacePackage10)); + verify(mMockWindowManagerInternal).removeTaskOverlay(eq(10), eq(mockSurfacePackage10)); + verifyNoMoreInteractions(mMockWindowManagerInternal); } @Test public void gameTaskStartedAndSessionRequested_multipleTimes_createsMultipleGameSessions() throws Exception { - CreateGameSessionRequest createGameSessionRequest10 = - new CreateGameSessionRequest(10, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession10Future = - captureCreateGameSessionFuture(createGameSessionRequest10); + mGameServiceProviderInstance.start(); - CreateGameSessionRequest createGameSessionRequest11 = - new CreateGameSessionRequest(11, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession11Future = - captureCreateGameSessionFuture(createGameSessionRequest11); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + + startTask(11, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(11); + + FakeGameSession gameSession11 = new FakeGameSession(); + SurfacePackage mockSurfacePackage11 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(11) + .complete(new CreateGameSessionResult(gameSession11, mockSurfacePackage11)); - mGameServiceProviderInstance.start(); - ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass( - IGameServiceController.class); - verify(mMockGameService).connected(controllerArgumentCaptor.capture()); - dispatchTaskCreatedAndTriggerSessionRequest(10, GAME_A_MAIN_ACTIVITY, - controllerArgumentCaptor.getValue()); - IGameSessionStub gameSession10 = new IGameSessionStub(); - gameSession10Future.get().complete(gameSession10); - - dispatchTaskCreatedAndTriggerSessionRequest(11, GAME_A_MAIN_ACTIVITY, - controllerArgumentCaptor.getValue()); - IGameSessionStub gameSession11 = new IGameSessionStub(); - gameSession11Future.get().complete(gameSession11); - - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(10, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(11, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any()); - mInOrder.verifyNoMoreInteractions(); assertThat(gameSession10.mIsDestroyed).isFalse(); assertThat(gameSession11.mIsDestroyed).isFalse(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); - assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1); } @Test - public void gameTaskStartedTwice_sessionRequestedSecondTimeOnly_createsOneGameSessions() + public void gameTaskStartedTwice_sessionRequestedSecondTimeOnly_createsOneGameSession() throws Exception { - CreateGameSessionRequest createGameSessionRequest11 = - new CreateGameSessionRequest(11, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession11Future = - captureCreateGameSessionFuture(createGameSessionRequest11); - - // The game task is started twice, but a session is requested only for the second one. mGameServiceProviderInstance.start(); - ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass( - IGameServiceController.class); - verify(mMockGameService).connected(controllerArgumentCaptor.capture()); - dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); - dispatchTaskCreatedAndTriggerSessionRequest(11, GAME_A_MAIN_ACTIVITY, - controllerArgumentCaptor.getValue()); - IGameSessionStub gameSession11 = new IGameSessionStub(); - gameSession11Future.get().complete(gameSession11); - - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(10, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(11, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any()); - mInOrder.verifyNoMoreInteractions(); - assertThat(gameSession11.mIsDestroyed).isFalse(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); - assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1); + startTask(10, GAME_A_MAIN_ACTIVITY); + startTask(11, GAME_A_MAIN_ACTIVITY); + + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + + assertThat(gameSession10.mIsDestroyed).isFalse(); + assertThat(mFakeGameSessionService.getCapturedCreateInvocations()).hasSize(1); } @Test - public void gameTaskRemoved_afterMultipleCreated_destroysOnlyThatGameSession() + public void gameTaskRemoved_multipleSessions_destroysOnlyThatGameSession() throws Exception { - CreateGameSessionRequest createGameSessionRequest10 = - new CreateGameSessionRequest(10, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession10Future = - captureCreateGameSessionFuture(createGameSessionRequest10); + mGameServiceProviderInstance.start(); - CreateGameSessionRequest createGameSessionRequest11 = - new CreateGameSessionRequest(11, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession11Future = - captureCreateGameSessionFuture(createGameSessionRequest11); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); - mGameServiceProviderInstance.start(); - ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass( - IGameServiceController.class); - verify(mMockGameService).connected(controllerArgumentCaptor.capture()); - dispatchTaskCreatedAndTriggerSessionRequest(10, GAME_A_MAIN_ACTIVITY, - controllerArgumentCaptor.getValue()); - IGameSessionStub gameSession10 = new IGameSessionStub(); - gameSession10Future.get().complete(gameSession10); - - dispatchTaskCreatedAndTriggerSessionRequest(11, GAME_A_MAIN_ACTIVITY, - controllerArgumentCaptor.getValue()); - IGameSessionStub gameSession11 = new IGameSessionStub(); - gameSession11Future.get().complete(gameSession11); + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + + startTask(11, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(11); + + FakeGameSession gameSession11 = new FakeGameSession(); + SurfacePackage mockSurfacePackage11 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(11) + .complete(new CreateGameSessionResult(gameSession11, mockSurfacePackage11)); dispatchTaskRemoved(10); - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(10, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(11, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any()); - mInOrder.verifyNoMoreInteractions(); assertThat(gameSession10.mIsDestroyed).isTrue(); assertThat(gameSession11.mIsDestroyed).isFalse(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1); } @Test - public void allGameTasksRemoved_destroysAllGameSessions() throws Exception { - CreateGameSessionRequest createGameSessionRequest10 = - new CreateGameSessionRequest(10, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession10Future = - captureCreateGameSessionFuture(createGameSessionRequest10); + public void allGameTasksRemoved_destroysAllGameSessionsAndGameSessionServiceIsDisconnected() { + mGameServiceProviderInstance.start(); - CreateGameSessionRequest createGameSessionRequest11 = - new CreateGameSessionRequest(11, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession11Future = - captureCreateGameSessionFuture(createGameSessionRequest11); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); - mGameServiceProviderInstance.start(); - ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass( - IGameServiceController.class); - verify(mMockGameService).connected(controllerArgumentCaptor.capture()); - dispatchTaskCreatedAndTriggerSessionRequest(10, GAME_A_MAIN_ACTIVITY, - controllerArgumentCaptor.getValue()); - IGameSessionStub gameSession10 = new IGameSessionStub(); - gameSession10Future.get().complete(gameSession10); - - dispatchTaskCreatedAndTriggerSessionRequest(11, GAME_A_MAIN_ACTIVITY, - controllerArgumentCaptor.getValue()); - IGameSessionStub gameSession11 = new IGameSessionStub(); - gameSession11Future.get().complete(gameSession11); + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + + startTask(11, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(11); + + FakeGameSession gameSession11 = new FakeGameSession(); + SurfacePackage mockSurfacePackage11 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(11) + .complete(new CreateGameSessionResult(gameSession11, mockSurfacePackage11)); dispatchTaskRemoved(10); dispatchTaskRemoved(11); - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(10, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(11, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any()); - mInOrder.verifyNoMoreInteractions(); assertThat(gameSession10.mIsDestroyed).isTrue(); assertThat(gameSession11.mIsDestroyed).isTrue(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isFalse(); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1); } @Test - public void gameTasksCreatedAndSessionsReq_afterAllPreviousSessionsDestroyed_createsSession() + public void createSessionRequested_afterAllPreviousSessionsDestroyed_createsSession() throws Exception { - CreateGameSessionRequest createGameSessionRequest10 = - new CreateGameSessionRequest(10, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession10Future = - captureCreateGameSessionFuture(createGameSessionRequest10); + mGameServiceProviderInstance.start(); - CreateGameSessionRequest createGameSessionRequest11 = - new CreateGameSessionRequest(11, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession11Future = - captureCreateGameSessionFuture(createGameSessionRequest11); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); - CreateGameSessionRequest createGameSessionRequest12 = - new CreateGameSessionRequest(12, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> unusedGameSession12Future = - captureCreateGameSessionFuture(createGameSessionRequest12); + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); - mGameServiceProviderInstance.start(); - ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass( - IGameServiceController.class); - verify(mMockGameService).connected(controllerArgumentCaptor.capture()); - dispatchTaskCreatedAndTriggerSessionRequest(10, GAME_A_MAIN_ACTIVITY, - controllerArgumentCaptor.getValue()); - IGameSessionStub gameSession10 = new IGameSessionStub(); - gameSession10Future.get().complete(gameSession10); - - dispatchTaskCreatedAndTriggerSessionRequest(11, GAME_A_MAIN_ACTIVITY, - controllerArgumentCaptor.getValue()); - IGameSessionStub gameSession11 = new IGameSessionStub(); - gameSession11Future.get().complete(gameSession11); + startTask(11, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(11); + + FakeGameSession gameSession11 = new FakeGameSession(); + SurfacePackage mockSurfacePackage11 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(11) + .complete(new CreateGameSessionResult(gameSession11, mockSurfacePackage11)); dispatchTaskRemoved(10); dispatchTaskRemoved(11); - dispatchTaskCreatedAndTriggerSessionRequest(12, GAME_A_MAIN_ACTIVITY, - controllerArgumentCaptor.getValue()); - IGameSessionStub gameSession12 = new IGameSessionStub(); - gameSession11Future.get().complete(gameSession12); - - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(10, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(11, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(12, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest12), any()); - mInOrder.verifyNoMoreInteractions(); + startTask(12, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(12); + + FakeGameSession gameSession12 = new FakeGameSession(); + SurfacePackage mockSurfacePackage12 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(12) + .complete(new CreateGameSessionResult(gameSession12, mockSurfacePackage12)); + assertThat(gameSession10.mIsDestroyed).isTrue(); assertThat(gameSession11.mIsDestroyed).isTrue(); assertThat(gameSession12.mIsDestroyed).isFalse(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(2); } @Test public void stop_severalActiveGameSessions_destroysGameSessionsAndUnbinds() throws Exception { - CreateGameSessionRequest createGameSessionRequest10 = - new CreateGameSessionRequest(10, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession10Future = - captureCreateGameSessionFuture(createGameSessionRequest10); + mGameServiceProviderInstance.start(); - CreateGameSessionRequest createGameSessionRequest11 = - new CreateGameSessionRequest(11, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession11Future = - captureCreateGameSessionFuture(createGameSessionRequest11); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + + startTask(11, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(11); + + FakeGameSession gameSession11 = new FakeGameSession(); + SurfacePackage mockSurfacePackage11 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(11) + .complete(new CreateGameSessionResult(gameSession11, mockSurfacePackage11)); - mGameServiceProviderInstance.start(); - ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass( - IGameServiceController.class); - verify(mMockGameService).connected(controllerArgumentCaptor.capture()); - dispatchTaskCreatedAndTriggerSessionRequest(10, GAME_A_MAIN_ACTIVITY, - controllerArgumentCaptor.getValue()); - IGameSessionStub gameSession10 = new IGameSessionStub(); - gameSession10Future.get().complete(gameSession10); - dispatchTaskCreatedAndTriggerSessionRequest(11, GAME_A_MAIN_ACTIVITY, - controllerArgumentCaptor.getValue()); - IGameSessionStub gameSession11 = new IGameSessionStub(); - gameSession11Future.get().complete(gameSession11); mGameServiceProviderInstance.stop(); - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(10, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(11, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any()); - mInOrder.verify(mMockGameService).disconnected(); - mInOrder.verifyNoMoreInteractions(); assertThat(gameSession10.mIsDestroyed).isTrue(); assertThat(gameSession11.mIsDestroyed).isTrue(); assertThat(mFakeGameServiceConnector.getIsConnected()).isFalse(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isFalse(); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1); } - private Supplier<AndroidFuture<IBinder>> captureCreateGameSessionFuture( - CreateGameSessionRequest expectedCreateGameSessionRequest) throws Exception { - final AtomicReference<AndroidFuture<IBinder>> gameSessionFuture = new AtomicReference<>(); - doAnswer(invocation -> { - gameSessionFuture.set(invocation.getArgument(1)); - return null; - }).when(mMockGameSessionService).create(eq(expectedCreateGameSessionRequest), any()); + private void startTask(int taskId, ComponentName componentName) { + RunningTaskInfo runningTaskInfo = new RunningTaskInfo(); + runningTaskInfo.taskId = taskId; + runningTaskInfo.displayId = 1; + runningTaskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 500, 800)); + mRunningTaskInfos.add(runningTaskInfo); - return gameSessionFuture::get; + dispatchTaskCreated(taskId, componentName); + } + + private void stopTask(int taskId) { + mRunningTaskInfos.removeIf(runningTaskInfo -> runningTaskInfo.taskId == taskId); + dispatchTaskRemoved(taskId); } + private void dispatchTaskRemoved(int taskId) { dispatchTaskChangeEvent(taskStackListener -> { taskStackListener.onTaskRemoved(taskId); }); } - private void dispatchTaskCreatedAndTriggerSessionRequest(int taskId, - @Nullable ComponentName componentName, IGameServiceController gameServiceController) - throws Exception { - dispatchTaskCreated(taskId, componentName); - gameServiceController.createGameSession(taskId); - } - private void dispatchTaskCreated(int taskId, @Nullable ComponentName componentName) { dispatchTaskChangeEvent(taskStackListener -> { taskStackListener.onTaskCreated(taskId, componentName); @@ -721,7 +609,113 @@ public final class GameServiceProviderInstanceImplTest { } } - private static class IGameSessionStub extends IGameSession.Stub { + static final class FakeGameService extends IGameService.Stub { + private IGameServiceController mGameServiceController; + + public enum GameServiceState { + DISCONNECTED, + CONNECTED, + } + + private ArrayList<GameStartedEvent> mGameStartedEvents = new ArrayList<>(); + private int mConnectedCount = 0; + private GameServiceState mGameServiceState = GameServiceState.DISCONNECTED; + + public GameServiceState getState() { + return mGameServiceState; + } + + public int getConnectedCount() { + return mConnectedCount; + } + + public ArrayList<GameStartedEvent> getGameStartedEvents() { + return mGameStartedEvents; + } + + @Override + public void connected(IGameServiceController gameServiceController) { + Preconditions.checkState(mGameServiceState == GameServiceState.DISCONNECTED); + + mGameServiceState = GameServiceState.CONNECTED; + mConnectedCount += 1; + mGameServiceController = gameServiceController; + } + + @Override + public void disconnected() { + Preconditions.checkState(mGameServiceState == GameServiceState.CONNECTED); + + mGameServiceState = GameServiceState.DISCONNECTED; + mGameServiceController = null; + } + + @Override + public void gameStarted(GameStartedEvent gameStartedEvent) { + Preconditions.checkState(mGameServiceState == GameServiceState.CONNECTED); + + mGameStartedEvents.add(gameStartedEvent); + } + + public void requestCreateGameSession(int task) { + Preconditions.checkState(mGameServiceState == GameServiceState.CONNECTED); + + try { + mGameServiceController.createGameSession(task); + } catch (RemoteException ex) { + throw new AssertionError(ex); + } + } + } + + static final class FakeGameSessionService extends IGameSessionService.Stub { + + private final ArrayList<CapturedCreateInvocation> mCapturedCreateInvocations = + new ArrayList<>(); + private final HashMap<Integer, AndroidFuture<CreateGameSessionResult>> + mPendingCreateGameSessionResultFutures = + new HashMap<>(); + + public static final class CapturedCreateInvocation { + private final CreateGameSessionRequest mCreateGameSessionRequest; + private final GameSessionViewHostConfiguration mGameSessionViewHostConfiguration; + + CapturedCreateInvocation( + CreateGameSessionRequest createGameSessionRequest, + GameSessionViewHostConfiguration gameSessionViewHostConfiguration) { + mCreateGameSessionRequest = createGameSessionRequest; + mGameSessionViewHostConfiguration = gameSessionViewHostConfiguration; + } + } + + public ArrayList<CapturedCreateInvocation> getCapturedCreateInvocations() { + return mCapturedCreateInvocations; + } + + public AndroidFuture<CreateGameSessionResult> removePendingFutureForTaskId(int taskId) { + return mPendingCreateGameSessionResultFutures.remove(taskId); + } + + @Override + public void create( + CreateGameSessionRequest createGameSessionRequest, + GameSessionViewHostConfiguration gameSessionViewHostConfiguration, + AndroidFuture createGameSessionResultFuture) { + + mCapturedCreateInvocations.add( + new CapturedCreateInvocation( + createGameSessionRequest, + gameSessionViewHostConfiguration)); + + Preconditions.checkState(!mPendingCreateGameSessionResultFutures.containsKey( + createGameSessionRequest.getTaskId())); + mPendingCreateGameSessionResultFutures.put( + createGameSessionRequest.getTaskId(), + createGameSessionResultFuture); + } + } + + private static class FakeGameSession extends IGameSession.Stub { boolean mIsDestroyed = false; @Override diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt index c2e0a04e3caa..555f4b8b5cac 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt @@ -216,6 +216,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { val displayMetrics: DisplayMetrics = mock() val domainVerificationManagerInternal: DomainVerificationManagerInternal = mock() val handler = TestHandler(null) + val defaultAppProvider: DefaultAppProvider = mock() } companion object { @@ -294,6 +295,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { whenever(mocks.injector.domainVerificationManagerInternal) .thenReturn(mocks.domainVerificationManagerInternal) whenever(mocks.injector.handler) { mocks.handler } + whenever(mocks.injector.defaultAppProvider) { mocks.defaultAppProvider } wheneverStatic { SystemConfig.getInstance() }.thenReturn(mocks.systemConfig) whenever(mocks.systemConfig.availableFeatures).thenReturn(DEFAULT_AVAILABLE_FEATURES_MAP) whenever(mocks.systemConfig.sharedLibraries).thenReturn(DEFAULT_SHARED_LIBRARIES_LIST) diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt new file mode 100644 index 000000000000..fe7e2d9eb047 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt @@ -0,0 +1,507 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm + +import android.content.Intent +import android.content.pm.PackageManagerInternal +import android.content.pm.SuspendDialogInfo +import android.os.Binder +import android.os.Build +import android.os.Bundle +import android.os.PersistableBundle +import android.os.UserHandle +import android.os.UserManager +import android.util.ArrayMap +import android.util.SparseArray +import com.android.server.pm.pkg.PackageStateInternal +import com.android.server.testutils.TestHandler +import com.android.server.testutils.any +import com.android.server.testutils.eq +import com.android.server.testutils.nullable +import com.android.server.testutils.whenever +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.argThat +import org.mockito.Mockito.spy +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@RunWith(JUnit4::class) +class SuspendPackageHelperTest { + + companion object { + const val TEST_PACKAGE_1 = "com.android.test.package1" + const val TEST_PACKAGE_2 = "com.android.test.package2" + const val DEVICE_OWNER_PACKAGE = "com.android.test.owner" + const val NONEXISTENT_PACKAGE = "com.android.test.nonexistent" + const val DEVICE_ADMIN_PACKAGE = "com.android.test.known.device.admin" + const val DEFAULT_HOME_PACKAGE = "com.android.test.known.home" + const val DIALER_PACKAGE = "com.android.test.known.dialer" + const val INSTALLER_PACKAGE = "com.android.test.known.installer" + const val UNINSTALLER_PACKAGE = "com.android.test.known.uninstaller" + const val VERIFIER_PACKAGE = "com.android.test.known.verifier" + const val PERMISSION_CONTROLLER_PACKAGE = "com.android.test.known.permission" + const val TEST_USER_ID = 0 + } + + lateinit var pms: PackageManagerService + lateinit var suspendPackageHelper: SuspendPackageHelper + lateinit var testHandler: TestHandler + lateinit var defaultAppProvider: DefaultAppProvider + lateinit var packageSetting1: PackageStateInternal + lateinit var packageSetting2: PackageStateInternal + lateinit var ownerSetting: PackageStateInternal + lateinit var packagesToSuspend: Array<String> + lateinit var uidsToSuspend: IntArray + + @Mock + lateinit var broadcastHelper: BroadcastHelper + @Mock + lateinit var protectedPackages: ProtectedPackages + + @Captor + lateinit var bundleCaptor: ArgumentCaptor<Bundle> + + @Rule + @JvmField + val rule = MockSystemRule() + var deviceOwnerUid = 0 + + @Before + @Throws(Exception::class) + fun setup() { + MockitoAnnotations.initMocks(this) + rule.system().stageNominalSystemState() + pms = spy(createPackageManagerService( + TEST_PACKAGE_1, TEST_PACKAGE_2, DEVICE_OWNER_PACKAGE, DEVICE_ADMIN_PACKAGE, + DEFAULT_HOME_PACKAGE, DIALER_PACKAGE, INSTALLER_PACKAGE, UNINSTALLER_PACKAGE, + VERIFIER_PACKAGE, PERMISSION_CONTROLLER_PACKAGE)) + suspendPackageHelper = SuspendPackageHelper( + pms, rule.mocks().injector, broadcastHelper, protectedPackages) + defaultAppProvider = rule.mocks().defaultAppProvider + testHandler = rule.mocks().handler + packageSetting1 = pms.getPackageStateInternal(TEST_PACKAGE_1)!! + packageSetting2 = pms.getPackageStateInternal(TEST_PACKAGE_2)!! + ownerSetting = pms.getPackageStateInternal(DEVICE_OWNER_PACKAGE)!! + deviceOwnerUid = UserHandle.getUid(TEST_USER_ID, ownerSetting.appId) + packagesToSuspend = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2) + uidsToSuspend = intArrayOf(packageSetting1.appId, packageSetting2.appId) + + whenever(protectedPackages.getDeviceOwnerOrProfileOwnerPackage(eq(TEST_USER_ID))) + .thenReturn(DEVICE_OWNER_PACKAGE) + whenever(rule.mocks().userManagerService.hasUserRestriction( + eq(UserManager.DISALLOW_APPS_CONTROL), eq(TEST_USER_ID))).thenReturn(true) + whenever(rule.mocks().userManagerService.hasUserRestriction( + eq(UserManager.DISALLOW_UNINSTALL_APPS), eq(TEST_USER_ID))).thenReturn(true) + mockKnownPackages(pms) + } + + @Test + fun setPackagesSuspended() { + val targetPackages = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2) + val failedNames = suspendPackageHelper.setPackagesSuspended(targetPackages, + true /* suspended */, null /* appExtras */, null /* launcherExtras */, + null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) + testHandler.flush() + + verify(pms).scheduleWritePackageRestrictionsLocked(eq(TEST_USER_ID)) + verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_SUSPENDED), + nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(), + nullable(), nullable(), nullable()) + verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_SUSPENDED), nullable(), + nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(), nullable(), nullable()) + verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_SUSPENDED), nullable(), + nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(), nullable(), nullable()) + + var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST) + assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2) + assertThat(failedNames).isEmpty() + } + + @Test + fun setPackagesSuspended_emptyPackageName() { + var failedNames = suspendPackageHelper.setPackagesSuspended(null /* packageNames */, + true /* suspended */, null /* appExtras */, null /* launcherExtras */, + null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) + + assertThat(failedNames).isNull() + + failedNames = suspendPackageHelper.setPackagesSuspended(arrayOfNulls(0), + true /* suspended */, null /* appExtras */, null /* launcherExtras */, + null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) + + assertThat(failedNames).isEmpty() + } + + @Test + fun setPackagesSuspended_callerIsNotAllowed() { + val failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_2), + true /* suspended */, null /* appExtras */, null /* launcherExtras */, + null /* dialogInfo */, TEST_PACKAGE_1, TEST_USER_ID, Binder.getCallingUid()) + + assertThat(failedNames).asList().hasSize(1) + assertThat(failedNames).asList().contains(TEST_PACKAGE_2) + } + + @Test + fun setPackagesSuspended_callerSuspendItself() { + val failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(DEVICE_OWNER_PACKAGE), + true /* suspended */, null /* appExtras */, null /* launcherExtras */, + null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) + + assertThat(failedNames).asList().hasSize(1) + assertThat(failedNames).asList().contains(DEVICE_OWNER_PACKAGE) + } + + @Test + fun setPackagesSuspended_nonexistentPackage() { + val failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(NONEXISTENT_PACKAGE), + true /* suspended */, null /* appExtras */, null /* launcherExtras */, + null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) + + assertThat(failedNames).asList().hasSize(1) + assertThat(failedNames).asList().contains(NONEXISTENT_PACKAGE) + } + + @Test + fun setPackagesSuspended_knownPackages() { + val knownPackages = arrayOf(DEVICE_ADMIN_PACKAGE, DEFAULT_HOME_PACKAGE, DIALER_PACKAGE, + INSTALLER_PACKAGE, UNINSTALLER_PACKAGE, VERIFIER_PACKAGE, PERMISSION_CONTROLLER_PACKAGE) + val failedNames = suspendPackageHelper.setPackagesSuspended(knownPackages, + true /* suspended */, null /* appExtras */, null /* launcherExtras */, + null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)!! + + assertThat(failedNames.size).isEqualTo(knownPackages.size) + for (pkg in knownPackages) { + assertThat(failedNames).asList().contains(pkg) + } + } + + @Test + fun setPackagesUnsuspended() { + val targetPackages = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2) + var failedNames = suspendPackageHelper.setPackagesSuspended(targetPackages, + true /* suspended */, null /* appExtras */, null /* launcherExtras */, + null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) + testHandler.flush() + assertThat(failedNames).isEmpty() + failedNames = suspendPackageHelper.setPackagesSuspended(targetPackages, + false /* suspended */, null /* appExtras */, null /* launcherExtras */, + null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) + testHandler.flush() + + verify(pms, times(2)).scheduleWritePackageRestrictionsLocked(eq(TEST_USER_ID)) + verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_UNSUSPENDED), + nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(), + nullable(), nullable(), nullable()) + verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED), + nullable(), nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(), + nullable(), nullable()) + verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED), + nullable(), nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(), + nullable(), nullable()) + + var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST) + assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2) + assertThat(failedNames).isEmpty() + } + + @Test + fun getUnsuspendablePackagesForUser() { + val suspendables = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2) + val unsuspendables = arrayOf(DEVICE_ADMIN_PACKAGE, DEFAULT_HOME_PACKAGE, DIALER_PACKAGE, + INSTALLER_PACKAGE, UNINSTALLER_PACKAGE, VERIFIER_PACKAGE, PERMISSION_CONTROLLER_PACKAGE) + val results = suspendPackageHelper.getUnsuspendablePackagesForUser( + suspendables + unsuspendables, TEST_USER_ID, deviceOwnerUid) + + assertThat(results.size).isEqualTo(unsuspendables.size) + for (pkg in unsuspendables) { + assertThat(results).asList().contains(pkg) + } + } + + @Test + fun getUnsuspendablePackagesForUser_callerIsNotAllowed() { + val suspendables = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2) + val results = suspendPackageHelper.getUnsuspendablePackagesForUser( + suspendables, TEST_USER_ID, Binder.getCallingUid()) + + assertThat(results.size).isEqualTo(suspendables.size) + for (pkg in suspendables) { + assertThat(results).asList().contains(pkg) + } + } + + @Test + fun getSuspendedPackageAppExtras() { + val appExtras = PersistableBundle() + appExtras.putString(TEST_PACKAGE_1, TEST_PACKAGE_1) + var failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_1), + true /* suspended */, appExtras, null /* launcherExtras */, + null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) + testHandler.flush() + assertThat(failedNames).isEmpty() + + val result = suspendPackageHelper.getSuspendedPackageAppExtras( + TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)!! + + assertThat(result.getString(TEST_PACKAGE_1)).isEqualTo(TEST_PACKAGE_1) + } + + @Test + fun removeSuspensionsBySuspendingPackage() { + val appExtras = PersistableBundle() + appExtras.putString(TEST_PACKAGE_1, TEST_PACKAGE_2) + val targetPackages = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2) + var failedNames = suspendPackageHelper.setPackagesSuspended(targetPackages, + true /* suspended */, appExtras, null /* launcherExtras */, + null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) + testHandler.flush() + assertThat(failedNames).isEmpty() + assertThat(suspendPackageHelper.getSuspendingPackage( + TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE) + assertThat(suspendPackageHelper.getSuspendingPackage( + TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE) + assertThat(suspendPackageHelper.getSuspendedPackageAppExtras( + TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isNotNull() + assertThat(suspendPackageHelper.getSuspendedPackageAppExtras( + TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isNotNull() + + suspendPackageHelper.removeSuspensionsBySuspendingPackage(targetPackages, + { suspendingPackage -> suspendingPackage == DEVICE_OWNER_PACKAGE }, TEST_USER_ID) + + testHandler.flush() + verify(pms, times(2)).scheduleWritePackageRestrictionsLocked(eq(TEST_USER_ID)) + verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_UNSUSPENDED), + nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(), + nullable(), nullable(), nullable()) + verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED), + nullable(), nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(), + nullable(), nullable()) + verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED), + nullable(), nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(), + nullable(), nullable()) + + assertThat(suspendPackageHelper.getSuspendingPackage( + TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isNull() + assertThat(suspendPackageHelper.getSuspendingPackage( + TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isNull() + assertThat(suspendPackageHelper.getSuspendedPackageAppExtras( + TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isNull() + assertThat(suspendPackageHelper.getSuspendedPackageAppExtras( + TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isNull() + } + + @Test + fun getSuspendedPackageLauncherExtras() { + val launcherExtras = PersistableBundle() + launcherExtras.putString(TEST_PACKAGE_2, TEST_PACKAGE_2) + var failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_2), + true /* suspended */, null /* appExtras */, launcherExtras, + null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) + testHandler.flush() + assertThat(failedNames).isEmpty() + + val result = suspendPackageHelper.getSuspendedPackageLauncherExtras( + TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)!! + + assertThat(result.getString(TEST_PACKAGE_2)).isEqualTo(TEST_PACKAGE_2) + } + + @Test + fun isPackageSuspended() { + var failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_1), + true /* suspended */, null /* appExtras */, null /* launcherExtras */, + null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) + testHandler.flush() + assertThat(failedNames).isEmpty() + + assertThat(suspendPackageHelper.isPackageSuspended( + TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isTrue() + } + + @Test + fun getSuspendingPackage() { + val launcherExtras = PersistableBundle() + launcherExtras.putString(TEST_PACKAGE_2, TEST_PACKAGE_2) + var failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_2), + true /* suspended */, null /* appExtras */, launcherExtras, + null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) + testHandler.flush() + assertThat(failedNames).isEmpty() + + assertThat(suspendPackageHelper.getSuspendingPackage( + TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE) + } + + @Test + fun getSuspendedDialogInfo() { + val dialogInfo = SuspendDialogInfo.Builder() + .setTitle(TEST_PACKAGE_1).build() + var failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_1), + true /* suspended */, null /* appExtras */, null /* launcherExtras */, + dialogInfo, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) + testHandler.flush() + assertThat(failedNames).isEmpty() + + val result = suspendPackageHelper.getSuspendedDialogInfo( + TEST_PACKAGE_1, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)!! + + assertThat(result.title).isEqualTo(TEST_PACKAGE_1) + } + + @Test + @Throws(Exception::class) + fun sendPackagesSuspendedForUser_withSameVisibilityAllowList() { + mockAllowList(packageSetting1, allowList(10001, 10002, 10003)) + mockAllowList(packageSetting2, allowList(10001, 10002, 10003)) + + suspendPackageHelper.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED, + packagesToSuspend, uidsToSuspend, TEST_USER_ID) + testHandler.flush() + verify(broadcastHelper).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(), + anyInt(), nullable(), nullable(), any(), nullable(), any(), nullable()) + + var changedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST) + var changedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST) + assertThat(changedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2) + assertThat(changedUids).asList().containsExactly( + packageSetting1.appId, packageSetting2.appId) + } + + @Test + @Throws(Exception::class) + fun sendPackagesSuspendedForUser_withDifferentVisibilityAllowList() { + mockAllowList(packageSetting1, allowList(10001, 10002, 10003)) + mockAllowList(packageSetting2, allowList(10001, 10002, 10007)) + + suspendPackageHelper.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED, + packagesToSuspend, uidsToSuspend, TEST_USER_ID) + testHandler.flush() + verify(broadcastHelper, times(2)).sendPackageBroadcast( + any(), nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(), + nullable(), any(), nullable()) + + bundleCaptor.allValues.forEach { + var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST) + var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST) + assertThat(changedPackages?.size).isEqualTo(1) + assertThat(changedUids?.size).isEqualTo(1) + assertThat(changedPackages?.get(0)).isAnyOf(TEST_PACKAGE_1, TEST_PACKAGE_2) + assertThat(changedUids?.get(0)).isAnyOf(packageSetting1.appId, packageSetting2.appId) + } + } + + @Test + @Throws(Exception::class) + fun sendPackagesSuspendedForUser_withNullVisibilityAllowList() { + mockAllowList(packageSetting1, allowList(10001, 10002, 10003)) + mockAllowList(packageSetting2, null) + + suspendPackageHelper.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED, + packagesToSuspend, uidsToSuspend, TEST_USER_ID) + testHandler.flush() + verify(broadcastHelper, times(2)).sendPackageBroadcast( + any(), nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(), + nullable(), nullable(), nullable()) + + bundleCaptor.allValues.forEach { + var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST) + var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST) + assertThat(changedPackages?.size).isEqualTo(1) + assertThat(changedUids?.size).isEqualTo(1) + assertThat(changedPackages?.get(0)).isAnyOf(TEST_PACKAGE_1, TEST_PACKAGE_2) + assertThat(changedUids?.get(0)).isAnyOf(packageSetting1.appId, packageSetting2.appId) + } + } + + @Test + @Throws(Exception::class) + fun sendPackagesSuspendModifiedForUser() { + suspendPackageHelper.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED, + packagesToSuspend, uidsToSuspend, TEST_USER_ID) + testHandler.flush() + verify(broadcastHelper).sendPackageBroadcast( + eq(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED), nullable(), bundleCaptor.capture(), + anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable()) + + var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST) + var modifiedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST) + assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2) + assertThat(modifiedUids).asList().containsExactly( + packageSetting1.appId, packageSetting2.appId) + } + + private fun allowList(vararg uids: Int) = SparseArray<IntArray>().apply { + this.put(TEST_USER_ID, uids) + } + + private fun mockAllowList(pkgSetting: PackageStateInternal, list: SparseArray<IntArray>?) { + whenever(rule.mocks().appsFilter.getVisibilityAllowList( + argThat { it?.packageName == pkgSetting.packageName }, any(IntArray::class.java), + any() as ArrayMap<String, out PackageStateInternal> + )) + .thenReturn(list) + } + + private fun mockKnownPackages(pms: PackageManagerService) { + Mockito.doAnswer { it.arguments[0] == DEVICE_ADMIN_PACKAGE }.`when`(pms) + .isPackageDeviceAdmin(any(), any()) + Mockito.doReturn(DEFAULT_HOME_PACKAGE).`when`(defaultAppProvider) + .getDefaultHome(eq(TEST_USER_ID)) + Mockito.doReturn(DIALER_PACKAGE).`when`(defaultAppProvider) + .getDefaultDialer(eq(TEST_USER_ID)) + Mockito.doReturn(arrayOf(INSTALLER_PACKAGE)).`when`(pms).getKnownPackageNamesInternal( + eq(PackageManagerInternal.PACKAGE_INSTALLER), eq(TEST_USER_ID)) + Mockito.doReturn(arrayOf(UNINSTALLER_PACKAGE)).`when`(pms).getKnownPackageNamesInternal( + eq(PackageManagerInternal.PACKAGE_UNINSTALLER), eq(TEST_USER_ID)) + Mockito.doReturn(arrayOf(VERIFIER_PACKAGE)).`when`(pms).getKnownPackageNamesInternal( + eq(PackageManagerInternal.PACKAGE_VERIFIER), eq(TEST_USER_ID)) + Mockito.doReturn(arrayOf(PERMISSION_CONTROLLER_PACKAGE)).`when`(pms) + .getKnownPackageNamesInternal( + eq(PackageManagerInternal.PACKAGE_PERMISSION_CONTROLLER), eq(TEST_USER_ID)) + } + + private fun createPackageManagerService(vararg stageExistingPackages: String): + PackageManagerService { + stageExistingPackages.forEach { + rule.system().stageScanExistingPackage(it, 1L, + rule.system().dataAppDirectory) + } + var pms = PackageManagerService(rule.mocks().injector, + false /*coreOnly*/, + false /*factoryTest*/, + MockSystem.DEFAULT_VERSION_INFO.fingerprint, + false /*isEngBuild*/, + false /*isUserDebugBuild*/, + Build.VERSION_CODES.CUR_DEVELOPMENT, + Build.VERSION.INCREMENTAL, + false /*snapshotEnabled*/) + rule.system().validateFinalState() + return pms + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt deleted file mode 100644 index 02ee35b9e7a8..000000000000 --- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.pm - -import android.content.Intent -import android.os.Build -import android.os.Bundle -import android.util.ArrayMap -import android.util.SparseArray -import com.android.server.pm.pkg.PackageStateInternal -import com.android.server.testutils.any -import com.android.server.testutils.eq -import com.android.server.testutils.nullable -import com.android.server.testutils.whenever -import com.google.common.truth.Truth.assertThat -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import org.mockito.ArgumentCaptor -import org.mockito.ArgumentMatchers.anyInt -import org.mockito.Captor -import org.mockito.Mockito.argThat -import org.mockito.Mockito.spy -import org.mockito.Mockito.times -import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations - -@RunWith(JUnit4::class) -class SuspendPackagesBroadcastTest { - - companion object { - const val TEST_PACKAGE_1 = "com.android.test.package1" - const val TEST_PACKAGE_2 = "com.android.test.package2" - const val TEST_USER_ID = 0 - } - - lateinit var pms: PackageManagerService - lateinit var packageSetting1: PackageStateInternal - lateinit var packageSetting2: PackageStateInternal - lateinit var packagesToSuspend: Array<String> - lateinit var uidsToSuspend: IntArray - - @Captor - lateinit var bundleCaptor: ArgumentCaptor<Bundle> - - @Rule - @JvmField - val rule = MockSystemRule() - - @Before - @Throws(Exception::class) - fun setup() { - MockitoAnnotations.initMocks(this) - rule.system().stageNominalSystemState() - pms = spy(createPackageManagerService(TEST_PACKAGE_1, TEST_PACKAGE_2)) - packageSetting1 = pms.getPackageStateInternal(TEST_PACKAGE_1)!! - packageSetting2 = pms.getPackageStateInternal(TEST_PACKAGE_2)!! - packagesToSuspend = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2) - uidsToSuspend = intArrayOf(packageSetting1.appId, packageSetting2.appId) - } - - @Test - @Throws(Exception::class) - fun sendPackagesSuspendedForUser_withSameVisibilityAllowList() { - mockAllowList(packageSetting1, allowList(10001, 10002, 10003)) - mockAllowList(packageSetting2, allowList(10001, 10002, 10003)) - - pms.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED, - packagesToSuspend, uidsToSuspend, TEST_USER_ID) - verify(pms).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(), - anyInt(), nullable(), nullable(), any(), nullable(), any(), nullable()) - - var changedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST) - var changedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST) - assertThat(changedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2) - assertThat(changedUids).asList().containsExactly( - packageSetting1.appId, packageSetting2.appId) - } - - @Test - @Throws(Exception::class) - fun sendPackagesSuspendedForUser_withDifferentVisibilityAllowList() { - mockAllowList(packageSetting1, allowList(10001, 10002, 10003)) - mockAllowList(packageSetting2, allowList(10001, 10002, 10007)) - - pms.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED, - packagesToSuspend, uidsToSuspend, TEST_USER_ID) - verify(pms, times(2)).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(), - anyInt(), nullable(), nullable(), any(), nullable(), any(), nullable()) - - bundleCaptor.allValues.forEach { - var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST) - var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST) - assertThat(changedPackages?.size).isEqualTo(1) - assertThat(changedUids?.size).isEqualTo(1) - assertThat(changedPackages?.get(0)).isAnyOf(TEST_PACKAGE_1, TEST_PACKAGE_2) - assertThat(changedUids?.get(0)).isAnyOf(packageSetting1.appId, packageSetting2.appId) - } - } - - @Test - @Throws(Exception::class) - fun sendPackagesSuspendedForUser_withNullVisibilityAllowList() { - mockAllowList(packageSetting1, allowList(10001, 10002, 10003)) - mockAllowList(packageSetting2, null) - - pms.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED, - packagesToSuspend, uidsToSuspend, TEST_USER_ID) - verify(pms, times(2)).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(), - anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable()) - - bundleCaptor.allValues.forEach { - var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST) - var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST) - assertThat(changedPackages?.size).isEqualTo(1) - assertThat(changedUids?.size).isEqualTo(1) - assertThat(changedPackages?.get(0)).isAnyOf(TEST_PACKAGE_1, TEST_PACKAGE_2) - assertThat(changedUids?.get(0)).isAnyOf(packageSetting1.appId, packageSetting2.appId) - } - } - - @Test - @Throws(Exception::class) - fun sendPackagesSuspendModifiedForUser() { - pms.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED, - packagesToSuspend, uidsToSuspend, TEST_USER_ID) - verify(pms).sendPackageBroadcast( - eq(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED), nullable(), bundleCaptor.capture(), - anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable()) - - var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST) - var modifiedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST) - assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2) - assertThat(modifiedUids).asList().containsExactly( - packageSetting1.appId, packageSetting2.appId) - } - - private fun allowList(vararg uids: Int) = SparseArray<IntArray>().apply { - this.put(TEST_USER_ID, uids) - } - - private fun mockAllowList(pkgSetting: PackageStateInternal, list: SparseArray<IntArray>?) { - whenever(rule.mocks().appsFilter.getVisibilityAllowList( - argThat { it?.packageName == pkgSetting.packageName }, any(IntArray::class.java), - any() as ArrayMap<String, out PackageStateInternal> - )) - .thenReturn(list) - } - - private fun createPackageManagerService(vararg stageExistingPackages: String): - PackageManagerService { - stageExistingPackages.forEach { - rule.system().stageScanExistingPackage(it, 1L, - rule.system().dataAppDirectory) - } - var pms = PackageManagerService(rule.mocks().injector, - false /*coreOnly*/, - false /*factoryTest*/, - MockSystem.DEFAULT_VERSION_INFO.fingerprint, - false /*isEngBuild*/, - false /*isUserDebugBuild*/, - Build.VERSION_CODES.CUR_DEVELOPMENT, - Build.VERSION.INCREMENTAL, - false /*snapshotEnabled*/) - rule.system().validateFinalState() - return pms - } -} diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index 0a0f7d7a0ae0..a6b4aecf1cb6 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -23,9 +23,11 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import static org.testng.Assert.assertThrows; import android.Manifest; +import android.app.admin.DevicePolicyManager; import android.companion.virtual.VirtualDeviceParams; import android.content.Context; import android.content.ContextWrapper; @@ -73,6 +75,8 @@ public class VirtualDeviceManagerServiceTest { private DisplayManagerInternal mDisplayManagerInternalMock; @Mock private VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback; + @Mock + private DevicePolicyManager mDevicePolicyManagerMock; @Before public void setUp() { @@ -84,6 +88,9 @@ public class VirtualDeviceManagerServiceTest { mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); doNothing().when(mContext).enforceCallingOrSelfPermission( eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString()); + when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn( + mDevicePolicyManagerMock); + mInputController = new InputController(new Object(), mNativeWrapperMock); mDeviceImpl = new VirtualDeviceImpl(mContext, /* association info */ null, new Binder(), /* uid */ 0, mInputController, @@ -92,6 +99,14 @@ public class VirtualDeviceManagerServiceTest { } @Test + public void onVirtualDisplayRemovedLocked_doesNotThrowException() { + final int displayId = 2; + mDeviceImpl.onVirtualDisplayCreatedLocked(displayId); + // This call should not throw any exceptions. + mDeviceImpl.onVirtualDisplayRemovedLocked(displayId); + } + + @Test public void createVirtualKeyboard_noDisplay_failsSecurityException() { assertThrows( SecurityException.class, diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java index 408d2c525f70..99edecfeed30 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java @@ -16,6 +16,7 @@ package com.android.server.pm; import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertBundlesEqual; +import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertEmpty; import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertExpectException; import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith; import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list; @@ -257,6 +258,10 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { .setLongLived(true) .setExtras(pb) .setStartingTheme(android.R.style.Theme_Black_NoTitleBar_Fullscreen) + .addCapabilityBinding("action.intent.START_EXERCISE", + "exercise.type", list("running", "jogging")) + .addCapabilityBinding("action.intent.START_EXERCISE", + "exercise.duration", list("10m")) .build(); si.addFlags(ShortcutInfo.FLAG_PINNED); si.setBitmapPath("abc"); @@ -294,6 +299,13 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(null, si.getDisabledMessageResName()); assertEquals("android:style/Theme.Black.NoTitleBar.Fullscreen", si.getStartingThemeResName()); + assertTrue(si.hasCapability("action.intent.START_EXERCISE")); + assertFalse(si.hasCapability("")); + assertFalse(si.hasCapability("random")); + assertEquals(list("running", "jogging"), si.getCapabilityParameterValues( + "action.intent.START_EXERCISE", "exercise.type")); + assertEmpty(si.getCapabilityParameterValues("action.intent.START_EXERCISE", "")); + assertEmpty(si.getCapabilityParameterValues("action.intent.START_EXERCISE", "random")); } public void testShortcutInfoParcel_resId() { @@ -947,6 +959,10 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { .setRank(123) .setExtras(pb) .setLocusId(new LocusId("1.2.3.4.5")) + .addCapabilityBinding("action.intent.START_EXERCISE", + "exercise.type", list("running", "jogging")) + .addCapabilityBinding("action.intent.START_EXERCISE", + "exercise.duration", list("10m")) .build(); sorig.setTimestamp(mInjectedCurrentTimeMillis); @@ -1008,6 +1024,14 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertNull(si.getIconUri()); assertTrue(si.getLastChangedTimestamp() < now); + assertTrue(si.hasCapability("action.intent.START_EXERCISE")); + assertFalse(si.hasCapability("")); + assertFalse(si.hasCapability("random")); + assertEquals(list("running", "jogging"), si.getCapabilityParameterValues( + "action.intent.START_EXERCISE", "exercise.type")); + assertEmpty(si.getCapabilityParameterValues("action.intent.START_EXERCISE", "")); + assertEmpty(si.getCapabilityParameterValues("action.intent.START_EXERCISE", "random")); + // Make sure ranks are saved too. Because of the auto-adjusting, we need two shortcuts // to test it. si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id2", USER_10); diff --git a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java index d164d2a4f581..0e98b5e79aa0 100644 --- a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java @@ -29,6 +29,7 @@ import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.argThat; import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -38,6 +39,7 @@ import android.app.ActivityManagerInternal; import android.app.StatusBarManager; import android.content.ComponentName; import android.content.Intent; +import android.content.om.IOverlayManager; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; @@ -96,6 +98,10 @@ public class StatusBarManagerServiceTest { private ApplicationInfo mApplicationInfo; @Mock private IStatusBar.Stub mMockStatusBar; + @Mock + private IOverlayManager mOverlayManager; + @Mock + private PackageManager mPackageManager; @Captor private ArgumentCaptor<IAddTileResultCallback> mAddTileResultCallbackCaptor; @@ -130,6 +136,7 @@ public class StatusBarManagerServiceTest { mStatusBarManagerService); mStatusBarManagerService.registerStatusBar(mMockStatusBar); + mStatusBarManagerService.registerOverlayManager(mOverlayManager); mIcon = Icon.createWithResource(mContext, android.R.drawable.btn_plus); } @@ -577,27 +584,56 @@ public class StatusBarManagerServiceTest { } @Test - public void testSetNavBarModeOverride_setsOverrideModeKids() { + public void testSetNavBarModeOverride_setsOverrideModeKids() throws RemoteException { int navBarModeOverrideKids = StatusBarManager.NAV_BAR_MODE_OVERRIDE_KIDS; + mStatusBarManagerService.setNavBarModeOverride(navBarModeOverrideKids); assertEquals(navBarModeOverrideKids, mStatusBarManagerService.getNavBarModeOverride()); + verify(mOverlayManager).setEnabledExclusiveInCategory(anyString(), anyInt()); } @Test - public void testSetNavBarModeOverride_setsOverrideModeNone() { + public void testSetNavBarModeOverride_setsOverrideModeNone() throws RemoteException { int navBarModeOverrideNone = StatusBarManager.NAV_BAR_MODE_OVERRIDE_NONE; + mStatusBarManagerService.setNavBarModeOverride(navBarModeOverrideNone); assertEquals(navBarModeOverrideNone, mStatusBarManagerService.getNavBarModeOverride()); + verify(mOverlayManager, never()).setEnabledExclusiveInCategory(anyString(), anyInt()); } @Test - public void testSetNavBarModeOverride_invalidInputThrowsError() { + public void testSetNavBarModeOverride_invalidInputThrowsError() throws RemoteException { int navBarModeOverrideInvalid = -1; assertThrows(UnsupportedOperationException.class, () -> mStatusBarManagerService.setNavBarModeOverride(navBarModeOverrideInvalid)); + verify(mOverlayManager, never()).setEnabledExclusiveInCategory(anyString(), anyInt()); + } + + @Test + public void testSetNavBarModeOverride_noOverlayManagerDoesNotEnable() throws RemoteException { + mOverlayManager = null; + int navBarModeOverrideKids = StatusBarManager.NAV_BAR_MODE_OVERRIDE_KIDS; + + mStatusBarManagerService.setNavBarModeOverride(navBarModeOverrideKids); + + assertEquals(navBarModeOverrideKids, mStatusBarManagerService.getNavBarModeOverride()); + verify(mOverlayManager, never()).setEnabledExclusiveInCategory(anyString(), anyInt()); + } + + @Test + public void testSetNavBarModeOverride_noPackageDoesNotEnable() throws Exception { + mContext.setMockPackageManager(mPackageManager); + when(mPackageManager.getPackageInfo(anyString(), + any(PackageManager.PackageInfoFlags.class))).thenReturn(null); + int navBarModeOverrideKids = StatusBarManager.NAV_BAR_MODE_OVERRIDE_KIDS; + + mStatusBarManagerService.setNavBarModeOverride(navBarModeOverrideKids); + + assertEquals(navBarModeOverrideKids, mStatusBarManagerService.getNavBarModeOverride()); + verify(mOverlayManager, never()).setEnabledExclusiveInCategory(anyString(), anyInt()); } private void mockUidCheck() { diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java index 2c22419d1372..5d4ffbb6aca2 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java @@ -319,6 +319,34 @@ public class VibrationSettingsTest { } } + + @Test + public void shouldIgnoreVibration_vibrateOnDisabled_ignoresUsagesNotAccessibility() { + setUserSetting(Settings.System.VIBRATE_ON, 0); + + for (int usage : ALL_USAGES) { + if (usage == USAGE_ACCESSIBILITY) { + assertVibrationNotIgnoredForUsage(usage); + } else { + assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS); + } + assertVibrationNotIgnoredForUsageAndFlags(usage, + VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF); + } + } + + @Test + public void shouldIgnoreVibration_vibrateOnEnabledOrUnset_allowsAnyUsage() { + deleteUserSetting(Settings.System.VIBRATE_ON); + for (int usage : ALL_USAGES) { + assertVibrationNotIgnoredForUsage(usage); + } + + setUserSetting(Settings.System.VIBRATE_ON, 1); + for (int usage : ALL_USAGES) { + assertVibrationNotIgnoredForUsage(usage); + } + } @Test public void shouldIgnoreVibration_withRingSettingsOff_disableRingtoneVibrations() { setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); @@ -560,10 +588,17 @@ public class VibrationSettingsTest { when(mVibrationConfigMock.getDefaultVibrationIntensity(eq(usage))).thenReturn(intensity); } + private void deleteUserSetting(String settingName) { + Settings.System.putStringForUser( + mContextSpy.getContentResolver(), settingName, null, UserHandle.USER_CURRENT); + // FakeSettingsProvider doesn't support testing triggering ContentObserver yet. + mVibrationSettings.updateSettings(); + } + private void setUserSetting(String settingName, int value) { Settings.System.putIntForUser( mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT); - // FakeSettingsProvider don't support testing triggering ContentObserver yet. + // FakeSettingsProvider doesn't support testing triggering ContentObserver yet. mVibrationSettings.updateSettings(); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java index a834e2b6cc5a..12cd834d1d66 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java @@ -394,7 +394,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { "disabledMessage", 0, "disabledMessageResName", null, null, 0, null, 0, 0, 0, "iconResName", "bitmapPath", null, 0, - null, null, null); + null, null, null, null); return si; } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index b48c9c3d4bcc..736fbd9d50ef 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -46,6 +46,7 @@ import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.IS import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.UID_FIELD_NUMBER; import static com.android.server.notification.PreferencesHelper.DEFAULT_BUBBLE_PREFERENCE; import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_COUNT_LIMIT; +import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT; import static com.android.server.notification.PreferencesHelper.UNKNOWN_UID; import static com.google.common.truth.Truth.assertThat; @@ -4250,6 +4251,52 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test + public void testTooManyGroups() { + for (int i = 0; i < NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT; i++) { + NotificationChannelGroup group = new NotificationChannelGroup(String.valueOf(i), + String.valueOf(i)); + mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true); + } + try { + NotificationChannelGroup group = new NotificationChannelGroup( + String.valueOf(NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT), + String.valueOf(NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT)); + mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true); + fail("Allowed to create too many notification channel groups"); + } catch (IllegalStateException e) { + // great + } + } + + @Test + public void testTooManyGroups_xml() throws Exception { + String extraGroup = "EXTRA"; + String extraGroup1 = "EXTRA1"; + + // create first... many... directly so we don't need a big xml blob in this test + for (int i = 0; i < NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT; i++) { + NotificationChannelGroup group = new NotificationChannelGroup(String.valueOf(i), + String.valueOf(i)); + mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true); + } + + final String xml = "<ranking version=\"1\">\n" + + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n" + + "<channelGroup id=\"" + extraGroup + "\" name=\"hi\"/>" + + "<channelGroup id=\"" + extraGroup1 + "\" name=\"hi2\"/>" + + "</package>" + + "</ranking>"; + TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())), + null); + parser.nextTag(); + mHelper.readXml(parser, false, UserHandle.USER_ALL); + + assertNull(mHelper.getNotificationChannelGroup(extraGroup, PKG_O, UID_O)); + assertNull(mHelper.getNotificationChannelGroup(extraGroup1, PKG_O, UID_O)); + } + + @Test public void testRestoreMultiUser() throws Exception { String pkg = "restore_pkg"; String channelId = "channelId"; diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java new file mode 100644 index 000000000000..687779d686d1 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.window.BackNavigationInfo.typeToString; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.annotation.NonNull; +import android.hardware.HardwareBuffer; +import android.platform.test.annotations.Presubmit; +import android.window.BackNavigationInfo; +import android.window.TaskSnapshot; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@Presubmit +@RunWith(WindowTestRunner.class) +public class BackNavigationControllerTests extends WindowTestsBase { + + private BackNavigationController mBackNavigationController; + + @Before + public void setUp() throws Exception { + mBackNavigationController = new BackNavigationController(); + } + + @Test + public void backTypeHomeWhenBackToLauncher() { + Task task = createTopTaskWithActivity(); + BackNavigationInfo backNavigationInfo = + mBackNavigationController.startBackNavigation(task, new StubTransaction()); + assertThat(backNavigationInfo).isNotNull(); + assertThat(typeToString(backNavigationInfo.getType())) + .isEqualTo(typeToString(BackNavigationInfo.TYPE_RETURN_TO_HOME)); + } + + @Test + public void backTypeCrossTaskWhenBackToPreviousTask() { + Task taskA = createTask(mDefaultDisplay); + createActivityRecord(taskA); + Task task = createTopTaskWithActivity(); + BackNavigationInfo backNavigationInfo = + mBackNavigationController.startBackNavigation(task, new StubTransaction()); + assertThat(backNavigationInfo).isNotNull(); + assertThat(typeToString(backNavigationInfo.getType())) + .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_TASK)); + } + + @Test + public void backTypeCrossActivityWhenBackToPreviousActivity() { + Task task = createTopTaskWithActivity(); + mAtm.setFocusedTask(task.mTaskId, createActivityRecord(task)); + BackNavigationInfo backNavigationInfo = + mBackNavigationController.startBackNavigation(task, new StubTransaction()); + assertThat(backNavigationInfo).isNotNull(); + assertThat(typeToString(backNavigationInfo.getType())) + .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_ACTIVITY)); + } + + /** + * Checks that we are able to fill all the field of the {@link BackNavigationInfo} object. + */ + @Test + public void backNavInfoFullyPopulated() { + Task task = createTopTaskWithActivity(); + createActivityRecord(task); + + // We need a mock screenshot so + TaskSnapshotController taskSnapshotController = createMockTaskSnapshotController(); + + mBackNavigationController.setTaskSnapshotController(taskSnapshotController); + + BackNavigationInfo backNavigationInfo = + mBackNavigationController.startBackNavigation(task, new StubTransaction()); + assertThat(backNavigationInfo).isNotNull(); + assertThat(backNavigationInfo.getDepartingWindowContainer()).isNotNull(); + assertThat(backNavigationInfo.getScreenshotSurface()).isNotNull(); + assertThat(backNavigationInfo.getScreenshotHardwareBuffer()).isNotNull(); + assertThat(backNavigationInfo.getTaskWindowConfiguration()).isNotNull(); + } + + @NonNull + private TaskSnapshotController createMockTaskSnapshotController() { + TaskSnapshotController taskSnapshotController = mock(TaskSnapshotController.class); + TaskSnapshot taskSnapshot = mock(TaskSnapshot.class); + when(taskSnapshot.getHardwareBuffer()).thenReturn(mock(HardwareBuffer.class)); + when(taskSnapshotController.getSnapshot(anyInt(), anyInt(), anyBoolean(), anyBoolean())) + .thenReturn(taskSnapshot); + return taskSnapshotController; + } + + @NonNull + private Task createTopTaskWithActivity() { + Task task = createTask(mDefaultDisplay); + ActivityRecord record = createActivityRecord(task); + when(record.mSurfaceControl.isValid()).thenReturn(true); + mAtm.setFocusedTask(task.mTaskId, record); + return task; + } +} diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 21967f4f4687..e1be973752fb 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -7572,10 +7572,6 @@ public class CarrierConfigManager { /** Specifies the PCO id for IPv4 Epdg server address */ public static final String KEY_EPDG_PCO_ID_IPV4_INT = KEY_PREFIX + "epdg_pco_id_ipv4_int"; - /** Controls if the IKE tunnel setup supports EAP-AKA fast reauth */ - public static final String KEY_ENABLE_SUPPORT_FOR_EAP_AKA_FAST_REAUTH_BOOL = - KEY_PREFIX + "enable_support_for_eap_aka_fast_reauth_bool"; - /** @hide */ @IntDef({AUTHENTICATION_METHOD_EAP_ONLY, AUTHENTICATION_METHOD_CERT}) public @interface AuthenticationMethodType {} @@ -7722,7 +7718,6 @@ public class CarrierConfigManager { defaults.putBoolean(KEY_ADD_KE_TO_CHILD_SESSION_REKEY_BOOL, false); defaults.putInt(KEY_EPDG_PCO_ID_IPV6_INT, 0); defaults.putInt(KEY_EPDG_PCO_ID_IPV4_INT, 0); - defaults.putBoolean(KEY_ENABLE_SUPPORT_FOR_EAP_AKA_FAST_REAUTH_BOOL, false); return defaults; } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/OWNERS b/tests/FlickerTests/src/com/android/server/wm/flicker/close/OWNERS new file mode 100644 index 000000000000..f7c0a87f5dac --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/OWNERS @@ -0,0 +1,2 @@ +# window manager > animations/transitions +# Bug component: 316275 diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OWNERS b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OWNERS new file mode 100644 index 000000000000..301fafa5309e --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OWNERS @@ -0,0 +1,2 @@ +# ime +# Bug component: 34867 diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OWNERS b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OWNERS new file mode 100644 index 000000000000..2c414a27cacb --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OWNERS @@ -0,0 +1,4 @@ +# System UI > ... > Overview (recent apps) > UI +# Bug template url: https://b.corp.google.com/issues/new?component=807991&template=1390280 = per-file *Overview* +# window manager > animations/transitions +# Bug template url: https://b.corp.google.com/issues/new?component=316275&template=1018192 diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/OWNERS b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/OWNERS new file mode 100644 index 000000000000..897fe5dee7fb --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/OWNERS @@ -0,0 +1,2 @@ +# System UI > ... > Launcher > Gesture nav +# Bug component: 565144 diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/OWNERS b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/OWNERS new file mode 100644 index 000000000000..f7c0a87f5dac --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/OWNERS @@ -0,0 +1,2 @@ +# window manager > animations/transitions +# Bug component: 316275 |